React的实现原理
- 虚拟DOM
React 使用 JavaScript 对象表示 DOM 信息和结构,当状态变更的时候,重新渲染这个 JavaScript 的对象结构。这个 JavaScript 对象称为virtual dom;
使用它的原因是 DOM 操作很慢,轻微的操作都可能导致页面重新排版,非常耗性能。相对于DOM对象,js对象处理起来更快,而且更简单。通过diff算法对比新旧vdom之间的差异,可以批量的、最小化的执行 dom 操作,从而提高性能。
-
diff 算法
diff算法的本质就是:找出两个对象之间的差异,目的是尽可能做到节点复用。传统的 diff 算法遍历整个结构逐一对比,效率很低,React用三大策略将 O(n3) 复杂度转化为 O(n) 复杂度
-
tree diff
React 通过 updateDepth 对 Virtual DOM 树进行层级控制。对树分层比较,两棵树只对同一层次节点进行比较。如果该节点不存在时,则该节点及其子节点会被完全删除,不会再进一步比较。只需遍历一次,就能完成整棵DOM树的比较。
-
component diff
React对不同的组件间的比较:同一类型的两个组件,按原策略(层级比较)继续比较Virtual DOM树即可,同一类型的两个组件,组件A变化为组件B时,可能Virtual DOM没有任何变化,如果知道这点(变换的过程中,Virtual DOM没有改变),可节省大量计算时间,所以用户可以通过 shouldComponentUpdate() 来判断是否需要判断计算。不同类型的组件,将一个(将被改变的)组件判断为dirtycomponent(脏组件),从而替换整个组件的所有节点。
-
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>
</>
);
}