当前位置: 首页 > Web前端 > HTML5

再也不用担心react性能了

时间:2023-04-05 14:29:44 HTML5

困惑写过react项目的同学都体验过react性能优化。一般的方法是缓存一些计算,或者使用purecomponent、memo等方法减少不必要的组件渲染,使用Context代替props透传等。下面我们来探讨一种从开发项目一开始就可以保证页面性能的方法。缺点常用方法一、Props透传大部分项目在堆砌React代码时,都是通过props逐层传递,将大量的组件代码拼凑起来,然后在发现性能问题的时候找到瓶颈的原因.父节点在任何位置的渲染都会导致子节点的渲染。这些效果图是必要的和非必要的。非必要的渲染是导致性能危机的地方。叶子节点的渲染可能依赖于某些props,这就要求props传输不阻塞,性能很难优化。2.可以使用pureComponent和shouldComponentUpdate这两个方法来比较props的变化。即使祖先render而自己的props没有变化,也不会render,节省性能,但是这也带来了其他的问题。2.1diff性能损失第一种方式是通过diff比较来判断道具是否发生变化。在某些情况下,由于写法等原因,props势必发生变化,导致盲目使用不仅不会带来性能提升,反而会增加部分diff计算时间2.2如果使用叶子节点在一个父元素上,即使父元素不需要渲染,后续的后代节点也需要根据某个props改变渲染,所以必须通过父元素的重新渲染来达到目的。总之,使用这两种方式无法准确控制渲染所有节点的必要性,这可能会使性能优化变得更加困难。3.Memo使用hooks的同学只能通过memo方法来判断组件是否需要渲染。使用备忘录需要在复杂场景中迈出第一步。两个参数的判断缺点和上面类似,会出现一些应该渲染但不渲染的陷阱pureComponent在某些情况下性能会更差。原因是写法如果没有缓存,diff计算时间会被浪费,比如{setValue(e.target.value)}}/>因为onchange的属性是匿名函数,组件每次渲染时,input传入的props都会发生变化,会导致memo、pureComponent等优化失败。所以请把传入组件的props用useCallback或useMemo包装起来,这样props除非必要不会改变。但是antd组件中并没有使用memo等包组件。即使传给antd的props使用了useCallback和useMemo,也不会带来性能的提升。但是我们自己开发时封装的组件需要做性能优化。在传入此类组件的props时,我们需要使用useCallback或useMemo封装。5、context上下文方法是一种比较简单的性能优化策略,大大减少了props的透传,结合useContext的使用,写法变得非常简洁。但是context并不能准确的控制渲染的必要性,因为组件订阅了context之后,只要context中某个值发生变化,即使不使用这个值,组件也会重新渲染。6.解决方案6.1使用rxjs精确控制组件渲染的时机在上下文中,所有想要订阅eventOutput的组件都使用useContext获取并监听eventOutput事件。由于eventInput和eventOutput是静态函数,这就保证了context中的value不会发生变化(context不存储变化的value值),变化总是在event中流转。达到的效果是精确控制任何你想改变的组件。上图中,子节点也可以在任意位置控制祖先节点的渲染,而不改变其他组件的渲染。6.2memo包裹父组件重新渲染必然会导致后续子组件的渲染,所以使用React.memo来包裹每个组件。这里有一个原则就是组件之间尽量不要传递props,只使用rxjs订阅需要的值。这样可以保证memo可以放心使用,不用担心是否有多余的diff和未知的坑。所有组件都不传递props,只关心自己订阅的值。只有当他们订阅的值发生变化时,他们才会渲染,从而实现手术刀式的控件渲染。6.3副作用将uieventInput和eventOutput从输入到输出分开,中间可以设置进程。ui中所有的sideeffects或者计算都可以放到这些processes里面,这样既可以保证ui文件的体积变小,又可以吸引注意点分离,ui只做渲染相关的事情。举个例子:--a.tsxeventInput({id:1})--hooks.tseventOutput.pipe(({id})=>{constres=awaitaxios.get(id)constcolors=res.map(v=>v.color)returncolors})--b.tsxeventOutput.sub(colors=>{setState(colors)})7.生活的例子provider.jsximport{useFetchResult,usePeriodChange}from'useData.js'exportconstcontext=React.createContext(({}))constProvider=({children})=>{const{fetchResultInput$,fetchResultOutput$}=useFetchResult()const{periodChangeInput$}=usePeriodChange(fetchResultInput$)返回({children})}export默认ProvideruseData.jsexportconstusePeriodChange=(fetchResultInput$)=>{const{periodChangeInput$}=useMemo(()=>{constperiodChangeInput$=newSubject()return{periodChangeInput$}},[])useEffect(()=>{constsub=periodChangeInput$.subscribe(period=>{constparams={...period,appid:123}fetchResultInput$.next(params)})return()=>sub()},[fetchResultInput$,periodChangeInput$])返回{periodChangeInput$}}exportconstuseFetchResult=(fetchResultInput$)=>{returnuseMemo(()=>{constfetchResultInput$=newSubject()constfetchResultOutput$=fetchResultInput$.pipe(switchMap(params=>{returnaxios.get('/',params)}),map(res=>{...calc(res)...returnresult}))return{fetchResultInput$,fetchResultOutput$}},[])}index.jsximport提供者from'./provider.jsx'exportdefault()=>()panel.jsxexportdefault()=>(<>

)preiod.jsxexportdefault()=>{const{periodChangeInput$}=useContext(context)constonChange=useCallback((period)=>{periodChangeInput$.next(period)},[periodChangeInput$])return}table.jsxexportdefault()=>{const[数据,setData]=useState([])const{fetchResultOutput$}=useContext(context)useEffect(()=>{constsub=fetchResultOutput$.subscribe(data=>setData(data))return()=>sub()},[fetchResultOutput$])return}