Vue 组件:父子组件通信

子组件是不能直接访问父组件中的数据的,但有时候父子组件之间需要进行数据交互,这就涉及到了父子组件通信的问题。简单来说,父组件向子组件通信是通过
props

进行的,而子组件向父组件通信则是通过 自定义事件
进行的。
我们用一个简易的 todolist 案例来理解这两个过程。

todolist 案例

1.父传子

假定我们现在有一个需求:在输入框中输入待办事项,点击添加按钮可以将事项展现在页面上。如下图所示:

分析:页面分为两个部分,一部分是操作区,一部分是展示区。展示区可以用 li
,那么这些 li
就可以看作是可复用的子组件,而其它部分则看作是父组件,我们在父组件中操作,结果却是在子组件中显示的,所以这里是父组件向子组件通信的问题。
首先将根实例作为父组件,然后注册一个子组件,写好大概的结构:


    const cpn = {
      template:"#cpn"
    }
    const app  = new Vue({
      el:'#app',
      data:{
        newvalue:'',
        list:[]
      },
      methods:{
        addItem(){
          this.list.push(this.newvalue);
          this.newvalue = ''
        }
      },
      components:{
        cpn
      }
    })
    

    表单元素需要双向数据绑定,所以我们这里使用 v-model="newvalue"
    newvalue
    初始化的时候是空字符串,后面就代表我们输入的待办事项,监听按钮的点击事件并把它 push
    到空数组中,之后为了用户操作方便(不需要手动删除输入框内容),我们再把 newvalue
    置空。这时候,父组件的操作已经完成了,接下来要把数据传递给子组件并显示出来。

    list
    是要传递的数据,首先把它交付给自定义属性 list2
    ,对于子组件,它需要通过 props
    (可以是数组或者对象)去接收。之后,我们在子组件模板中进行列表的遍历,遍历的对象就是 list2 数组。
    代码如下:

    
    
    • {{item}}
    const cpn = {
      template:"#cpn",
      props:["list2"]
    }
    const app  = new Vue({
      el:'#app',
      data:{
        newvalue:'',
        list:[]
      },
      methods:{
        addItem(){
          this.list.push(this.newvalue);
          this.newvalue = ''
        }
      },
      components:{
        cpn
      }
    })
    

    2.子传父

    作为一个 todolist,除了添加之外应该还可以删除,所以接下来的需求是点击待办事项可以进行删除。如下图所示:


    分析:因为这里子组件只负责点击操作,实际的删除需要父组件自己去操作数据(类似于子组件打个电话告诉父组件该删除哪个东西了),所以这里涉及到了子组件向父组件通信的问题。

    这里首先还是监听待办事项的点击事件,点击后调用函数,之后执行函数中的 this.$emit('eventName',args)
    ,作用是由实例向外触发一个自定义事件(参数可选),之后父组件再监听这个自定义事件,一旦监听到事件就调用父组件(即根实例)下挂载的方法,来删除待办事项。
    代码如下:

    • {{item}}
    const cpn = {
      template:"#cpn",
      props:["list2"],
      methods:{
        remove(index){
          this.$emit("receive",index)  // 2.向外触发自定义事件 receive
        }
      }
    }
    const app  = new Vue({
      el:'#app',
      data:{
        newvalue:'',
        list:[]
      },
      methods:{
        addItem(){
          this.list.push(this.newvalue);
          this.newvalue = ''
        },
        // 4.执行 deleteItem 删除数组元素
        deleteItem(index){
          this.list.splice(index,1)
        }
      },
      components:{
        cpn
      }
    })
    

    props

    前面使用的 props
    是数组,实际开发中用的更多的其实是对象。作为对象的 props
    可以配置高级选项,如类型检测、自定义校验和设置默认值等。
    假定上面的子组件还接受了其它数据:

    const cpn = {
      template:"#cpn",
      methods:{
        .......
      },
      props:{
        propA:Array, // 接受的 propA 类型必须是数组。也可以指定自定义类型
        propB:{String,Number}, // propB 必须是字符串或者数字
        propC:{
          type:String,
          required:true    // 必须接受 propC,否则报错
        },
        propD:{
          type:String,
          default:"demo"   // 没有接受到 propD 时使用这个默认值
        },
        propD:{
          type:Object,
          default:function(){   // 数组或对象指定默认值时必须是一个函数
            return {message:"Hello"}
          }
        },
        propE:{
          validator(value){
            // 这个值必须匹配下列字符串中的一个
            return {'aaa','bbb'}.indexOf(value) !== -1
          }
        }
      }
    }
    

    到这里的话,父子组件之间的通信就已经结束了。使用 Vue 的时候应该避免直接去操作 dom,而是通过数据的改变让页面自动变化。

    • 父组件向子组件传值:在父组件中通过 v-on
      绑定自定义属性以存储父组件数据,然后子组件通过 props
      接收,这样就可以拿到父组件中的数据;
    • 子组件向父组件通信:子组件监听到事件后,通过 $emit
      向外触发自定义事件,父组件监听到该事件后操作数据。