Vue 和 React


Vue


内部流程图

vue内部流程图

虚拟DOM和Diff算法

  • Virtual DOM 其实就是一棵以 JavaScript 对象( VNode 节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。
  • vnode 有几个重要的属性
    • tag 属性即这个vnode的标签属性
    • data 属性包含了最后渲染成真实dom节点后,节点上的class,attribute,style以及绑定的事件
    • children 属性是vnode的子节点
    • text 属性是文本属性
    • elm 属性为这个vnode对应的真实dom节点
    • key 属性是vnode的标记,在diff过程中可以提高diff的效率,就地复用
  • Diff算法:主要体现在 patchVnode函数 。将新产生的 VNode 节点与老 VNode 进行一个 patch 的过程,比对得出「差异」,最终将这些「差异」更新到视图上。
  • 由于 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说浏览器平台、Weex、Node 等。(依赖一层适配层,将不同平台的 API 封装在内,以同样的接口对外提供。)
  • diff 算法(虚拟DOM:h函数,patch函数)
    • 逐层、同层比对,一层不匹配,就不再比对,直接替换
    • key值可以提升虚拟DOM比对的性能(就地复用 —— 复用的是没有发生改变的元素,其他的还要依次重排 )
  • initial render 和 updates(初始渲染和更新)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
...
// 如果需要diff的prevVnode不存在,那么就用新的vnode创建一个真实dom节点
if (!prevVnode) {
// initial render
// 第一个参数为真实的node节点
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false /* removeOnly */,
vm.$options._parentElm,
vm.$options._refElm
)
} else {
// updates
// 如果需要diff的prevVnode存在,那么首先对prevVnode和vnode进行diff,并将需要的更新的dom操作已patch的形式打到prevVnode上,并完成真实dom的更新工作
vm.$el = vm.__patch__(prevVnode, vnode)
}
...
}

//vm.__patch__ 函数
function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
// 当oldVnode不存在时
if (isUndef(oldVnode)) {
// 创建新的节点
createElm(vnode, insertedVnodeQueue, parentElm, refElm)
} else {
//当oldVnode存在,且和vnode满足sameVnode时
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
// 对oldVnode和vnode进行diff,并对oldVnode打patch
patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
}
}
}
  • sameVnode( patchVnode在符合 sameVnode 的条件下触发的,所以会进行「比对」)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* sameVnode会对传入的2个vnode进行基本属性的比较,只有当基本属性相同的情况下才认为这个2个vnode只是局部发生了更新,然后才会对这2个vnode进行diff,如果2个vnode的基本属性存在不一致的情况,那么就会直接跳过diff的过程,进而依据vnode新建一个真实的dom,同时删除老的dom节点。*/
function sameVnode(){
return (
a.key === b.key &&
a.tag === b.tag &&
a.isComment === b.isCommet &&
(!!a.data) === (!!b.data) &&
sameInputType(a,b)
)
}
function sameInputType (a, b{
    if (a.tag !== 'input'return true
    let i
    const typeA = (i = a.data) && (i = i.attrs) && i.type
    const typeB = (i = b.data) && (i = i.attrs) && i.type
    return typeA === typeB
}
/* key,tag,isComment(是否为注释结点),data同时定义或者不定义,并且满足当标签类型为input的时候 type 相同 */
  • patchVnode ( diff 算法的体现 )

    • 接收两个参数 oldVnode和 vnode
    • 实现
      • 第一种情况是新老 VNode 节点相同的情况下,就不需要做任何改变了,直接 return
      • 第一种情况是新老 VNode 节点都是 isStatic (静态的),并且 key 相同时,只要将老节点的 componentInstance 与 elm 赋值给新节点即可(模板编译的optimize阶段会标记静态结点,优化了patch的性能)
      • 第三种情况是当新 VNode 节点是非文本节点的时候,oldCh 与 ch 都存在且不相同时,使用 updateChildren 函数来更新子节点 ( 比对vnode下面的子节点 )
  • updateChildren

    • 头头类型相同、尾尾类型相同的节点
    • 头尾类型相同的节点
    • 新增的节点
    • 删除的节点
    • 更新节点(位置移动)

实现虚拟DOM

参考资料:让虚拟DOM和DOM-diff不再成为你的绊脚石

DOM-diff过程
  1. 用JS对象模拟DOM(虚拟DOM)
  2. 把此虚拟DOM转成真实DOM并插入页面中(render)
  3. 如果有事件发生修改了虚拟DOM,比较两棵虚拟DOM树的差异,得到差异对象(diff)
  4. 把差异对象应用到真正的DOM树上(patch)
  • 创建虚拟DOM

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //elment.js
    //vnode类
    class ELement{
    constructor(type,props,children){//type--->tag,props--->data,children--->children
    this.type=type;
    this.props=props;
    this.children=children;
    }
    }

    //创建虚拟DOM,返回vnode
    function createElement(type,props,children){
    return new Element(type,props,children);
    }

    export {
    Element,
    createElement
    }
  • 渲染虚拟DOM

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    //element.js
    class Element{ ... }
    function createElement(){ ... }
    //render方法可以将虚拟DOM转化成真实DOM
    function render(domObj) {
    // 根据type类型来创建对应的元素
    let el = document.createElement(domObj.type);

    // 再去遍历props属性对象,然后给创建的元素el设置属性
    for (let key in domObj.props) {
    // 设置属性的方法
    setAttr(el, key, domObj.props[key]);
    }

    // 遍历子节点
    // 如果是虚拟DOM,就继续递归渲染
    // 不是就代表是文本节点,直接创建
    domObj.children.forEach(child => {
    child = (child instanceof Element) ? render(child) : document.createTextNode(child);
    // 添加到对应元素内
    el.appendChild(child);
    });

    return el;
    }
    //设置属性
    function setAttr(node,key,value){ ... };
    //将元素插入页面
    function renderDom(el,target){
    target.appendChild(el);
    }
  • DOM-diff

    • 比较规则
      • 新的DOM节点不存在{type: ‘REMOVE’, index}
      • 文本的变化{type: ‘TEXT’, text: 1}
      • 当节点类型相同时,去看一下属性是否相同,产生一个属性的补丁包{type: ‘ATTR’, attr: {class: ‘list-group’}}
      • 节点类型不相同,直接采用替换模式{type: ‘REPLACE’, newNode}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    //diff.js
    function diff(oldTree,newTree){
    //声明变量patches用来存放补丁对象
    let patches={};
    let index=0;
    //递归树,比较后的结果放到补丁里
    walk(oldTree,newTree,index,patches);
    return patches;
    }

    function walk(oldNode, newNode, index, patches) {
    // 每个元素都有一个补丁
    let current = [];

    if (!newNode) { // rule1
    current.push({ type: 'REMOVE', index });
    } else if (isString(oldNode) && isString(newNode)) {
    // 判断文本是否一致
    if (oldNode !== newNode) {
    current.push({ type: 'TEXT', text: newNode });
    }

    } else if (oldNode.type === newNode.type) {
    // 比较属性是否有更改
    let attr = diffAttr(oldNode.props, newNode.props);
    if (Object.keys(attr).length > 0) {
    current.push({ type: 'ATTR', attr });
    }
    // 如果有子节点,遍历子节点
    diffChildren(oldNode.children, newNode.children, patches);
    } else { // 说明节点被替换了
    current.push({ type: 'REPLACE', newNode});
    }

    // 当前元素确实有补丁存在
    if (current.length) {
    // 将元素和补丁对应起来,放到大补丁包中
    patches[index] = current;
    }
    }
    function isString(obj) {
    return typeof obj === 'string';
    }
    function diffAttr(oldAttrs, newAttrs) { ... }

    // 所有都基于一个序号来实现
    let num = 0;
    function diffChildren(oldChildren, newChildren, patches) { ... }
  • patch补丁更新:打补丁需要传入两个参数,一个是要打补丁的元素(真实dom),另一个就是所要打的补丁

    • 步骤

      • 用一个变量来得到传递过来的所有补丁allPatches
      • patch方法接收两个参数(node, patches) (在方法内部调用walk方法,给某个元素打上补丁)

      • walk方法里获取所有的子节点

        • 给子节点也进行先序深度优先遍历,递归walk
        • 如果当前的补丁是存在的,那么就对其打补丁(doPatch)
      • doPatch打补丁方法会根据传递的patches进行遍历

        • 判断补丁的类型来进行不同的操作

          1. 属性ATTR for in去遍历attrs对象,当前的key值如果存在,就直接设置属性setAttr; 如果不存在对应的key值那就直接删除这个key键的属性

          2. 文字TEXT 直接将补丁的text赋值给node节点的textContent即可

          3. 替换REPLACE 新节点替换老节点,需要先判断新节点是不是Element的实例,是的话调用render方法渲染新节点;

            不是的话就表明新节点是个文本节点,直接创建一个文本节点就OK了。

            之后再通过调用父级parentNode的replaceChild方法替换为新的节点

          4. 删除REMOVE 直接调用父级的removeChild方法删除该节点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    //patch.js
    import { Element, render, setAttr } from './element';

    let allPatches;
    let index = 0; // 默认哪个需要打补丁

    function patch(node, patches) {
    allPatches = patches;
    // 给某个元素打补丁
    walk(node);
    }
    function walk(node) {
    let current = allPatches[index++];
    let childNodes = node.childNodes;
    // 先序深度,继续遍历递归子节点
    childNodes.forEach(child => walk(child));
    if (current) {
    doPatch(node, current); // 打上补丁
    }
    }
    function doPatch(node, patches) {
    // 遍历所有打过的补丁
    patches.forEach(patch => {
    switch (patch.type) {
    case 'ATTR':
    for (let key in patch.attr) {
    let value = patch.attr[key];
    if (value) {
    setAttr(node, key, value);
    } else {
    node.removeAttribute(key);
    }
    }
    break;
    case 'TEXT':
    node.textContent = patch.text;
    break;
    case 'REPLACE':
    let newNode = patch.newNode;
    newNode = (newNode instanceof Element) ? render(newNode) : document.createTextNode(newNode);
    node.parentNode.replaceChild(newNode, node);
    break;
    case 'REMOVE':
    node.parentNode.removeChild(node);
    break;
    default:
    break;
    }
    });
    }
    export default patch;

编译(模板如何被解析的)

  • 三个阶段

    • parse(分析)

      • parse 会用正则等方式解析 template 模板中的指令、class、style 等数据,形成 AST
    • optimize(优化)

      • 主要作用是标记 static 静态节点,这是 Vue 在编译过程中的一处优化,后面当 update 更新界面时,会有一个 patch 的过程, diff 算法会直接跳过静态节点,从而减少了比较的过程,优化了 patch 的性能。
    • generate(生成)

      • generate 是将 AST 转化成 render function 字符串的过程,得到结果是 render 的字符串
1
2
3
4
5
6
7
8
9
function isStatic (node{
    if (node.type === 2) {//表达式结点
        return false
    }
    if (node.type === 3) {//文本节点
        return true
    }
    return (!node.if && !node.for);//表达式结点
}

响应式

  • 「 依赖收集」的过程就是把 Subscriber 实例存放到对应的 Publisher 对象中去。 get 方法可以让当前的 Subscriber 对象( Publisher.target )存放到它的 subs 中( addSub )方法,在数据变化时, set 会调用 Publisher对象的 notify 方法通知它内部所有的 Subscriber 对象进行视图更新。

( get 进行「依赖收集」。 set 通过发布者通知订阅者进行更新。更新操作会触发patch函数,接收 vnode做参数。patch函数用到diff算法 )

  • 「 依赖收集」的前提条件还有两个:
    • 1.触发 get 方法;( render function 进行渲染,那么其中的依赖的对象都会被「读取」)
    • 2.新建一个 Subscriber 对象。( 在 Vue 的构造类中处理 ,data字段)
  • Object.defineProperty 和发布订阅模式
  • Proxy和Reflect
  • 实现
    • 数据劫持结合发布—订阅模式(publisher 发布者、event center 事件中心、subscriber 订阅者)
    • Object.defineProperty (vue 3.0 proxy)
    • 将data的属性代理到vm上
    • 发布订阅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//Publisher 依赖收集,收集事件有哪些订阅者,同时事件更新时通知更新
class Publisher{
constructor(){
this.subs=[];//收集订阅者
}
//添加订阅者
addSub(sub){
this.subs.push(sub);
}
//通知更新
notify(){
this.subs.forEach(sub=>{
sub.update();
})
}
}

//Subscriber 订阅者,在Vue实例 mount 的时候实例化,订阅事件,让发布者的收集目标指向自己
class Subscriber{
constructor(){
Publisher.target=this;
}
update(){
console.log("视图更新啦~")
}
}
Publisher.target=null;//用于收集订阅者,一个接一个的收集

//Event center 事件中心,发布和订阅的一个代理,就像报刊亭,发布者是出版社,订阅者是订报人
function observe(obj){
if(!obj || typeof obj !== 'object'){
return
}
Object.keys(obj).forEach(key =>{
defineReactive(obj,key,obj[key]);
})
}

function defineReactive(obj,key,val){
observe(val);//递归子属性
let pub=new Publisher();//每个属性都有一个发布者
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get:function reactiveGetter(){//代理订阅事件
if(Publisher.target){
pub.addSub(Publisher.target);
}
return val;
}
set:function reactiveSetter(newVal){//代理发布更新事件
if(newVal===val) return;
//通知更新
pub.notify();
}
})
}
//基于Proxy来实现vue的观察者机制
function observe(data) {
const that = this;
let handler = {
get: function (target, key, receiver) {
if(Publisher.target){
pub.addSub(Publisher.target);
}
return Reflect.get(target, key, receiver)
},
set: function (target, key, value, receiver) {
pub.notify();
return Reflect.set(target, key, value, receiver);
}
}
this.$data = new Proxy(data, handler);
}

Vuex

  • 使用 State 和 Getter 对状态进行定义;使用 Mutation 和 Action 对状态进行变更;引入 Module 对状态进行模块化分割

    • state 用来数据共享数据存储
    • getters 用来对共享数据进行过滤操作
    • mutation 用来注册改变数据状态
    • action 解决异步改变共享数据
  • vuex 使用过程

vuex使用过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.Vue的插件机制,安装Vuex。  Vue.use(Vuex)
2.实例化store
let store = new Vuex.Store({
state,
getters,
mutations,
actions,
modules,
});
3.在Vue实例中注入store
new Vue({
store,
render: h=>h(app)
}).$mount('#app');

vuex构成

  • Vuex的store是如何注入到组件中的?

    • 1.利用Vue插件机制装载vuex
    • 2.install Vuex.store()
    • 3.利用Vue.mixin() 在Vue生命周期beforeCreate之前进行VuexInit (得益于 mixin 机制,this 将指向 Vue 组件实例。我们可以在 Vue 组件实例上获得 Vuex 的 store 对象的引用 $store)

    总结:Vuex 利用了 Vue 的 mixin 机制,混合 beforeCreate 钩子,将 store 注入至 Vue 组件实例上,并注册了 Vuex store 的引用属性 $store。

store注入到组件

  • Vuex 的state 和 getter 是如何映射到各个组件实例中自动更新的呢?
    • 1.state 是借助 Vue 的响应式 data 实现的。
    • 2.getter 是借助 Vue 的计算属性 computed 特性实现的。

state个getter实现原理

  • Vuex 核心代码
1
2
3
4
5
6
7
8
9
10
11
// src/store.js
function resetStoreVM (store, state, hot) {
// 省略无关代码
Vue.config.silent = true
store._vm = new Vue({
data: {
$$state: state
},
computed
})
}

​ 将我们传入的state作为一个隐藏的vue组件的data,也就是说,我们的commit操作,本质上其实是修改这个组件的data值。vuex中的store本质就是没有template的隐藏着的vue组件

  • 使用场景

    • 多个视图依赖于同一状态。
      • 有A,B,C,D,E,F组件…都需要同一个数据,如果每次都去请求一下接口获取数据,那么每多一次请求就会对服务器多一份负担。这个时候就可以利用vuex,只访问接口一次,获取到这个数据,然后存到这个”全局变量“里,用的时候直接取,不管有多少组件需要,都可以直接拿来用
    • 来自不同视图的行为需要变更同一状态。
      • 1、在首页、分类、商品详情页添加商品需要触发一次
      • 2、在购物车进入编辑状态,删除购物车项,需要触发一次
  • 缺点:刷新页面后数据消失

    • 原因

      • js代码是运行在内存(栈内存和堆内存)中的,代码运行时的所有变量、函数也都是保存在内存中的。刷新页面,以前申请的内存被释放,重新加载脚本代码,变量重新赋值。
    • 问题场景

      • 用户已经登录,登录状态放到state中,一刷新页面,还要重新登录。
      • 购物车里的添加的数据,一刷新要重新添加。
    • 解决方案

      • 利用webStorage

跨层级组件通信(provide + inject)

  • 应用场景:删除一条数据或者新增数据之后需要重新刷新当前页面
  • 为什么选择这个方案去实现

    • 1.用vue-router重新路由到当前页面,页面是不进行刷新的

      1
      2
      3
      4
      this.$router.replace({
      path:...,
      name:...
      });
    • 2.采用window.reload(),或者router.go(0)刷新时,整个浏览器进行了重新加载,闪烁,体验不好

      • 具体实现

        • provide + inject 组合

          • 作用:允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
        • App.vue

          • 声明reload方法,控制router-view的显示或隐藏,从而控制页面的再次加载

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            1. <router-view v-if="isRouterAlive"/>
            2. provide(){
            return {
            reload:this.reload
            }
            }
            3. methods:{
            reload(){
            this.isRouterAlive=false;
            this.$nextTick(()=>{
            this.isRouterAlive=true;
            })
            }
            }
            • 在页面注入App.vue组件提供(provide)的 reload 依赖,在逻辑完成之后(删除或添加…),直接this.reload()调用,即可刷新当前页面。
            1
            2
            3
            4
            5
            6
            7
            8
            //刷新项目信息
            freshPro(){
            if(this.curProPage){
            this.getPageData(this.curProPage);
            }else{
            this.reload();
            }
            }

React


Redux

  • 基本做法:用户发出 Action,Reducer 函数算出新的 State,View 重新渲染。
  • Store
    • 保存数据的地方,可以看成是一个容器,整个应用只能有一个 Store。
    • Redux 提供createStore这个函数,用来生成 Store
1
2
import { createStore } from 'redux';
const store = createStore(reducer);
  • State
    • Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。
    • Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。
  • Action
    • State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 是 View 发出的通知,表示 State 要发生变化了
    • Action 是一个对象。其中的type属性是必须的,表示 Action 的名称,其他属性可以自由设置
    • Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。
  • Action Creator
    • View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。
  • store.dispatch()
    • View 发出 Action 的唯一方法,接受一个 Action 对象作为参数,将它发送出去。
  • Reducer
    • Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
    • store.dispatch方法会触发 Reducer 的自动执行
    • Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
    • 为什么这个函数叫做 Reducer 呢?因为它可以作为数组的reduce方法的参数。
1
2
3
4
5
6
7
const actions = [
{ type: 'ADD', payload: 0 },
{ type: 'ADD', payload: 1 },
{ type: 'ADD', payload: 2 }
];

const total = actions.reduce(reducer, 0);
  • Reducer 是一个纯函数,只要是同样的输入,必定得到同样的输出

  • 纯函数

    • 定义(要求)

      • 不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数
      • 不会产生任何可观察的副作用,例如网络请求,输入和输出设备或数据突变(mutation)。
    • 可观察的副作用

      • 在函数内部与其外部的任意交互,可能是在函数内修改外部的变量,或者在函数里调用另外一个函数等。

      • 副作用来自,但不限于:

        • 进行一个 HTTP 请求

        • Mutating data

        • 输出数据到屏幕或者控制台

        • DOM 查询/操作

        • Math.random()

        • 获取的当前时间

Redux-thunk

  • 中间件就是一个函数,对store.dispatch方法进行了改造,可以接受函数作为参数,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

  • 同步操作只要发出一种 Action 即可,异步操作的差别是它要发出三种 Action。

  • 异步操作的思路

    • 操作开始时,送出一个 Action,触发 State 更新为”正在操作”状态,View 重新渲染
    • 操作结束后,再送出一个 Action,触发 State 更新为”操作结束”状态,View 再一次重新渲染

Vue VS React

生命周期

  • Vue 创建 = > 挂载 = > 更新 = > 销毁 (vue 中操作DOM是异步的)

    • beforeCreate/created 创建阶段没有el选项
      • beforeCreate el,data都未被初始化
      • created 已经和data属性进行绑定,el属性还不存在
    • beforeMount/mounted
      • beforeMount el,data都被初始化,但还未渲染到视图中,还是虚拟dom的形式
      • mounted el,data都被初始化,也被渲染到视图中,是真实dom的形式
    • beforeUpdate/updated
      • beforeUpdate data已经改变,但并未更新到视图中
      • updated 视图已经更新
    • beforeDestory/destroyed
      • beforeDestroy 实例仍然完全可用
      • destroyed Vue 实例指示的所有东西都会被解绑,所有的事件监听器会被移除,所有的子实例也会被销毁。
  • React 生成期 => 存在期 => 销毁期

    • 生成期 : constructor componentWillMount render componentDidMount
    • 存在期: componentWillReceiveProps shouldComponentUpdate(nextProps, nextState) componentWillUpdate render ComponentDidUpdate
    • 销毁期 componentWillUnmount
  • 区别

    • 生命周期

      • Vue 四个时期
      • React 三个时期 render 函数生成虚拟dom,使用diff算法 shouldComponentUpdate 性能优化
    • 设计思想

      • Vue 是响应式的设计思想,推崇数据可变,支持双向数据流。追求开发简单。
      • React 是函数式的设计思想,推崇数据不可变,单向数据流。追求方式是否正确。
    • 模板

      • Vue 采用基于HTML的模板语法

      • React采用JSX

Vuex VS Redux

  • Redux

    • 三大原则:唯一数据源、状态只读、数据的改变只能通过纯函数(reducer)完成
    • 核心三部分:store、reducer、action
      • Redux的核心是store,它由Redux提供的 createStore(reducer, defaultState) 这个方法生成,生成三个方法,getState(),dispatch(),subscribe()。
      • reducer是一个纯函数,它根据previousState和action计算出新的state。reducer(previousState,action)
      • action本质上是一个JavaScript对象,其中必须包含一个type字段来表示将要执行的动作,其他的字段都可以根据需求来自定义。
    • React:负责组件的UI界面渲染;Redux:数据处理中心;React-Redux:连接组件和数据中心,也就是把React和Redux联系起来。

    React、Redux、React-Redux三者关系

  • React-Redux

    • Redux 本身和 React 没有关系,只是数据处理中心,是React-Redux让他们联系在一起
    • React-rRedux提供两个方法:connect和Provider。
    • connect连接React组件和Redux store。connect实际上是一个高阶函数,返回一个新的已与 Redux store 连接的组件类。
1
2
3
4
5
6
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
//mapStateToProps:从Redux状态树中提取需要的部分作为props传递给当前的组件。
//mapDispatchToProps:将需要绑定的响应事件(action)作为props传递到组件上。
  • Provider实现store的全局访问,将store传给每个组件。
  • 两者区别
    • vuex的流向:view—>commit—>mutations—>state变化—>view变化(同步操作)
      view—>dispatch—>actions—>commit—>mutations—>state变化—>view变化(异步操作)
    • redux的流向:view —>dispatch->actions —>reducer —>state变化 —>view变化(同步异步一样)

Vue面试题

30 道 Vue 面试题,内含详细讲解(涵盖入门到精通,自测 Vue 掌握程度)