最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • react性能新姿势

    正文概述 掘金(shijian890717)   2021-02-07   411

    react性能新姿势

    困惑

    写过react项目的同学,都经历过react性能优化,一般的方法是缓存某些计算,或者使用purecomponent、memo等方法减少不必要的组件渲染,以及使用context替代props透传等,下面探讨一种能从开始开发项目时就能保证页面性能的方法。

    ##常用弊端方法

    1.props透传

    大多数的项目堆砌react代码时,都采用props一层层传递的方式,拼凑出一大堆组件代码,等到发现性能问题时再去查找瓶颈的原因。 react性能新姿势

    任意位置父节点的渲染都会导致子孙节点的渲染,这些渲染有必要的也有非必要的,非必要的渲染就是导致性能危机的地方。 叶子节点的渲染可能依赖于某个props,这就需要props传递不被阻断,性能很难优化。

    2.pureComponent、shouldComponentUpdate

    这两种方式可以用来对比props的变化,即使祖先渲染了而自己的props非改变时就不渲染,节省性能,但这样也带来了其他的问题

    2.1diff性能损失

    这两种方式是通过diff比较来判断props是否变化的,而有些情况下由于写法等原因,props是必然变化的,就导致了盲目的使用不仅没有带来性能提升,反而多出了一部分diff计算时间

    2.2叶子节点

    如果用在某个父元素上,即使这个父元素不需要渲染,但是后面的子孙节点需要基于某个props来改变渲染,所以也必然要通过这个父元素的重新渲染来达到目的

    3.memo

    使用hooks的同学只能通过memo方法来判断组件是否需要渲染,使用memo在复杂场景下需要第二个参数的判断,弊端与上面类似,而且还会遇到一些该渲染而未渲染的坑点

    4.useCallback、useMemo

    配合memo等diff计算非必要渲染的手段,将props缓存起来,就是上面提到的pureComponent在有些情况下性能会变的更糟,原因就是在写法上没有缓存的话,就会浪费diff计算时间,例如

    <Input onChange={e=>{setValue(e.target.value)}} />
    

    由于onchange的属性是个匿名函数,每次组件渲染时,input传入的props都会变,就会导致memo、pureComponent等优化失效。所以请将传入组件的props使用useCallback或useMemo包裹起来,让props非必要不改变。 但是在antd组件中,没有使用memo等包裹组件,即使传入antd的props使用了useCallback、useMemo,也不能带来性能提升。 但是我们自己开发时封装的组件是需要做性能优化的,在传入这类组件的props时,需要使用useCallback或useMemo包装。

    5.context

    context方法是比较简单的性能优化策略,大量减少props的透传,再配合useContext的使用,写法变得非常简洁。 但是context也没法做到精确控制渲染的必要性,因为组件订阅了context后只要context中某个值发生变化,即使没有使用这个值也会导致组件重新渲染。 react性能新姿势

    6.解决方法

    6.1使用rxjs精确控制组件渲染时机

    先不了解rxjs具体的概念,把rxjs当做是eventEmitters订阅工具,先手写两个方法eventInput(输入)、eventOutput(输出),并且把这两个方法放到context中,所有想要订阅eventOutput的组件,就使用useContext获取并且监听eventOutput事件,由于eventInput、eventOutput都是静态不变的函数,这就保证了context中的value不会变化(context不存放变化的value值),变化的始终是event中的流。达到的效果就是精确控制任意想变化的组件。 react性能新姿势 上图中,子孙节点也能控制任意位置的祖先节点的渲染,而不改变其他组件的渲染

    ###6.2memo包裹 父组件重新渲染必然导致后续子组件的渲染,所以使用React.memo包裹每个组件。

    这样就保证了可以放心使用memo,而不担心是否有多余的diff和未知的坑点。所有组件没有props传递,只关心自己订阅的值,只有自己订阅的值改变了才去渲染,做到手术刀式的控制渲染。

    6.3副作用分离出ui

    eventInput、eventOutput从输入到输出,中间是可以设定过程的,把ui中的所有副作用或计算都可以放到这些过程中去,既可以保证ui文件体积减小,也可以让关注点分离,ui只做跟渲染有关的事情。举个例子:

    --a.tsx
    eventInput({id:1})
    
    --hooks.ts
    eventOutput.pipe(({id})=>{
       const res = await axios.get(id)
       const colors = res.map(v=>v.color) 
       return colors
    })
    
    
    --b.tsx
    eventOutput.sub(colors=>{
        setState(colors)
    })
    

    7.活生生的例子

    provider.jsx

    import {useFetchResult,usePeriodChange} from 'useData.js'
    
    export const context = React.createContext(({}))
    
    const Provider = ({ children }) => {
      const { fetchResultInput$,fetchResultOutput$ } = useFetchResult()
      const { periodChangeInput$ } = usePeriodChange(fetchResultInput$)
      return (
        <context.Provider
          value={{
            fetchResultOutput$,
            periodChangeInput$,
          }}
        >
          {children}
        </context.Provider>
      )
    }
    
    export default Provider
    

    useData.js

    export const usePeriodChange = (fetchResultInput$) => {
      const {periodChangeInput$} = useMemo(() => {
        const periodChangeInput$ = new Subject()
        return { periodChangeInput$ }
      }, [])
      useEffect(()=>{
        const sub = periodChangeInput$.subscribe(period=>{
            const params = {...period,appid:123}
            fetchResultInput$.next(params)
        })
        return ()=>sub()
      },[fetchResultInput$,periodChangeInput$])
      return {periodChangeInput$}
    }
    
    export const useFetchResult = (fetchResultInput$) => {
      return useMemo(() => {
        const fetchResultInput$ = new Subject()
        const fetchResultOutput$ = fetchResultInput$.pipe(
            switchMap(params=>{
                return axios.get('/',params)
            }),
            map(res=>{
                ...
                calc(res)
                ...
                return result
            })
        )
        return { fetchResultInput$, fetchResultOutput$ }
      }, [])
    }
    

    index.jsx

    import Provider from './provider.jsx'
    export default () => (
      <Provider>
        <Panel />
      </Provider>
    )
    

    panel.jsx

    export default () => (
      <>
        <Period />
        <Table />
      </>
    )
    

    preiod.jsx

    export default () => {
        const {periodChangeInput$} = useContext(context)
        const onChange = useCallback((period)=>{
            periodChangeInput$.next(period)
        },[periodChangeInput$])
        return <Select onChange={onChange} />
    }
    

    table.jsx

    export default () => {
        const [data,setData] = useState([])
        const {fetchResultOutput$} = useContext(context)
        useEffect(()=>{
            const sub = fetchResultOutput$.subscribe(data=>setData(data))
            return ()=>sub()
        },[fetchResultOutput$])
        return <Table data={data} />
    }
    

    起源地下载网 » react性能新姿势

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元