在react存活期间, 被管控的更新会被优化,异步的更新会产生多次重新渲染。 需要减少render的次数
shouldComponentUpate
该方法在组件实例中用来决定组件是否参与本次更新,会影响后续节点。
返回true会参与更新
// v15
// shouldComponentUpdate 的返回值决定了是否更新
// 在shouldUdate 返回false时, 不会重新渲染而是赋值最新的props和state
{
// If it's determined that a component should not update, we still want
// to set props and state but we shortcut the rest of the update.
this._currentElement = nextParentElement;
this._context = nextUnmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
}
// 如果更新会对比前后两个组件。 是进入对组件的递归调合还是对组件的卸载重新挂载
function shouldUpdateReactComponent(prevElement, nextElement) {
var prevEmpty = prevElement === null || prevElement === false;
var nextEmpty = nextElement === null || nextElement === false;
if (prevEmpty || nextEmpty) {
return prevEmpty === nextEmpty;
}
var prevType = typeof prevElement;
var nextType = typeof nextElement;
if (prevType === 'string' || prevType === 'number') {
return nextType === 'string' || nextType === 'number';
} else {
return nextType === 'object' &&
prevElement.type === nextElement.type &&
prevElement.key === nextElement.key;
}
}
在v16.8以后如果shouldUpdate
为true,当前workInProgress
对effectTag会被打上update 的标记,后续更新。如果为false,对下一次props和state重新赋值为最新的
PureComponent
是react提供的基类, 默认采用优化,间接实现了shouldUpdate方法。 但都是浅比较。如果是基础数据类型还是可以hold住的。 如果是引用类型可以手动实现。但在这里不推荐深层次的比较。可以使用第三方库,不可变数据
function shallowEqual(objA, objB) {
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (var i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty$1.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
memo
memo 为高阶组件,如果函数组件被memo包裹,可以检查props的变更。
如果组件内部使用了hook,比如当context变更时,组件仍然会被穿透更新
第二个参数就是手动用了比较props的,返回false时会参与更新, 与should相反
const Child = memo((props) => {
return (
<div>
{props.count}
</div>
)
},
(prevProps, nextProps) => {
return prevProps.count === nextProps.count
}
)
function App() {
return <Child count={1123} />
}
更新粒度和更新单元
每一次执行渲染的时候,如果没有特别的操作,渲染的最小点会执行到最近的一次变化,也就是说子组件没有变化,而父组件变化了,还是会参与本次更新,所以可以使用上面的两种方式避免这个问题。
如果是两个子组件在同一个容器里,二者的props均来自与此容器,其中一个更新之后,另外一个也会更新
const B = (props) => {
console.log("b");
return <div> B,--{props.count} </div>;
};
const A = (props) => {
console.log("a");
return <div> A ,,,{props.count}</div>;
};
export default function () {
const [count1, setCount1] = React.useState(0);
const [count2, setCount2] = React.useState(0);
const clickHandler1 = () => setCount1((v) => v + 1);
const clickHandler2 = () => setCount2((v) => v + 1);
return (
<div>
<button onClick={clickHandler1}>setCount1</button>
<br />
<button onClick={clickHandler2}>setCount2</button>
<B count={count1} />
<A count={count2} />
</div>
);
}
比如👆的A组件,就是count2没有变化,但是仍然会被重新渲染。因为父级组件的变化连带着子组件一起变化。这种情况就可以使用memo包裹一下
const A = React.memo((props) => {
console.log("a");
return <div> A ,,,{props.count}</div>;
}, (pp, np) => pp.count === np.count ) ;
此时的A组件,在count2没有变化的时候不会参与更新。此时b组件也会更新
可以把两个组件的状态自行维护,不提升到父级容器,不存在共享就不会变更
拆分到组件按照更新到粒度,如果不存在数据的关联关系。可以避免父组件更新连带的更新。如果多个字组件的状态都放在父组件,每一次的更新都会重新render。 使用shouldUpdate、 useMemo、 useCallback、memo
对类组件和函数组件执行优化,选择性的更新
合理的拆分组件试图与容器的分离可以达到某种优化手段,但优化是多种组合在一起的
使用hooks缓存
对于函数组件,props可以使用hook包裹,在依赖项变更时,hook重新计算,props也会随之变化,反正前后两个props相等不需要更新
比如useMemo和useCallback都是对组件其他的状态变更时才会重新执行create函数。如果没有,就不会更新。
如果传递个子组件的一个回调,回调用来做一些变更操作时不需要依赖其他状态,不需要动态的变化,这个时候可以使用useCallback包裹一下,传递给子组件时,props每次前后都是相同的
key
key值是必须的、唯一的、稳定的
当基于下标的组件重新排序时,组件state就会遇到问题,比如删除和新建与预期不一致
组件实例是基于它们的key来决定是否更新以及复用,如果key是一个下标,那么修改顺序时会修改当前的key,导致非受控组件的state可能相互篡改出现无法预期的变动
key是组件身份的标识,react利用key来识别组件,目的就是找新节点与对应的老节点是否复用还是删除新建