• Vue.js 的一些小技巧


    给 props 属性设置多个类型

    这个技巧在开发组件的时候用的较多,为了更大的容错性考虑,同时代码也更加人性化:

    export default {
      props: {
         {
          type: [String, Number],
          default: '100px'
        }
        // 或者这样
        //  [String, Number]
      }
    }
    

    比如一个 <my-button> 上暴露了一个 width 属性,我们既可以传 100px,也可以传 100

    <!-- my-button.vue -->
    <template>
      <button :style="computedWidth">{{ computedWidth }}</button>
    </template>
    
    <script>
      export default {
        props: {
           [String, Number]
        },
        computed: {
          computedWidth () {
            let o = {}
            if (typeof this.width === 'string') o.width = this.width
            if (typeof this.width === 'number') o.width = this.width + 'px'
            return o
          }
        }
      }
    </script>
    

    使用:

    <!-- 在其他组件中使用 -->
    <template>
      <my-button :width="100px"></my-button>
      <!-- or -->
      <my-button :width="100"></my-button>
    </template>
    

    data 初始化

    因为 props 要比 data 先完成初始化,所以我们可以利用这一点给 data 初始化一些数据进去,看代码:

    export default {
      data () {
        return {
          buttonSize: this.size
        }
      },
     props: {
       size: String
     }
    }
    

    除了以上,子组件的 data 函数也可以有参数,且该参数是当前实例对象。所有我们可以利用这一点做一些自己的判断。如,改写上面的代码:

    export default {
      data (vm) {
        return {
          buttonSize: vm.size
        }
      },
     props: {
       size: String
     }
    }
    

    template

    我们在做 v-if 判断的时候,可以把判断条件放在 template 组件上,最终的渲染结果将不包含 <template> 元素。

    <template>
      <div class="box">
        <template v-if="isVal">
          <h2>...</h2>
        </template>
        <template v-else>
          <h2>...</h2>
        </template>
      </div>
    </template>
    

    v-for 也同样适用。

    Lifecycle hook

    生命周期钩子可以是一个数组类型,且数组中的函数会依次执行。

    export default {
     ...
     created: [
       function one () {
         console.log(1)
       },
       function two () {
         console.log(2)
       }
     ]
     ...
    }
    

    没什么用,知道就行了。事实上生命周期钩子还可以作用于 DOM 元素上,利用这一点,我们可以用父组件中的方法来初始化子组件的生命周期钩子:

    <!-- Child.vue -->
    <template>
      <h3>I'm child!</h3>
    </template>
    
    <!-- Parent.vue -->
    <template>
     <child @hook:created="handleChildCreated"></child>
    </template>
    
    <script>
       import Child from './child.vue'
       export default {
         components: [ Child ],
         methods: {
           handleChildCreated () {
             console.log('handle child created...')
           }
         }
       }
    </script>
    

    其他钩子雷同,不再赘述。

    v-for 和 v-if 一起使用

    由于 v-forv-if 渲染优先级更高,所以有时候可以一起使用。下面两种常见的情况下会倾向于把 v-forv-if 放在同一个标签上使用:

    • 筛选一些不想显示的条目
    • 为了避免渲染本应该被隐藏的列表

    举个栗子:

    <template>
      <ul class="items">
        <!-- 只有激活的用户才可以显示 -->
        <li 
          v-for="(user, index) in users" 
          v-if="user.isActive" 
          :key="user.id">
          {{ user.name }}
        </li>
      </ul>
    </template>
    

    关于以上两点不明白的地方可以参见 Vue 风格指南

    混合

    如果好多组件都共用到一些像 propsdatamethods 等,可以单独抽出来放到 mixins 混合器中。比如,在用户管理列表中使用。

    分页混合器:

    // paging-mixin.vue
    export default {
      props: {
        pageSize: 1,
        pageLength: 10,
        currentPage: 1
        total: 20
      },
      methods: {
        /**
         * 上一页
         */
        prevPage (page) {
          ...
        },
        /**
         * 下一页
         */
        nextPage (page) {
          ...
        }
        /**
         * 跳转到当前页
         */
        currentPage (page) {
          ...
        }
      }
    }
    

    Users.vue:

    <template>
      <div class="user-model">
        <my-table :data="users"></my-table>
        <my-paging
          :page-length="pageLength"
          :page-size="pageSize"
          :current-page="currentPage"
          :total="total">
        </my-paging>
      </div>
    </template>
    
    <script>
      import PagingMixin from '../mixins/paging-mixin.vue'
      export default {
        mixins: [PagingMixin],
        data () {
          return {
            users: [],
            pageLength: 10,
            pageSize: 1,
            currentPage: 1,
            total: 20
          }
        }
      }
    </script>
    

    不用每个页面都写一遍 propsmethods 了。

    render 函数

    下面是一段简单的 template 模板代码:

    <template>
      <div class="box">
        <h2>title</h2>
        this is content
      </div>
    </template>
    

    我们用渲染函数来重写上面的代码:

    export default {
      render (h) {
        let _c = h
        return _c('div', 
          { class: 'box'}, 
          [_c('h2', {}, 'title'), 'this is content'])
      }
    }
    

    事实上,Vue 会把模板(template)编译成渲染函数(render),你可以通过一个在线工具 实时查看编译结果。上面的 template 模板会被编译成如下渲染函数:

    let render = function () {
      return _c('div',
        {staticClass:"box"},
        [_c('h2', [_v("title")]), _v("this is content")])
    }
    

    是不是很像? 正如官方说的,渲染函数比 template 更接近编译器。如果用一个流程图来解释的话,大概是这个样子:

    template
        ↓
    预编译工具(vue-loader + vue-template-compile)
        ↓
      render
        ↓
    resolve vnode
    

    具体参见 Vue声明周期图示

    渲染函数用处:

    • 开发组件库,Element 源码用的都是 render
    • 封装一些高阶组件。组件里面嵌套组件就是高阶组件,前提是要满足组件三要素:propseventslot
    • 用于处理一些复杂的逻辑判断。如果我们一个组件里面有很多 v-if 判断的话,用模板就显得不合适了,这个时候可以用渲染函数来轻松处理

    errorCaptured

    捕获一个来自子孙组件的错误时被调用。有时候当我们想收集错误日志,却不想把错误暴露到浏览器控制台的时候,这很有用。下面是个例子:

    Child.vue

    <template>
      <!-- 省略一些无关代码 -->
    </template>
    <script>
      export default {
        mounted () {
          // 故意把 console 写错
          consol.log('这里会报错!')
        }
      }
    </script>
    复制代码

    Parent.vue

    <template>
      <child></child>
    </template>
    <script>
      import Child from './Child.vue'
      export default {
        components: [ Child ],
        /**
         * 收到三个参数:
         * 错误对象、发生错误的组件实例
         * 以及一个包含错误来源信息的字符串。
         * 此钩子可以返回 false 以阻止该错误继续向上传播。
         */
        errorCaptured (err, vm, info) {
          console.log(err)
          // -> ReferenceError: consle is not defined ...
          console.log(vm)
          // -> {_uid: 1, _isVue: true, $options: {…}, _renderProxy: o, _self: o,…}
          console.log(info)
          // -> `mounted hook`
          // 告诉我们这个错误是在 vm 组件中的 mounted 钩子中发生的
          
          // 阻止该错误继续向上传播
          return false
        }
      }
    </script>
    

    关于 errorCaptured 更多说明,请移步官网->

    v-once

    通过 v-once 创建低开销的静态组件。渲染普通的 HTML 元素在 Vue 中是非常快速的,但有的时候你可能有一个组件,这个组件包含了大量静态内容。在这种情况下,你可以在根元素上添加 v-once 特性以确保这些内容只计算一次然后缓存起来,就像这样:

    <template>
      <div class="box" v-once>
        <h2> 用户协议 </h2>
        ... a lot of static content ...
      </div>
    </template>
    

    只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。关于 v-once 更多介绍,请移步官网->

    slot-scope

    作用域插槽。vue@2.5.0 版本以前叫 scope,之后的版本用 slot-scope 将其代替。除了 scope 只可以用于 <template> 元素,其它和 slot-scope 都相同。

    用过 Element 组件的同学都知道,当我们在使用 <el-table> 的时候会看到如下代码:

    Element@1.4.x 的版本:

    <el-table-column label="操作">
      <template scope="scope">
      <el-button
        size="small"
        @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
      <el-button
        size="small"
        type="danger"
        @click="handleDelete(scope.$index, scope.row)">删除</el-button>
      </template>
    </el-table-column>
    

    但在 2.0 之后的版本替换成了 slot-scope

    Element@2.0.11:

    <el-table-column label="操作">
      <template slot-scope="scope">
        <el-button
          size="mini"
          @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
        <el-button
          size="mini"
          type="danger"
          @click="handleDelete(scope.$index, scope.row)">删除</el-button>
      </template>
    </el-table-column>
    

    说白了,slot-scope 相当于函数的回调,我把结果给你,你想怎么处理就怎么处理,一切随你:

    function getUserById (url, data, callback) {
      $.ajax({
        url,
        data,
        success: function (result) {
          callback(result)
        }
      })
    }
    
    // 使用
    getUserById('/users', { id: 1 }, function (response) {
      // 拿到数据并开始处理自己的页面逻辑
    })
    

    下面我们来简单模拟下 <el-table> 组件内部是怎么使用 slot-scope 的,看代码:

    模拟的 <el-table> 组件:

    // 定义模板
    let template = `
     <ul class="table">
      <li v-for="(item, index) in data" :key="index">
        <!-- 我希望数据由调用者自己处理 -->
        <!-- 'row' 相当于变量名,随便定义,比如 aaa,bbb 啥的 -->
        <slot :row="item">
          <!-- 当使用者什么都没写的时候,默认值才会显示-->
          {{ item.name }}
        </slot>
      </li>
     </ul>
    `
    Vue.component('el-table', {
      template,
      props: {
        data: Array,
        default: []
      }
    })
    

    在你需要的地方使用 <el-table> 组件:

    HTML:

    <div id="app">
      <el-table :data="userData">
        <!-- 使用的时候可以用 template -->
        <!-- `scope` 也是个变量名,随便命名不是固定的,比如 foo, bar -->
        <template slot-scope="scope">
          <!-- 其中 `scope.row` 中的 row 就是我们上边定义的变量啦-->
          <!-- `scope.row`返回的是 `item` 对象 -->
          <template v-if="scope.row.isActived">
            <span class="red">{{ scope.row.name }}</span>
          </template>
          <template v-else>
            {{ scope.row.name }}
          </template>
        </template>
      </el-table>
    </div>
    

    JavaScript:

    new Vue({
      el: '#app',
      data: {
        userData: [
          {id: 1, name: '张三', isActived: false},
          {id: 2, name: '李四', isActived: false},
          {id: 1, name: '王五', isActived: true},
          {id: 1, name: '赵六', isActived: false},
        ]
      }
    })
    

    CSS:

    .red {
      color: red
    }
    

    你可以狠狠的戳这里查看上面的效果!最后,我们再使用 render 函数来重构上面的代码:

    JavaScript:

    // `<el-table>` 组件
    Vue.component('el-table', {
      name: 'ElTable',
      render: function (h) {
        return h('div', { 
          class: 'el-table'
        }, this.$slots.default)
      },
      props: {
        data: Array
      }
    })
    
    // `<el-table-column>`
    Vue.component('el-table-column', {
      name: 'ElTableColumn',
      render: function (h) {
        // 定义一个存放 li 元素的数组
        let lis = [], 
           // 获取父组件中的 data 数组
           data = this.$parent.data
        // 遍历数组,也就是上面的 `v-for`,生成 `<li>` 标签
        // `this.$scopedSlots.default` 获取的就是上面 slot-scope 作用于插槽的部分,
        // 并把 `{ row: item }` 传给上面的 `scope` 变量
        data.forEach((item, index) => {
          let liEl = h('li', {
            key: item.id
          }, [ this.$scopedSlots.default({ row: item }) ])
          // 把生成的 li 标签存到数组
          lis.push(liEl)
        })
        return h('ul', {
          class: 'el-table-column'
        }, lis)
      }
    })
    

    在你的页面这样来使用:

    HTMl:

    <div id="app">
      <el-table :data="list">
        <el-table-column>
          <template slot-scope="scope">
            <span class="red" v-if="scope.row.actived">{{ scope.row.name }}</span>
            <span v-else>{{ scope.row.name }}</span>
          </template>
        </el-table-column>
      </el-table>
    </div>
    

    JavaScript:

    new Vue({
      el: '#app',
      data: {
        list: [
          { id: 1, name: '张三', actived: false },
          { id: 1, name: '李四', actived: false },
          { id: 1, name: '王五', actived: true },
          { id: 1, name: '赵六', actived: false },
        ]
      }
    })
    

    疑问:我们完全可以在 <li> 中进行逻辑判断,为什么还要放到外面进行处理呢? 因为有时候我们用的不是自己开发的组件,比如上面的 <el-table> ,所以就有必要这么做了。


    作者:gongph
    链接:https://juejin.im/post/5be01d0ce51d450700084925
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    学而不用则罔 用而不学则殆
  • 相关阅读:
    SPI传输协议笔记
    Linux power supply class
    linux ramdisk 参数问题
    Android事件处理过程分析
    PWM 参数计算
    6.828 lab3
    6.828 lab1
    i.MX53 上电启动过程
    linux jiffies的比较
    在arm板上安装Debian
  • 原文地址:https://www.cnblogs.com/songyifan427/p/9952810.html
Copyright © 2020-2023  润新知