reactjs - 使用 useMemo() 防止上下文重新渲染
问题描述
在研究上下文时,我不止一次偶然发现了一个常见模式:
const storeContext = React.createContext()
const Provider = (children) => {
const [state, setState] = React.useState();
const ctxValue = React.useMemo(() => [state, setState], [state]);
return (
<storeContext.Provider value={ctxValue}>
{children}
</storeContext.Provider>
)
}
很多人似乎useMemo
在这种情况下使用,我不知道为什么。乍一看,这似乎是有道理的,因为直接传递一个对象/数组意味着这个对象将在每次渲染时重新创建,并在传递一个新引用后触发子级重新渲染。乍一看,我对这种模式感到困惑。
我不确定这种模式如何可能阻止任何重新渲染。如果树上的一个组件发生更改,所有子项(包括上下文)无论如何都会重新呈现,如果state
发生更改,则提供者(正确地)重新呈现自己以及所有子项。
我看到的唯一一件事useMemo
就是节省一点内存,因为我们不是ctxValue
在每次渲染时都重新创建对象。
我不确定我是否在这里遗漏了一些东西,并且会喜欢一些输入。
解决方案
原因是每次渲染都会创建一个新的 Array 实例。通过使用 useMemo,传递给上下文的数组仅在状态更改时才会更改。更好的方法是将 useState 的结果传递给上下文:
const storeContext = React.createContext()
const Provider = (children) => {
const ctxValue = React.useState();
return (
<storeContext.Provider value={ctxValue}>
{children}
</storeContext.Provider>
)
}
更新
所以,我很好奇 React 将如何处理重新渲染和上下文更新。我真的很惊讶地发现 React 只会在 props 发生变化时重新渲染组件。
例如,如果你有这样的事情:
function MyComponent(){
const ctxValue = useContext(MyContext);
return (<div>
value: {ctxValue}
<MyOtherComponent />
</div>);
}
你渲染那个组件,MyOtherComponent
永远只会渲染一次,假设组件不使用任何可以更新其内部状态的钩子。
Context.Provider 也是如此。它会重新渲染的唯一时间是值更新时,但其子级不会重新渲染,除非它们直接依赖于该上下文。
我创建了一个简单的沙箱来演示这一点。
基本上,我设置了很多不同的方式来嵌套孩子,有些孩子依赖上下文,有些则不。如果单击“+”按钮,上下文将更新,您将看到渲染计数仅增加这些组件。
const Ctx = React.createContext();
function useRenderCount(){
const count = React.useRef(0);
count.current += 1;
return count.current;
}
function wrapInRenderCount(name,child){
const count = useRenderCount();
return (
<div>
<div>
{name} was rendered {count} times.
</div>
<div style={{ marginLeft: "1em" }}>{child}</div>
</div>
);
}
function ContextProvider(props) {
const [count, setState] = React.useState(0);
const increase = React.useCallback(() => {
setState((v) => v + 1);
}, []);
const context = React.useMemo(() => [increase, count], [increase, count]);
return wrapInRenderCount('ContextProvider',
<Ctx.Provider value={context}>
{props.children}
</Ctx.Provider>
);
}
function ContextUser(props){
const [increase, count] = React.useContext(Ctx);
return wrapInRenderCount('ContextUser',
<div>
<div>
Count: {count}
<button onClick={increase}>
++
</button>
</div>
<div>
{props.children}
</div>
</div>
);
}
function RandomWrapper(props){
return wrapInRenderCount('RandomWrapper',<div>
<div>
Wrapped
</div>
<div style={{marginLeft:'1em'}}>
{props.children}
</div>
</div>);
}
function RandomContextUserWrapper(props){
return wrapInRenderCount('RandomContextUserWrapper',
<div>
<ContextUser></ContextUser>
{props.children}
</div>
)
}
function App() {
return wrapInRenderCount('App',
<RandomWrapper>
<ContextProvider>
<RandomWrapper>
<ContextUser>
<RandomWrapper>
<RandomContextUserWrapper>
<RandomWrapper>
<ContextUser/>
</RandomWrapper>
</RandomContextUserWrapper>
</RandomWrapper>
</ContextUser>
<RandomWrapper>
<RandomContextUserWrapper />
</RandomWrapper>
</RandomWrapper>
</ContextProvider>
</RandomWrapper>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<App />,
rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
推荐阅读
- android - 如何在 kotlin 中实现 Android 应用购买?
- java - Spring Boot Rest 中的 504 Gateway Time-out 问题需要大量记录(超过 80K)
- javascript - 如何通过动态组合来自多个其他 json 行的元素来创建新的 json 行
- cassandra - 使用 Apache Nifi 将批量记录从 Db2 插入 Cassandra
- python - 修改功能,保持代码可操作性
- ruby-on-rails - 渲染在屏幕上显示 - Ruby on rails
- regex - golang 正则表达式键:值问题
- python-3.x - 对“r”(原始字符串字符)的功能感到困惑
- javascript - 如何过滤 mogodb 中的模式并使用过滤的数据重定向同一页面
- uwp - Unable to test Store trial mode for UWP app