Component-data
Vue
组件中的data
必须写成一个函数返回对象的形式,目的就是让每一个组件实例维护一份被返回对象的独立的拷贝。
容易理解的说就是,为了让每一个组件实例都可以单独的对自己的数据进行需要的操作而且不会对其他的组件实例的数据造成影响。
Vue.component("hello",{
template:"<h1>{{msg}}</h1>",
data(){
return {
msg:"我是组件的数据"
}
}
})
每一个组件的数据的作用域都是该数据的组件,在某些场景,组件之间要想get
或者set
某个组件的数据是不能直接操作的。所以就有了组件之间的传值。
1、父传子
什么是单向数据流?
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
Vue单向数据流的特点,就是说组件之间的传值是可以有父组件传给子组件的,而且也仅能有父组件传给子组件。
new Vue({
el:"#app",
components:{
father:{
template:"<h1>我是父组件,这是子组件:<son :fatherData='myMsg'></son></h1>",
data(){
return {
myMsg:"我是父组件的data"
}
},
components:{
son:{
template:"<h3>{{fatherData}}</h3>",
data(){
return {
myMsg:"我是子组件的data"
}
},
props:["fatherData"]
}
}
}
}
})
数据由父组件声明,同组件通过行内属性给子组件传递数据(传递的时候要注意使用v-bind
属性绑定),然后子组件通过props
接收父组件传过来的属性,props
是一个数组的形式,每一个数组的元素都是父组件传值用的属性的名字。
-
如果父组件给子组件传值,而子组件没有用
props
接收该数据,那么该属性就会自动的放到子组件最外层的标签中。 -
子组件通过
props
接收到的数据是不能set
的,子组件一旦直接更改接收到的属性就会报错。(这也符合Vue
单向数据流的特征) -
如果子组件非得要改变父组件传过来的属性,也是有办法的,就是父组件给子组件传递一个引用数据类型的数据,子组件接收到数据之后就可以任意的更改该引用数据类型的值。原因就是引用数据类型实质上是一个地址,当改变其中数据的值的时候,该地址并没有发生变化。所以对
Vue
来说,该数据并没有发生变化,因为数据的地址并没有变,变化的只是地址中存储的数据而已。
props属性验证
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
2、子传父
单向数据流决定了在vue
中数据是不能从子组件流向父组件的。但是子组件可以通过调用父组件传过来的方法实现子组件改变父组件的数据,虽然不是真正的父传子,但是却可以实现父传子的效果。
<div id="app">
<father></father>
</div>
<template id="father">
<div>
<h2>父组件的内容:{{myMsg}}</h2>
<son :change-father="change"></son>
</div>
</template>
<template id="son">
<div>
<input v-model="changeData" type="text">
<button @click="changeFather(changeData)">我是子组件,点我让父组件的内容改变!</button>
</div>
</template>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
<script>
Vue.component("father",{
template:"#father",
methods:{
change(val){
this.myMsg = val
}
},
data(){
return {
myMsg:"父组件的data"
}
}
})
Vue.component("son",{
template:"#son",
data(){
return {
changeData:"父组件数据要变成的值"
}
},
props:["changeFather"]
})
new Vue({
el:"#app",
})
</script>
类似于这种的其实真正意义上数据还是由父组件流向了子组件,因为单向数据流是我们不能改变的,之所以实现了子传父的效果,就是利用了父组件流向了子组件一个方法,而这个方法正好就是改变父组件数据的一个方法,子组件接收到该方法后,要想改变父组件的数据就只需要调用该父组件的方法去改变父组件的数据,实质上数据还是有父组件自己改变的,只不过发起者是子组件罢了。
3、通过事件绑定实现子传父
Vue中每一个组件实例都有自定义事件和触发事件的能力,因此,可以通过子组件出发父组件自定义的事件处理函数的方式去更改父组件的数据,实现子传父的效果。(这种方法和上面子传父的方法的原理上是一致的,只不过实现的形式不一样)
<div id="app">
<father></father>
</div>
<template id="father">
<div>
<h2>父组件的内容:{{myMsg}}</h2>
<son @change-father="change"></son>
</div>
</template>
<template id="son">
<div>
<input v-model="changeData" type="text">
<button @click="sonChangeFather">我是子组件,点我让父组件的内容改变!</button>
</div>
</template>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
<script>
Vue.component("father",{
template:"#father",
methods:{
change(val){
this.myMsg = val
}
},
data(){
return {
myMsg:"父组件的data"
}
}
})
Vue.component("son",{
template:"#son",
data(){
return {
changeData:"父组件数据要变成的值"
}
},
methods:{
sonChangeFather(){
this.$emit("change-father",this.changeData) //第一个参数是时间处理函数,后面的参数是处理函数要传递的实参
}
}
})
new Vue({
el:"#app",
})
</script>
不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或属性名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。
4、viewModel链和ref链
viewModel链
每一个组件实例上都会挂载$parent
、$children
、$root
,用于获取对应关系的组件。通过$parent
、$children
、$root
会将实例中的组件之间组成一条组件链,叫做viewModel
链,理论上在一个vue
实例中可以通过viewModel
链在任何两个组件之间进行组件之间的交互。
<div id="app">
<father></father>
</div>
<template id="father">
<div>
<h2>父组件的内容:{{myMsg}}</h2>
<son></son>
</div>
</template>
<template id="son">
<div>
<button @click="getFatherData">点击获取父组件数据</button>
</div>
</template>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
<script>
Vue.component("father",{
template:"#father",
data(){
return {
myMsg:"父组件的data"
}
}
})
Vue.component("son",{
template:"#son",
methods:{
getFatherData(){
console.log(this.$parent.myMsg);
}
}
})
new Vue({
el:"#app",
})
</script>
ref链
ref
链看起来和viewModel
链似乎是一个东西,只是在原来的viewMode
链上打了一些标记,当我们想要从链上获取某一环的时候,就可以直接通过这个标记拿到想要的环,而不必通过$parent
、$children
、$root
一环一环的去顺藤摸瓜。
<div id="app">
<father></father>
</div>
<template id="father">
<div>
<h2>父组件的内容:{{myMsg}}</h2>
<son ref="son"></son>
<button @click="getRefs">点击打印$refs</button>
</div>
</template>
<template id="son">
<div></div>
</template>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
<script>
Vue.component("father",{
template:"#father",
data(){
return {
myMsg:"父组件的data"
}
},
methods:{
getRefs(){
console.log(this.$refs)
}
}
})
Vue.component("son",{
template:"#son",
})
new Vue({
el:"#app",
})
</script>
- 如果多个组件或者元素标记了同一个名字,获取的应该是一个数组。
- 图片中获取的正是子组件实例。
- 在
Vue
中可以通过viewModel
链和ref
链获取到任意一个组件实例。
5、Event Bus事件总线
前面说过,每一个Vue
实例和组件实例都有自定义事件和触发事件的能力。
因此可以专门创建一个单独的Vue
实例来对实例中的事件进行管理,这个特殊的Vue
实例就像一条线一样,每一个实例都可以给这条线挂载事件也可以为达到某个目的去触发调用线上已经挂载的事件。所以叫做事件总线。
<div id="app">
<father></father>
</div>
<template id="father">
<div>
<h2>父组件的内容:{{myMsg}}</h2>
<son></son>
</div>
</template>
<template id="son">
<div>
<button @click="sonChangeFather">改变父组件的数据</button>
</div>
</template>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
<script>
var angel = new Vue();
Vue.component("father",{
template:"#father",
data(){
return {
myMsg:"父组件的data"
}
},
methods:{
change(){
this.myMsg = "变化后的data"
}
},
mounted(){
angel.$on("change-father",this.change)
}
})
Vue.component("son",{
template:"#son",
methods:{
sonChangeFather(){
angel.$emit("change-father")
}
}
})
new Vue({
el:"#app",
})
</script>
该例中,父组件在事件总线angel
上面挂载了一个改变自己数据的方法change-father
,子组件在点击按钮的时候触发了这个事件,并执行change-father
方法去改变父组件的数据。
同样的,在任何一个组件中都可以在事件总线上面挂载和出发调用事件。