Vue2 文档地址

Vue 核心知识体系 - 图1

Vue 基础特性

Class和Style绑定

条件渲染

v-if 和 v-show
v-if可以结合v-else、v-else-if使用

列表渲染

事件处理

表单输入绑定

知识点:

  • v-model
  • 常见表单项: input、textarea、checkbox、radio、select
  • 修饰符: lazy、number、trim

v-model指令可以在在表单 <input><textarea><select> 元素上创建双向数据绑定。

v-model 在内部为不同的输入元素使用不同的 property ,并抛出不同的事件:

  • text 和 textarea 元素使用 valueproperty 和 input 事件;
  • checkbox 和 radio 使用 checkedproperty 和 change 事件;
  • select 元素将 value 作为 prop 并将 change 作为事件。

Vue 高级特性

  • 自定义v-model
  • $nextTick
  • refs
  • slot
  • 动态组件 & 异步组件
  • keep-alive
  • mixin

自定义v-model

使用v-model分为两种场景:

  1. 用在自定义组件上,实现父子组件之间的双向绑定
  2. 用在原生DOM元素上

1. 自定义组件上

  1. <v-input v-model="val"></v-input>
  2. <!-- 等价于 -->
  3. <v-input :value="val" @input="val = $event"></v-input>

2. 原生DOM元素上

用于 input、select、textarea

  1. <input v-model="msg" />
  2. <!-- v-model相当于绑定了value属性和input事件 -->
  3. <input v-bind:value="msg" v-on:input="msg = $event.target.value" />

动态组件 & 异步组件

  1. // 动态组件
  2. <component v-bind:is="currentTabComponent"></component>
  1. // 异步组件
  2. Vue.component(
  3. 'async-component-name',
  4. () => import('./async-component') // 这个动态导入会返回一个 `Promise` 对象。
  5. )

$nextTick

前置知识
Vue是异步渲染,data改变之后,DOM不会立刻。而$nextTick会在DOM渲染之后被触发,以获取最新DOM节点。

Mixin 抽离公共逻辑

Mixin特点

  • 抽离多个组件的相同逻辑,复用性高
  • 组件支持多个mixin混入
  • mixin的data、computed会混入到组件一起,且
  • mixin的生命周期优先级高于组件自身的生命周期

Mixin缺点:

  • 变量来源不确定,可读性不高
  • 多mixin混入可能会造成命名冲突

补充:

  • 在Vue3中用Composition API来解决这些问题

    Vuex知识点

    文档地址
    image.png

Vue-Router知识点

文档地址
知识点:

  • 路由模式(hash、H5 history)
  • 路由配置(动态路由、懒加载)

HTML5 History 模式,需要相应的后端配置

  1. const router = new VueRouter({
  2. mode: 'history',
  3. routes: [...]
  4. })

Vue原理

组件化的基础上出现了数据驱动视图
对于传统组件化和数据驱动视图

  • 传统组件化,只是静态渲染,更新还要依赖JS对于DOM的操作。
  • 数据驱动视图(Vue的MVVM)

响应式

Object.defineProperty是监听data变化的核心API。

  1. /**
  2. * target {Object} 对象
  3. * key {String} 属性名
  4. */
  5. Object.defineProperty(target, key, {
  6. get() {
  7. return value
  8. },
  9. set(newValue) {
  10. value = newValue
  11. }
  12. })

Object.defineProperty 的缺点

  • 深度监听需要递归到底,计算量大
  • 无法监听新增属性/删除属性(Vue.set / Vue.delete)
  • 无法监听原生数组(需要特殊处理)

实现数组变化监听

改变「Vue中需要实现响应式的数组」的原型

  1. // 重新定义数组原型
  2. const _ArrayProperty = Array.prototype
  3. // 创建新对象,原型指向 _ArrayProperty ,再扩展新的方法不会影响原型
  4. const arrProto = Object.create(_ArrayProperty);
  5. // 「改变数组结构」的部分常用的方法
  6. ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
  7. arrProto[methodName] = function () {
  8. updateView() // 触发视图更新
  9. _ArrayProperty[methodName].call(this, ...arguments)
  10. }
  11. })
  12. // 监听对象属性
  13. function observer(target) {
  14. ...
  15. if (Array.isArray(target)) {
  16. target.__proto__ = arrProto
  17. }
  18. ...

虚拟DOM

问题现状:DOM操作非常耗费性能,并且操作JS来控制DOM
解决方案:
1、 将DOM相关的计算转移到JS处理;
2、 用JS模拟DOM结构,计算出最小的变更,最后再操作DOM

Vue模版编译

模版不是html,它含有指令、插值、JS表达式,能实现判断、循环。
模版一定是转换为JS代码,即编译模版

  1. const compiler = require('vue-template-compiler')
  2. // 插值
  3. const template = `<p>{{message}}</p>`
  4. const res = compiler.compile(template)
  5. console.log(res.render)
  6. // 输出结果(如下)
  7. with(this) {
  8. return _c(
  9. 'p',
  10. [_v(_s(message))]
  11. )
  12. }

  • 模版编译为render函数,执行redner函数返回vnode
  • 基于vnode执行 patch 和 diff
  • 使用webpack的vue-loader

vue 源码中找到缩写函数的含义

其中,_c 就是 createElement,比h函数更加语义化。

  1. function installRenderHelpers (target) {
  2. target._o = markOnce;
  3. target._n = toNumber;
  4. target._s = toString;
  5. target._l = renderList;
  6. target._t = renderSlot;
  7. target._q = looseEqual;
  8. target._i = looseIndexOf;
  9. target._m = renderStatic;
  10. target._f = resolveFilter;
  11. target._k = checkKeyCodes;
  12. target._b = bindObjectProps;
  13. target._v = createTextVNode;
  14. target._e = createEmptyVNode;
  15. target._u = resolveScopedSlots;
  16. target._g = bindObjectListeners;
  17. target._d = bindDynamicKeys;
  18. target._p = prependModifier;
  19. }

在Vue组件中,使用 render 代替 tempalte

  1. Vue.component('anchored-heading', {
  2. render: function (createElement) {
  3. return createElement(
  4. 'h' + this.level, // 标签名称
  5. this.$slots.default // 子节点数组
  6. )
  7. },
  8. props: {
  9. level: {
  10. type: Number,
  11. required: true
  12. }
  13. }
  14. })

组件的渲染和更新过程

patch方法,计算新旧vnode的差异

  1. function patch(oldVnode: VNode | Element, vnode: VNode): VNode {
  2. const insertedVnodeQueue: VNodeQueue = [];
  3. // 第一个参数不是vnode
  4. if (!isVnode(oldVnode)) {
  5. // 创建一个空的vnode,关联到这个 DOM 元素
  6. oldVnode = emptyNodeAt(oldVnode);
  7. }
  8. // 相同的 vnode(key 和 sel 都相等)
  9. if (sameVnode(oldVnode, vnode)) {
  10. // vnode 对比
  11. patchVnode(oldVnode, vnode, insertedVnodeQueue);
  12. // 不同的 vnode ,直接删掉重建
  13. } else {
  14. ...
  15. }
  16. }
  17. function patchVnode(oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
  18. }
  • 响应式:监听data属性getter\setter(包括数组)
  • 模版编译: 模版到render函数,再到vnode
  • vdom: patch(elem,vnode)和patch(vnode, newVnode)

    挂载的逻辑

  1. 判断是否有

初次渲染过程

  1. 解析模版render函数
  2. 触发响应式,监听data属性的getter和setter(因为render函数的变量是this指向的;也包括依赖收集)
  3. 执行render函数,生成vnode
  4. patch(elem, vnode)

更新过程

  1. 修改data,触发setter(必须是之前已在getter中被监听)
  2. 重新执行render函数,生成newVnode
  3. patch(vnode, newVnode)