tomoya06 / web-developer-guidance

Actually it's just a notebook for keeping down some working experience.
4 stars 0 forks source link

Vue - 基础 #14

Open tomoya06 opened 4 years ago

tomoya06 commented 4 years ago

概述

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。Vue的设计收到MVVM模型影响。

MVC/MVVM等软件架构简介

本文参考阮一峰的博客

MVC

image

组成部分:

通信方式

所有通信都是单向的。

交互方式

接受用户指令时,MVC 可以分成两种方式。一种是通过 View 接受指令,传递给 Controller;另一种是直接通过controller接受指令。但实际上还有其他灵活的方式。

image

MVVM

image

通信方式

tomoya06 commented 4 years ago

生命周期

示意图

image

高清图见Vue官方文档

生命周期钩子

生命周期钩子 组件状态 最佳实践 可访问变量
beforeCreate 实例初始化之后,this指向创建的实例,不能访问到data、computed、watch、methods上的方法和数据 常用于初始化非响应式变量 None
created 实例创建完成,可访问data、computed、watch、methods上的方法和数据,未挂载到DOM,不能访问到$el属性,$ref属性内容为空数组 常用于简单的ajax请求,页面的初始化 data/computed/watch/methods
beforeMount 在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数 + $el
mounted 实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,$refs属性可以访问 常用于获取VNode信息和操作,ajax请求 +$refs
beforeupdate 响应式数据更新时调用,发生在虚拟DOM打补丁(patch)之前 适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器 更新的data
updated 虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作 避免在这个钩子函数中操作数据,可能陷入死循环 同上
beforeDestroy 实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例 常用于销毁定时器、解绑全局事件、销毁插件对象等操作
destroyed 实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁

另外,<keep-alive>元素独有的生命周期,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数。

钩子函数输出演示

image

父子组件的生命周期

  1. 加载渲染过程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

  2. 子组件更新过程 父beforeUpdate->子beforeUpdate->子updated->父updated

  3. 父组件更新过程 父beforeUpdate->父updated

  4. 销毁过程 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

tomoya06 commented 4 years ago

内部运行机制

直接看小册吧,就是这本

运作流程图

image

关键点

响应式原理

核心组成:

参考掘金博客总结

响应化过程:

编译

VNode、Virtual DOM

16cbef3b0284f75e

VNode 生成真实DOM

本质上是通过原生DOM操作方式来生成DOM节点,然后插入$el中。完整流程可以参考掘金博客。关键点如下:

image

  1. 无论VNode是什么类型的节点,只有三种类型的节点会被创建并插入到的Dom中:元素节点、注释节点、和文本节点。
  2. 生成过程是递归完成的,是逐步生成子节点然后插入到父节点中的过程。

数据更新后patch

163777930be304eb

图片来自这里

nextTick、批量更新

tomoya06 commented 4 years ago

疑难杂症

Vue data() 为什么是函数,而不是一个对象

data是Vue原型链上的一个属性,每个Vue组件都是一个Vue实例,通过new Vue()实例化。如果data直接是一个对象的话,那么一旦修改其中一个组件的数据,其他组件相同数据就会被改变。因此Vue规定,data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。

参考这篇博客,Vue的data定义类似如下,是在原型链上绑定的:

// 创建一个简单的构建函数
var MyComponent = function() {
    // ... code here
}
// 原型链对象上设置data数据,为里为Object
MyComponent.prototype.data = {
  name: 'abc',
  age: 20,
}
// 创建两个实例:小明,小红
var xiaoming = new MyComponent()
var xiaohong = new MyComponent()
// 默认状态下小明和小红的年龄一样
console.log(xiaoming.data.age === xiaohong.data.age) // true
// 改变一下小明的年龄
xiaoming.data.age = 30;
// 打印下小红的年龄,发现因为改变了小明的年龄,结果造成小红的年龄也变了
console.log(xiaoming.data.age)// 30
console.log(xiaohong.data.age) // 30

组件通信

不能检测的变化

  1. 对象: Vue 无法检测 property 的添加或移除;解决:Vue.set(object, propertyName, value)
  2. 数组:Vue 不能检测以下数组的变动:
    1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue;解决:Vue.set(vm.items, indexOfItem, newValue) / vm.items.splice(indexOfItem, 1, newValue)
    2. 当你修改数组的长度时,例如:vm.items.length = newLength;解决:vm.items.splice(newLength)

进一步分析:真的是因为Object.defineProperty不行吗?

参考这篇博客的测试分析,Object.defineProperty在数组中的表现和在对象中的表现是一致的,数组的索引就可以看做是对象中的 key。

所以,Object.defineProperty 是有监控数组下标变化的能力的,只是vue2.x放弃了这个特性。在2.x中vue重新hack了数组的内置方法,包括push / pop / shift / unshift / splice / sort / reverse

Object.defineProperty vs Proxy

Vue3.x将改用Proxy来实现响应化。用法参考MDN文档。与 Object.defineProperty 相比,proxy的好处如下:

  1. Object.defineProperty只能劫持对象的属性,而Proxy是直接代理对象
  2. proxy可以监听到新增的属性,包括数组
  3. proxy支持更多拦截操作,除了get/set之外还有delete/has等共13种

但缺点在于兼容性差。

watch & computed

Vue组件中支持的另外两个API。用于实现侦听器和计算属性。

特性比较

watch:

computed:

实现原理

本段参考掘金博客

watch

给要监听属性的Dep中注册一个watcher,该watcher的cb就是watch定义的handler。当属性更新时,按照一般的响应式流程触发watcher执行。

computed

  1. computed属性本身也会建立一套相应系统,也就是有自己的Dep,与vm.data的Dep类似,也用来触发computed属性的监听者的更新,比如用到computed属性的视图。
  2. computed属性一般通过data的属性值计算而得。在计算过程中也就调用到了data属性的get方法,从而computed本身就成为了这些data属性的watcher,被添加到这些data属性的dep中。
  3. 依赖的data属性更新时,触发computed属性更新。computed属性内部会有一份缓存值,记录当前本属性的最新结果;但data更新的时候并不会立刻去更新缓存值,而是用另一个标志位来说明缓存值有更新,当computed自己的watcher来读取自己的值时,才会去计算并更新缓存值,并更新标志位,直到下次更新前都直接从缓存值读取。
  4. computed被触发更新时,也会出发自己的watcher来更新,例如页面更新,这时就会调用computed的getter,从而触发计算新值或者读取缓存

v-if & v-show & v-html

tomoya06 commented 4 years ago

Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

数据流

image

通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。

组成部分

注册了Vuex的组件可以通过this.$store来进行vuex的相关操作

经过测试,在mutation中使用setTimeout异步更新state也是可以的,action中不借助mutation、而是直接更新state也可以。参考尤雨溪的知乎回答,Vuex文档中对mutation和action的操作限制更多的是建议而非强制规定,目的是为了能更好地调试,例如在devTools中做state回滚的操作。以测试为例,如果在action中直接修改state,devTools中是不记录这次修改记录的;mutation中的异步更新,devTools添加的记录也有延迟。

实现原理

更新操作

Flux

参考阮一峰的博客

Vuex的实现参考了Flux、Redux等状态管理的基本思想。下图是Flux的数据流。

image

组成部分

更新操作

Flux 的最大特点,就是数据的"单向流动"。