Vue.js 你不知道的冷(qi)门(ji)知(yin)识(qiao)

面试官:MVVM 和 MVC 的区别是什么?

自己先想一分钟。

vue.js

关于上面的面试题的具体解释,请移步这里,本文不在累述。正文开始,下面列举的一些奇技淫巧有的或许你用过,有的或许你没用过。不管有的没的,希望你看完之后有所收获吧。文笔和知识有限,不对的地方,请留言斧正!

给 props 属性设置多个类型

这个技巧在开发组件的时候用的较多,为了更大的容错性考虑。比如一个 <my-button> 上暴露了一个 width 属性:

// my-button.vue
export default {
  props: {
    width: [String, Number],
    default: '100px'
  }
}
复制代码

我们既可以传 100px,也可以传 100

<!-- my-button.vue -->
<template>
  <button :style="computedWidth">width: {{ computedWidth }}</button>
</template>

<script>
  export default {
    props: {
      width: [String, Number],
      default: '100px'
    },
    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>
</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
复制代码

具体参见 Vue声明周期图示

渲染函数用处:

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

errorCaptured

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

Child.vue

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

Parent.vue

<template>
  <!-- 省略一些无关代码 -->
</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

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

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

[email protected] 的版本:

<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

[email protected]

<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 的,看代码:

// 定义模板
let template = `
 <ul class="table">
  <li v-for="(item, index) in data" :key="index">
    <!-- 我希望数据由调用者自己处理 -->
    <!-- 'row' 相当于变量名,随便起名 -->
    <slot :row="item">ddd</slot>
  </li>
 </ul>
`
// 声明 `el-table` 组件
Vue.component('el-table', {
  template,
  props: {
    data: Array,
    default: []
  }
})

// 根组件
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},
    ]
  }
})
复制代码

组件使用:

<div id="app">
  <el-table :data="userData">
    <!-- 使用的时候可以用 template -->
    <!-- `scope` 只是个形参,随便起名 -->
    <template slot-scope="scope">
      <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>
复制代码
.red {
  color: red
}
复制代码

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

结尾!

啰嗦了这么多,希望看到的同学或多或少有点收获吧。不对的地方还请留言指正,不胜感激。俗话说,三人行则必有我师! 希望更多志同道合的小伙伴能聚在一起交流技术!下面的群快满了,可以加我 Q1769617251 。备注 vue 即可。

Q1769617251

猜你喜欢

转载自juejin.im/post/5be01d0ce51d450700084925