双向绑定(two-way data bindings)是指把画面上的 DOM 与资料透过 Vue 实体来绑定。当其中一方有更动时,另一方都会随即更新。
单向资料流是 Vue 官方提倡的一种管理元件资料状态模式。重点在於,当子层元件透过 props 来接收父层元件传来的资料时,子层不应该直接修改 props,不然开发时就难以追踪资料状态的变化。建议做法是透过 emit,触发事件来修改在父层的资料。也就是常见口诀:「props in, event out」的意思。
以下会再详细解说双向绑定、单向资料流、以及使用 props 时须注意的事。
双向绑定的重点就是当画面或资料有更新,对方也会随之更新。最明显的例子就是 v-model
,我们很常在 input 栏位上 v-model
来绑定画面中输入栏目前的内容,以及在 data 里的资料。一旦用户输入内容,我们的 Vue 里的 data 资料也会同步拥有相同的资料,反之亦然。
单向资料流的概念是针对父子层元件的资料传递时的模式,它跟双向绑定的概念并没有冲突。当要把资料从父元件传递到子元件时,子元件会使用 props 来接收,但子元件不应该直接修改 props 来修改父元件的资料 ,而是使用 emit。
假如你是传入基本型别的资料,例如数字、字串等,你在子元件不会修改到父元件的资料,因为当父元件再被渲染时,它依旧要指向父元件所定义的资料,像以下例子:
HelloWorld.vue(子层)
<input v-model="childMsg" />
export default {
props: {
value: String,
childMsg: {
type: String,
default: "default msg"
}
}
};
App.vue (父层)
<template>
<div>
{{ parentMsg }}
<HelloWorld :childMsg="parentMsg" />
</div>
</template>
export default {
name: "Home",
components: {
HelloWorld
},
data() {
return {
parentMsg: "Hello vue!"
};
}
};
警告:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
父层的 parentMsg
并没有被修改,依旧为 "Hello vue!"。但 Vue 会把你以上的行为视作尝试修改父层资料,因此跳警告提示此举是无效。
如果是资料是物件呢?会否跳警告?
在父元件加入物件资料:
App.vue
data() {
return {
parentMsg: "Hello vue!",
parent: {
a: 123
}
};
}
绑定 props:
<template>
<div>
<p>{{ parentMsg }}</p>
<p>{{ parent }}</p>
<HelloWorld :childMsg="parentMsg" :child="parent" />
</div>
</template>
HelloWorld.vue(子元件):
<template>
<div>
<input v-model="childMsg" />
<input v-model="child.a" />
</div>
</template>
<script>
export default {
props: {
value: String,
childMsg: {
type: String,
default: "default msg"
},
child: {
type: Object,
default: {}
}
}
};
</script>
结果会直接改掉父层资料,而且 console 没有跳警告:
官方文件有解释,这是因为物件和阵列是传址(pass by reference),因此会直接修改到父元件的资料。官方不建议这做法。
在 Vue 3,如果你的 Vue CLI 专案有加入 ESLint,当你的子元件的 input ,使用 v-model
直接绑定 props时,ESLint 就都会报错:
Unexpected mutation of "child" prop
但如果没有加入 ESLint,不管是修改物件还是基本型别资料,也不会报错,而且也能把父元子的资料改掉。
为了保持单向资料流,就不能在子元件绑定 props。
如果 props 是基本型别资料,那就在 data 里建立属性来拷贝 props 的值即可,或者使用 computed 来处理资料再掉回画面使用,这些都是官方文件有提到的做法,因此就不作解释了。
但如果资料是物件或阵列,考虑到传址的问题,可以用以下方式解决:
v-bind
,自动解构赋值如果物件资料的属性不多的话,可以用 v-bind
来处理。示范如下:
假设在父元件有一笔物件资料,并传送到子元件的 input 里:
App.vue 资料(父元件):
data() {
return {
user: {
name: "Tom",
age: 20,
},
};
},
传入子元件(Child1):
<Child1 v-bind="user" />
在子元件里,设定 name
和 age
这两个 props,解构 user 物件并赋值到这两个 props 里,最後再在 data
拷贝这两个 props:
<template>
<div>
<input type="text" v-model="userName">
</div>
<div>
<input type="text" v-model="userAge">
</div>
</template>
<script>
export default {
props: ["name", "age"],
data() {
return {
userName: this.name,
userAge: this.age,
}
}
};
</script>
但如果该物件资料的属性很多,就需要写很多个 props,因此此方法就不适合。
JSON.parse
处理遇上物件属性很多的情况,我们可以直接把整个物件传进去子元件,并在 data 里使用 JSON.parse()
和 JSON.stringify()
深拷贝这个物件,最後在 input 绑定拷贝的结果:
Child2(子元件):
<template>
<div>
<input type="text" v-model="user.name" />
</div>
<div>
<input type="text" v-model="user.age" />
</div>
</template>
<script>
export default {
props: ["userInfo"],
data() {
return {
user: JSON.parse(JSON.stringify(this.userInfo)),
// 用展开语法也可以,但注意不要用在处理多层物件,但因为只能做浅拷贝
// user: {...this.userInfo}
};
},
};
</script>
v-bind
才能传入数字当我们要以 props 传入数字,必须要使用 v-bind
才可以。没有使用 v-bind
的话,一律当作传入纯字串。
字串:
<HelloWorld num="3" />
数字
<HelloWorld :num="3" />
注意,:
是 v-bind
的缩写。
官方在 style guide 提及,不建议以下的写法:
props: ['status']
Vue 建议用物件方式,加入型别检查、预设值等等:
props: {
status: {
type: String,
required: true,
validator: function (value) {
return [
'syncing',
'synced',
'version-conflict',
'error'
].indexOf(value) !== -1
}
}
}
这是为了更严谨传入 props,减少错误。因为当错误传入 props时,Vue 会跳警告提醒。在例子中,如果 validator
回传 false
,同样会跳黄字警告。
另外,有些情况我亦会使用 default
属性来设定预设值。例如在呼叫 API 时,需要等一笔阵列资料回传过来,再放入 props 里传给子元件,再在子元件使 v-for
显示阵列里每一笔的资料。如果在子元件的 props 先建立预设值,那麽使用 v-for
来绑定 props 时就不会报错。
v-bind
自动解构赋值,或者是深拷贝方法 JSON.parse()
和 JSON.stringify
来处理。props
时,建议用物件方式来建立,而非阵列。从而更严谨和仔细处理 props 的资料。
终於到了30天的尾声,该学的都学了! 接下来就是运用在实际的案例上。剩下的这几天我要跟着「重新认识V...
1. = =是判别左右相等为真 != 是判别左右不相等为真 2.function同名字,会执行後面的...
前言 今天来学习SwiftUI 的按钮 — Button。 实作 宣告一个 text 按钮 打开一个...
本篇文章分享笔者第一次接触到 Linux 作业系统的故事。 进入正题 相信大部分的读者看到标题就会知...
终於要进入到我熟悉的後端了,在Day5安装XAMPP时有勾选了PHP、MySQL、phpMyAdmi...