你应该知道的Vue高级特征

本文使用的Vue版本:2.6.10

Vue为我们提供了许多高级特征,学习和掌握它们有助于提高你的代码水平。

一、watch进阶

从我们刚开始学习Vue的时刻,对于侦听属性,都是简朴地如下面一样平常使用:

watch:{
	a(){
	 //doSomething
	}
}

现实上,Vue对watch提供了许多进阶用法。

handler函数

以工具和handler函数的方式来界说一个监听属性,handler就是处置监听更改时的函数:

watch:{
	a:{
		handler:'doSomething'
	}
},
methods:{
	doSomething(){
		//当 a 发生转变的时刻,做些处置
	}
}

handler有啥用?是画蛇添足么?用途主要有两点:

  • 将处置逻辑抽象出去了,以method的方式被复用
  • 给界说下面两个主要属性留出了编写位置

deep属性

不知道你注重到了没有?

当watch的是一个Object类型的数据,若是这个工具内部的某个值发生了改变,并不会触发watch动作!

也就是说,watch默认情形下,不监测内部嵌套数据的更改。然则许多情形下,我们是需要监测的!

为解决这一问题,就要使用deep属性:

watch:{
	obj:{
		handler:'doSomething',
		deep:true
	}
},
methods:{
	doSomething(){
		//当 obj 发生转变的时刻,做些处置
	}
}

deep属性默以为false,也就是我们常用的watch模式。

immediate属性

watchhandler函数通常情形下只有在监听的属性发生改变时才会触发。

但有些时刻,我们希望在组件建立后,或者说watch被声明和绑定的时刻,马上执行一次handler函数,这就需要使用immediate属性了,它默以为false,改为true后,就会马上执行handler。

watch:{
	obj:{
		handler:'doSomething',
		deep:true,
		immediate:true
	}
},
methods:{
	doSomething(){
		//当 obj 发生转变的时刻,做些处置
	}
}

同时执行多个方式

使用数组可以设置多项,形式包罗字符串、函数、工具

  watch: {
    // 你可以传入回调数组,它们会被逐一挪用
    a: [
        
      'handle1',
        
      function handle2 (val, oldVal) { /* ... */ },
        
      {
        handler: function handle3 (val, oldVal) { /* ... */ },
        /* ... */
      }
        
    ],
    
  }

二、$event的差异显示

$event 是事宜工具的特殊变量,在两种场景下,它有差异的意义,代表差异的工具。

  • 在原生事宜中示意事宜自己。可以通过$event.target获得事宜所在的DOM工具,再通过value进一步获取详细的值。
<template>
    <div>
        <input type="text" @input="inputHandler('hello', $event)" />
    </div>
</template>


export default {
    methods: {
        inputHandler(msg, e) {
            console.log(e.target.value)
        }
    }
}
  • 而在父子组件通过自界说事宜举行通讯时,示意从子组件中通报出来的参数值

看下面的例子:

//blog-post组件的模板

<button v-on:click="$emit('enlarge-text', 0.1)">
  Enlarge text
</button>

在父级组件监听这个事宜的时刻,可以通过 $event 接见到blog-post子组件通报出来的0.1这个值:

<blog-post
  ...
  v-on:enlarge-text="postFontSize += $event"
></blog-post>

此时,$event的值就是0.1,而不是前面的事宜工具。

三、异步更新行列

  • Vue 在更新 DOM 时是异步执行的。
  • 只要侦听到数据转变,Vue 将开启一个行列,并缓冲在统一事宜循环中发生的所有数据调换。
  • 若是统一个 watcher 被多次触发,只会被推入到行列中一次。

这种在缓冲时去除重复数据对于制止不必要的盘算和 DOM 操作是异常主要的。然后,在下一个的事宜循环“tick”中,Vue 刷新行列并执行现实 (已去重的) 事情。Vue 在内部对异步行列实验使用原生的 Promise.thenMutationObserversetImmediate,若是执行环境不支持,则会接纳 setTimeout(fn, 0) 取代。

例如,当你设置 vm.someData = 'new value',该组件不会立刻重新渲染。当刷新行列时,组件会在下一个事宜循环“tick”中更新。

多数情形我们不需要体贴这个历程,然则若是你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。

虽然 Vue.js 通常激励开发人员使用“数据驱动”的方式思索,制止直接接触 DOM,然则有时我们必须要这么做。为了在数据转变之后守候 Vue 完成更新 DOM,可以在数据转变之后立刻使用 Vue.nextTick(callback)

这样回调函数将在 DOM 更新完成后被挪用。例如:

<div id="example">{{message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})

在组件内使用 vm.$nextTick() 实例方式稀奇利便,由于它不需要全局 Vue,而且回调函数中的 this 将自动绑定到当前的 Vue 实例上:

Vue.component('example', {
  template: '<span>{{ message }}</span>',
  data: function () {
    return {
      message: '未更新'
    }
  },
  methods: {
    updateMessage: function () {
      this.message = '已更新'
      console.log(this.$el.textContent) // => '未更新'
      this.$nextTick(function () {
        console.log(this.$el.textContent) // => '已更新'
      })
    }
  }
})

由于 $nextTick() 返回一个 Promise 工具,以是你可以使用新的 ES2017 async/await 语法完成相同的事情:

methods: {
  updateMessage: async function () {
    this.message = '已更新'
      //在这里可以看出,message并没有马上被执行
      //要明白页面刷新和代码执行速率的差异
      //通常我们在页面上马上就能看到效果,那是由于一轮行列执行实在很快,感受不出DOM刷新的历程和所花费的时间
      //但对于代码的执行,属于马上级别,DOM没更新就是没更新,就是会有问题
    console.log(this.$el.textContent) // => '未更新'
      
    await this.$nextTick()
    console.log(this.$el.textContent) // => '已更新'
  }
}

通俗的注释

  • Vue的DOM刷新机制是个异步行列,并不是你想象中的马上、马上、即时更新!

  • 这个异步行列是一轮一轮的执行并刷新

  • 上面带来的问题是,一些依赖DOM更新完毕才气举行的操作(好比对新增添的DOM元素举行事宜绑定),无法马上执行,必须守候一轮行列执行完毕

  • 最容易碰着上面问题的地方:created生命周期钩子函数中对DOM举行操作

  • 解决办法:使用this.nextTick(回调函数)方式,将对DOM的操作作为它的回调函数使用。

四、函数式组件

由于传统编写模板的能力不足,我们引入了渲染函数createElement。我们又希望获得更多的天真度,于是引入了JSX。最后,我们发现有些简朴的模板可以更简朴更小巧的实现,于是引入了函数式组件。Vue总是试图为每一种场景提供差异的能力。

有这么一类组件,它的特点是:

  • 比较简朴
  • 没有治理任何状态,也就是说无状态,没有响应式数据
  • 没有监听任何通报给它的状态
  • 没有写生命周期方式
  • 本质上只是一个吸收一些prop的函数
  • 没有实例,没有this上下文

那么这个组件可以界说为函数式组件。与通俗组件相比,函数式组件是无状态的,无法实例化,没有任何的生命周期和方式,适合只依赖于外部数据的转变而转变的组件,因其轻量,渲染性能会有所提高。

建立函数式组件

  • 以界说全局组件的方式
Vue.component('my-component', {
  functional: true,
  // Props 是可选的
  props: {
    // ...
  },
  // 为了填补缺少的实例
  // 提供第二个参数作为上下文
  render: function (createElement, context) {
    // ...
  }
})

注重其中的functional: true,

在 Vue 2.3.0 或以上的版本中,你可以省略 props 选项,所有组件上的 attribute 都会被自动隐式剖析为 prop。

当使用函数式组件时,该引用将会是 HTMLElement,由于他们是无状态的也是无实例的。

  • 对于单文件组件,建立函数式组件的方式是在模板标签内,添加functional属性
<template functional>
...
</template>

<script>
...
</script>

<style>
...
</style>

最主要的context参数

由于无状态,没有this上下文,以是函数式组件需要的一切都是通过 context 参数来通报,它是一个包罗如下字段的工具:

  • props:提供所有 prop 的工具
  • children:VNode 子节点的数组
  • slots:一个函数,返回了包罗所有插槽的工具
  • scopedSlots:(2.6.0+) 一个露出传入的作用域插槽的工具。也以函数形式露出通俗插槽。
  • data:通报给组件的整个数据工具,作为 createElement 的第二个参数传入组件
  • parent:对父组件的引用
  • listeners:(2.3.0+) 一个包罗了所有父组件为当前组件注册的事宜监听器的工具。这是 data.on 的一个别名。
  • injections:(2.3.0+) 若是使用了 inject 选项,则该工具包罗了应当被注入的 property。

应用场景

函数式组件的一个典型应用场景是作为包装组件,好比当你碰着下面需求时:

从零开始学习docker之在docker中搭建redis(集群)

  • 程序化地在多个组件中选择一个来代为渲染;
  • 在将 childrenpropsdata 通报给子组件之前操作它们。

下面是一个 smart-list 组件的例子,它能凭据传入 prop 的值来代为渲染更详细的组件:

var EmptyList = { /* ... */ }
var TableList = { /* ... */ }
var OrderedList = { /* ... */ }
var UnorderedList = { /* ... */ }

Vue.component('smart-list', {
  functional: true,
  props: {
    items: {
      type: Array,
      required: true
    },
    isOrdered: Boolean
  },
  render: function (createElement, context) {
    function appropriateListComponent () {
      var items = context.props.items

      if (items.length === 0)           return EmptyList
      if (typeof items[0] === 'object') return TableList
      if (context.props.isOrdered)      return OrderedList

      return UnorderedList
    }

    return createElement(
      appropriateListComponent(),
      context.data,
      context.children
    )
  }
})

五、监听子组件的生命周期

如果我们有父组件Parent和子组件Child,若是在父组件中需要监听子组件的mounted这个生命周期函数,并做一些逻辑处置,通例写法可能如下:

// Parent.vue
<Child @mounted="doSth" />

//Child.vue
mounted(){
    this.$emit('mounted');
}

然则,Vue给我们提供了一种更简捷的方式,子组件无需做任何处置,只需要在父组件引用子组件时使用@hook事宜来监听即可,代码如下:

// Parent.vue

<Child @hook:mounted="doSth" />  

methods:{
    doSth(){
        //some codes here
    }
}

焦点是@hook:mounted="doSth"的写法!

固然这里不仅仅可以监听mounted,其他生命周期都可以监听,例如created、updated等。

六、样式穿透

我们知道,在单文件组件的style中使用 scoped 属性后,父组件的样式将不会渗透到子组件中。

不外一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从结构的角度出发,调整其子组件根元素的样式。

若是你希望父组件的 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,可以使用深度选择器: >>> 操作符。

<style scoped>
.a >>> .b { /* ... */ }
</style>

上述代码将会编译成:

.a[data-v-f3f3eg9] .b { /* ... */ }

然则,有些像 Sass 之类的预处置器无法准确剖析 >>>。这种情形下你可以使用 /deep/::v-deep 操作符,这两者都是 >>> 的别名,实现同样的功效。

我们都知道,通过 v-html 建立的 DOM 内容不受 scoped 样式影响,可以通过深度作用选择器>>>来为他们设置样式。

七、路由的props属性

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

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

当你随便用用,暂且凑手,这没什么问题,究竟解决了需求。

可我们要随时谨记:组件是用来复用的!组件应该有高度的封闭性!

在组件中使用 $route 会使它与路由系统形成高度耦合,从而使组件只能在使用了路由功效的项目内,或某些特定的 URL 上使用,限制了其天真性。

试想一下,若是你的组件被人拿去复用了,然则那个人并没有使用路由系统,而是通过其余方式通报id参数,那么他该怎么办?

准确的做法是通过 props 解耦

首先,为组件界说一个叫做id的prop:

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

若是组件没有对应路由,那么这个id也可以通过父组件向子组件传值的方式使用。

若是使用了路由,可以通过路由的prop属性,通报id的值:

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

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

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

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

实在,上面的技巧,在VueRouter的官档都有说明。

八、异步组件

在大型应用中,我们可能需要将应用分割成小一些的代码块,而且只在需要的时刻才从服务器加载一个模块。

为了简化,Vue 允许你以一个工厂函数的方式界说你的组件,这个工厂函数会异步剖析你的组件界说。Vue 只有在这个组件需要被渲染的时刻才会触发该工厂函数,且会把效果缓存起来供未来重渲染。例如:

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回调通报组件界说
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

如你所见,这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器获得组件界说的时刻被挪用。

你也可以挪用 reject(reason) 来示意加载失败。这里的 setTimeout 是为了演示用的,若何获取组件取决于你自己。

一个推荐的做法是将异步组件和 webpack 的 code-splitting 功效一起配合使用:

Vue.component('async-webpack-example', function (resolve) {
  // 这个特殊的 `require` 语法将会告诉 webpack
  // 自动将你的构建代码切割成多个包,这些包
  // 会通过 Ajax 请求加载
  require(['./my-async-component'], resolve)
})

你也可以在工厂函数中返回一个 Promise,以是把 webpack 2 和 ES2015 语法加在一起,我们可以写成这样:

Vue.component(
  'async-webpack-example',
  // 这个 `import` 函数会返回一个 `Promise` 工具。
  () => import('./my-async-component')
)

当使用局部注册组件的时刻,你也可以直接提供一个返回 Promise 的函数:

new Vue({
  // ...
  components: {
    'my-component': () => import('./my-async-component')
  }
})

若是你想实现异步加载组件的功效,提高首屏显示速率,那么可以使用上面例子中的界说组件的方式,也就是:箭头函数+import语句!

处置加载状态

2.3.0+ 新增

异步组件的工厂函数也可以返回一个如下花样的工具,用来天真定制异步加载历程:

const AsyncComponent = () => ({
  // 需要加载的组件 (应该是一个 `Promise` 工具)
  component: import('./MyComponent.vue'),
  // 异步组件加载时使用的组件
  loading: LoadingComponent,
  // 加载失败时使用的组件
  error: ErrorComponent,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 若是提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000
})

注重若是你希望在 Vue Router 的路由组件中使用上述语法的话,必须使用 Vue Router 2.4.0+ 版本。

九、批量导入组件

许多时刻我们会编写一些类似输入框或按钮之类的基础组件,它们是相对通用的组件,称为基础组件,它们会在更大一些的组件中被频仍的用到。

这很容易导致大的组件里有一个很长的导入基础组件的语句列表,例如:

import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'
//更多导入

export default {
  components: {
    BaseButton,
    BaseIcon,
    BaseInput
  }
}

当你的基础组件许多的时刻,这个历程将异常重复、贫苦和无聊。

require.context()

若是你正好使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),那么就可以使用 require.context 方式批量导入这些组件,然后将它们注册为全局组件,这样就可以在任何地方直接使用它们了,再也不用为导入的事情烦恼了!

下面是一个示例代码:

import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

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
        .split('/')
        .pop()
        .replace(/\.\w+$/, '')
    )
  )

  // 全局注册组件
  Vue.component(
    componentName,
    // 若是这个组件选项是通过 `export default` 导出的,
    // 那么就会优先使用 `.default`,
    // 否则回退到使用模块的根。
    componentConfig.default || componentConfig
  )
})

记着全局注册的行为必须在根 Vue 实例 (通过 new Vue) 建立之前发生

更多内容请接见: https://www.liujiangblog.com

更多视频教程请接见: https://www.liujiangblog.com/video/

原创文章,作者:7h28新闻网,如若转载,请注明出处:https://www.7h28.com/archives/8039.html