VUE.js组件通讯精髓概括(基础篇)

前言

文章涉及的内容可能不全面,但量不少,须要慢慢看。我花了很长的时间整理,用心分享心得,但愿对你们有所帮助。可是不免会有打字的错误或理解的错误点,但愿发现的能够邮箱告诉我1163675970@qq.com,我会及时的进行修改,只但愿对你有所帮助,谢谢。javascript

vue 何为组件化?

咱们能够很直观的将一个复杂的页面分割成若干个独立组件html

每一个组件包含本身的逻辑和样式再将这些独立组件组合完成一个复杂的页面。vue

将页面中重复的的功能抽离出来封装成一个单独的组件,在任何须要的地方使用该组件便可;java

这样既减小了 逻辑复杂度 , 提升代码的可复用程度和可维护性;node

页面是组件的容器,组件自由组合造成完整的界面,当不须要某个组件时,或者想要替换某个组件时,能够随时进 行替换和删除,而不影响整个应用的运行。数组

每一个组件都是一个 Vue 的实例,那么这个组件也有本身的生命周期,而且也有 data、computed、methods、watch这些属性,每一个组件都有本身私有的数据;还能够接受来自上层组件传入的数据;浏览器

组件化开发的好处

  • 提升开发效率
  • 方即可复用性
  • 便于协同开发
  • 更容易被管理和维护
  • 每个VUE组件都是一个独立的个体(独立的VM实例):DATA是独立的(不一样组件的DATA应该互不干扰)、有完整的生命周期、方法等也是独立的
  • 可以实现组件的嵌套:须要掌握组件之间的信息通讯
  • ....

VUE中的组件

  • vue中的组件是自定义的标签,能够扩展原生的html元素,封装可重用的代码
  • 一个 自定义标签 vue 就会把他当作一个组件 (除w3c规定之外的标签), 自定义标签本来没有实际意义,可是vue会给这些标签赋予必定的意义

全局组件 & 局部组件

  • 全局组件:无需单独引用或者配置,直接在大组件中调取全局组件便可bash

    能够声明一次在任何地方使用app

    通常在写插件时全局组件使用的多一些ide

    vue.filters 它也是放到全局中,不用声明直接用

    全局组件是声明在 vue类 上的

    <!-- IMPORT CSS-->
    <body> <div id="app"> <h3 v-text="title"></h3> <my-handlesome></my-handlesome> <!-- 我但愿 my-handlesome 实现效果和下面的div实现一样的效果 <div> 我很帅气!!!</div> --> </div> <!-- IMPORT JS--> <script src="./20191007/node_modules/vue/dist/vue.js"></script> <script> /* * 全局组件:无需单独引用或者配置,直接在大组件中调取全局组件便可 * Vue.component(componentName,options) * + options可使用的有VM实例具有的大部分(DATA、METHODS、生命周期函数...) * + 每调用一次组件都是建立一个单独的VUE实例(VueComponent -> Vue) * * 组件命名的规范: * 1.组件名不要带有大写 多个单词用 — 中划线分隔 kebab-case * 2.只要组件名和定义名字相同是能够的 (只有首字母能够大写) PasalCase * 3.html采用短横线隔开命名法js中转驼峰也是能够的 * */ Vue.component('my-handlesome',{ //=> 一个对象能够成一个组件 /* 至关于用 template 中的 div 标签替换 咱们自定义的 my-handlesome 标签*/ template:'<div>{{msg}}</div>', //=> 组件是中data数据必须是一个函数 ,返回一个实例(对象)做为组件的数据 data(){ return{ msg:'我很帅气!!!' } } }); let vm = new Vue({ el:'#app', data:{ title:"你自认为本身颜值???" } }); </script> </body> 复制代码
  • 局部组件 :1- 建立 2- 注册 3- 引用

    必须告诉这个组建属于谁

    局部组件是声明在 实例 vm 上的

    组件是相互独立的 不能直接跨做用域 实例(vm)也是一个组件, 组件中拥有生命周期函数

    <!-- IMPORT CSS-->
    <body> <div id="app"> <h3 v-text="title"></h3> <handelesome1></handelesome1> <handelesome2></handelesome2> </div> <!-- IMPORT JS--> <script src="./20191007/node_modules/vue/dist/vue.js"></script> <script> /* * 局部组件-使用三部曲: * 1. 建立组件 * 2. 注册组件 * 3. 引用组件 * 组件是相互独立的 不能直接跨做用域 实例(vm)也是一个组件, 组件中拥有生命周期函数 * * */ let obj ={ face:'丑到爆' }; //=> 若是组件共用了数据,会致使数据同时更新 (=错误写法=) (可是组件 特色 独立性) /* 子组件不能直接使用父组件的数据 (组件之间的数据交互) 组件理论上能够无限嵌套 */ //=> 1. 建立这个组件 let handelesome1 ={ template:`<div @click="fn">1.帅气 {{face}} {{a}}</div>`, /* face='颜值爆表' === @click="fn"*/ data(){ //=> 这里的data 必须是一个函数 return obj }, methods:{ fn(){ //=> this 为当前的组件 this.face = '颜值爆表' } } } ; let handelesome2 ={ template:'<div>2.霸气 {{face}}{{a}}</div>', data(){ return obj } } ; let vm = new Vue({ el:'#app', data:{ //=> 只有这里的data是 对象 title:"你自认为本身颜值???", //=> 这里的a 属性 在 局部组件中调用引起报错 //=> a:1 }, components:{ //=> 2. 注册这个组件 ES6名字同样写一个 handelesome1, handelesome2 } }); </script> </body>
    复制代码

嵌套组件

  • 1- 建立 2- 注册 3- 引用
  • 若是要在一个组件使用另外一个组件,
    • 1.先保证使用的组件得是真实存在,
    • 2.在须要引用这个组件的实例上经过components注册这个组件,
    • 3.组件须要在父级的模板中经过标签的形式引入
<body>
<div id="app">

</div>

<!-- IMPORT JS-->
<script src="./20191007/node_modules/vue/dist/vue.js"></script>
<script>
   /*
   最终页面渲染效果
   parent
   son
   grandson
   */
   
   //=> 首先建立三个 组件 顺序颠倒过来  父亲 儿子 孙子   、
     
   /*
   * 若是要在一个组件使用另外一个组件,
   *     1.先保证使用的组件得是真实存在,
   *     2.在须要引用这个组件的实例上经过components注册这个组件,
   *     3.组件须要在父级的模板中经过标签的形式引入
   * */
   //=> 孙子
   let grandson = { template:'<div>grandson</div>'};
   //=> 儿子
   let son = { template:'<div>son <grandson></grandson></div>',
       components:{
           grandson
       }
   };
    //=> 父亲
    let parent = {
        template:'<div>parent <son></son></div>',
        components: {
            son
        }
    };
    let vm = new Vue({
        el:'#app',
        //=> 在此处注册组件
        template:"<parent></parent>",
        components:{
            parent
        },
        data:{

        }
    });

</script>
</body>
复制代码

父传子-props--属性传递

  • props:[]方式传递属性 以及 对象 的方式传递属性
  • 对象属性传递的参数校验
    • type:[] 校验类型
    • default:0, 设置默认值
    • required: true, 设置是必须传递 属性,可是不能和default同时使用
    • validator(val){ } 自定义校验器
<body>
<div id="app">
    <!--父亲基于胡子语法取本身的 money 属性 -->
    父亲:{{money}}
    <!--
        当前组件的属性 = 父级的值
        给儿子加了一个m属性,属性对应的数据是属于父亲的  传递给儿子,能够传递多个
    -->
    <child :m="money"></child> <!--若是想传的是一个数字就加 : 否则是字符串-->
    <!--<child> </child> //=>这样才是没有传 而不是 :m="" -->
</div>
<!-- IMPORT JS-->
<script src="./20191007/node_modules/vue/dist/vue.js"></script>
<script>
    <!--vm 组件也是根实例-->
    //=> 父传递子 属性传递
    let vm = new Vue({ //=> parent
        el:'#app',
        data:{
            money:400
        },
        components:{
            child:{
                /*
                 template:'<div>儿子 {{money}}</div>'
                 儿子 组件取 父亲的 money 属性
                 属性没有拿到还引起了报错
                 由于 两个组件间做用域是独立的
                 
                 */
                 
                props:{ //=> 对象的形式能够对传的值进行检验 ,校验时不能阻断代码执行,只是阻断而已
                    m:{ //=> 校验属性的类型 不带 : 号获得的是字符串money  :m='1' :m='true'

                        type:[String,Function,Boolean,Array,Number],
                      /*  default:0, //=> 能够给m设置默认值,若是不传默认调用 default*/
                        required: true, //=> 此属性是必须传递,可是不能和default同时使用
                        validator(val){ //=>  val===money第一个参数是当前传递的值,返回true表示经过 false 反之
                            return val>300//=> 自定义校验器 

                        }
                    }
                },
                 //=> 基于 props:[]属性 儿子这里要接受一下父亲的’money‘
               /* props:['m'], 
               //=> this.m = 100;会在当前 子组件实例上 声明一个 m的属性,存的父亲传过来的money
                    也和data同样会影响视图渲染吗,可是子组件不能基于this.xxx来直接修改属性
                    
               */
                template:'<div>儿子:{{m}}</div>'
            }
        }
    });
</script>
</body>
复制代码

基于ref实现父子组件信息通讯

  • ref 若是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;若是用在子组件上,引用就指向组件实例,基于此能够快速获取和操做子组件中的数据
  • parent和children是获取组件和子组件的实例,只不过$children是一个数组集合,须要咱们记住组件顺序才能够

VUE--发布emit  订阅on

订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象

  1. 建立一个实例,至关于传建一个任务队列
  2. vm.$on(自定义事件,要执行的方法) 把自定义事件和方法订阅 到任务队列中;值得注意的是:咱们调取子组件的时候,基于 @xxx='func也至关于给子组件所在实例的任务队列订阅一个方法
  3. vm.$emit(自定义事件,须要传参的信息)把指定自定义事件中的 方法触发执行,能够给方法传递对应的信息

vue的单向数据流

全部的 prop 都使得其父子 prop 之间造成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,可是反过来则不行。

Vue 的父组件和子组件生命周期钩子函数执行顺序能够归类为如下 4 部分:

  • 加载渲染过程:父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

  • 子组件更新过程:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

  • 父组件更新过程:父 beforeUpdate -> 父 updated

  • 销毁过程:父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

每次父级组件发生更新时,子组件中全部的 prop 都将会刷新为最新的值。这意味着你不该该在一个子组件内部改变 prop。若是你这样作了,Vue 会在浏览器的控制台中发出警告。子组件想修改时,只能经过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。

有两种常见的试图改变一个 prop 的情形 :

  • 这个 prop 用来传递一个初始值;这个子组件接下来但愿将其做为一个本地的 prop 数据来使用。 在这种状况下,最好定义一个本地的 data 属性并将这个 prop 用做其初始值
  • 这个 prop 以一种原始的值传入且须要进行转换。 在这种状况下,最好使用这个 prop 的值来定义一个计算属性
<!DOCTYPE html>
<html> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <son :msg="pmsg"></son> </div> <script src="vue.js"></script> <script> let son = { data() { return { hello: 'xxxx' } }, props: ['msg', 'changePMSG'], template: '<span>{{msg}} <button @click="fn">dddd</button></span>', methods: { fn() { this.msg = 1233; // props 中的数据也会代理到子组件的实例身上,能够直接经过 this 访问 } } }; let vm = new Vue({ el: '#app', data: { pmsg: 'msg from parent' }, methods: { changeMsg() { this.pmsg = 123445 } }, components: { son } }); // 1. 子组件中的数据不能全是写死的,而是有一部分从父组件传递过来的 // 2. 为了把父组件的数据传递给子组件,子组件在标签上动态绑定一个属性,这个属性绑定父组件的数据,而且在子组件的props中注册这个属性 // 3. 子组件若是想使用父组件的数据,就使用对应的 props 就能够(父传子用props) // 单向数据流:数据只能经过父组件传递给子组件,而不能直接从子组件传给父组件,子组件也不能直接修改父组件的数据;当父组件的数据发生改变以后,子组件的收到的数据也会跟着变化;如上面的例子,直接修改从父组件中的数据会引起Vue的报错; // 若是子组件想修改父组件的数据,只能通知父组件,让父组件修改数据; </script> </body> </html> 复制代码

子传父--发布$emit 通知

  • 基于 发布订阅模式 on 及emit
  • 单向数据流来实现子通知父
<body>

<div id="app"> 父亲:{{money}} <!-- 儿子里面绑定了一个事件 child.$on('child-msg', things) 绑定的事叫作 child-msg ,事情主体是父亲的 things 此时咱们已经在儿子上绑定了一个child-msg ,当咱们点击的时候让child-msg触发,触发时能够传参, --> <child :m="money" @child-msg="things"></child> </div>
<!--
    父亲绑定一些事件,儿子触发这个事件将参数传递过去 ,
    单项数据流, 父亲数据刷新,儿子就刷新
-->
<!-- IMPORT JS--> <script src="./20191007/node_modules/vue/dist/vue.js"></script> <script> //=> let vm = new Vue({ //=> parent el:'#app', data:{ money:400 }, methods:{ //=> 儿子要触发这个事件,而且传一个参数 , 将这次事件绑定给 things(val){ //=> 至关于 $on('xxx',things) //=>儿子通知父亲多给点钱 当父亲接收到过来的800 更新本身的价格 //=> this为父亲 this.money=val; } }, components:{ child:{ props:['m'], template:'<div>儿子:{{m}} <button @click="getMoney()">多要钱</button></div>', methods: { getMoney(){ /* this为当前的 child 当点击儿子组件时触发的事件child-msg , 当child-msg事件触发的时候,会触发 things 而且将参数 800 传给 父亲的things 的 val */ this.$emit('child-msg',800);//=> 触发本身的自定义事件,让父亲的方法执行 ,方法是父亲的,属性永远是当前组件的 } } } } }); </script> </body>
复制代码

事件总线 -- EventBus

什么是事件总线?

  • 每一个组件都是一个 Vue 的实例,相互之间不能互通数据;

  • 要修改数据必定要通知,因此找一个第三方,让第三方监听事件,在事件触发时执行对应的修改数据的操做,这个第三方就是事件总线;

事件总线的用法

  • 建立一个空的 Vue 实例;let eventBus = new Vue();

  • eventBus.$on(自定义事件名, 事件函数) 监听事件

  • eventBus.$emit(事件名) 触发事件

<!DOCTYPE html>
<html> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <prev></prev> <next></next> </div> <script src="vue.js"></script> <script> // 每一个组件都是一个 Vue的实例,相互之间不能互通数据; // 要修改数据必定要通知,因此找一个第三方,让第三方监听事件,在事件触发时执行对应的修改数据的操做; // eventBus.$on(自定义事件名, 事件函数) 监听事件 // eventBus.$emit(事件名) 触发事件 let eventBus = new Vue(); // 这第三方就是一个空的 Vue 实例,叫作事件总线 EventBus let prev = { data() { return { color: 'green' } }, created() { eventBus.$on('changeRed', this.toRed) // 监听 changeRed 事件,当事件触发时,会执行 this.toRed 方法 }, methods: { toRed(x) { console.log(x); x 是事件触发时,传递的数据 this.color = 'red'; } }, template: `<div :style="{background: color}">{{color}}</div>` }; let next = { data () { return { color: '红色' } }, methods: { red() { eventBus.$emit('changeRed', 'hahaha') } }, template: `<div><button @click="red">变红</button></div>` }; let vm = new Vue({ el: '#app', data: {}, components: { prev, next } }); // 兄弟组件之间通讯,经过 EventBus,谁的数据须要被改变,谁监听事件,谁发起改变谁触发事件;例如本例中,next 要修改 prev 的数据,因此在 prev 的 created 中 eventBus.$on() 监听事件,而 next 发起改变,因此在 next 中 $emit() 事件; // 哥哥改弟弟同理; </script> </body> </html> 复制代码

插槽 slot

插槽是什么?

当引用组件时,咱们能够向组件的标签中嵌入内容。这些内容能够嵌入到子组件中,可是须要使用插槽机制即,slot;

如何使用插槽

  • 首先在建立子组件时须要声明一个 slot 标签占位;

  • 在组件标签中嵌入内容

具名slot和匿名slot

  • 匿名 slot,在声明 slot 时不写 name 属性,嵌入在组件标签中没有写 slot 属性的标签都会放在匿名 slot 中

  • 具名 slot,在声明 slot 时要写上 name 属性;嵌入在组件标签中的内容须要指定 slot=“slot的名字”

<!DOCTYPE html>
<html> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <panel> <div>这是默认的内容</div> <p>hahahah </p> <div slot="header">这是个头</div> <div slot="body">主体</div> <div slot="footer">尾部</div> </panel> </div> <template id="tpl"> <div> <slot></slot> <slot name="header"></slot> <slot name="body"></slot> <slot name="footer"></slot> </div> </template> <script src="vue.js"></script> <script> let panel = { template: '#tpl', data() { return { x: 1 } } }; let vm = new Vue({ el: '#app', data: {}, components: { panel } }); // 若是要想子组件中嵌入内容,须要使用插槽 slot;而且须要在子组件中提早用 slot 标签占位; // slot 分为匿名 slot 和具名 slot </script> </body> </html> 复制代码

基于 provide 和 inject 实现祖先与后代的通讯

  • 祖先组件基于provide注册须要供后代组件使用的数据
{
    provide:{ //=>对象或者返回对象的函数均可以(属性值若是是data中的数据,则必须使用函数的方法进行处理)
        name:'zhufeng',
        year:10
    },
    ...
}
复制代码
  • 后代组件基于inject声明须要使用的数据并调取使用
{
    inject:['name'],
    methods:{
        func(){
            let name=this.name;
        }
    }
}
复制代码
相关文章
相关标签/搜索