响应式系统与 React学习笔记(下) | 青训营

61 阅读6分钟

React的实现原理

  • 虚拟DOM

React 使用 JavaScript 对象表示 DOM 信息和结构,当状态变更的时候,重新渲染这个 JavaScript 的对象结构。这个 JavaScript 对象称为virtual dom;

使用它的原因是 DOM 操作很慢,轻微的操作都可能导致页面重新排版,非常耗性能。相对于DOM对象,js对象处理起来更快,而且更简单。通过diff算法对比新旧vdom之间的差异,可以批量的、最小化的执行 dom 操作,从而提高性能。

  • diff 算法

    diff算法的本质就是:找出两个对象之间的差异,目的是尽可能做到节点复用。传统的 diff 算法遍历整个结构逐一对比,效率很低,React用三大策略将 O(n3) 复杂度转化为 O(n) 复杂度

  1. tree diff

    React 通过 updateDepth 对 Virtual DOM 树进行层级控制。对树分层比较,两棵树只对同一层次节点进行比较。如果该节点不存在时,则该节点及其子节点会被完全删除,不会再进一步比较。只需遍历一次,就能完成整棵DOM树的比较。

  2. component diff

    React对不同的组件间的比较:同一类型的两个组件,按原策略(层级比较)继续比较Virtual DOM树即可,同一类型的两个组件,组件A变化为组件B时,可能Virtual DOM没有任何变化,如果知道这点(变换的过程中,Virtual DOM没有改变),可节省大量计算时间,所以用户可以通过 shouldComponentUpdate() 来判断是否需要判断计算。不同类型的组件,将一个(将被改变的)组件判断为dirtycomponent(脏组件),从而替换整个组件的所有节点。

  3. element diff

    当节点处于同一层级时,diff提供三种节点操作:删除、插入、移动:组件 C 不在集合(A,B)中,需要插入;组件 D 在集合(A,B,D)中,但 D的节点已经更改,不能复用和更新,所以需要删除 旧的D ,再创建新的。组件D之前在集合(A,B,D)中,但集合变成新的集合(A,B)了,D 就需要被删除。组件D已经在集合(A,B,C,D)里了,且集合更新时,D没有发生更新,只是位置改变,如新集合(A,D,B,C),D在第二个,无须像传统diff,让旧集合的第二个B和新集合的第二个D 比较,并且删除第二个位置的B,再在第二个位置插入D,而是 (对同一层级的同组子节点) 添加唯一key进行区分,移动即可。

React 状态管理库

状态管理库的就是将转换抽取到 UI 外部进行统一管理

  • redux

由于react的单向数据流问题,导致state状态传递和复用十分困难。比如一个组件向兄弟组件传递信息时,需要先传入父组件,再传到兄弟组件,十分的不方便。或者在不太相关的一个A组件中,使用B组件的状态,都是难以实现的。

redux想出一个办法:将所有需要复用的状态集中存放在一起,就可以在任意组件中调用需要的状态。  而存放这些state的一个集中的库,我们就叫它store

import { createStore } from 'redux'
const store = createStore(reducer)

action 指的是视图层发起的一个操作,告诉 Store 我们需要改变。比如用户点击了按钮,我们就要去请求列表,列表的数据就会变更。每个 action 必须有一个 type 属性,这表示 action 的名称,然后还可以有一个 payload 属性,这个属性可以带一些参数,用作 Store 变更:

const action = {
  type: 'ADD_ITEM',
  payload: 'new item', // 可选属性
}

Action不会自己主动发出变更操作到Store,所以这里我们需要一个叫dispatch的东西,它专门用来发出action,在redux里面,store.dispatch()是 View发出 Action 的唯一方法

store.dispatch({
  type: 'ADD_ITEM',
  payload: 'new item', // 可选属性
})

当 dispatch 发起了一个 action 之后,会到达 reducer,这个reducer就是用来计算新的store的,reducer接收两个参数:当前的state和接收到的action,然后它经过计算,会返回一个新的state:

const reducer = function(prevState, action) {
  ...
  return newState;
};

下面是一个完整的例子:

store.js文件

constant.js
//该文件专门用于暴露一个store对象,整个应用只有一个store对象

//引入createStore,,专门用于创建redux中最核心的store对象
import {createStore, applyMiddleware} from 'redux';
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
const store = createStore(countReducer, applyMiddleware(thunk))
//暴露store
export default store

constant.js

/*
 该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止在书写单词的时候,出现错误
*/

export const INCREMENT = 'increment'
export const DECREMENT = 'decrement';

count_reducer.js

/*
1、该文件是用于创建一个Count组件服务的reducer,reducer的本质就是一个函数
2、reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
3、reducer被第一次调用时,是store自动触发的,传递的preState是undefined,传递的action是类似于:{
 type: '@@REDUX/INIT_a.2.b.4'
}
*/
const {INCREMENT, DECREMENT} from './constant'
const initState = 0;//初始化状态,推荐写法

export default function countReducer (preState = initState, action) {
//if(preState === undefined) preState = 0
//从action对象中获取:type、data
 const { type, data } = action
 //根据type决定如何加工数据
 switch (type) {
  case INCREMENT://如果是加
    return preState + data;
   case DECREMENT://如果是减
    return preState - data;
   default: 
    return preState
 }
}

count_action.js

/*
 该文件专门为Count组件生成action对象
*/

function createIncrementAction(data) {
 return {
  type:'increment',
  data
 }
}

function createDecrementAction(data) {
 return {
  type:'decrement',
  data
 }
}

//改造之后
const {INCREMENT, DECREMENT} from './constant'

//同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})

//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const createIncrementAsyncAction = (data, time) => {
 return (dispatch) => {
  setTimeout(() => {
   //函数体
   dispatch(createDecrementAction(data));
  },time)
 }
}

引入:

//引入store,用于获取redux中保存的状态
import store from '../../redux/store'
//引入actionCreator专门用于创建action对象
import {createIncrementAction,createDecrementAction,createIncrementAsyncAction} from '../../redux/count_action'

//加法
increment = () => {
 const { value } = this.selectNumber;
 //store.dispatch({type:'increment', date : value * 1});
 store.dispatch(createIncrementAction(value * 1));
}
//减法
decrement = () => {
 const { value } = this.selectNumber;
 //store.dispatch({type:'decrement', date : value * 1});
 store.dispatch(createDecrementAction(value * 1));
}

incrementAsync = () => {
 const { value } = this.selectNumber;
 store.dispatch(createIncrementAsyncAction(value * 1 , 500))
}

render() {
 return (
   <div>
     <h1>当前和为: {store.getState()}</h1>
   </div>
 )
}

监听redux变化

import React from "react";
import ReactDOM from "react-dom";
import App from './App';
import store from './store/store';
ReactDOM.render(<App/>,document.getElementById('root'))
// 在这里需要明确的是:redux只是一个状态的管理机制,它不会自动的触发页面的更新,需要我们自己去写
store.subscribe(() => ReactDOM.render(<App/>,document.getElementById('root')))
  • useReducer

在React hooks 中,可以使用 useReducer 作为状态管理的工具,接收两个参数:第一个参数是reducer函数,没错就是我们上一篇文章介绍的。第二个参数是初始化的state。返回值为最新的state和dispatch函数(用来触发reducer函数,计算对应的state)。

// 官方 useReducer Demo
    // 第一个参数:应用的初始化
    const initialState = {count: 0};

    // 第二个参数:state的reducer处理函数
    function reducer(state, action) {
        switch (action.type) {
            case 'increment':
              return {count: state.count + 1};
            case 'decrement':
               return {count: state.count - 1};
            default:
                throw new Error();
        }
    }

    function Counter() {
        // 返回值:最新的state和dispatch函数
        const [state, dispatch] = useReducer(reducer, initialState);
        return (
            <>
                // useReducer会根据dispatch的action,返回最终的state,并触发rerender
                Count: {state.count}
                // dispatch 用来接收一个 action参数「reducer中的action」,用来触发reducer函数,更新最新的状态
                <button onClick={() => dispatch({type: 'increment'})}>+</button>
                <button onClick={() => dispatch({type: 'decrement'})}>-</button>
            </>
        );
    }