• vue学习记录⑤(组件通信-父与子)


    今天我们看一下组件通信。

    经过前面几篇文章,我们已经可以构建出完整的单个组件,并利用路由使其串联起来访问了。

    但这明显还是不够的。一个页面不可能就是个单组件,一般是由多个组件合成的。正因为如此,组件之间肯定是有相互关系的,我们就称这种现象叫组件通信

    比如父组件发生了某项改变,子组件会跟着相应发生变化;反过来,子组件有了某种改变,父组件有时也会随之做出调整。那么这种现象我们称之为双向数据流动

    然而,vue的作者敏锐的认识到,双向数据流带来便捷的同时,也存在着极大的安全隐患。

    父组件将变化传递给子组件(父影响子),这是没问题的,在我们日常生活中也是极为普遍的现象(老子教训儿子是天经地义的),然而反过来子影响父的话,这就变得不可理喻了(儿子教训老子?)

    子组件修改父组件,增大了组件之间的耦合度。有时开发者根本没有意识到这种修改,这犹如埋下一颗定时炸弹,随着日后项目代码的膨胀,一旦引爆,问题排查难度也会呈指数级的徒增。

    为了斩断这种安全隐患,vue提倡的是单向数据流动——也就是只能父影响子,而反过来则不成立。

    父传子,我们利用props作为桥梁。下面看一个例子。

    首先我将目录调整一下:

    其中,helloworld.vue组件,我们设定其为父组件;而children目录下的child.vue,我们设定为其子组件。

    Helloworld.vue代码如下:

    <!--模板部分-->
    <template>
     <div class="container">
      <h1>我是父亲</h1>
    <!--绑定父组件的某个变量myMsg-->
      <div>
    <!--注意子组件接受变量时,需要变为驼峰命令法sendMessage-->
       <child v-bind:send-message="myMsg"></child>
      </div>
    
     </div>
    </template>
    <!--js部分-->
    <script>
    //引入子组件,需要设定子组件的name为child
    import child from './children/child.vue'
    export default {
     name:'helloworld',
     data(){
      return {
       myMsg:'hello,my son!'
      };
     },
     components:{child}//局部注册子组件
    }
    </script>
    <!--样式部分-->
    <style>
    .container{
     background: #ccc;
     color:greenyellow;
    }
    
    </style>

    大家注意下别把组件和路由的概念搞混了,二者之间一毛钱关系都没有。要使用组件,需做局部或者全局注册。我这里只做局部注册,大家仔细看一下写法。

    我们在父组件定义了一个myMsg这个变量,然后通过v-bind绑定到了child组件中。这时,child组件就可以通过props来接收这个变量了。

    child.vue代码如下:

    <!--模板部分-->
    <template>
     <div class="sub-container">
      <h3>我是儿子</h3>
    
      <div>我从父亲那边接收过来的信息:</div>
      <div>{{sendMessage}}</div>
     </div>
    </template>
    <!--js部分-->
    <script>
    //子组件
    export default {
     name:'child',//必须设定name,否则无法在父组件import
     props:['sendMessage'],
     data(){
      return {
    
      };
     }
    }
    </script>
    <!--样式部分-->
    <style>
    .sub-container{
     background: blue;
     color:red;
    }
    </style>

    一个简单的父子通信案例就完成了,看一下路由router/index.js,代码如下:

    import Vue from 'vue'
    import Router from 'vue-router'
    import Hello from '@/components/Hello'
    import HelloWorld from '@/components/Helloworld' //我们新定义的组件
    
    Vue.use(Router)
    
    export default new Router({
      routes: [{
          path: '/',
          name: 'Hello',
          component: Hello
        },
        { //新路由
          path: '/helloworld/:id',
          name: 'HelloWorld',
          component: HelloWorld,
        }
      ]
    })

    运行一下npm run dev,看一下结果:

    可以看到,子组件顺利接收到了父组件传来的信息。

    那么,到底子组件能否影响父组件呢?我们将以上代码做一下调整。

    调整后的Helloworld.vue

    <!--模板部分-->
    <template>
     <div class="container">
      <h1>我是父亲</h1>
    
      <input type="text" v-model="myMsg">
      <br>
    <!--绑定父组件的某个变量myMsg-->
      <div>
    <!--注意子组件接受变量时,需要变为驼峰命令法sendMessage-->
       <child v-bind:send-message="myMsg"></child>
      </div>
    
     </div>
    </template>
    <!--js部分-->
    <script>
    //引入子组件,需要设定子组件的name为child
    import child from './children/child.vue'
    export default {
     name:'helloworld',
     data(){
      return {
       myMsg:'hello,my son!'
      };
     },
     components:{child}//局部注册子组件
    }
    </script>
    <!--样式部分-->
    <style>
    .container{
    background: #ccc;
    color:greenyellow;
    }
    
    </style>

    调整后的child.vue

    <!--模板部分-->
    <template>
     <div class="sub-container">
      <h3>我是儿子</h3>
    
      <div>我从父亲那边接收过来的信息:</div>
      <div>{{sendMessage}}</div>
      <div>是否可以子影响父呢?</div>
      <input type="text" v-model="sendMessage">
     </div>
    </template>
    <!--js部分-->
    <script>
    //子组件
    export default {
     name:'child',//必须设定name,否则无法在父组件import
     props:['sendMessage'],
     data(){
      return {
    
      };
     }
    }
    </script>
    <!--样式部分-->
    <style>
    .sub-container{
     background: blue;
     color:red;
    }
    </style>

    运行一下npm run dev,看一下结果:

    可以看出,子组件是无法影响父组件的,这样父组件就成功解耦了。

    大家要注意一个问题——传递的数据类型问题。

    传递的是值类型肯定没问题,假如是对象或者数组这样的引用类型,变量共用同一内存,所以父子会出现双向影响,这个问题一定要注意。

    为了杜绝隐患,提高安全性,我们在子组件将prop接收的类型做一下强制检测,假如传入的数据类型并不是我需要的,可以抛出异常。

    Helloworld.vue

    <!--模板部分-->
    <template>
     <div class="container">
      <h1>我是父亲</h1>
    
      <input type="text" v-model="myMsg">
    <!--<input type="text" v-model="myNum">-->
      <ul v-for="item of myObj">
       <li>{{item.name}}</li>
      </ul>
     <br>
    <!--绑定父组件的某个变量myMsg-->
      <div>
    <!--注意子组件接受变量时,需要变为驼峰命令法sendMessage-->
       <child :send-message="myMsg" :send-num="myNum" :send-obj="myObj"></child>
      </div>
    
     </div>
    </template>
    <!--js部分-->
    <script>
    //引入子组件,需要设定子组件的name为child
    import child from './children/child.vue'
    export default {
     name:'helloworld',
     data(){
      return {
       myMsg:'hello,my son!',
       myNum:'123',
       myObj:[{id:1,name:'Tom_Lo'},{id:2,name:'tom'}]
      };
     },
     components:{child}//局部注册子组件
    }
    </script>
    <!--样式部分-->
    <style>
    .container{
     background: #ccc;
     color:green;
    }
    </style>

    父组件这次传了三个不同类型的信息,分别是字符串、数字和json对象。其中,数字类型我故意写错了,看一下子组件是否会检测出来。

    child.vue

    <!--模板部分-->
    <template>
     <div class="sub-container">
      <h3>我是儿子</h3>
    
      <div>我从父亲那边接收过来的信息:</div>
      <div>字符串:{{sendMessage}}</div>
      <div>数字:{{sendNum}}</div>
      <div>
       <ul v-for="item of list">
        <li>{{item.name}}</li>
       </ul>
      </div>
    
     </div>
    </template>
    <!--js部分-->
    <script>
    //子组件
    export default {
     name:'child',//必须设定name,否则无法在父组件import
     props:{
     sendMessage:{
      type:String,//传入类型必须是字符串
      required:true//必传
     },
     sendNum:{
      type:Number,//传入类型必须是数字
      required:true
     },
    //如果是数组或对象
     sendObj:{
      validator:function(val){
    
       if(Object.prototype.toString.call(val) === '[object Array]' || Object.prototype.toString.call(val) === '[object Object]'){
        return true;
        }
       }
      }
     },//声明验证类型
     data(){
      var parentObj = this.sendObj;//获取父组件传来的json
      var childObj =JSON.parse(JSON.stringify(parentObj));//复制一份,避免污染父组件的数据
      childObj.push({id:3,name:'mike'});//追加一个对象
       return {
        list:childObj
       };
     }
    }
    </script>
    <!--样式部分-->
    <style>
    .sub-container{
    background: blue;
    color:red;
    }
    </style>

    大家需要注意下子组件props的验证规则。

    那么对于容易引发错误的引用类型,大家应该如何避免呢?

    当我们接收过来这个对象数据后,先将其拷贝一份副本,然后用副本做增加修改等操作,这样便不会影响到父组件的数据了。

    我们运行一下看看:

    看起来验证规则生效了哈~~

    那么以上都是同步的情况,那么异步呢?

    假设父组件的数据是异步ajax获取的,然后渲染页面。那么此时,子组件如何跟着更新视图呢?

    这就得有个监听机制。当子组件监听到父组件的数据到位后,将本地数据更新即可。

    vue提供的watch可以达到此目的,我们看一下例子。

    Helloworld.vue

    <!--模板部分-->
    <template>
     <div class="container">
      <h1>我是父亲</h1>
      <input type="text" v-model="myMsg">
    <!--<input type="text" v-model="myNum">-->
      <ul v-for="item of myObj">
       <li>{{item.name}}</li>
      </ul>
     <br>
    <!--绑定父组件的某个变量myMsg-->
      <div>
    <!--注意子组件接受变量时,需要变为驼峰命令法sendMessage-->
       <child :send-message="myMsg" :send-num="myNum" :send-obj="myObj"></child>
      </div>
    
     </div>
    </template>
    <!--js部分-->
    <script>
    //引入子组件,需要设定子组件的name为child
    import child from './children/child.vue'
    export default {
     name:'helloworld',
     data(){
    //初始化obj
      var obj = {
       myMsg:'hello,my son!',
       myNum:123,
       myObj:[]
      };
    //延时返回myObj,模拟ajax的延时情况
      setTimeout(function(){
       obj['myObj'] = [{id:1,name:'jack'},{id:2,name:'tom'}];
    
      },2000);
      return obj;
     },
     components:{child}//局部注册子组件
    }
    </script>
    <!--样式部分-->
    <style>
    .container{
     background: #ccc;
     color:green;
    }
    </style>

    child.vue

    <!--模板部分-->
    <template>
     <div class="sub-container">
      <h3>我是儿子</h3>
    
      <div>我从父亲那边接收过来的信息:</div>
      <div>字符串:{{sendMessage}}</div>
      <div>数字:{{sendNum}}</div>
      <div>
       <ul v-for="item of list">
         <li>{{item.name}}</li>
       </ul>
      </div>
    
     </div>
    </template>
    <!--js部分-->
    <script>
    //子组件
    export default {
     name:'child',//必须设定name,否则无法在父组件import
     props:{
      sendMessage:{
       type:String,//传入类型必须是字符串
       required:true//必传
      },
      sendNum:{
       type:Number,//传入类型必须是数字
       required:true
      },
    //如果是数组或对象
      sendObj:{
       validator:function(val){
    
        if(Object.prototype.toString.call(val) === '[object Array]' || Object.prototype.toString.call(val) === '[object Object]'){
         return true;
        }
       }
      }
     },//声明验证类型
     data(){
    
       return {
        list:null
       };
     },
     watch:{//监控父组件sendObj的变化
      sendObj(newval,oldval){
    
       if(newval !== oldval){
    
        var newobj =JSON.parse(JSON.stringify(newval));//复制一份,避免污染父组件的数据
        newobj.push({id:3,name:'mike'});//追加一个对象
        this.list = newobj;//将新值赋予data的list
       }
    
      }
     }
    }
    </script>
    <!--样式部分-->
    <style>
    .sub-container{
     background: blue;
     color:red;
    }
    </style>

    watch的函数名称,就是要监听的值,这里我们监听父组件传来的prop;而newval和oldval参数,分别代表了新值和旧值。

    在父组件未更新之前,子组件监听到的sendObj,新值和旧值都是空数组,二者是相等的;而当监听到二者发生不等时,就说明父组件传来的信息发生了改变。我们取新值,并将拷贝后的数据传给data里面的list,然后更新到视图。

    看一下运行情况:

    父组件与子组件的交互就实现了。

    参考文章:http://mp.weixin.qq.com/s/rrKGRPTIt-aiZXijIli3ww

    参考文章:http://www.cnblogs.com/ghostwu/p/7518002.html

  • 相关阅读:
    2018 BAT最新 php面试必考题
    2018 BAT最新《前端必考面试题》
    BAT-Java必考面试题集
    BAT-Python面试题
    BAT大数据面试题
    特殊密码锁 的通过码是:(请注意,在openjudge上提交了程序并且通过以后,就可以下载到通过码。请注意看公告里关于编程作业的说明)
    OpenJudge百炼-2747-数字方格-C语言-枚举
    poj2814-拨钟问题-C语言-枚举算法
    中国MOOC_面向对象程序设计——Java语言_第4章 继承与多态_第4周编程题
    中国MOOC_面向对象程序设计——Java语言_第3周 对象容器_1查找里程
  • 原文地址:https://www.cnblogs.com/luxiaoxing/p/7586630.html
Copyright © 2020-2023  润新知