You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionProviderComponent(){const[contextValue,setContextValue]=useState({a: 1,b: 2});return(<MyContext.Providervalue={contextValue}><SomeLargeComponentTree/></MyContext.Provider>);}functionChildComponent(){const{ a }=useContext(MyContext);return<div>{a}</div>;}
而对于 React-Redux, 虽然内部确实用到 context, 但他传递给 provider 的是 store 实例本身, 而非 store 内部的状态. 其基本实现可以看做如下:
functionuseSelector(selector){const[,forceRender]=useReducer((counter)=>counter+1,0);const{ store }=useContext(ReactReduxContext);constselectedValueRef=useRef(selector(store.getState()));useLayoutEffect(()=>{constunsubscribe=store.subscribe(()=>{conststoreState=store.getState();constlatestSelectedValue=selector(storeState);if(latestSelectedValue!==selectedValueRef.current){selectedValueRef.current=latestSelectedValue;forceRender();}});returnunsubscribe;},[store]);returnselectedValueRef.current;}
My personal summary is that new context is ready to be used for low frequency unlikely updates (like locale/theme). It's also good to use it in the same way as old context was used. I.e. for static values and then propagate updates through subscriptions. It's not ready to be used as a replacement for all Flux-like state propagation.
这篇文章主要参考了 Blogged Answers: React, Redux, and Context Behavior
关于 React Redux 和 Context 网上存在一些误解:
上面说法说法均错误
其实这是一个
context
老生常谈的问题, 如果给context
传入的是一个非原始类型, 比如数组或者对象, 那么当你的组件只订阅了部分对象属性, 即使该属性没有发生变化, 但如果其他属性发生变化你的组件仍旧会被迫重新渲染. 可以简单理解为没办法进行局部订阅, 除非你自己去做好性能优化.举个例子:
如果
ProviderComponent
调用了setContextValue({ a: 1, b: 3 })
,ChildComponent
仍旧会被重新渲染, 即使他解构了对象并且只用到了a
属性. 原因很简单, 一个新的对象引用被传递给了 provider, 所有的 consumer 都需要重新渲染. 事实上, 如果显示的强制调用一遍<MyContext.Provider value={{a: 1, b: 2}}>
,ChildComponent
仍旧会被重新渲染, 因为这是一个新的对象引用. 可以看做两个用===
在进行严格比较. 所以理论上尽量不要给 Context 传递对象类型...而对于 React-Redux, 虽然内部确实用到 context, 但他传递给 provider 的是 store 实例本身, 而非 store 内部的状态. 其基本实现可以看做如下:
其基本原理也是相当清晰了. 通过订阅 Redux 的 store, 来获知是否有 action 被 dispatch 了, 然后通过
ref
和useLayoutEffect
来获取 store 里新旧值并进行比对来判断组件是否需要重新渲染. 注意这里还是进行的严格比较. 这也是useSelector
和mapStateToProps
的区别. 虽然 react-redux 帮忙做了部分性能优化. 但是更加具体的还是需要自己来. 这里不展开.注意由于通过 context 传递的是 store 的实例, 所以本质上
useLayoutEffect
不会触发多次渲染, 监听也只会监听一次.关于 context 的行为可以参考: React issue #14110: Provide more ways to bail out of hooks.. 该 issue 很长, 这里只截取 Dan 在开头提到的两个点:
useState
并不提供一种能够 bail out 的方式(不触发组件 render). 但是对于 children 可以触发 bail out 逻辑, 如果 state 和之前是一样的话. 具体可以看: React中的bailout是什么意思,该怎么翻译?useContext
不提供局部订阅 context 只的方法. 解决方法可以参考: Preventing rerenders with React.memo and useContext hook. #15156 (comment)而对于什么时候使用 context, 在该 issue 下有人总结了一下:
中文翻译过来就是传给 context 的值一般是比较少会触发更新的, 比如 locale 或者 theme. context 更被常用的方式应该为注入一个服务, 而不是注入一个状态.
而对于 React-Redux v6, 作者曾尝试把 store state 作为 value 传递给 context. 但是最后证明这种方式存在很大问题. 具体细节可以参考: which is why we had to rewrite the internal implementation to use direct subscriptions again in React-Redux v7. 以及如果想要了解更多关于 React-Redux 工作原理的, 可以看另一篇博客: The History and Implementation of React-Redux
题外话
原文章的作者是 Mark Erikson, 是目前 Redux 和 Redux Toolkit 的维护者, 虽然相比于 Dan Abramov 可能没有那么出名. 但是他的博客质量还是非常高的, 他写了挺多关于 React 和 Redux 的文章, 文章都很长, 而且相当硬核. 不过可能由于过于硬核或者其他原因我是没找到国内的翻译版.
他的博客: https://blog.isquaredsoftware.com/
参考
The text was updated successfully, but these errors were encountered: