表单标签的双向绑定是一个很有亮点的功能。在不同框架中他实现这个功能大同小异,这里我们介绍几个常见的框架中他是如何实现双向绑定的。
原生的input输入框是没有双向绑定的功能的。取而代之的,它的input上有一个event对象,这个对象中有一个属性target,而这个target的值就是此时输入框内部的数据。所以如果是原生input,我们需要手动将这个input绑定到我们用于接收这个数据的参数上。
<!-- 原生input -->
<input type="text" value="初始值" oninput="viewToData(event)">
<script>
let data = {
value: "初始值"
};
function dataToView(){
//数据变化的时候触输入框数据更新
document.querySelector('input').value = data.value;
}
function viewToData(rvent){
//输入框数据变化触发底层数据更新
data.value = event.target.value;
}
</script>
非常的难看吧。现在很多框架已经帮你做了这一步,自动处理了数据和视图的同步,并且还能处理复杂的数据结构和组件通信,让开发更高效
双向绑定
vue2中想要绑定数据 只需要使用v-model指定参数即可。这是一个语法糖 其实就是将
:value="data",@input = "function"
化简成了一句“v-model"而已。
然后的然后 这样监听数据变化的底层原理又是什么呢?
其实是vue底层的响应式系统在做支撑。
响应式系统
特点
响应式系统是react和vue,特别是vue的一大核心特性。响应式系统是一种能够持续响应变化并自动进行适应的系统,在vue和react中,体现在数据的变化会触发视图的更新。
让我们从0开始,看看要如何才能实现”数据的变化触发视图的更新"。
如果是让我们自己设计,我们会怎么做?肯定就是要监听数据变化,当数据变化的时候给视图更新函数打报告对吧。
那么其实就分为两步,监听数据的变化,以及传递数据数据变化的消息给视图,触发更新。
我们先讲第一个。
如何监听数据变化
一般来说,想要实现监听数据的变化,有这几大方法(设计思路):
数据绑定
数据绑定的核心思路是将数据和视图层进行连接,使得数据变化时视图自动更新,视图变化时数字更新。在vue中,主要用v-bind实现单项绑定(数据->视图)和v-model(数据<=>视图)。
观察者模式
观察者模式是一种一对多的关系,一个被观察对象多个观察者。当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并且自动更新。这种方式能使得对象中的耦合度降低。
发布-订阅者模式
发布-订阅者模式可以说是一对一关系,允许发布者发送消息,订阅者接收。发布者和订阅者之间没有直接直接通信 而是通过一个中央事件总线或消息代理进行沟通,发布者将事件发送到事件总线,订阅者从事件总线接收消息。有点像js的事件处理队列。
事件驱动模式
事件驱动模式通过事件的发布和处理来驱动程序的执行,组件通过监听特定的事件来响应用户操作或系统状态变化。使用事件监听器和数据触发机制,组件可以注册事件处理函数。
数据劫持
通过调用defineProperty(或proxy),定义setter和getter方法,在数据被读取或修改时执行特定的逻辑。在响应式系统中,数据劫持会在setter时进行以来手机,也就是记录当前的观察者-正在渲染的组件。这通常通过一个全局的依赖数组实现,数据中存储了所有依赖于该数据的组件。在setter执行时,会通知所有依赖于该数据的组件进行更新。
在响应式设计中,实现监听数据变化主要依赖于观察者模式和数据劫持。
数据劫持专注于数据的拦截和响应,确保数据变化时能自动更新视图;而观察者模式则关注对象之间关系和通知机制,适用于更复杂的交互场景。换而言之,数据劫持保证了基础的响应式能力,而观察者模式则允许多个组件或对象之间的解耦和交互。通过这两个手段,实现了响应式设计。
如何通知对应组件进行更新
上文在数据劫持中提到过,依赖收集会在getter中通知所有需要更新的组件。看似很完美的背后其实也是存在问题的:这样的更新,消耗必然不小。那么如何保持尽量高效的速度实现视图的更新呢?
基本分成下面几步:
标记更新
当数据被更新是,响应式系统会遍历其依赖列表,标记所有依赖于该数据的组件为"需要个更新".
异步更新
为了提高性能,响应式系统会将需要更新的组件放进一个队列中,使用异步的方式进行更新处理。
是不是很眼熟?没错,正是js的事件循环机制。事件循环机制允许视图更新事操作放进微任务队列中,顺着队列的执行顺序进行更新。
虚拟dom
在更新视图时,框架首先会生成新的虚拟DOM树,接着和旧的虚拟DOM树进行比较(diff)。通过比较,框架能找出哪些部分发生了变化,完成最小的更新操作。通过将多个操作合并为一次DOM操作,框架能显著提升性能,减少重排和重绘次数。
实战
实现一个简单的双向绑定
<template>
<title>This is title</title>
<input type="text" id="id-input" @input="handleInput"/><br />
<div id="id-div">{{ inputValue }}</div>
</template>
<script setup>
import { ref,watch } from 'vue';
const inputValue = ref('');
// 监听 input 事件
const handleInput = (event) => {
console.log(event.target.value);
//input被触发时更改inputValue
inputValue.value = event.target.value;
};
//inputValue被更改时更改输入框表单的值
watch(inputValue, (newValue) => {
document.getElementById('id-input').value = newValue;
});
</script>