zoukankan      html  css  js  c++  java
  • VUE进阶知识

    组件通信常用方式

    • props

    • event

    • vuex

    边界情况

    • $parent

    • $root

    • $children

    • $refs

    • provide/inject

    ⾮prop特性

    • $attrs

    • $listeners

    $parent/$root

    兄弟组件之间通信可通过共同祖辈搭桥,$parent或$root。

    			// 兄弟组件1
                sayBai() {
                    // this.$parent.$emit('handle', '我是老大');
                    this.$root.$emit('handle', '我是老大');
                }
                
            // 兄弟组件2
             // this.$parent.$on('handle', message => {
                this.$root.$on('handle', message => {
                    console.log(message);
                })
    

    但是这方式,存在耦合过高问题,因为一旦组件层级发生了改变,那么就会有问题,特别是$parent。

    例如当我们自己封装组件时(自己封装表单),有嵌套关系,很多人会直接梭哈使用$parent,但是如果后面重构层级关系变了,那么很多逻辑都会改,导致很大麻烦。那么官方是怎么做的呢?

    element源码地址

    我们来查看下element中怎么做的呢:

    // 广播: 从上到下派发事件
    function broadcast(componentName, eventName, params) {
    	// componentName: 组件的componentName名
    	// eventName:事件名
    	// params: 参数,需要是一个数组
    	
    	// 遍历所有的子组件:树形的向下遍历,只要名字相同,就都派发事件
      this.$children.forEach(child => {
        var name = child.$options.componentName;
    	// 如果子组件的componentName和传入的componentName名字相同,就派发事件
    	// 需要注意: 组件需要写componentName(和我们在组件中写的name相似)
        if (name === componentName) {
          child.$emit.apply(child, [eventName].concat(params));
        } else {
          broadcast.apply(child, [componentName, eventName].concat([params]));
        }
      });
    }
    export default {
      methods: {
      	// 从下到上派发事件(类似于冒泡)
        dispatch(componentName, eventName, params) {
          var parent = this.$parent || this.$root;
          var name = parent.$options.componentName;
    		
    	  // 向上查找,直到找到componentName和传入的componentName名字相同的组件
          while (parent && (!name || name !== componentName)) {
            parent = parent.$parent;
    
            if (parent) {
              name = parent.$options.componentName;
            }
          }
          // 如果找到,就派发事件
          if (parent) {
            parent.$emit.apply(parent, [eventName].concat(params));
          }
        },
        broadcast(componentName, eventName, params) {
          broadcast.call(this, componentName, eventName, params);
        }
      }
    };
    

    $children

    ⽗组件可以通过$children访问⼦组件实现⽗⼦通信。

    	// 在子组件2中,存在一个状态msg
    	
    	// 父组件中
     	changeChildren1Msg() {
           this.$children[1].msg='变';
        }
    

    注意:

    • 组件中,$children只能访问到自定义组件。
    • $children访问到的是一个数组,并且不能保证⼦元素顺序,例如:如果异步加载组件,那么顺序就会发生变化。

    $attrs

    当父组件传递数据到子组件时,如果没有在props中声明(声明了的就不能通过$attrs访问),那么就会被他们所绑定,通过 v-bind="$attrs" 传⼊到子组件,这在我们创建⾼级别的组件时⾮常有⽤。

     	// 父组件中
        <Children2 name='pyy' />
        
        // 子组件2中
        <span>子组件2: {{msg}} - {{$attrs.name}}</span>
    

    $listeners

    例如当我们封装组件时,在组件中有个回调函数,但是这个回调函数设置是在父组件中设置的,在这个组件中只是负责触发它,不负责相应的实现逻辑,这个时候,我们就可以使用到$listeners。

    	// 在父组件中
    	<Children2 name='pyy' @click="onClickHandle" />
    
    	onClickHandle(){
          console.log('父组件中: onClickHandle');
          this.$children[2].msg='变变变';
        }
    
    	// 在子组件中
    	<span v-on="$listeners">子组件2: {{msg}} - {{$attrs.name}}</span>
    

    v-on="$listeners"解析: $listeners本身是个对象(可以使用v-on展开),键值对的形式,键是父组件中所有事件监听器的名称,在这儿,父组件Children2上有个click事件,那么在子组件Children2中,$listeners中有个键就叫click,值就是父组件中设置的回调函数,也就是说,在子组件Children2中,这个span标签上有一个click事件=父组件中设置的回调函数。这样在子组件中不需要关心这个回调函数的处理,只需要绑定并触发它,在封装组件库时比较常用。

    $refs

    这是我们常用的获取⼦节点引⽤。

    父组件中:

     <Children2 ref="CR2" />
     
      changeChildren1Msg() {
           this.$children[1].msg='变'; // 只会找到第二个子组件修改它状态
           this.$refs.CR2.msg = '变2';
        },
    

    provide/inject

    provide/inject能够实现祖先和后代之间传值,当我们不使用vuex时,vue提供给了我们这种原生接口的方式来实现隔代传值。

    例如:

     // 在app.vue中:
     provide(){ // 提供的意思
        // 隔代传参,用法类似于data
        return {
          foo: 'foo',
        }
      },
    
    // 在需要的后代组件中
    inject: ['foo'], // 注入,注入需要的属性
    

    注意:

    • 如果传递的是基本数据类型,那么这种方式,不是响应式的。只有是引用数据类型-对象,并且这个对象是响应式的,那么传递下去的时候,才会是响应式的。

    • 如果在子组件data中,已经声明了相同的属性,那么子组件中的属性才会生效(就近原则)。

    • 如果在子组件data中,已经声明了相同的属性,那么怎么使用provide提供的属性呢?这个时候我们需要改造inject

      inject: {
      	 foo1: 'foo' // 使用别名foo1
      }
      

    代码

    // app.vue中
    <template>
      <div id="app">
        <Father />
      </div>
    </template>
    
    <script>
    import Father from './components/Father.vue'
    
    export default {
      provide(){ // 提供的意思
        // 隔代传参,用法类似于data
        return {
          foo: 'foo',
        }
      },
      name: 'App',
      components: {
        Father
      }
    }
    </script>
    
    // Father.vue中
    <template>
      <div>
        <span>父组件</span>
        <Children1 />
        <button @click="changeChildren1Msg">我是父组件按钮</button>
        <Children2 name='pyy' @click="onClickHandle" />
        <Children2 ref="CR2" />
      </div>
    </template>
    
    <script>
    import Children1 from "./Children1";
    import Children2 from "./Children2";
    export default {
      methods: {
        changeChildren1Msg() {
           this.$children[1].msg='变'; // 只会找到第二个子组件修改它状态
           this.$refs.CR2.msg = '变2';
        },
        onClickHandle(){
          console.log('父组件中: onClickHandle');
          this.$children[2].msg='变变变';
        }
      },
      components: {
        Children1,
        Children2
      }
    };
    </script>
    
    // Children1.vue中
    <template>
        <div>
            <button @click="sayBai">子组件1</button>
        </div>
    </template>
    
    <script>
        export default {
            methods: {
                sayBai() {
                    // this.$parent.$emit('handle', '我是老大');
                    this.$root.$emit('handle', '我是老大');
                }
            },
             mounted () {
                // this.$parent.$on('handle', message => {
                this.$root.$on('handle', message => {
                    console.log(message);
                })
             },
        }
    </script>
    
    // Children2.vue中
    <template>
        <div>
            <!-- $listeners/$attrs -->
            <span v-on="$listeners">子组件2: {{msg}} - {{$attrs.name}}</span>
            <!-- provide/inject -->
            <span>------{{foo}} </span>
        </div>
    </template>
    
    <script>
        export default {
            // inject: ['foo'], // 注入,注入需要的属性
            inject: {
                foo: 'foo'
            },
            data() {
                return {
                    msg: 'msg',
                }
            },
            // 监听事件
             mounted () {
                // this.$parent.$on('handle', message => {
                this.$root.$on('handle', message => {
                    console.log(message);
                })
             },
        }
    </script>
    

    过滤器filters

    作用: 过滤处理数据的格式,可被用于一些常见的文本格式化。

    使用场景:过滤器可以用在两个地方:双花括号插值和v-bind表达式,注意过滤器要被添加在表达式的尾部,由“管道”符号|表示。

    语法:

    		<!-- 在双花括号中 -->
    		<div>{{ msg | 函数名 }}</div>
    		
    		<!-- 在 `v-bind` 中 --> 
    		<div v-bind:id="msg | 函数名"></div>
    
    
     	// 过滤器
        filters: {
            函数名(msg) {
                return 过滤结果
            }
        }
    

    例如:

    <div id="app">
          <ul>
            <li v-for='item of goodList'>
            	<!-- 不使用过滤器时这么写,但是货币符号是固定的 -->
              <!-- {{item.name}} -  ${{item.price}} -->
              {{item.name}} -  {{item.price | symbol}}
            </li>
          </ul>
        </div>
    
        <script src="vue.js"></script>
        <script>
        
        new Vue({
          el: '#app',
          data() {
            return {
              goodList: [{name: '花生', price: 10},{name: '瓜子', price: 40},{name: '啤酒', price: 90}],
            }
          },
          filters: {
            symbol: function(value) {
              return '$' + value;
            }
          }
        });
    

    把上面的例子稍微改造一下,符号可以动态传递而不是写死的。

    		<div id="app">
          <ul>
            <li v-for='item of goodList'>
              <!-- 和方法一样调用,传递参数 -->
              {{item.name}} - {{item.price | symbol('¥')}}
            </li>
          </ul>
        </div>
    
        <script src="vue.js"></script>
        <script>
        
        new Vue({
          el: '#app',
          data() {
            return {
              goodList: [{name: '花生', price: 10},{name: '瓜子', price: 40},{name: '啤酒', price: 90}],
            }
          },
          filters: {
            symbol: function(value, sym = '$') { // 第一个参数,理解为上面的item.price  第二个参数,就是('¥')中传递过来的符号,  为了容错处理 默认值给个$
              return sym + value;
            }
          }
        });
        </script>
    

    自定义指令

    除了核心功能默认内置的指令 ,Vue 允许注册自定义指令。有的情况下,仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

    官方地址

    例如官方输入框自动获取焦点例子:

    		<div id="app">
          <input type="text" v-focus>
        </div>
    
        <script src="vue.js"></script>
        <script>
          // 注册一个全局自定义指令 `v-focus`
          Vue.directive('focus', {
            // 当被绑定的元素插入到 DOM 中时……
            // binding很重要,详细信息参看官网
            inserted: function (el, binding) {
              // 聚焦元素
              el.focus()
            }
          });
    
          new Vue({
            el: '#app',
          });
        </script>
    

    然后我们再来自定义做一个,根据当前登陆用户级别,来做权限设置。

    		<div id="app">
          <input type="text" v-focus>
    
          <!-- 特别需要注意: 指令里,""中是表达式,如果需要传递字符串,则需要加上字符串 -->
          <button v-permission="'superAdmin'">删除</button>
    
        </div>
    
        <script src="vue.js"></script>
        <script>
          // 假设当前登陆用户是会员
          const user = 'member';
    
          // 注册一个全局自定义指令 `v-focus`
          Vue.directive('focus', {
            // 当被绑定的元素插入到 DOM 中时……
            // binding很重要,详细信息参看官网
            inserted: function (el, binding) {
              // 聚焦元素
              el.focus()
            }
          });
          
          // 第一个参数: 指令名,注意使用时要加上v-
          // 第二个参数: 配置项
          Vue.directive('permission', {
            inserted: function (el, binding) {
              console.log(binding);
               // 若指定用户角色和当前用户角色不匹配则删除当前指令绑定的元素
              if (user !== binding.value) {
                el.parentElement.removeChild(el)
              }
            }
          });
    
          new Vue({
            el: '#app',
          });
        </script>
    

    渲染函数

    官方地址

    Vue 推荐在绝大多数情况下使用模板来创建HTML。然而在一些场景中,真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。

    基础:

    render: function (createElement) { 
    	// createElement函数返回结果是VNode(虚拟DOM) 
    	return createElement( // 接收三个参数
        tagname, // 标签名称 
        data, // 传递数据 
        children // 子节点数组
    	) 
    }
    

    基于官网的例子:

    		<div id="app">
          <!-- 用render实现一个组件 : 实现标题 -->
          <!-- level是指需要生成h1-6哪一个标签 -->
          <my-head :level='1' :title='title'>{{title}}</my-head>
          <my-head :level='3' :title='title'>我是另一个我</my-head>
    
          <!-- <h2 :title='title'>
            {{title}}
          </h2> -->
        </div>
    
        <script src="vue.js"></script>
        <script>
          Vue.component('my-head',{
            props: ['level', 'title'],        
            // render函数接收一个 createElement参数,我们一般简写为h    h === createElement
            // 因为Vdom底层的算法是snabbdom算法,这个算法里面生成虚拟dom的方法名就叫h
            render(h){ 
              // 注意这儿一定要有return, return出createElement返回的Vnode。
             return h(
                'h'+this.level, // 参数1:标签名字
                {attr:  { title: this.title }},// 参数2
                this.$slots.default, // 参数3: 子节点数组(虚拟节点)   标签之间的内容,需要使用默认插槽来获取
              )
            }
          });
    
          new Vue({
              el: '#app', 
              data() { 
                return {
                  title: 'hello, vue!'
                }
              },
          });
        </script>
    

    然后我们再来进阶来试一试:

    当用户使用组件时,

    <my-head :level='1' :title='title' icon='Food'>{{title}}</my-head>
    

    我们希望渲染成:

    			<!-- 阿里矢量图使用方式 -->
    			<h1 :title='title'>
              <svg class="icon"><use xlink:href="#icon-iconfinder_Food_C_"></use></svg>
            {{title}}
          </h1>
    

    最终代码为:

    		<div id="app">
          <my-head :level='1' :title='title' icon='Food'>{{title}}</my-head>
    
          <!-- <h3 :title='title'>
              <svg class="icon"><use xlink:href="#icon-iconfinder_Food_C_"></use></svg>
            {{title}}
          </h3> -->
        </div>
    
        <script src="./iconfont.js"></script>
        <script src="vue.js"></script>
        <script>
          Vue.component('my-head',{
            props: ['level', 'title', 'icon'],        
            render(h){ 
              let children = [];           
              // 思路: 第一步,把用户传入的icon,生成<svg class="icon"><use xlink:href="#icon-icon名"></use></svg>添加到children数组中
              // 第二步: 把默认插槽内容this.$slots.default放到children数组中
              // 第三步:h函数参数3就替换为children数组
              // 第一步: 生成svg,添加图标   同样是调用h函数生成
              const svgVnode = h(
                'svg',
                { class: 'icon' }, // 添加固定类名为icon  详见官网createElement参数2
                [h('use',{attrs: {"xlink:href": `#icon-iconfinder_${this.icon}_C_`}})] // 参数3: 子节点数组(虚拟节点),svg还有个子级use,所以再调用h方法生成use,需要注意的是需要是数组,所以将返回的vnode放到一个数组中
              );
              children = [svgVnode, ...this.$slots.default];
    
             return h(
                'h'+this.level, // 参数1:标签名字
                {attrs:  { title: this.title }},// 参数2
                children, // 参数3: 子节点数组(虚拟节点)   标签之间的内容,需要使用默认插槽来获取
              )
            }
          });
    
          new Vue({
              el: '#app', 
              data() { 
                return {
                  title: 'hello, vue!'
                }
              },
          });
        </script>
    

    模板语法是如何实现的

    在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。

    之前的例子中原本代码如下:

     <!-- 宿主容器(根节点) -->
      <div id="app">
        <ul>
          <!-- class绑定 --> 
          <li v-for="item in goodList" 
            :class="{active: (selected === item)}" 
            @click="selected = item">{{item}}</li> 
            <!-- style绑定 --> 
            <!-- <li v-for="item in goodList" 
                  :style="{backgroundColor: (selected === item)?'#ddd':'transparent'}" 								@click="selectedCourse = item">{{item}}</li> --> 
        </ul>
      </div>
      
      
      <script src="vueJs所在路径"></script>
        <script>
         const vm = new Vue({
              el: '#app', 
              data() {
                return {
                  goodList: ['花生','瓜子','啤酒'],
                  selected: ''
                }
              },
          });
        </script>
    

    然后我们去输出vue替我们生成的渲染函数 :

    执行代码: console.log(vm.$options.render)

    我们看到输出信息:

    (function anonymous(
    ) {
    with(this){return _c('div',{attrs:{"id":"app"}},[_c('ul',_l((goodList),function(item){return _c('li',{class:{active: (selected === item)},on:{"click":function($event){selected = item}}},[_v(_s(item))])}),0)])}
    })
    

    然后我们基于这一个点,改写为渲染函数版本。

     <!-- 宿主容器(根节点) -->
      <div id="app"></div>
      
      
      // 创建vue实例
      new Vue({
          el: '#app',
          data() {
            return {
              goodList: ['花生','瓜子','啤酒'],
              selected: ''
            }
          },
          methods: {},
          render() {
            with(this){
              return _c('div',{attrs:{"id":"app"}},[_c('ul',_l((goodList),function(item){return _c('li',{class:{active: (selected === item)},on:{"click":function($event){selected = item}}},[_v(_s(item))])}),0)])}
          }
        })
    

    我们可以看到,结果是一样的。

    结论:Vue通过它的编译器将模板编译成渲染函数,在数据发生变化的时候再次执行渲染函数,通过对比两次执行结果得出要做的dom操作,模板中的神奇魔法得以实现。

    函数式组件

    官方地址

    没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法。实际上,它只是一个接受一些 prop 的函数。在这样的场景下,我们可以将组件标记为 functional,这意味它无状态 (没有响应式数据),也没有实例 (没有 this 上下文)。

    修改上一个例子为函数式组件:

    		<div id="app">
          <my-head :level='1' :title='title' icon='Food'>{{title}}</my-head>
        </div>
    
        <script src="./iconfont.js"></script>
        <script src="vue.js"></script>
        <script>
          Vue.component('my-head',{
            functional: true,  // 1. functional设置为true,标示是函数式组件
            props: ['level', 'title', 'icon'],   
            // 在函数式组件中,没有this
            // 所以render函数,提供第二个参数作为上下文             
            render(h, context){ 
              // 之前从this上拿取'level', 'title', 'icon',就要变化了
              // 2. 从context.props上去拿取
              const { level, title, icon } = context.props;
              let children = [];           
              const svgVnode = h(
                'svg',
                { class: 'icon' },
                [h('use',{attrs: {"xlink:href": `#icon-iconfinder_${icon}_C_`}})] 
              );
              // 3. 子元素获取: 增加context参数,并将this.$slots.default更新为context.children,然后将this.level更新为context.props.level。
              children = [svgVnode, ...context.children];
    
             return h(
                'h'+level, 
                {attrs:  { title: title }},
                children,
              )
            }
          });
    
          new Vue({
              el: '#app', 
              data() { 
                return {
                  title: 'hello, vue!'
                }
              },
          });
        </script>
    

    混入

    官方地址

    混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

    // 定义一个混入对象 
    let myMixin = { 
    	created: function () { 
    		this.hello() 
    	},
    	methods: { 
    		hello: function () { 
    			console.log('hello from mixin!') 
    		} 
    	} 
    }
    // 定义一个使用混入对象的组件 
    Vue.component('mycomponent', { mixins: [myMixin] })
    

    插件

    官方地址

    Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一

    个可选的选项对象.

    const MyPlugin = { 
    	install (Vue, options) { 
    		Vue.component('my-head', {...}) 
    	} 
    }
    if (typeof window !== 'undefined' && window.Vue) { 
    	window.Vue.use(MyPlugin) 
    }
    

    例如把上面的标题组件,封装成插件。

    首先新建个js文件,存放插件代码:

    const MyPlugin = { 
      // 插件需要install方法
    	install (Vue, options) { 
    		Vue.component('my-head',{
          functional: true,  // 1. functional设置为true,标示是函数式组件
          props: ['level', 'title', 'icon'],   
          // 在函数式组件中,没有this
          // 所以render函数,提供第二个参数作为上下文             
          render(h, context){ 
            // 之前从this上拿取'level', 'title', 'icon',就要变化了
            // 2. 从context.props上去拿取
            const { level, title, icon } = context.props;
            let children = [];           
            const svgVnode = h(
              'svg',
              { class: 'icon' },
              [h('use',{attrs: {"xlink:href": `#icon-iconfinder_${icon}_C_`}})] 
            );
            // 3. 子元素获取: 增加context参数,并将this.$slots.default更新为context.children,然后将this.level更新为context.props.level。
            children = [svgVnode, ...context.children];
    
           return h(
              'h'+level, 
              {attrs:  { title: title }},
              children,
            )
          }
        });
    	} 
    }
    // 判断当前环境  并且判断是否已经存在Vue
    if (typeof window !== 'undefined' && window.Vue) { 
    	window.Vue.use(MyPlugin);
    }
    

    然后在页面上,直接使用插件即可。

     		<div id="app">
          <my-head :level='1' :title='title' icon='Food'>{{title}}</my-head>
        </div>
    
        <script src="./iconfont.js"></script>
        <script src="vue.js"></script>
        <script src="./plugins/head.js"></script>
        <script>   
          new Vue({
              el: '#app', 
              data() { 
                return {
                  title: 'hello, vue!'
                }
              },
          });
        </script>
    

    代码github地址

  • 相关阅读:
    Ubuntu配置sublime text 3的c编译环境
    ORA-01078错误举例:SID的大写和小写错误
    linux下多进程的文件拷贝与进程相关的一些基础知识
    ASM(四) 利用Method 组件动态注入方法逻辑
    基于Redis的三种分布式爬虫策略
    Go语言并发编程总结
    POJ2406 Power Strings 【KMP】
    nyoj 会场安排问题
    Server Tomcat v7.0 Server at localhost was unable to start within 45 seconds. If the server requires more time, try increasing the timeout in the server editor.
    Java的String、StringBuffer和StringBuilder的区别
  • 原文地址:https://www.cnblogs.com/zz-zrr/p/14468678.html
Copyright © 2011-2022 走看看