1、列表&key
一、React更新流程
React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树。React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI。
- 同层节点之间相互比较,不会垮节点比较;
- 不同类型的节点,产生不同的树结构;
- 开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定;
1、对比不同类型的元素
当节点为不同的元素,React会拆卸原有的树,并且建立起新的树
<div><Child />
</div>
~~~~~~~~~~~
<span><Child />
</span>
2、对比同一类型的元素
当比对两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性
1)、React 知道只需要修改 DOM 元素上的 className 属性
<div classname="before" title="stuff" />
<div classname="after" title="stuff" />
2)、当更新 style 属性时,React 仅更新有所更变的属性
<div style={{ color: 'red', fontWeight: 'bold' }} />
<div style={{ color: 'green', fontWeight: 'bold' }} />
3)、同类型的组件元素
组件会保持不变,React会更新该组件的props,并且调用componentWillReceiveProps() 和 componentWillUpdate() 方
法
3、对子节点进行递归
在默认条件下,当递归 DOM 节点的子元素时,React 会同 时遍历两个子元素的列表;当产生差异时,生成一个 mutation。
1)、在最后插入一条数据的情况,前面两个比较是完全相同的,所以不会产生mutation,最后一个比较,产生一个mutation,将其插入到新的 DOM树中即可;
<ul><li>first</li><li>second</li>
</ul>
~~~~~~~~~
<ul><li>first</li><li>second</li><li>third</li>
</ul>
最后一次插入third,只会产生一个mutation
2)、如果我们是在中间插入一条数据
React会对每一个子元素产生一个mutation,这种低效的比较方式会带来一定的性能问题
<ul><li>first</li><li>second</li>
</ul>
~~~~~~~~~
<ul><li>zero</li><li>first</li><li>second</li>
</ul>
在第一次插入zero,后面first、second都会产生mutation。
二、keys的优化
我们在前面遍历列表时,总是会提示一个警告,让我们加入一个key属性
- 在最后位置插入数据,这种情况,有无key意义并不大
- 在前面插入数据,这种做法,在没有key的情况下,所有的li都需要进行修改
- 当子元素(这里的li)拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素
key的注意事项:
1)、key应该是唯一的;
2)、key不要使用随机数(随机数在下一次render时,会重新生成一个数字);
3)、使用index作为key,对性能是没有优化的;
2、 shouldComponentUpdate
在render调用之前会调用shouldComponentUpdate
,不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。这样非常影响效率,且会损害性能。
该方法有两个参数:
- nextProps 修改之后,最新的props属性
- nextState 修改之后,最新的state属性
该方法返回值是一个boolean类型
- 返回值为true(默认返回true),那么就需要调用render方法
- 返回值为false,那么就不需要调用render方法
shouldComponentUpdate(nextProps, nextState) {if (this.state.counter !== nextState.counter) {return true;}return false;
}
源码分析
// react-reconciler/src/forks/ReactFiberClassComponent.new.js Line 291
function checkShouldComponentUpdate(workInProgress,ctor,oldProps,newProps,oldState,newState,nextContext,
) {const instance = workInProgress.stateNode;if (typeof instance.shouldComponentUpdate === 'function') { // 判断instance有无shouldComponentUpdate方法const shouldUpdate = instance.shouldComponentUpdate(newProps,newState,nextContext,);return shouldUpdate;}// 如果isPureReactComponent=true就会进行浅层比较if (ctor.prototype && ctor.prototype.isPureReactComponent) {return (!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState));}return true;
}
3、PureComponent
React.PureComponent
与 React.Component
很相似。两者的区别在于 React.Component
并未实现 shouldComponentUpdate()
,而 React.PureComponent
中以浅层对比 prop 和 state 的方式来实现了该函数。
import React, { Component, PureComponent } from 'react'
// header
class Header extends PureComponent {render() {console.log('App Header被调用')return (<h2>我是Header组件</h2>)}
}export class App extends PureComponent {constructor(props) {super(props);this.state = {counter: 0}}render() {console.log('App 被调用')return (<div><h2>{"当前计数:" + this.state.counter}</h2><button onClick={e => this.increment()}>+1</button><Header/></div>)}increment () {this.setState({counter: this.state.counter + 1})}
}
export default App
源码分析
PureComponent
源码
// react/src/ReactBaseClasses.js Line 129
/*** Convenience component with default shallow equality check for sCU.*/
function PureComponent(props, context, updater) {this.props = props;this.context = context;// If a component has string refs, we will assign a different object later.this.refs = emptyObject;this.updater = updater || ReactNoopUpdateQueue;
}const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
// isPureReactComponent标记为true
pureComponentPrototype.isPureReactComponent = true; export {Component, PureComponent};
shallowEqual
源码
// shared/shallowEqual
/*** Performs equality by iterating through keys on an object and returning false* when any key has values which are not strictly equal between the arguments.* Returns true when the values of all keys are strictly equal.*/
function shallowEqual(objA: mixed, objB: mixed): boolean {if (is(objA, objB)) { // 如果是同一个对象,直接返回truereturn true;}if (typeof objA !== 'object' ||objA === null ||typeof objB !== 'object' ||objB === null) {return false;}const keysA = Object.keys(objA);const keysB = Object.keys(objB);if (keysA.length !== keysB.length) {return false;}// Test for A's keys different from B.for (let i = 0; i < keysA.length; i++) {if (!hasOwnProperty.call(objB, keysA[i]) ||!is(objA[keysA[i]], objB[keysA[i]])) {return false;}}return true;
}
4、memo
React.memo
为高阶组件。
如果组件在相同 props 的情况下渲染相同的结果,那么可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
React.memo
仅检查 props 变更。如果函数组件被 React.memo
包裹,且其实现中拥有 useState
,useReducer
或 useContext
的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现
function MyComponent(props) {/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {/*如果把 nextProps 传入 render 方法的返回结果与将 prevProps 传入 render 方法的返回结果一致则返回 true,否则返回 false*/
}
export default React.memo(MyComponent, areEqual);
源码分析
memo
函数入口
// react/src/ReactMemo.js Line 12
export function memo<Props>(type: React$ElementType,compare?: (oldProps: Props, newProps: Props) => boolean,
) {// 调用compare方法const elementType = {$$typeof: REACT_MEMO_TYPE, // memo函数的标志type,compare: compare === undefined ? null : compare,};return elementType;
}
updateMemoComponent
:使用memo
函数实现的类会调用此函数
// react-reconciler/src/forks/ReactFiberBeginWork.new.js Line 384
function updateMemoComponent(current: Fiber | null,workInProgress: Fiber,Component: any,nextProps: any,updateLanes: Lanes,renderLanes: Lanes,
): null | Fiber {if (current === null) {const type = Component.type;if (isSimpleFunctionComponent(type) &&Component.compare === null &&// SimpleMemoComponent codepath doesn't resolve outer props either.Component.defaultProps === undefined) {let resolvedType = type;// If this is a plain function component without default props,// and with only the default shallow comparison, we upgrade it// to a SimpleMemoComponent to allow fast path updates.workInProgress.tag = SimpleMemoComponent;workInProgress.type = resolvedType;return updateSimpleMemoComponent(current,workInProgress,resolvedType,nextProps,updateLanes,renderLanes,);}const child = createFiberFromTypeAndProps(Component.type,null,nextProps,workInProgress,workInProgress.mode,renderLanes,);child.ref = workInProgress.ref;child.return = workInProgress;workInProgress.child = child;return child;}const currentChild = ((current.child: any): Fiber); // This is always exactly one childif (!includesSomeLane(updateLanes, renderLanes)) {// This will be the props with resolved defaultProps,// unlike current.memoizedProps which will be the unresolved ones.const prevProps = currentChild.memoizedProps;// Default to shallow comparison// 判断compare是否存在,决定使用compare还是shallowEquallet compare = Component.compare;compare = compare !== null ? compare : shallowEqual;if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);}}// React DevTools reads this flag.workInProgress.flags |= PerformedWork;const newChild = createWorkInProgress(currentChild, nextProps);newChild.ref = workInProgress.ref;newChild.return = workInProgress;workInProgress.child = newChild;return newChild;
}
喜欢的朋友记得点赞、收藏、关注哦!!!