react性能新姿式

困惑

寫過react項目的同窗,都經歷過react性能優化,通常的方法是緩存某些計算,或者使用purecomponent、memo等方法減小沒必要要的組件渲染,以及使用context替代props透傳等,下面探討一種能從開始開發項目時就能保證頁面性能的方法。react

##經常使用弊端方法ios

1.props透傳

大多數的項目堆砌react代碼時,都採用props一層層傳遞的方式,拼湊出一大堆組件代碼,等到發現性能問題時再去查找瓶頸的緣由。 axios

任意位置父節點的渲染都會致使子孫節點的渲染,這些渲染有必要的也有非必要的,非必要的渲染就是致使性能危機的地方。 葉子節點的渲染可能依賴於某個props,這就須要props傳遞不被阻斷,性能很難優化。緩存

2.pureComponent、shouldComponentUpdate

這兩種方式能夠用來對比props的變化,即便祖先渲染了而本身的props非改變時就不渲染,節省性能,但這樣也帶來了其餘的問題性能優化

2.1diff性能損失

這兩種方式是經過diff比較來判斷props是否變化的,而有些狀況下因爲寫法等緣由,props是必然變化的,就致使了盲目的使用不只沒有帶來性能提高,反而多出了一部分diff計算時間markdown

2.2葉子節點

若是用在某個父元素上,即便這個父元素不須要渲染,可是後面的子孫節點須要基於某個props來改變渲染,因此也必然要經過這個父元素的從新渲染來達到目的antd

總而言之,使用這兩種方法並不能精確控制全部的節點渲染必要性,可能使性能優化變得更加棘手app

3.memo

使用hooks的同窗只能經過memo方法來判斷組件是否須要渲染,使用memo在複雜場景下須要第二個參數的判斷,弊端與上面相似,並且還會遇到一些該渲染而未渲染的坑點ide

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中某個值發生變化,即便沒有使用這個值也會致使組件從新渲染。

6.解決方法

6.1使用rxjs精確控制組件渲染時機

先不瞭解rxjs具體的概念,把rxjs當作是eventEmitters訂閱工具,先手寫兩個方法eventInput(輸入)、eventOutput(輸出),而且把這兩個方法放到context中,全部想要訂閱eventOutput的組件,就使用useContext獲取而且監聽eventOutput事件,因爲eventInput、eventOutput都是靜態不變的函數,這就保證了context中的value不會變化(context不存放變化的value值),變化的始終是event中的流。達到的效果就是精確控制任意想變化的組件。 上圖中,子孫節點也能控制任意位置的祖先節點的渲染,而不改變其餘組件的渲染

###6.2memo包裹 父組件從新渲染必然致使後續子組件的渲染,因此使用React.memo包裹每一個組件。

這裏有個原則就是組件之間儘可能不傳遞props,只使用rxjs訂閱須要的值。

這樣就保證了能夠放心使用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} />
}
複製代碼
相關文章
相關標籤/搜索