Vue.js 组件间的数据传递方法

2022-03-30 20:16:22 浏览数 (1)

背景

想总结一下组件中传递数据的方法。

父组件向子组件传数据

这种应该是 vue.js 中最常见也是最为自然的一种方式了,要求我们在子组件中声明 props 然后在父组件中为子组件的 prop 赋值。

1、第一步在子组件中声明 props 为父组件传递数据做好准备工作

代码语言:javascript复制
<template>
    <ul>
        <li v-for="person in persons" :key="person.id"> name={{person.name}} gender={{person.gender}} </li>
    </ul>
</template>

<script>
export default {
    name:'Child',
    props:['persons']
}
</script>

2、在父组件中通过 prop 传递值给子组件

代码语言:javascript复制
<template>
    <div>
        <Child :persons="persons" />
    </div>
</template>

<script>
import Child from './Child.vue'

export default {
  components: { Child },
    name:'Parent',
    data() {
        return {
            persons:[
                {id:'001',name:'张三',gender:'男'},
                {id:'002',name:'李四',gender:'男'},
                {id:'003',name:'王五',gender:'男'},
            ]
        }
    }
}
</script>

子组件向父组件传递数据 - props 实现

通过 props 传递进来的数据,不允许在子组件里更改的,如果强行更改的话 Vue.js 会报错。OOP 的原则,哪个对象的数据就应该调用哪个对象的方法来管理,在 Vue.js 中也是这样。

那现在就变成了怎么调用到父组件的方法了,办法说来也简单那不就是,把父组件的方法通过 props 传递给子组件,这样子组件就能调用到了。

1、第一步 在子组件中定义用于传方法的 prop

代码语言:javascript复制
<template>
<div>
    <ul>
        <li v-for="person in persons" :key="person.id"> name={{person.name}} gender={{person.gender}} </li>
    </ul>

    <span>name:</span><input type="text" v-model="name"/><br>
    <span>gender:</span><input type="text" v-model="gender"/><br>
    <button @click="addPerson">增加</button>
</div>
</template>

<script>
export default {
    name:'Child',
    props:['persons','addPersonCallBack'], // 添加用户调用父组件方法的 prop -- addPersonCallBack
    data(){
        return {
            name:'',
            gender:''
        }
    },
    methods:{
        addPerson(){
            if(this.name == ''){
                return
            }
            else if(this.gender == ''){
                return
            }

            let person = {
                name:this.name,
                gender:this.gender
            }

            this.addPersonCallBack(person)

            this.name = ''
            this.gender = ''
        }
    }
}
</script>

2、父组件把更新数据的方法传给子组件

代码语言:javascript复制
<template>
    <div>
        <Child :persons="persons" :addPersonCallBack="addPerson"/>
    </div>
</template>

<script>
import Child from './Child.vue'

export default {
  components: { Child },
    name:'Parent',
    data() {
        return {
            persons:[
                {id:'001',name:'张三',gender:'男'},
                {id:'002',name:'李四',gender:'男'},
                {id:'003',name:'王五',gender:'男'},
            ]
        }
    },
    methods:{
        addPerson(person) {
            this.persons.push(person)
        }
    }
}
</script>

工作正常。

子组件向父组件传递数据 - 自定义事件实现

这个实现和 props 在代码上差不多,props 实现是在子组件里直接调用父组件的函数。事件的实现手法是子组件触发事件,并配置好对应的参数值,父组件只要注册好事件的监听就行了。

1、子组件的代码如下

代码语言:javascript复制
<template>
<div>
    <ul>
        <li v-for="person in persons" :key="person.id"> name={{person.name}} gender={{person.gender}} </li>
    </ul>

    <span>name:</span><input type="text" v-model="name"/><br>
    <span>gender:</span><input type="text" v-model="gender"/><br>
    <button @click="addPerson">增加</button>
</div>
</template>

<script>
export default {
    name:'Child',
    props:['persons'],
    data(){
        return {
            name:'',
            gender:''
        }
    },
    methods:{
        addPerson(){
            if(this.name == ''){
                return
            }
            else if(this.gender == ''){
                return
            }

            let person = {
                name:this.name,
                gender:this.gender
            }

            // 只用改这一行,把之前的函数调用,改成事件触发
            this.$emit('addPersonEvent',person)

            this.name = ''
            this.gender = ''

        }
    }
}
</script>

2、父组件的代码

代码语言:javascript复制
<template>
    <div>
        <Child :persons="persons" @addPersonEvent="addPerson"/> <!-- 注册事件监听-->
    </div>
</template>

<script>
import Child from './Child.vue'

export default {
  components: { Child },
    name:'Parent',
    data() {
        return {
            persons:[
                {id:'001',name:'张三',gender:'男'},
                {id:'002',name:'李四',gender:'男'},
                {id:'003',name:'王五',gender:'男'},
            ]
        }
    },
    methods:{
        addPerson(person) {
            this.persons.push(person)
        }
    }
}
</script>
可以看到 vue.js 中的自定义事件,是对 dom 原生事件的一个包装。另外除了用 html 表达监听之外,功能更加强大的是通过 js 来表达监听,js 写法也更加常用。
代码语言:javascript复制
<template>
    <div>
        <Child :persons="persons" ref="child"/> <!-- 注册事件监听-->
    </div>
</template>

<script>
import Child from './Child.vue'

export default {
  components: { Child },
    name:'Parent',
    data() {
        return {
            persons:[
                {id:'001',name:'张三',gender:'男'},
                {id:'002',name:'李四',gender:'男'},
                {id:'003',name:'王五',gender:'男'},
            ]
        }
    },
    methods:{
        addPerson(person) {
            this.persons.push(person)
        }
    },
    mounted(){
        this.$refs.child.$on('addPersonEvent',this.addPerson)
    }
}
</script>

数据传递集大成者 - 全局事件总线

通过前面用 js 实现监听的例子我们发现,我们只要在事件的生产者上调用 emit 方法,事件消费者通过 on 主动的注册事件回调,就能完成数据传递了。

现在有两个事实,1 Vue 实例身上一定有 emit 和 on 方法,2、只要把 Vue 实例的引用添加到 Vue.prototype 的属性上,那么所有的组件都能用到 emit 和 on 方法。这样做的话事件的触发和回调都集中了。

1、改一下 main.js 的写法

代码语言:javascript复制
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),

  beforeCreate(){
    Vue.prototype.$bus = this; // 给它把引用加上去
  }
}).$mount('#app')

2、触发的代码要改一下

代码语言:javascript复制
<template>
<div>
    <ul>
        <li v-for="person in persons" :key="person.id"> name={{person.name}} gender={{person.gender}} </li>
    </ul>

    <span>name:</span><input type="text" v-model="name"/><br>
    <span>gender:</span><input type="text" v-model="gender"/><br>
    <button @click="addPerson">增加</button>
</div>
</template>

<script>
export default {
    name:'Child',
    props:['persons'],
    data(){
        return {
            name:'',
            gender:''
        }
    },
    methods:{
        addPerson(){
            if(this.name == ''){
                return
            }
            else if(this.gender == ''){
                return
            }

            let person = {
                name:this.name,
                gender:this.gender
            }

            // 只用改这一行,把之前的函数调用,改成事件触发
            this.$bus.$emit('addPersonEvent',person)

            this.name = ''
            this.gender = ''

        }
    },
    beforeDestroy(){
        this.$bus.off("addPersonEvent")
    }
}
</script>

3、注册回调的代码也要改一下

代码语言:javascript复制
<template>
    <div>
        <Child :persons="persons" ref="child"/> <!-- 注册事件监听-->
    </div>
</template>

<script>
import Child from './Child.vue'

export default {
  components: { Child },
    name:'Parent',
    data() {
        return {
            persons:[
                {id:'001',name:'张三',gender:'男'},
                {id:'002',name:'李四',gender:'男'},
                {id:'003',name:'王五',gender:'男'},
            ]
        }
    },
    methods:{
        addPerson(person) {
            this.persons.push(person)
        }
    },
    mounted(){
        this.$bus.$on('addPersonEvent',this.addPerson)
    }
}
</script>

这样做的好处是完全不用在乎组件父子之间的关系,任何两个组件之间都能传递数据了。

总结

组件间关系

适合的传递类型

父给子传

props

子给父传

自定义事件

其它

全局事件总线

0 人点赞