Context是个好东西。撇开代数效应等纯理论概念不谈,能够在组件树上不分深度透明传输状态,确实能给开发带来极大的便利。但是,如果在Context的使用中不注意一些细节,使用不当,可能会对应用程序的性能造成灾难性的影响。最近在优化一个产品的性能时,总结了两个琐碎的“常识”。关于顺序,先看一张图,我愿称之为世界名画:这只是一个demo界面,没什么,我的逻辑也很简单:importReact,{memo,useReducer}from'react';import{ConfigProvider,Tooltip}from'antd';importZH_CNfrom'antd/lib/locale-provider/zh_CN';import'antd/dist/antd.min.css';constText=({text})=>{return({text}
);};constMemoedText=memo(Text);constConfigProviderInside=()=>{const[counter,inc]=useReducer((v)=>v+1,1);return(AppCounter:{counter}
FORCEUPDATE );};点击按钮,整个界面又更新了,还正常吗?那如果你做一个“简单”的优化:是不是很酸?我做了什么:constConfigProviderOutside=()=>{const[counter,inc]=useReducer((v)=>v+1,1);返回(AppCounter:{ccounter}
FORCEUPDATE );};render();我把antd的ConfigProvider放在外面了。这行代码的原因也很简单。Antd的ConfigProvider没有做任何优化。每次它赋予Context的值都是一个全新的对象(虽然内容没变),这会导致所有关联的Components全部触发更新(虽然没有意义)。在你的系统中这样做的最终结果是你用memo、PureComponent等方法优化你写的组件,但是里面的antd组件却乐呵呵地渲染到停不下来的地步。所以第一个经验就是:梳理一下Context的上下层关系,理论上不会变化的放在最外面,会经常变化的放在里面。不会改变的,比如Locale,Constant,还有一些系统级的依赖注入,这些在整个生命周期中往往是不会改变的。那么系统启动的时候,比如CurrentUser,请求一次数据,就会从null变成一个固定值,之后就不会再变了。这个类也尽量放。最后,像Router这样的东西会经常变化,所以应该放在最里面,以免带入其他因为它的更新而变化不大的Context。关于粒度,看一个很经典的Context:constDEFAULT_VALUE={props:null,openGlobalModal:()=>undefined,closeGlobalModal:()=>undefined,};constGlobalModalContext=createContext(DEFAULT_VALUE);constGlobalModalContextProvider=({children})=>{const[props,setProps]=useState(null);constcloseGlobalModal=useCallback(()=>setProps(null),[]);constcontextValue=useMemo(()=>{return{props,closeGlobalModal,openGlobalModal:setProps,};},[props,closeGlobalModal,setProps]);return({children});};使用一个Context来统一管理全局单例对话框,也是一种比较常见的玩法。如果你使用这个:constEditUserLabel=({id})=>{const{openGlobalModal}=useContext(GlobalMoadlContext);constedit=useCallback(()=>openGlobalModal({title:'EditUser',children:}),[openGlobalModal,id]);returnedit;};constcolumns=[//...{title:'operation',key:'action',dataIndex:'id'render:id=>,}]constUserList=({数据源})=>();在表格的每一行上放置一个“编辑”标签,然后全局放置一个对话框:constGlobalModal=()=>{const{props}=useContext(GlobalMoadlContext);return!!props&&;};您会惊讶地发现,每次编辑用户(或在别处触发对话)时,表中每一行的编辑标签都会更新。原因很容易分析,因为当你打开对话框时,props发生了变化,所以contextValue也发生了变化,所以虽然edit标签只使用了永远不会变化的openGlobalModal,但是渲染硬生生的站了起来。如果想要追求更少的渲染,就要注意第二个体验:一个Context中的东西往往不会一起使用,根据使用场景将它们分开,尤其是把可变的和不变的分开。像上面的代码,可以这样优化:constGlobalModalPropsContext=createContext(null);constDEFAULT_ACTION={openGlobalModal:()=>undefined,closeGlobalModal:()=>undefined,};constGlobalModalActionContext=createContext(DEFAULT_ACTION);constGlobalModalContextProvider=({children})=>{const[props,setProps]=useState(null);constcloseGlobalModal=useCallback(()=>setProps(null),[]);constactionValue=useMemo(()=>{return{closeGlobalModal,openGlobalModal:setProps,};},[closeGlobalModal,setProps]);return(//注意第一块经验,少外面{children});};只要根据实际需要访问两个不同的Context,就可以做到属性粒度最优化,渲染最少。当然我也建议不要直接暴露Context本身,而是根据使用场景暴露成几个hook,这样一开始可以不用做特别的优化,等性能瓶颈的时候再拆掉Context,只需要修改hook实现即可实现外部兼容。小结注意应用中使用的Context的顺序,不变的放在外层,可变的放在内层。Context中的内容可以根据使用场景和变化变化拆分成多个更细粒度的artisan来减少渲染。