• vue开发技巧总结


    1.require.context()

    1.场景:如页面需要导入多个组件,原始写法:

    import titleCom from '@/components/home/titleCom'
    import bannerCom from '@/components/home/bannerCom'
    import cellCom from '@/components/home/cellCom'
    components:{titleCom,bannerCom,cellCom}

    2.这样就写了大量重复的代码,利用 require.context 可以写成

    const path = require('path')
    const files = require.context('@/components/home', false, /.vue$/)
    const modules = {}
    files.keys().forEach(key => {
     const name = path.basename(key, '.vue')
     modules[name] = files(key).default || files(key)
    })
    components:modules

    这样不管页面引入多少组件,都可以使用这个方法

    3.API 方法

    实际上是 webpack 的方法,vue 工程一般基于 webpack,所以可以使用
    require.context(directory,useSubdirectories,regExp)
    接收三个参数:
    directory:说明需要检索的目录
    useSubdirectories:是否检索子目录
    regExp: 匹配文件的正则表达式,一般是文件名
    

    2.优雅更新props

    更新 prop 在业务中是很常见的需求,但在子组件中不允许直接修改 prop,因为这种做法不符合单向数据流的原则,在开发模式下还会报出警告。因此大多数人会通过 $emit 触发自定义事件,在父组件中接收该事件的传值来更新 prop

    child.vue:

    export defalut {
        props: {
            title: String  
        },
        methods: {
            changeTitle(){
                this.$emit('change-title', 'hello')
            }
        }
    }

    parent.vue:

    <child :title="title" @change-title="changeTitle"></child>
    export default {
        data(){
            return {
                title: 'title'
            }  
        },
        methods: {
            changeTitle(title){
                this.title = title
            }
        }
    }
    

      

    这种做法没有问题,我也常用这种手段来更新 prop。但如果你只是想单纯的更新 prop,没有其他的操作。那么 sync 修饰符能够让这一切都变得特别简单。

    parent.vue:

    <child :title.sync="title"></child>
     

    child.vue:

    export defalut {
        props: {
            title: String  
        },
        methods: {
            changeTitle(){
                this.$emit('update:title', 'hello')
            }
        }
    }

    只需要在绑定属性上添加 .sync,在子组件内部就可以触发 update:属性名 来更新 prop。可以看到这种手段确实简洁且优雅,这让父组件的代码中减少一个“没必要的函数”。

    自定义组件双向绑定

    默认情况下,v-model 是 @input 事件侦听器和 :value 属性上的语法糖。但是,你可以在你的Vue组件中指定一个模型属性来定义使用什么事件和value属性——非常棒!

    组件 model 选项:
    
    允许一个自定义组件在使用 v-model 时定制 prop 和 event。默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event,但是一些输入类型比如单选框和复选框按钮可能想使用 value prop 来达到不同的目的。使用 model 选项可以回避这些情况产生的冲突。
    
    input 默认作为双向绑定的更新事件,通过 $emit 可以更新绑定的值
    
    <my-switch v-model="val"></my-switch>
    
    export default {
        props: {
            value: {
                type: Boolean,
                default: false
            }
        },
        methods: {
            switchChange(val) {
                this.$emit('input', val)
            }
        }
    }
    复制代码
    修改组件的 model 选项,自定义绑定的变量和事件
    
    <my-switch v-model="num" value="some value"></my-switch>
    
    export default {
        model: {
            prop: 'num',
            event: 'update'
        },
        props: {
            value: {
                type: String,
                default: ''
            },
            num: {
                type: Number,
                default: 0
            }
        },
        methods: {
            numChange() {
                this.$emit('update', num++)
            }
        }
    }
    
    

      

     3.provide/inject

    这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。

    简单来说,一个组件将自己的属性通过 provide 暴露出去,其下面的子孙组件 inject 即可接收到暴露的属性。

    App.vue:

    export default {
        provide() {
            return {
                app: this
            }
        } 
    }

    child.vue:

    export default {
        inject: ['app'],
        created() {
            console.log(this.app) // App.vue实例
        }
    }

    在 2.5.0+ 版本可以通过设置默认值使其变成可选项:

    export default {
        inject: {
            app: {
                default: () => ({})
            }
        },
        created() {
            console.log(this.app) 
        }
    }

    如果你想为 inject 的属性变更名称,可以使用 from 来表示其来源:

    export default {
        inject: {
            myApp: {
                // from的值和provide的属性名保持一致
                from: 'app',
                default: () => ({})
            }
        },
        created() {
            console.log(this.myApp) 
        }
    }

    需要注意的是 provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。但是某些时候,或许它能帮助到我们。

    4.巧用template

    相信 v-if 在开发中是用得最多的指令,那么你一定遇到过这样的场景,多个元素需要切换,而且切换条件都一样,一般都会使用一个元素包裹起来,在这个元素上做切换。

    <div v-if="status==='ok'">
        <h1>Title</h1>
        <p>Paragraph 1</p>
        <p>Paragraph 2</p>
    </div>

    如果像上面的 div 只是为了切换条件而存在,还导致元素层级嵌套多一层,那么它没有“存在的意义”。

    我们都知道在声明页面模板时,所有元素需要放在 <template> 元素内。除此之外,它还能在模板内使用,<template> 元素作为不可见的包裹元素,只是在运行时做处理,最终的渲染结果并不包含它。

    <template>
        <div>
            <template v-if="status==='ok'">
              <h1>Title</h1>
              <p>Paragraph 1</p>
              <p>Paragraph 2</p>
            </template>
        </div>
    </template>

    同样的,我们也可以在 <template> 上使用 v-for 指令,这种方式还能解决 v-for 和 v-if 同时使用报出的警告问题。

    <template v-for="item in 10">
        <div v-if="item % 2 == 0" :key="item">{{item}}</div>
    </template>

    template使用v-if,
    template使用v-for

    5.小型状态管理器 Vue.observable

     2.6.0 新增
    用法:让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象;

    返回的对象可以直接用于渲染函数和计算属性内,并且会在发生改变时触发相应的更新;
    也可以作为最小化的跨组件状态存储器,用于简单的场景。

    通讯原理实质上是利用Vue.observable实现一个简易的 vuex

    // 文件路径 - /store/store.js
    import Vue from 'vue'
    
    export const store = Vue.observable({ count: 0 })
    export const mutations = {
     setCount (count) {
     store.count = count
     }
    }
    
    //使用
    <template>
     <div>
      <label for="bookNum">数 量</label>
       <button @click="setCount(count+1)">+</button>
       <span>{{count}}</span>
       <button @click="setCount(count-1)">-</button>
     </div>
    </template>
    
    <script>
    import { store, mutations } from '../store/store' // Vue2.6新增API Observable
    
    export default {
     name: 'Add',
     computed: {
     count () {
      return store.count
     }
     },
     methods: {
     setCount: mutations.setCount
     }
    }
    </script>
    

    6.卸载watch监听 

    通常定义数据观察,会使用选项的方式在 watch 中配置:

    export default {
        data() {
            return {
                count: 1      
            }
        },
        watch: {
            count(newVal) {
                console.log('count 新值:'+newVal)
            }
        }
    }
    
    // 或则
    
    @Watch("visible")
      clearSearch(visible: boolean) {
        if (visible) {
          this.searchValue = "";
        }
      }
    

      

     

    除此之外,数据观察还有另一种函数式定义的方式:

    export default {
        data() {
            return {
                count: 1      
            }
        },
        created() {
            this.$watch('count', function(){
                console.log('count 新值:'+newVal)
            })
        }
    }
    

     

    它和前者的作用一样,但这种方式使定义数据观察更灵活,而且 $watch 会返回一个取消观察函数,用来停止触发回调:

    let unwatchFn = this.$watch('count', function(){
        console.log('count 新值:'+newVal)
    })
    this.count = 2 // log: count 新值:2
    unwatchFn()
    this.count = 3 // 什么都没有发生...
    $watch 第三个参数接收一个配置选项:
    
    this.$watch('count', function(){
        console.log('count 新值:'+newVal)
    }, {
        immediate: true // 立即执行watch
    })
    

    7.$on(‘hook:’)删除事件监听器

    删除事件监听器是一种常见的最佳实践,因为它有助于避免内存泄露并防止事件冲突。

    如果你想在 created 或 mounted 的钩子中定义自定义事件监听器或第三方插件,并且需要在 beforeDestroy 钩子中删除它以避免引起任何内存泄漏,那么这是一个很好的特性。下面是一个典型的设置:

    mounted () {
        window.addEventListener('resize', this.resizeHandler);
    },
    beforeDestroy () {
        window.removeEventListener('resize', this.resizeHandler);
    }

    使用 $on('hook:')方法,你可以仅使用一种生命周期方法(而不是两种)来定义/删除事件。

    mounted () {
      window.addEventListener('resize', this.resizeHandler);
      this.$on("hook:beforeDestroy", () => {
        window.removeEventListener('resize', this.resizeHandler);
      })
    }
    

    8.@hook:【event】父组件监听子组件的生命周期钩子函数

    比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:

    1 // Parent.vue
    2 <Child @mounted="doSomething"/>
    3 
    4 // Child.vue
    5 mounted() {
    6   this.$emit("mounted");
    7 }

     以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:

     1 //  Parent.vue
     2 <Child @hook:mounted="doSomething" ></Child>
     3 
     4 doSomething() {
     5    console.log('父组件监听到 mounted 钩子函数 ...');
     6 },
     7 
     8 //  Child.vue
     9 mounted(){
    10    console.log('子组件触发 mounted 钩子函数 ...');
    11 },    
    12 
    13 // 以上输出顺序为:
    14 // 子组件触发 mounted 钩子函数 ...
    15 // 父组件监听到 mounted 钩子函数 ...

     当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。

     9.动态指令参数

      Vue 2.6的最酷功能之一是可以将指令参数动态传递给组件。假设你有一个按钮组件,并且在某些情况下想监听单击事件,而在其他情况下想监听双击事件。这就是这些指令派上用场的地方:

    <template>
        ...
        <aButton @[someEvent]="handleSomeEvent()" />...
    </template>
    <script>
      ...
      data(){
        return{
          ...
          someEvent: someCondition ? "click" : "dbclick"
        }
      },
      methods: {
        handleSomeEvent(){
          // handle some event
        }
      }  
    </script>
    

      

    场景
    1.为组件添加loading效果
    2.按钮级别权限控制 v-permission
    3.代码埋点,根据操作类型定义指令
    4.input 输入框自动获取焦点
    
    注意事项
    注意:
        1.自定义指令名称,不能使用驼峰规则,而应该使用"my-dir" 或 “my_dir” 或 “mydir”
        2.使用时,必须加v-    如:<p v-my-dir="xxxx"></p>
    
    指令的生命周期
    1.bind
     只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
    
    2.inserted
     被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
    
    3update
     所在组件的 VNode 更新时调用,但是可能发生在其孩子的 VNode 更新之前。指令的值可能发生了改变也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新。
    
    3.componentUpdated
     所在组件的 VNode 及其孩子的 VNode 全部更新时调用。
    
    4.unbind
        只调用一次, 指令与元素解绑时调用。
    
    钩子函数的参数
    1.el 
        指令所绑定的元素,可以用来直接操作 DOM。
    2.binding一个对象,包含以下属性:
        name: 指令名,不包括 v- 前缀。
        value: 指令的绑定值, 例如: v-my-directive="1 + 1", value 的值是 2。
        oldValue: 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
        expression: 绑定值的字符串形式。 例如 v-my-directive="1 + 1" , expression 的值是 "1 + 1"。
        arg: 传给指令的参数。例如 v-my-directive:foo, arg 的值是 "foo"。
        modifiers: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是 { foo: true, bar: true }。
    
    3.vnode 
        编译生成的虚拟节点。
    4.oldVnode】
        上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
    
    示例: 
        bind: function (el, binding, vnode) {}
      
    [注意]除了 el 之外,其它参数都是只读的,尽量不要修改他们。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
    
    声明局部指令
    <template>
      <div class="hello">
          <div v-test='name'></div>
      </div>
    </template>
    <script>
    export default {
      data () {
        return {
         name:'我是名字',
        }
      },
      directives:{
          test:{
            inserted: function (el,binding) {// 指令的定义
               el.style.position = 'fixed'
                   el.style.top = binding.value + 'px'
            },
            bind: function (el, binding, vnode) {
    
                }
          }
      }
    }
    
    全局声明指令
    main.js
    示例:
        import Vue from 'vue';
        Vue.directive('focus',{
           bind:function(e,v){
               console.log('bind指令')
           },
           inserted:function(e,v){
               console.log('inserted指令')
               console.log(this)        //  指令内部this指向window
               e.focus();
           },
           update:function(){
               console.log('update指令')
           }
        })
    
    
    动态指令参数
    指令的传参类型有两种:
        1. v-xxxx="参数"
            通过binding.value接收
        2. v-xxx:参数1="参数2"   
            通过binding.arg
    场景:我们需要把元素需要动态的固定在左或者顶部?
    
    使用:
    <div v-for="(item, index) in list" :key="index">
        <div v-zoom:{direction:item.direcition}="{ item.width, height: item,height}"></div>
    </div>
    <script>
        data () {
            return {
                list: [
                    { 100, height: 200, direction: 'left'},
                    { 140, height: 240, direction: 'top'}
                ]
            }
        }
    </script>
    
    声明指令
    directive('pin', {
        bind: function (el, binding, vnode) {
            el.style.position = 'fixed'
            var s = (binding.arg.direction == 'left' ? 'left' : 'top')
            el.style[s] = binding.value + 'px'
        }
    })
    

      

    Vue.directive('role', {
        inserted: function (el, binding, vnode) {
          let role = binding.value
          if(role){
            const applist = sessionStorage.getItem("applist")
            const hasPermission = role.some(item => applist.includes(item)) 
            // 是否拥有权限
            if(!hasPermission){
              el.remove() //没有权限则删除模块节点
            }
          }
        }
    })
    
    
    Vue.directive('role', {
        inserted: function (el, binding, vnode) {
          let role = binding.value
          if(role){
            // vnode.context 为当前实例
            const applist = vnode.context.$store.state.applist
            const hasPermission = role.some(item => applist.includes(item)) 
            if(!hasPermission){
              el.remove()
            }
          }
        }
    })
    

    10.解耦  

    一般在组件内使用路由参数,大多数人会这样做:

    export default {
        methods: {
            getParamsId() {
                return this.$route.params.id
            }
        }
    }

    在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。

    正确的做法是通过 props 解耦

    const router = new VueRouter({
        routes: [{
            path: '/user/:id',
            component: User,
            props: true
        }]
    })

    将路由的 props 属性设置为 true 后,组件内可通过 props 接收到 params 参数

    export default {
        props: ['id'],
        methods: {
            getParamsId() {
                return this.id
            }
        }
    }

    另外你还可以通过函数模式来返回 props

    const router = new VueRouter({
        routes: [{
            path: '/user/:id',
            component: User,
            props: (route) => ({
                id: route.query.id
            })
        }]
    })

     

    重用相同路由的组件

     

    开发人员经常遇到的情况是,多个路由解析为同一个Vue组件。问题是,Vue出于性能原因,默认情况下共享组件将不会重新渲染,如果你尝试在使用相同组件的路由之间进行切换,则不会发生任何变化。

    const routes = [
      {
        path: "/a",
        component: MyComponent
      },
      {
        path: "/b",
        component: MyComponent
      },
    ];

    如果你仍然希望重新渲染这些组件,则可以通过在 router-view 组件中提供 :key 属性来实现。

    <template>
        <router-view :key="$route.path"></router-view>
    </template>

    把所有Props传到子组件很容易

    这是一个非常酷的功能,可让你将所有 props 从父组件传递到子组件。如果你有另一个组件的包装组件,这将特别方便。所以,与其把所有的 props 一个一个传下去,你可以利用这个,把所有的 props 一次传下去:

    <template>
      <childComponent v-bind="$props" />
    </template>

    代替:

    <template>
      <childComponent :prop1="prop1" :prop2="prop2" :prop="prop3" :prop4="prop4" ... />
    </template>

    把所有事件监听传到子组件很容易

    如果子组件不在父组件的根目录下,则可以将所有事件侦听器从父组件传递到子组件,如下所示:

    <template>
        <div>
        ...
            <childComponentv-on="$listeners" />...    
      <div>
    </template>

    如果子组件位于其父组件的根目录,则默认情况下它将获得这些组件,因此不需要使用这个小技巧。

    11.样式穿透

    在开发中修改第三方组件样式是很常见,但由于 scoped 属性的样式隔离,可能需要去除 scoped 或是另起一个 style 。这些做法都会带来副作用(组件样式污染、不够优雅),样式穿透在css预处理器中使用才生效。

    我们可以使用 >>> 或 /deep/ 解决这一问题:

    <style scoped>
    外层 >>> .el-checkbox {
      display: block;
      font-size: 26px;
     
      .el-checkbox__label {
        font-size: 16px;
      }
    }
    </style>
    
    <style scoped>
    /deep/ .el-checkbox {
      display: block;
      font-size: 26px;
     
      .el-checkbox__label {
        font-size: 16px;
      }
    }
    </style>
    

    12.watch高阶使用

    立即执行:immediate:true

    watch 是在监听属性改变时才会触发,有些时候,我们希望在组件创建后 watch 能够立即执行

    可能想到的的方法就是在 create 生命周期中调用一次,但这样的写法不优雅,或许我们可以使用这样的方法

    深度监听 deep:true

    在监听对象时,对象内部的属性被改变时无法触发 watch ,我们可以为其设置深度监听

    触发监听执行多个方法

    使用数组可以设置多项,形式包括字符串、函数、对象

    export default {
        data: {
            name: 'Joe'
        },
        watch: {
            name: [
                'sayName1',
                function(newVal, oldVal) {
                    this.sayName2()
                },
                {
                    handler: 'sayName3',
                    immaediate: true
                }
            ]
        },
        methods: {
            sayName1() {
                console.log('sayName1==>', this.name)
            },
            sayName2() {
                console.log('sayName2==>', this.name)
            },
            sayName3() {
                console.log('sayName3==>', this.name)
            }
        }
    }
    

      

    watch监听多个变量

    watch本身无法监听多个变量。但我们可以将需要监听的多个变量通过计算属性返回对象,再监听这个对象来实现“监听多个变量”

    export default {
        data() {
            return {
                msg1: 'apple',
                msg2: 'banana'
            }
        },
        compouted: {
            msgObj() {
                const { msg1, msg2 } = this
                return {
                    msg1,
                    msg2
                }
            }
        },
        watch: {
            msgObj: {
                handler(newVal, oldVal) {
                    if (newVal.msg1 != oldVal.msg1) {
                        console.log('msg1 is change')
                    }
                    if (newVal.msg2 != oldVal.msg2) {
                        console.log('msg2 is change')
                    }
                },
                deep: true
            }
        }
    }

    13.自定义验证 Props

    你可能已经知道可以将props验证为原始类型,例如字符串,数字甚至对象。你也可以使用自定义验证器——例如,如果你想验证一个字符串列表:

    props: {
      status: {
        type: String,
        required: true,
        validator: function (value) {
          return [
            'syncing',
            'synced',
            'version-conflict',
            'error'
          ].indexOf(value) !== -1
        }
      }
    }
    

      

  • 相关阅读:
    简单测试AF3.0.4
    好玩的Mac键盘
    黑盒测试和白盒测试
    iOS开发之原生二维码生成与扫描
    Swift
    JavaScript null and undefined
    java防止表单重复提交
    Java http post
    Redhat 6.5 x64 下载地址
    Spring 官方下载地址(非Maven)
  • 原文地址:https://www.cnblogs.com/ygunoil/p/13489507.html
Copyright © 2020-2023  润新知