精华内容
下载资源
问答
  • vue中关于插槽的文档说明很短,语言又写的很凝练,再加上其和methods,data,computed等常用选项在使用频率、使用先后上的差别,这就有可能造成初次接触插槽的开发者容易产生“算了吧,回头再学,反正已经可以写基础...

    写在前面

    vue中关于插槽的文档说明很短,语言又写的很凝练,再加上其和methods,data,computed等常用选项在使用频率、使用先后上的差别,这就有可能造成初次接触插槽的开发者容易产生“算了吧,回头再学,反正已经可以写基础组件了”的想法,于是就关闭了vue的说明文档。

    实际上,插槽的概念很简单,下面通过分三部分来讲。

    单个插槽

    首先是单个插槽,单个插槽是vue的官方叫法,但是其实也可以叫它默认插槽,或者与具名插槽相对,我们可以叫它匿名插槽。因为它不用设置name属性。

    单个插槽可以放置在组件的任意位置,但是就像它的名字一样,一个组件中只能有一个该类插槽。相对应的,具名插槽就可以有很多个,只要名字(name属性)不同就可以了。

    父组件内容:

    <template>
      <div>
        <child>若子组件没有slot,则这句话不会显示</child>
      </div>
    </template>
     
    <script>
    import Child from './Child.vue'
    export default {
      name: 'HelloWorld',
      components:{
        'child':Child
      }
    }
    </script>
    

    子组件内容:

    <template>
        <div>
            <h1>我是子组件</h1>
        </div>
    </template>
    

    运行结果:
    在这里插入图片描述
    因为子组件没有 元素,所以父组件的内容被抛弃,现在我们在子组件加上 元素

    <template>
        <div>
            <h1>我是子组件</h1>
            <slot></slot>
        </div>
    </template>
    

    此时浏览器显示
    在这里插入图片描述
    此时,父组件的内容就显示在了子组件的内容里了。

    具名插槽

    匿名插槽没有name属性,所以是匿名插槽,那么,插槽加了name属性,就变成了具名插槽。具名插槽可以在一个组件中出现N次,出现在不同的位置。下面的例子,就是一个有两个具名插槽和单个插槽的组件,这三个插槽被父组件用同一套css样式显示了出来,不同的是内容上略有区别。

    父组件:

    <template>
      <div class="father">
        <h3>这里是父组件</h3>
        <child>
          <div class="tmpl" slot="up">
            <span>菜单1</span>
            <span>菜单2</span>
            <span>菜单3</span>
            <span>菜单4</span>
            <span>菜单5</span>
            <span>菜单6</span>
          </div>
          <div class="tmpl" slot="down">
            <span>菜单-1</span>
            <span>菜单-2</span>
            <span>菜单-3</span>
            <span>菜单-4</span>
            <span>菜单-5</span>
            <span>菜单-6</span>
          </div>
          <div class="tmpl">
            <span>菜单->1</span>
            <span>菜单->2</span>
            <span>菜单->3</span>
            <span>菜单->4</span>
            <span>菜单->5</span>
            <span>菜单->6</span>
          </div>
        </child>
      </div>
    </template>
    /*以下是样式*/
    <figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
    

    子组件:

    <template>
      <div class="child">
        // 具名插槽
        <slot name="up"></slot>
        <h3>这里是子组件</h3>
        // 具名插槽
        <slot name="down"></slot>
        // 匿名插槽
        <slot></slot>
      </div>
    </template>
    

    运行结果:
    在这里插入图片描述
    注: v-slot::‘xxx’ 可以简写为 #xxx

    作用域插槽 | 带数据的插槽

    最后,就是我们的作用域插槽。这个稍微难理解一点。官方叫它作用域插槽,实际上,对比前面两种插槽,我们可以叫它带数据的插槽。什么意思呢,就是前面两种,都是在组件的template里面写

    匿名插槽
    <slot></slot>
    具名插槽
    <slot name="up"></slot>
    

    但是作用域插槽要求,在slot上面绑定数据。也就是你得写成大概下面这个样子。

    <slot name="up" :data="data"></slot>
     export default {
        data: function(){
          return {
            data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
          }
        },
    }
    

    我们前面说了,插槽最后显示不显示是看父组件有没有在child下面写模板,像下面那样。

    <child>
       html模板   hello,我是老电影故事
    </child>
    

    写了,插槽就总得在浏览器上显示点东西,东西就是html该有的模样,没写,插槽就是空壳子,啥都没有。 OK,我们说有html模板的情况,就是父组件会往子组件插模板的情况,那到底插一套什么样的样式呢,这由父组件的html+css共同决定,但是这套样式里面的内容呢?

    正因为作用域插槽绑定了一套数据,父组件可以拿来用。于是,情况就变成了这样:样式父组件说了算,但内容可以显示子组件插槽绑定的。

    我们再来对比,作用域插槽跟单个插槽和具名插槽的区别,因为单个插槽和具名插槽不绑定数据,所以父组件提供的模板一般要既包括样式又包括内容,上面的例子中,你看到的文字,“菜单1”,“菜单2”都是父组件自己提供的内容;而作用域插槽,父组件只需要提供一套样式(在确实用作用域插槽绑定的数据的前提下)。

    下面的例子,你就能看到,父组件提供了三种样式(分别是flex、ul、直接显示),都没有提供数据,数据使用的都是子组件插槽自己绑定的那个数组(一堆人名的那个数组)。

    父组件:

    <template>
      <div class="father">
        <h3>这里是父组件</h3>
        <!--第一次使用:用flex展示数据-->
        <child>
          <template slot-scope="user">
            <div class="tmpl">
              <span v-for="item in user.data">{{item}}</span>
            </div>
          </template>
    
        </child>
    
        <!--第二次使用:用列表展示数据-->
        <child>
          <template slot-scope="user">
            <ul>
              <li v-for="item in user.data">{{item}}</li>
            </ul>
          </template>
    
        </child>
    
        <!--第三次使用:直接显示数据-->
        <child>
          <template slot-scope="user">
           {{user.data}}
          </template>
    
        </child>
    
        <!--第四次使用:不使用其提供的数据, 作用域插槽退变成匿名插槽-->
        <child>
          我就是模板
        </child>
      </div>
    </template>
    

    子组件:

    <template>
      <div class="child">
    
        <h3>这里是子组件</h3>
        // 作用域插槽
        <slot  :data="data"></slot>
      </div>
    </template>
    
     export default {
        data: function(){
          return {
            data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
          }
        }
    }
    

    运行结果:
    在这里插入图片描述

    展开全文
  • 书接上回,上篇介绍了vue组件通信比较有代表性的几种方法,本篇主要讲述一下组件的高级用法和最佳实践,争取用最少的篇幅占领高地!(多说一句,后续这个系列会有vue最佳实践和源码解读,我总有办法能让大家看懂,...

    世界上有太多孤独的人害怕先踏出第一步。 ---绿皮书

    书接上回,上篇介绍了vue组件通信比较有代表性的几种方法,本篇主要讲述一下组件的高级用法和最佳实践,争取用最少的篇幅占领高地!(多说一句,后续这个系列会有vue最佳实践和源码解读,我总有办法能让大家看懂,所以点赞关注,不迷路啊,小老弟

    本篇主要内容

    • 递归组件
    • 动态组件
    • 异步组件
    • 内联模板 inline-template的使用
    • 全局组件批量自动注册
    • Vue 的构造器——extend
    • vue 修饰符sync深入解析

    正文开始

    递归组件

    函数的递归是自己调用自己,这个过程有两个必要条件:

    • 这个函数必须有函数名称
    • 这个递归函数必须有结束条件,不然就会报Maximum call stack size exceeded,内存溢出

    本质上讲,组件也是一个函数,递归组件自然也是自己调用自己,所以也要满足两个条件:

    • 这个组件必须有确定的name,也就是要给组件设置name
    • 必须要有一个结束条件,告诉组件什么时候递归结束

    上代码

    非常简单的功能,让你看懂递归,A.vue中引入B.vue,B组件是递归的核心所在

    <template>
      // 要将treeData通过prop进行传递
      <B :propTreeData="treeData"/>
    </template>
    <script>
    import B from './B'
    export default {
      name: "Tree",
      data() {
        return {
          treeData: [
            {
              id: "1",
              menuName: "笑傲江湖",
              menuCode: "1",
              children: [
                {
                  menuName: "令狐冲",
                  menuCode: "10"
                },
                {
                  menuName: "东方不败",
                  menuCode: "11"
                }
              ]
            },
            {
              id: "2",
              menuName: "射雕英雄",
              menuCode: "2",
              children: [
                {
                  menuName: "蓉儿",
                  menuCode: "20"
                },
                {
                  menuName: "郭靖",
                  menuCode: "21"
                }
              ]
            }
          ]
        };
      },
      components:{B}
    };
    </script>
     
    复制代码

    B组件,递归的核心,由于是递归,必须结构一致,因此就必须将A中的treeData通过Props传给B才能实现递归,这就是为啥A和B必须分离,而绝不可能通过一个组件来实现递归,不知道你懂了没?体会一下

    <template>
      <ul>
        <li v-for="item in propTreeData" :key="item.menuCode">
          {{item.menuName}}
          // 要有一个结束条件
          <tree v-if="item.children&&item.children.length" :propTreeData="item.children"></tree>
        </li>
      </ul>
    </template>
    <script>
    export default {
      name: "Tree",
      props:{
        propTreeData:Array,
        default:()=>([])
      },
    };
    </script>
     
    复制代码

    看下实现效果,这是一个最最简单的递归,但是后续的所有的复杂功能,折叠,选中,拖拽哪一个又不是基于这个来进行的呢?复杂功能大家可以继续扩展,都不难

    <figcaption style="box-sizing: border-box; outline: 0px; display: block; text-align: center; margin: 8px; color: rgb(153, 153, 153); font-size: 14px; overflow-wrap: break-word;"></figcaption>

    动态组件

    让多个组件使用同一个挂载点,并动态切换,这就是动态组件。

    通过使用保留的 <component> 元素,动态地绑定到它的 is 特性,可以实现动态组件

    <template>
      <div id="example">
        <button @click="change">切换页面</button>
      <component :is="currentView"></component>
      </div>
    </template>
     
    <script>
    var LZL = { template: "<div>林志玲</div>" };
    var GYY = { template: "<div>高圆圆</div>" };
    var JJW = { template: "<div>贾静雯</div>" };
     
    export default {
      name: "App",
      components: {
        LZL,
        GYY,
        JJW
      },
      data() {
        return {
           index:0,
        arr:['LZL','GYY','JJW'],
        };
      },
      computed:{
        currentView(){
            return this.arr[this.index];
        }
      },
      methods:{
        change(){
          this.index = (++this.index)%3;
        }
      }
    };
    </script>

    动态组件的缓存,会将不活动的组件实例缓存起来,而不是销毁它们,比如切换tab后,还能保持切换之前的状态,如果有多个条件性的子元素,<keep-alive> 要求同时只有一个子元素被渲染

    <keep-alive>
        <component :is="currentView"></component>  
    </keep-alive>
    -----------------------------------------------
          
    <keep-alive>` 要求同时只有一个子元素被渲染
     
    <keep-alive>
        <LZL v-if="index===0"></LZL>
        <GYY v-else-if="index===1"></GYY>
        <JJW v-else></JJW>  
    </keep-alive>
    复制代码

    使用的时候,比如keep-alive下有A,B,C三个组件,但是我只想缓存A和B,这时候该怎么办,就要用到include和exclude】来进行筛选过滤,根据你的过滤 条件确定缓存哪个组件

    include会匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称(父组件 components 选项的键值)。匿名组件不能被匹配

    <!-- 逗号分隔字符串 -->
    <keep-alive include="a,b">
      <component :is="view"></component>
    </keep-alive>
    <!-- 正则表达式 (使用 v-bind) -->
    <keep-alive :include="/a|b/">
      <component :is="view"></component>
    </keep-alive>
    <!-- Array (use v-bind) -->
    <keep-alive :include="['a', 'b']">
      <component :is="view"></component>
    </keep-alive>
    复制代码

    异步组件

    异步组件说白了就是按需加载,使用时才装入需要的组件,可以有效的提高首次装入页面的速度。比如在路由切换时。

    {
                path: '/promisedemo',
                name: 'PromiseDemo',
                component: resolve => require(['../components/PromiseDemo'], resolve)
      }
    复制代码

    利用此特性,我们便能做很多针对前端的优化。 比如:将页面核心功能(音、视频播放、文章、商品等等)打包成一个核心模块,通过框架优先加载。 其他的一些周边功能打包后,通过服务器异步加载,从而解决业务需求越来越多导致的系统难维护、访问慢问题。

    既然是异步加载,就会存在加载过程(正在加载中)、以及加载失败等异常情况,看下图一目了然。。。

    <figcaption style="box-sizing: border-box; outline: 0px; display: block; text-align: center; margin: 8px; color: rgb(153, 153, 153); font-size: 14px; overflow-wrap: break-word;"></figcaption>

    内联模板 inline-template的使用

    Vue提供了一种内联模板的功能,在使用组件时,给标签加上inline-complate特性,组件就会把它的内容当作模板,而不是当内容分发。其实也就是说,不在创建一个组件时定义它的模板,而是在声明的外部创建。

    父组件
    <child-component inline-template>
         <div>
            <h2>在父组件中定义子组件的模板</h2>
            <p>{{msg}}</p>
         </div>
    </child-component>
     
    子组件
    export default{
        name:'ChildComponent',
        data(){
            return{
                msg:'张不怂'
            }
        }
    }
     
    最终显示
    <div data-v-763db97b="">
        <h2 data-v-763db97b="">在父组件中定义子组件的模板</h2>
        <p data-v-763db97b="">张不怂</p>
    </div>
    复制代码

    inline-template使得各个组件的结构混乱,建议不要轻易使用内联模板

    全局组件批量自动注册

    我们vue组件中引入组件的一般方式是这样的

    <template>
        <A  v-model="searchText"  @keydown.enter="search"/>
        <B @click="search">
            <C name="search"/>
        </B>
    </template>
    <script>
        import B from './B'
        import C from './C'
        import A from './A'
        export default {
          components: { B, C, A }
        }
    </script>
    复制代码

    有没有办法可以自动全局进行注册呢?答案是肯定的,在你要引入的包含所有组件的文件夹components的同级目录下建register.js,写入下边的代码

    const requireComponent = require.context(
      './components',   // 其组件目录的相对路径
      false,   // 是否查询其子目录
      /base_[A-Z]\w+\.(vue|js)$/   // 匹配基础组件文件名的正则表达式
    )
     
    requireComponent.keys().forEach(fileName => {
      // 获取组件配置
      const componentConfig = requireComponent(fileName)
     
      // 获取组件的 PascalCase 命名
      const componentName = upperFirst(
        camelCase(
          // 剥去文件名开头的 `./` 和结尾的扩展名
          fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
        )
      )
     
      // 全局注册组件
      Vue.component(
        componentName,
        componentConfig.default || componentConfig
      )
    })
     
    复制代码

    然后在你的vue项目的main.js中进行引入,就直接可以使用

    import @/register.js;
    复制代码

    Vue 的构造器——extend

    Vue.extend(options)

    用法:使用Vue构造器,创建一个“子类”,参数是一个包含组件选项的对象,其中,data选项中必须是函数

    描述:Vue.extend返回的是一个“扩展实例构造器”,也就是预设了部分选项的Vue的实例构造器

    自定义一个无参数标签

    var foo = Vue.extend({
     template: "<p><a :href='url'>{{foo}}</a></p>",
     data : function() {
      return {
       foo : 'vamous',
       url : 'https://juejin.im/editor/drafts/5cd2da7a5188253e8c23baf6'
      }
     }
    });
     
    对应的html
    <foo></foo>
     
    此时的页面必然是没有任何效果的,因为扩展实例构造器还需要挂载,如下:
    new foo().$mount('#app');
    复制代码

    可以利用propsData传递参数

    var author = Vue.extend({
     template: "<p><a :href='url'>{{bar}} & {{name}}</a></p>",
     data : function() {
      return {
       bar : 'vamous',
       url : 'https://juejin.im/editor/drafts/5cd2da7a5188253e8c23baf6'
      }
     },
     props : ['name']
    });
     
    new author({propsData: {name : 'foo'}}).$mount('#author');
    复制代码

    vue 修饰符sync深入解析

    实现子组件与父组件双向绑定的【sync】修饰符:其实sync这个修饰符是vue1.0就有的,它可以实现父子组件的双向绑定,但是Vue2.0被移除了,直到2.3.0版本发布后,又重新开始启用,【.sync】可以很轻松的实现子组件同步修改父组件的值

    如果子组件想修改父组件的值,推荐以update:my-prop-name 的模式触发事件取而代之,也就是这样:

    父组件
    <text-document
      v-bind:title="doc.title"
      v-on:update:title="doc.title = $event"
    ></text-document>
    ---------------------------------------------------------------
    子组件
    this.$emit("update:title".newTitle)
    复制代码

    而上边的 v-on:update:title="doc.title = $event",本质上就可以用sync这个语法糖来表示,.sync后面紧接的就是父组件中需要被改变的值,看下边的例子体会一下

    父组件
    <template>
        <div>
            <child-com :value.sync="text" ></child-com>
        </div>
    </template>
    <script>
        export default{
            data(){
                return {
                    text:"父组件的值",
                }
            },
        }
    </script>
    ==================================================================================
    //子组件中修改父组件的值
    <template>
        <div @click="post"></div>
    </template>
     
    <script>
        export default{
            methods:{
                post(){
                    this.$emit('update:value',"子组件的值")
                }
            }
        }
    </script>
    复制代码

    本篇基本就是这些,这三篇概述了vue的一些知识点,更多的vue实践还需要在工作中自己总结和挖掘,道阻且长,行则将至~

    觉得对你有帮助,不妨点个

    ,后续持续输出这种简短有效的文章,帮助你用最短的时间内掌握最多的内容,毕竟谁不喜欢一劳永逸不是? ❥(^_-) thank you ~

    后续目录

    vue高级进阶( 一 ) 组件精髓概述

    vue高级进阶( 二 ) 8种组件通信详解

    vue高级进阶( 三 ) 组件高级用法及最佳实践

    展开全文
  • vue高级用法

    2021-10-11 07:45:12
    插槽就是子组件的提供给父组件使用的一个占位符,父组件可以对插槽填充任何内容。 插槽和组件的区别是: ①组件之间的传递是单纯的数据的传递,而插槽的传递是视图的传递。 ②插槽的内容显示是由谁调用就由谁来...

    1. 动画特效----transition

    vue自带的 用来实现过渡效果

    基本使用
    设置name属性(如:name =“xxx” xxx = “v”),在组件过渡过程中,会有六个CSS类名进行切换
    请添加图片描述
    transition.jpg
    v-enter: 定义进入过渡的开始状态。在元素被插入时生效,只应用一帧后立刻删除。
    v-enter-active: 定义过渡的状态。在元素整个过渡过程中作用,在元素被插入时生效,在 transition/animation 完成之后移除。 可以用来定义过渡的过程时间,延迟和曲线函数。
    v-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入一帧后生效(于此同时 v-enter 被删除),在 transition/animation 完成之后移除。
    v-leave: 定义离开过渡的开始状态。在离开过渡被触发时生效,在下一个帧移除。
    v-leave-active: 定义过渡的状态。在元素整个过渡过程中作用,在离开过渡被触发后立即生效,在 transition/animation 完成之后移除。 这个类可以被用来定义过渡的过程时间,延迟和曲线函数。
    v-leave-to: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发一帧后生效(同时 v-leave 被删除),在 transition/animation 完成之后移除。

    transition 实现路路由切换动画
    home==>list==>detail
    detail==>list==>home

    // router.js
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import Home from '../components/Home.vue'
    import List from '../components/List.vue'
    import Detail from '../components/Detail.vue'
    
    Vue.use(VueRouter)
    
    export default new VueRouter({
      routes: [
        {
          path: '/',
          name: 'home',
          component: Home,
          meta: {
            deep: 1
          }
        },
        {
          path: '/list',
          name: 'list',
          component: List,
          meta: {
            deep: 2
          }
        },
        {
          path: '/detail/:id',
          name: 'detail',
          component: Detail,
          meta: {
            deep: 3
          }
        }
      ]
    });
    
    //=========================================================
    // 组件有关代码自行脑补,嘻嘻
    //=========================================================
    
    //App.vue
    <template>
      <div id="app">
        <transition :name="transitionName">
          <router-view />
        </transition>
      </div>
    </template>
    
    <script>
    
    export default {
      name: 'App',
      data() {
        return {
          transitionName: ''
        }
      },
      watch: {
        // 监听路由变化
        $route: {
          handler (to, from){
            this.transitionName = to.meta.deep > from.meta.deep ? 'slide-left' : 'slide-right'
          },
          // 深度观察监听
          deep: true
        }
      },
    }
    </script>
    
    <style lang="less" scoped>
      #app {
        width: 100%;
        height: 100%;
      }
      .slide-left-enter-active,
      .slide-right-enter-active,
      .slide-left-leave-active,
      .slide-right-leave-active {
        will-change: transform;
        transition: transform 350ms;
        position: absolute;
        overflow: hidden;
      }
      .slide-right-enter-active,
      .slide-left-leave-active {
        transform: translate(-100%, 0);
      }
    
      .slide-left-enter-active,
      .slide-right-leave-active {
        transform: translate(100%, 0);
      }
    </style>
    

    2. 插槽 slot

    插槽就是子组件中的提供给父组件使用的一个占位符,父组件可以对插槽填充任何内容。通过插槽可以让用户拓展组件,去更好地复用组件、对其做定制化处理

    1. 插槽和组件的区别是:

    ①组件之间的传递是单纯的数据的传递,而插槽的传递是视图的传递。
    ②插槽的内容显示是由谁调用就由谁来决定插槽的内容,组件的内容是提前已经写好了,只进行数据的简单改变。

    2. 插槽使用

    1. 默认插槽
    2. 具名插槽
    3. 作用域插槽 ---- 父子组件用来传递数据

    vue2.6.0以后,使用新语法v-slot指令(缩写为#)

    // 父组件
    <template>
      <div>
        <use-slot>
          <template>
            <div>
              我是默认插槽
            </div>
          </template>
          <template v-slot:juming>
            <div>
              我是具名插槽
            </div>
          </template>
          <template v-slot:zuoyongyu='pros'>
            <div>
              我是作用域插槽:
              <br>
              data1 = {{pros.data1}}
              <br>
              data2 = {{pros.data2}}
            </div>
          </template>
        </use-slot>
      </div>
    </template>
    
    <script>
    import UseSlot from '../components/UseSlot.vue'
    export default {
      name: 'Home',
      components: { UseSlot }
    }
    </script>
    
    //=========================================================
    
    // 子组件
    <template>
      <div>
        默认插槽
        <slot></slot>
        具名插槽
        <slot name="juming"></slot>
        作用域插槽
        <slot :data1="shuju1" :data2="shuju2"  name="zuoyongyu"></slot>
      </div>
    </template>
    
    <script>
    export default {
      name: 'use-slot',
      data() {
        return {
          shuju1: '我是作用于插槽的数据',
          shuju2: '我也是作用于插槽的数据'
        }
      }
    }
    </script>
    

    3. 插槽实现原理

    先编译(先对父组件进行编译,执行渲染函数,获取slot)再替换(再遍历插槽组件,找到应该替换的位置,替换插槽内容)

    3. Mixin

    Mixin本质其实就是⼀个js对象,它可以包含我们组件中任意功能选项,如data、components、 methods、computed以及生命周期函数等等。我们只要将共⽤的功能以对象的⽅方式传入 mixins选项中,当组件使用 mixins对象时所有mixins 对象的选项都将被混⼊该组件本身的选项中来,以达到代码的复用。

    1. mixin使用

    建议mixin里定义的都以mixin_开头,增强可读性

    1. 全局混⼊【全局混⼊常用于编写vue插件】

      // main.js
      import Vue from 'vue'
      import App from './App.vue'
      import router from './router/router'
      
      Vue.config.productionTip = false
      
      // 全局混入
      Vue.mixin({
        methods: {
          showDialog() {
            confirm('欢迎使用,小猪猪')
          }
        }
      })
      
      new Vue({
        render: h => h(App),
        router
      }).$mount('#app')
      
    2. 局部混⼊

      // 局部混入
      // common.js
      export default {
        methods: {
          showDialog() {
            confirm('欢迎使用,小猪猪')
          }
        }
      }
      
      //=========================================================
      
      // List.vue
      <template>
        <div class="list">
          <h1 @click="showDialog">List</h1>
        </div>
      </template>
      
      <script>
      import comm from '../mixins/common'
      export default {
        name: 'List',
        mixins: [ comm ]
      }
      </script>
      

    当组件存在与mixin对象相同的数据的时候,组件的数据会覆盖mixin的数据 。如果相同数据为生命周期函数的时候,会先执行mixin的钩⼦函数,再执⾏组件的钩⼦函数(原因见下文的mixin 实现原理)

    2. mixin 实现原理

    【可看作组件继承了mixins】
    优先递归处理 mixins
    先遍历合并parent (mixin)中的key,调用mergeField方法进⾏合并,然后保存在变量options
    再遍历 child(组件),合并补上 parent (mixin)中没有的key,调⽤mergeField⽅法进行合并,保存在变量 options

    export function mergeOptions (parent: Object,child: Object,vm?: Component): Object {
    	if (child.mixins) { 
    	// 判断有没有mixin⾥面挂mixin的情况,有的话递归进行合并 
    		for (let i = 0, l = child.mixins.length; i < l; i++) {
    			parent = mergeOptions(parent, child.mixins[i], vm)
    		}
    	}
    	const options = {}
    	for (let key in parent) {
    		// 先遍历parent的key调对应的strats[XXX]⽅法进行合并 
    		mergeField(key) 
    	}
    	for (key in child) {
    		// 如果parent已经处理过某个key就不处理了
    		if (!hasOwn(parent, key)) { 
    			// 处理child中有的,parent中没有处理过的key 
    			mergeField(key) 
    		}
        }
       	function mergeField (key) {
         	const strat = strats[key] || defaultStrat
         	// 根据不同类型的options调用strats中不同的⽅法进行合并
    		options[key] = strat(parent[key], child[key], vm, key)
    	}
        return options
    }
    

    合并策略

    合并mixin和当前组件的各种数据, 为四种策略:

    1. 替换型策略 - 同名的props、methods、inject、computed会被后来者代替

       strats.props = strats.methods = strats.inject = strats.computed = function ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string): ?Object {
       	// 如果parentVal没有值,直接返回childVal
      	if (!parentVal) 
      		return childVal 
      	// 创建一个第三方对象 ret
      	const ret = Object.create(null) 
      	// extend⽅法实际是把parentVal的属性复制到ret中
      	extend(ret, parentVal) 
      	// 把childVal的属性复制到ret中 
      	if (childVal) 
      		extend(ret, childVal) 
      	return ret
      }
      
    2. 合并型策略 - data, 通过set方法进行合并和重新赋值

      strats.data = function(parentVal, childVal, vm) {
          return mergeDataOrFn(parentVal, childVal, vm)
      };
      
      function mergeDataOrFn(parentVal, childVal, vm) {
          return function mergedInstanceDataFn() {
          	// 执行data挂的函数得到对象
      		var childData = childVal.call(vm, vm) 
      		var parentData = parentVal.call(vm, vm)
      		if (childData) {
      			// 将2个对象进行合并
      			return mergeData(childData, parentData) 
      		} else {
      			// 如果没有childData 直接返回parentData 
      			return parentData 
      		}
      	} 
      }
      
      function mergeData(to, from) {
          if (!from) 
          	return to
          var key, toVal, fromVal;
          var keys = Object.keys(from);
          for (var i = 0; i < keys.length; i++) {
      		key = keys[i];
      		toVal = to[key];
      		fromVal = from[key];
      		// 如果不存在这个属性,就重新设置 
      		if (!to.hasOwnProperty(key)) {
                  set(to, key, fromVal);
              }else if (typeof toVal =="object" && typeof fromVal =="object") {
              	// 存在相同属性,合并对象
                  mergeData(toVal, fromVal);
              }
      	}
      	return to 
      }
      
    3. 队列型策略 - 生命周期函数和watch,原理是将函数存入一个数组,然后正序遍历依次执⾏

    4. 叠加型策略 - component、filters、directives,通过原型链进行层层的叠加

      strats.components = strats.directives = strats.filters = function mergeAssets(parentVal, childVal, vm, key){
      	var res = Object.create(parentVal || null)
      	if (childVal) {
                for (var key in childVal) {
                    res[key] = childVal[key];
      		} 
      	}
      	return res 
      }
      

    4. 过滤器

    过滤器实质不改变原始数据,只是对数据进行加工处理后返回过滤后的数据再进⾏调⽤处理,可以理解其为一个纯函数。Vue3中已弃⽤了, 建议使⽤computed对数据加工处理.

    过滤器的使用

    filter 实现原理

    在编译阶段通过parseFilters函数将过滤器编译成函数调用(串联过滤器则是一个嵌套的函数调用,前一个过滤器执行的结果是后一个过滤器函数的参数)

    5. keep-alive的实现原理

    keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中;使用keep-alive包裹组件时,会缓存不活动的组件实例,而不是销毁它们。

    export default new Router({
      mode: 'history',
      routes: [
        {
          path: '/',
          name: 'home',
          component: Home,
          redirect: 'goods',
          children: [
            {
              path: 'goods',
              name: 'goods',
              component: Goods,
              meta: {
            	keepAlive: false // 不需要缓存
          	  }
            },
            {
              path: 'ratings',
              name: 'ratings',
              component: Ratings,
              meta: {
            	keepAlive: true  // 需要缓存
          	  }
            }
          ]
        }
      ]
    })
    
    <keep-alive>
       //会缓存路由配置中meta属性对象中keepAlive为true的页面
       //this.$route.meta.keepAlive可以读取路由配置中meta属性对象里的数据
      <router-view v-if="$route.meta.keepAlive"></router-view>
    </keep-alive>
    

    keep-alive有两个属性:include和exclude。include定义缓存白名单,keep-alive会缓存命中的组件;exclude定义缓存黑名单,被命中的组件将不会被缓存

    • 当组件在 < keep-alive> 内被缓存,该组件的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行,切换到该缓存组件时,该组件的activated生命周期钩子函数将会被对应执行,当离开该缓存组件时,该组件的deactivated 生命周期钩子函数将会被对应执行 。

    6. vue.$nextTick(callback) / Vue.nextTick(callback)

    • vue.$nextTick()使用原理:
      Vue组件是异步渲染的,即数据改变后,DOM不会立刻渲染,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。vue.$nextTick()的回调会在DOM渲染之后被自动触发,以便获取最新的DOM节点。

    • vue.$nextTick()的应用场景

      1.在vue的生命周期created()钩子函数中进行dom操作,一定要放在 vue.$nextTick()函数中执行。在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进vue.$nextTick()的回调函数中。

      2.在数据变化后要执行某个随着数据改变而改变DOM结构的操作时,这个操作都是需要放在vue.$nextTick()的回调函数中。

    7. Vue中ref和$refs

    8. vue插件 - Plugin

    插件就是指对Vue的功能的增强或补充。

    1. 插件使用

    以剪切板插件为例
    npm install --save vue-clipboard2

    //main.js
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router/router'
    import VueClipboard from 'vue-clipboard2'
    
    Vue.config.productionTip = false
    
    VueClipboard.config.autoSetContainer = true // add this line
    Vue.use(VueClipboard)
    
    new Vue({
      render: h => h(App),
      router
    }).$mount('#app')
    
    //=========================================================
    
    // detail.vue
    <template>
      <div @click="copyDetail">内容: {{detail}}</div>
    </template>
    
    <script>
    export default {
      name: 'Detail',
      data() {
        return {
          detail: '这是需要复制的内容'
        }
      },
      methods: {
        copyDetail() {
          // 使用插件扩展的方法$copyText
          this.$copyText(this.detail).then(function (e) {
            alert('Copied')
            console.log(e)
          }, function (e) {
            alert('Can not copy')
            console.log(e)
          })
        }
      }
    }
    </script>
    

    2. vue插件编写

    // 新建文件 MyPlugin.js
    const MyPlugin = {}
    MyPlugin.install = function (Vue, options) {
        // 1. 添加全局方法或 property
        // Vue.myGlobalMethod = function () {
        //     // 逻辑...
        // }
    
    
        // 2. 注入组件选项
        // Vue.mixin({
        //     created: function () {
        //     // 逻辑...
        //     }
        // })
    
        // 3. 添加实例方法
        Vue.prototype.$myMethod = function () {
            if (options.kkk === 1){
                confirm('HelloWorld----plugin')
            } else {
                confirm('加油小迟迟')
            }
        }
    
        // ....
    }
    
    export default MyPlugin
    
    //============================================
    // main.js
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router/router'
    import MyPlugin from './plugin/MyPlugin'
    
    Vue.config.productionTip = false
    
    // 注册使用自定义插件
    Vue.use(MyPlugin, {kkk: 1})
    
    new Vue({
      render: h => h(App),
      router
    }).$mount('#app')
    
    //============================================
    // Detail.vue
    <template>
     <div @click="showDetail">内容: {{detail}}</div>
    </template>
    
    <script>
    export default {
      name: 'Detail',
      data() {
        return {
          detail: '这是详细内容'
        }
      },
      methods: {
        showDetail() {
    	  // 使用自定义插件扩展的方法$myMethod
          this.$myMethod()
        }
      }
    }
    </script>
    
    
    • 进一步尝试编写vue插件

    3. Vue.use做了什么?

    1. 判断当前插件是否已经安装过, 防止重复安装

    2. 处理参数, 调⽤插件的install⽅法, 第⼀个参数是Vue实例

      // util/index.ts
      export function toArray (list: any, start?: number): Array<any> {
          start = start || 0
          let i = list.length - start
          const ret: Array<any> = new Array(i)
          while (i--) {
            ret[i] = list[i + start]
          }
          return ret
        }
      
      // Vue源码⽂文件路路径:src/core/global-api/use.js
      import { toArray } from '../util/index'
      export function initUse (Vue: GlobalAPI) {
      	Vue.use = function (plugin: Function | Object) {
      		// this === Vue
      		// this._installedPlugins 曾经注册过的插件
      		const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
      		// 判断vue是否已经注册过这个插件
      		if (installedPlugins.indexOf(plugin) > -1) {
      			// 返回this的原因是可以链式调用
      			return this
      		}
      		// 处理参数
      		const args = toArray(arguments, 1)
      		// 把Vue传进去
      		args.unshift(this)
      		if (typeof plugin.install === 'function') {
      		 	plugin.install.apply(plugin, args)
      		} else if (typeof plugin === 'function') {
      		 	plugin.apply(null, args)
      		}
      		//最后告知vue该插件已经注册过,保证每个插件只会注册一次。
      		this._installedPlugins.push(plugin)
      		return this
      	} 
      }
      

    9. 常⻅组件库介绍

    展开全文
  • Vue.js高级面试题及答案

    千次阅读 2020-12-24 19:13:58
    Vue-loader 是 Webpack 的加载模块,它使我们可以用 Vue 文件格式编写单文件组件。单文件组件文件有三个部分,(模板、脚本和样式)。 vue-loader 模块允许 webpack 使用单独的加载模块 (例如 SASS 或 SCSS 加载器) ...

    Vue-loader 是 Webpack 的加载模块,它使我们可以用 Vue 文件格式编写单文件组件。

    单文件组件文件有三个部分,(模板、脚本和样式)。 vue-loader 模块允许 webpack 使用单独的加载模块 (例如 SASS 或 SCSS 加载器) 提取和处理每个人部分。该设置使我们可以使用 Vue 文件无缝编写程序。

    vue-loader 模块还允许把静态资源视为模块依赖性,并允许使用 webpack 加载器进行处理。 而且还允许还开发过程中进行热重装。

    2、prop 如何指定其类型要求 ?

    通过实现 prop 验证选项,可以单个 prop 指定类型要求。这对生产没有影响,但是会在开发段发出警告,从而帮助开发人员识别传人数据和 prop 的特定类型要求的潜在问题。

    配置三个 prop 的例子 :

    props : {

    accountNumber:{

    type : Number,

    required : true

    },

    name :{

    type : String,

    required : true

    },

    favoriteColors : Array

    }

    3、什么是虚拟 DOM ?

    文档对象模型或 DOM 定义一个接口,该接口允许 JavaScript 之类的语言访问和操作 HTML 文档。 元素由树中的节点表示,并且接口允许我们操作它们。但是此接口需要付出代价,大量非常频繁的 DOM 操作会使页面速度变慢。

    Vue 通过在内存中实现文档结构的虚拟表示来解决此问题,其中虚拟节点 (Node) 表示 DOM 树中的节点。当需要操纵时,可以在虚拟 DOM 的 内存中执行计算和操作,而不是在真实 DOM 上进行操纵。这自然会更快,并且允许 DOM 算法计算最优化算出计算出最优化的方式来更新实际 DOM 结构。

    一旦计算出,就将其应用于实际的 DOM 树,这就提高了性能,这就是为什么基于虚拟 DOM 的框架 (例如 Vue 和 React) 如此突出的原因。

    4、什么是 Vue 插件 ?

    Vue 插件允许开发人员构建全局级别的功能并将其添加到 Vue。用于向程序添加可以全局访问的方法和属性、资源、选项,mixin 以及其他自定义 APl。 VueFire 是 Vue 插件的一个例子,该插件添加了 Firebase 特定的方法并将其绑定到整个程序。 之后 firebase 函数可在程序结构中的任何位置的 this 上下文中使用。

    5、什么是 mixin ?

    Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。 如果你希望再多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。

    6、 什么是渲染函数 ?举个例子。

    Vue 允许我们以多种方式构建模板,其中最常见的方式是只把 HTML 与特殊指令和mustache 标签一起用于相响应功能。但是你也可以通过 JavaScript 使用特殊的函数类 (称为渲染函数) 来构建模板。这些函数与编译器非常接近,这意味它们比其他模板类型更高效、快捷。由于你使用 JavaScript 编写渲染函数,因此可以在需要的地方自由使用该语言直接添加自定义函数。

    展开全文
  • 一、handler方法和immdiate属性 watch默认绑定,页面首次加载时,是不会执行的。只有值发生改变才会执行。 watch:{ name:{ handler(newName,oldName){ //执行代码 }, immediate:true //true就表示会立即执行 ...
  • vue 高级特性keepalive

    2021-02-07 13:45:15
    keep-alive 路由被加载过一次后就会把内容放到内存,会触发新的activated生命周期钩子(我们可以在这里搞事情),还有deactivated生命周期 缓存组件keep-alive,频繁切换,但不重复渲染,使用场景tab切换等,性能...
  • Vue组件高级用法之组件递归 在项目有时需要使用组件递归,如后台管理系统的侧边栏 我们可以在组件内部写上name值 export default { name: "submenu", // 必须给name值 } 然后在本组件直接调用自身即可,必须要...
  • 在ES5,我们使用var关键字来声明变量,但是使用var声明变量存在作用范围混淆的问题,为了解决该问题,ES6提出了新的变量声明方式: let : 用来声明局部变量。其作用范围严谨,从代码声明...
  • 当需要使用Vuex的状态state或方法mutations/actions时,简单的用法是这样的: dom里:$store.state.hasLogined, scirpt里:this.$store.state.hasLogined 当项目Vuex仓库比较庞大时,推荐使用如下写法: 1.state/...
  • 我们通过实例代码给大家分享了Vue中watch的高级用法,对此知识点有需要的朋友可以跟着学习下。假设有如下代码:FullName: {{fullName}}FirstName: new Vue({el: '#root',data: {firstName: 'Dawei',lastName: 'Lou',...
  • 例如:在路由实例创建 访问/,重定向为home { path: '/' redirect: '/home' } 二、路由高亮 通过两种方式,将激活的路由链接进行高亮显示: ① 使用默认的高亮class类 被激活的路由链接,默认会应用一个叫做...
  • 针对vue的知识: 一、基础知识 1、mvc和mvvm的认识 2、vue双向绑定原理 3、v-model实现例子 =》如何自定义一个v-model 4、vue的生命周期 5、watch和computed的区别 6、vue的v-forkey的作用 7、组件的data为什么...
  • Vue高级面试题汇总(一)

    千次阅读 2021-11-21 17:04:41
    是一种只需要将单个页面加载到服务器之的web应用程序。当浏览器向服务器发出第一个请求时,服务器会返回一个index.html文件,它所需的js,css等会在显示时统一加载,部分页面按需加载。url地址变化时不会向服务器...
  • 15个 Vue.js 高级面试题,必收藏

    千次阅读 2020-12-24 19:13:01
    渲染项目列表时,key 属性允许 Vue 跟踪每个 Vnode。key 值必须是唯一的。如果没有使用 key 属性,并且列表的内容发生了改变(例如对列表进行排序),则虚拟 DOM 宁愿使用更新的数据来修补节点,来反映更改,而不是...
  • 能够构建出色的单页应用程序(SPA)是 Vue.js 最具有吸引力的功能之一。SPA 非常好,因为它们不需要在...在本教程,我将介绍设置 Vue Router 的基础知识,并研究一些更高级的技术,例如:动态路由匹配导航挂钩(Na...
  • Vue中子组件不能直接引用父组件的数据,需要通过props选项接收来自父组件的数据。
  • VUE中switch使用

    2021-06-16 18:15:18
    let title = '' switch (this.activityCode) { //想要判断的变量 case 'ManuallyPassed': //状态一 title = this.$t('member.credit.activity.review.approve') //状态一返回的结果 break case 'EditCredit': /...
  • 假设有如下代码:FullName: {{fullName}}FirstName: new Vue({el: '#root',data: {firstName: 'Dawei',lastName: 'Lou',fullName: ''},watch: {firstName(newName, oldName) {this.fullName = newName + ' ' + this....
  • data:{ obj:{ a:1 } }, watch:{ obj:{ handler(newName,oldName){ //执行代码 }, deep:true //为true,表示深度监听,这时候就能监测到a值变化 } } deep为true,就可以监测到对象每个属性的变化。 它会一层层遍历...
  • Vue高级

    2021-09-12 16:18:15
    Vue高级 组件化 什么叫做组件化 什么叫做组件化,为什么要组件化?接下来我就针对这两个问题一一解答,所谓组件化,就是把页面拆分成多个组件,每个组件依赖的 CSS、JS、模板、图片等资源放在一起开发和维护。 因为...
  • Vue:在Vue中使用echarts

    2020-12-19 00:46:46
    前言公司的项目需要对数据做可视化处理,高级点的D3.js目前还没接触到,因此选用了大众化的Echarts, 在vue的生态系统已经有实现好的vue-echarts,但是使用现成的就意味着必须使用它定制好的数据结构,我也没办法...
  • mixin高级用法,混入通用方法,自定义通用方法参数,实现对上传文件的大小,类型,图片尺寸进行上传拦截 el-upload 的beforeUpload钩子方法,在对上传之前进行处理在很多上传的地方都会用到,可能因为上传文件不同,大小...
  • 1.假设有如下代码: <div id="root"> <p>FullName: {{fullName}}</p> <...input type="text" v-model="firstName">...new Vue({ el: '#root', data: { firstName: 'Dawei', lastName: 'Lou'
  • 通过创建Vue组件,我们可以将接口的可重复部分提取到可重用的代码段,没有Composition API之前Vue相关业务的代码需要配置到option的特定区域,如果在大型项目这种方式会导致后期的维护性比较复杂,同时代码可...
  • 所以 vue3 新增了 setup 配置项,用它来写组合式API。 一、选项式API 和 组合式API 区别 有些同学稀里糊涂用了vue一年了,竟然不知道选项式api !你是那个同学吗?是的话,赶紧收藏起来。 vue2 的 Option
  • 前言通过前面的文章,我们认识了页面的响应是由Vue实例里的data函数所返回的数据变化而驱动,也重点学习了页面的响应与数据变化之间是是如何来联系起来的,并且分别在Vue2.x与3.x,从零到一实现了两个版本下的数据...
  • 目前仅实现了简单的例子,日后把模型给应用上之后,也许会来更新一些使用碰到的坑。 参考资料 [1]Web Worker 使用教程@阮一峰 http://www.ruanyifeng.com/blog/2018/07/web-worker.html [2]如何无痛的为你的前端...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 20,863
精华内容 8,345
关键字:

vue中的高级用法

vue 订阅