Skip to content

高级特性

自定义model

  • input元素的value = this.name
  • 绑定input事件,this.name = $event.target.value

$nextTick

Vue是异步渲染的
data改变之后,Dom不会立刻渲染
$nextTick会在Dom渲染之后出发,以回去最新Dom节点

插槽

Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 [Web Components](https://github.com/WICG/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md) 规范草案,将元素作为承载分发内容的出口。

匿名插槽(默认插槽)

没有命名默认只有一个。

vue
// 子组件
<button type="submit">
  <slot>Submit</slot> // Submit作为默认值
</button>

<submit-button></submit-button> // 渲染出默认值Submit

<submit-button>
  Save // 替换默认值,渲染出Save
</submit-button>

具名插槽

与匿名插槽对应,slot标签带name命名。

  • 子组件
vue
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

一个不带 name<slot> 出口会带有隐含的名字“default”。

在向具名插槽提供内容的时候,我们可以在一个<template>元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

  • 父组件
vue
<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

作用域插槽

子组件内数据可以被父页面拿到(解决了数据只能从父组件传递到子组件的问题)。

  • 子组件
vue
<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>
  • 父组件
vue
<current-user>
    <template v-slot:default="slotProps" />
    <template scope="slotProps"> // 另一种写法
        {{ slotProps.user.firstName }}
    </template>
</current-user>

自定义指令

提供一种机制,将数据的变化映射为Dom的行为。

Vue.directive()

局部指令

vue
directives: {
	focus: {
  		// 绑定完成
      bind: function () {
          console.log('bind', ...arguments);
      },
      //
      update: function () {
          console.log('update', ...arguments);
      },
      // 解绑
      unbind: function () {
          console.log('unbind', ...arguments);
      },
      // 当被绑定的元素插入到 DOM 中时……
      inserted: function (el) {
          // 聚焦元素
          el.focus()
      }
	}
}

全局指令

vue

Vue.directive('focus', {
    // 绑定完成
    bind: function () {
        console.log('bind', ...arguments);
    },
    //
    update: function () {
        console.log('update', ...arguments);
    },
    // 解绑
    unbind: function () {
        console.log('unbind', ...arguments);
    },
    // 当被绑定的元素插入到 DOM 中时……
    inserted: function (el) {
        // 聚焦元素
        el.focus()
    }
})

钩子函数

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

钩子函数的参数

指令钩子函数会被传入以下参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM。
  • binding:一个对象,包含以下 property:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

动态、异步组件

动态组件

通过 is 属性来实现动态组件的切换。

vue

<component v-bind:is="currentTabComponent"></component>

异步组件

异步组件是指在渲染过程中,需要等待的组件。

vue

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

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

// 简写
const AsyncComp = () => import('./MyComp.vue')

Vue组件库的二次封装

实现一个自动loading的按钮。

attrs属性介绍

vm-attrs

包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

组件代码

通过 v-bind="$attrs",去继承父组件传过来的属性。
或者通过 $props 去接受所有的参数。

vue
<template>
    <Button :loading="loadingStatus" v-bind="$attrs" @click="handleClick"><slot /></Button>
</template>

<script>
import { Button } from 'vant';
export default {
    name: "MuButton",
  	/**
    props: {
        ...Button.props,
        autoLoading: {
            type: Boolean,
            default: false
        }
    },
  	*/
    props: {
        autoLoading: {
            type: Boolean,
            default: false
        }
    },
    data () {
        return {
            loadingStatus: false,
        }
    },
    components: {
        Button
    },
    methods: {
        handleClick() {
            if (this.autoLoading) {
                // 判断是否开启自动loading
                this.loadingStatus = true;
            }
            // 点击的回调
            this.$emit('click', () => {
                // 在异步完成之后,去除loading
                this.loadingStatus = false;
            })
        }
    }
}
</script>

<style scoped>

</style>

页面中使用

vue
<template>

    <div class="home">
        <MyButton type="primary" round :autoLoading="true" :loading-text="'提交中...'" @click="handleClick">提交</MyButton>
    </div>

</template>

<script>
import { mapState } from 'vuex';
import MyButton from '@/components/MyButton';

export default {
    name: "Home",
    data() {
        return {
        }
    },
    computed: {
        ...mapState('router', [
            'routers'
        ])
    },
    components: {
        MyButton
    },
    methods: {
        handleClick (done) {
            setTimeout(() => {
                // 执行该回调,去关闭loading
                done();
            }, 1500)
        }
    }
}
</script>

keep-alive

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。

缓存 vdom,避免重新渲染。

vue

<keep-alive>
    <component :is="currentTabComponent"></component>
</keep-alive>

路由

路由的模式

  • hash
  • history

hash

  • hash变化会触发网页跳转,即浏览器的前进、后退
  • hash变化不会刷新页面,SPA必须得特点
  • hash不会提交到server端
javascript
// 监听路由变化
window.addEventListener("hashchange", function () {
});

// 监听路由变化
window.onhashchange = function (event) {
}

history

  • history变化不会触发网页跳转
  • history.pushState
  • window.onpopstate
javascript
history.pushState({
    name: 'pageName'
});

// 监听浏览器前进后退
window.onpopstate = function (event) {
    console.log(event.state);
}

路由自动加载

  • 思路

我们可以将用到的页面都引入,然后通过文件名去筛选拿到路由名称

可以通过webpack的api来获取对应的文件,即上下文来实现。

require.context();

可以给这个函数传入三个参数:一个要搜索的目录,一个标记表示是否还搜索其子目录, 以及一个匹配文件的正则表达式。

javascript
require.context(directory, useSubdirectories = false, regExp = /^\.\//);
// require.context(
//   directory: String, 要搜索的文件夹目录
//   includeSubdirs: Boolean /* optional, default true */, 搜索它的子目录
//   filter: RegExp /* optional, default /^\.\/.*$/, any file */, 匹配文件的正则表达式
//   mode: String  /* optional, 'sync' | 'eager' | 'weak' | 'lazy' | 'lazy-once', default 'sync' */
// )

官方

  • 实现
javascript

import Vue from 'vue';
import Router from 'vue-router';

// 先定义一个空的路由数组
let routes = [
];

let views = require.context('../views/', true, /\.vue$/);

// 导出的方法有 3 个属性: resolve, keys, id。
// - resolve 是一个函数,它返回请求被解析后得到的模块 id。
// - keys 也是一个函数,它返回一个数组,由所有可能被上下文模块处理的请求组成。
// - id 是上下文模块里面所包含的模块 id. 它可能在你使用 module.hot.accept 的时候被用到


// 这里只用到 keys,返回搜索到的数组
views.keys().forEach((key) => {
    let routerName = views(key).default.name;
    // 将对应路由push到路由的数组
    routes.push({
        path: routerName === 'Home' ? '/' : `/${routerName}`,
        title: routerName,
        name: routerName,
        component: views(key).default
    });
});

// console.log(routes);

Vue.use(Router);

export default new Router({
    routes
});

组件的自动加载

同理,我们也可以通过该api实现组件的自动加载

  • 在组件文件夹下新建index.js,编码如下
javascript
const files = require.context('./', false, /\.vue$/); //引入当前路径下所有的vue组件

let components = {};

files.keys().forEach((key) => {
    components[files(key).default.name] = files(key).default;
});

console.log(components);

export default components;
  • 在需要用到组件的地方
javascript

import subComponents from '/components';

component: {
   subComponent
}