前言
从 Vue 0.x 开始,Vue 就用 v-model 来实现双向绑定,自 Vue2.0 以后,Vue 已经不是双向绑定,而大家通常所说的双绑只是看起来像双绑而已。
原因是当 FLUX 单向数据流兴起之后,Vue 的作者重新审视了「双向绑定」,发现「双向绑定」的一些问题之后,就更倾向于「单向绑定」了。
v-model 被拆成两部分。
基本用法
// template
<div id="app">
<h1>{{ name }}</h1>
value: <input type="text" :value="name" /><br/>
v-model: <input type="text" v-model="name" /><br/>
v-model的本质:<input type="text" :value="name" @input="inputEvent" /><br/>
<!--还可以不用写下面的 methods 直接写-->
<input type="text" :value="name" @input=" name = $event.target.value " /><br/>
</div>
// script
export default {
data() {
return {
name: "hhh",
};
},
methods: {
inputEvent(event) {
this.name = event.target.value;
},
},
};
页面由 data 中的数据渲染而来,当 data 中的数据发生改变时,页面会自动更新;当操作页面时,data 中的数据允许改变,页面才会发生改变。这就是 '双向数据绑定',也就是 MVVM。
contenteditable 的 bug
<!--在上面template 中添加如下代码 -->
BUG:
<span contenteditable @input="name = $event.target.innerText">
{{ name }}
</span>
我的光标为什么会自动到前面去?
当我在 span里面输入 1 的时候,1 确确实实的写到里面了,这个 span 就会触发 input 事件,从而执行 name = $event.target.innerText
。
当 name (数据)变化以后,这个 span 就要重新渲染,Vue 记得住这个 span 里面的内容,但是记不住光标的位置。
这个里面当我第一次输入 1,再次输入的时候 span 重新渲染,光标就会自动跑到最前面(Vue忘记了光标的位置)。
这就是 Vue 的 “双向绑定”,实际上是在 {{ name }}
做了一个单向绑定,在 @input="name = $event.target.innerText"
做了一个单向绑定。
小栗子1🌰
如果只有一个 input 双向绑定是非常爽的,那如果有两个 input 呢? 当我把第一个改成 ‘jack' 的时候,其他人在另一个 input上改成 ‘tom', 现在两个 input 里面的内容是什么? 不确定,因为不确定哪个是最后改的。
graph TD
hhh --> tom --> jack
hhh --> jack -->tom
因为不确定性,那么在团队协作中就会导致,起点奇怪或者结果奇怪的现象。
这也就是双向绑定存在的问题,你的数据很可能在任何一个时间,被任何人改,改的时候不会通知你,你只知道在用的时候发现不对。虽然出现概率低,但会一直是一个隐患。
小概率事件必然发生 -- 墨菲定律
所以双向绑定在没有同级元素互相篡改的情况的下,是 OK 的。一旦出现了多个改变数据的源,那么数据就会变得不可控。
如何解决?
一个数据只能一个组件改,谁拥有这个数据谁就可以改这个数据。
这个 APP 它把 name 当作数据分别传给两个 input 显示,这两个组件不准改数据,只能显示数据。
如果要改就必须让这个组件通知数据的拥有者(图示的数据是 name),然后这个数据的拥有者才会去改,拥有者在改的时候就会同时改这两个的数据(比如都改成 jack)。 如果一个组件把这个改为 tom,那么组件自己不会先去改,而是通知数据拥有者(name),拥有者改了之后,这个组件才会去改成 tom。
<input type="text"
:value="name"
@input=" name =$event.target.value "/>
data(){
return {
name: 'hhh'
}
}
这样一来,数据的变更权就回到数据拥有者手上了。
用一个 contenteditable 的 bug 来理解单向数据流。
小栗子2🌰
<input type="text" :value="amount" @input="inputEvent"/>
<input type="text" :value="amount" @input="inputEvent"/>
data() {
return {
amount: 500
};
},
methods: {
inputEvent(event) {
this.amount = event.target.value;
},
},
当改变第一个 input 的值的时候,它会通知它爸爸,我要修改数据,它爸爸同意了,才可以修改值的数据。
什么时候不同意呢?添加条件
data() {
return {
amount: 500
};
},
methods: {
inputEvent(event) {
if( event.target.value > 100 ){
this.amount = event.target.value
}else{
event.target.value = 100
}
},
},
这就是单向数据流,因为这么写很麻烦,所以 Vue 提供了 v-model 的语法糖
<input type="text" v-model="amount"/>
<input type="text" v-model="amount"/>
如果以双向绑定的思维去理解的话,就是当我 input 去改 amount ,amount 就变了,实际上不是。
Vue 通过两个单向绑定,来模拟双向绑定,所以我们可以去拦截数据。
小栗子3🌰
以下代码为完整版本的 Vue
<div id="app">
爸爸
<div style="border: 1px solid red">
<child :selected="value"></child>
</div>
</div>
Vue.component('child', {
props: ['selected'],
template: `
<div>
selected: {{selected}} <!-- 这个 selected 不是这个组件拥有的-->
<hr/>
<button @click="selected=1">1</button>
<button @click="selected=2">2</button>
</div>
`
})
var vm = new Vue({
el:"#app",
data: {
value: 2
}
})
当我点击按钮 1 的时候,数据可以改变,但是会出现警告,告诉你应该避免直接修改 prop 数据
一个数据只能有一个人去改,这个人就是这个数据的拥有者
Vue.component('child', {
props: ['selected'],
template: `
<div>
selected: {{selected}}
<hr/>
<button @click="$emit("xxx", 1)">1</button>
<button @click="$emit("xxx", 2)>2</button>
</div>
`
})
let vm = new Vue({
el:"#app",
data: {
value: 2
},
template:`
<div>
爸爸
<div style="border: 1px solid red">
<child :selected="value" @xxx="value=$event"></child>
</div>
</div>
`
})
以上代码点击按钮没有警告,说明 Vue 更倾向于单向数据流。
同时为了巩固这一模式,Vue 规定子组件不能修改父组件传给它的 props,一旦发现就会打印出一个警告。
Vue.component('child', {
props: ['selected'],
template: `
<div>
selected: {{selected}}
<hr/>
<button @click="$emit("update:selected", 1)">1</button>
<button @click="$emit("update:selected", 2)>2</button>
</div>
`
})
let vm = new Vue({
el:"#app",
data: {
value: 2
},
template:`
<div>
爸爸
<div style="border: 1px solid red">
<child :selected.sync="value" @xxx="value=$event"></child>
</div>
</div>
`
})
<child :selected.sync="value" @xxx="value=$event"></child>
等价于
<child :selected="value" @update:selected="selected=$event"></child>
.sync 的作用和 v-model 一模一样,就是用双向绑定的语法糖,实现两个单向绑定。
单向数据流的优点
- 数据拥有者清楚地知道数据变化的原因和时机(因为是他自己操作数据的)
- 数据拥有者可以阻止数据变化
这些都是在双向绑定中很难做到的。
渐进式的 Vue
Vue 的另一个大特点就是「渐进式」,意思就是可以渐渐地用 Vue。而 React 几乎做不到这一点。
- 你可以继续操作 DOM
- 你可以很方便地做 SEO
- 你可以局部做单页面
- 你可以整体做单页面
computed V.S methods V.S. watch
- 触发时机
watch: 被 watch 的值变化的时候执行一个函数
methods: getMessage() 出现在视图里的时候,或视图更新的时候调用 getMessage
computed: 同时满足两个情况:一、依赖的属性变化了 二、message 出现在视图里了或视图更新了 - 使用形式
watch 的值需要用 data 承载,本身返回值没有用
methods 的返回值可以直接展示在视图,但是要加括号
computed 不能加括号