没有Context
案例
import React from "react";
import { useState, useCallback } from "react";
const ChildComponent = React.memo(({ changeText }) => {
console.log('子组件执行了');
return (
<p>test</p>
)
});
const ParentComponent = () => {
const [number, setNumber] = useState(1);
const handleChange = () => {
setNumber(number + 1);
};
// const changeText = (newText) => {
// };
//依赖项不变,引用不会变
const changeText = useCallback(() => {
},[])
return (
<>
<button onClick={handleChange}>click me</button>
<p>count: {number}</p>
{/* style={{}} 还有这种写法 */}
<ChildComponent changeText={changeText}/>
</>
)
};
export default ParentComponent
React.memo没有比较函数时默认检查props变更。
当
context(用了React Context API) 发生变化时,它仍会重新渲染,memo失效!!!
引用类型比较的是地址值
上源码(React 18)
function shallowEqual(objA, objB) {
if (objectIs(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++) {
var currentKey = keysA[i];
if (!hasOwnProperty.call(objB, currentKey) || !objectIs(objA[currentKey], objB[currentKey])) {
return false;
}
}
return true;
}
function updateSimpleMemoComponent(current, workInProgress, Component, nextProps, renderLanes) {
// TODO: current can be non-null here even if the component
// hasn't yet mounted. This happens when the inner render suspends.
// We'll need to figure out if this is fine or can cause issues.
{
if (workInProgress.type !== workInProgress.elementType) {
// Lazy component props can't be validated in createElement
// because they're only guaranteed to be resolved here.
var outerMemoType = workInProgress.elementType;
if (outerMemoType.$$typeof === REACT_LAZY_TYPE) {
// We warn when you define propTypes on lazy()
// so let's just skip over it to find memo() outer wrapper.
// Inner props for memo are validated later.
var lazyComponent = outerMemoType;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
outerMemoType = init(payload);
} catch (x) {
outerMemoType = null;
} // Inner propTypes will be validated in the function component path.
var outerPropTypes = outerMemoType && outerMemoType.propTypes;
if (outerPropTypes) {
checkPropTypes(outerPropTypes, nextProps, // Resolved (SimpleMemoComponent has no defaultProps)
'prop', getComponentNameFromType(outerMemoType));
}
}
}
}
if (current !== null) {
var prevProps = current.memoizedProps;
if (shallowEqual(prevProps, nextProps) && current.ref === workInProgress.ref && ( // Prevent bailout if the implementation changed due to hot reload.
workInProgress.type === current.type )) {
didReceiveUpdate = false; // The props are shallowly equal. Reuse the previous props object, like we
// would during a normal fiber bailout.
//
// We don't have strong guarantees that the props object is referentially
// equal during updates where we can't bail out anyway — like if the props
// are shallowly equal, but there's a local state or context update in the
// same batch.
//
// However, as a principle, we should aim to make the behavior consistent
// across different ways of memoizing a component. For example, React.memo
// has a different internal Fiber layout if you pass a normal function
// component (SimpleMemoComponent) versus if you pass a different type
// like forwardRef (MemoComponent). But this is an implementation detail.
// Wrapping a component in forwardRef (or React.lazy, etc) shouldn't
// affect whether the props object is reused during a bailout.
workInProgress.pendingProps = nextProps = prevProps;
if (!checkScheduledUpdateOrContext(current, renderLanes)) {
// The pending lanes were cleared at the beginning of beginWork. We're
// about to bail out, but there might be other lanes that weren't
// included in the current render. Usually, the priority level of the
// remaining updates is accumulated during the evaluation of the
// component (i.e. when processing the update queue). But since since
// we're bailing out early *without* evaluating the component, we need
// to account for it here, too. Reset to the value of the current fiber.
// NOTE: This only applies to SimpleMemoComponent, not MemoComponent,
// because a MemoComponent fiber does not have hooks or an update queue;
// rather, it wraps around an inner component, which may or may not
// contains hooks.
// TODO: Move the reset at in beginWork out of the common path so that
// this is no longer necessary.
workInProgress.lanes = current.lanes;
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// This is a special case that only exists for legacy mode.
// See https://github.com/facebook/react/pull/19216.
didReceiveUpdate = true;
}
}
}
return updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes);
}
bailoutOnAlreadyFinishedWork是复用Fiber节点的逻辑
Context更新逻辑
案例
import React from 'react';
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext(null);
const App = () => {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<div onClick={() => {
setTheme(theme == 'dark' ? 'light' : 'dark');
}}>App</div>
<Child />
</ThemeContext.Provider>
)
}
const Child = React.memo(() => {
const theme = useContext(ThemeContext)
console.log('虽然Memo包了一层,但还是更新了,因为是Context');
return (
<div>Child {theme}</div>
)
})
export default App
源码部分
遗留问题
怎样解决Context更新引起的重新渲染问题???