寫過react項目的同窗,都經歷過react性能優化,通常的方法是緩存某些計算,或者使用purecomponent、memo等方法減小沒必要要的組件渲染,以及使用context替代props透傳等,下面探討一種能從開始開發項目時就能保證頁面性能的方法。react
##經常使用弊端方法ios
大多數的項目堆砌react代碼時,都採用props一層層傳遞的方式,拼湊出一大堆組件代碼,等到發現性能問題時再去查找瓶頸的緣由。 axios
任意位置父節點的渲染都會致使子孫節點的渲染,這些渲染有必要的也有非必要的,非必要的渲染就是致使性能危機的地方。 葉子節點的渲染可能依賴於某個props,這就須要props傳遞不被阻斷,性能很難優化。緩存
這兩種方式能夠用來對比props的變化,即便祖先渲染了而本身的props非改變時就不渲染,節省性能,但這樣也帶來了其餘的問題性能優化
這兩種方式是經過diff比較來判斷props是否變化的,而有些狀況下因爲寫法等緣由,props是必然變化的,就致使了盲目的使用不只沒有帶來性能提高,反而多出了一部分diff計算時間markdown
若是用在某個父元素上,即便這個父元素不須要渲染,可是後面的子孫節點須要基於某個props來改變渲染,因此也必然要經過這個父元素的從新渲染來達到目的antd
總而言之,使用這兩種方法並不能精確控制全部的節點渲染必要性,可能使性能優化變得更加棘手app
使用hooks的同窗只能經過memo方法來判斷組件是否須要渲染,使用memo在複雜場景下須要第二個參數的判斷,弊端與上面相似,並且還會遇到一些該渲染而未渲染的坑點ide
配合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包裝。
context方法是比較簡單的性能優化策略,大量減小props的透傳,再配合useContext的使用,寫法變得很是簡潔。 可是context也無法作到精確控制渲染的必要性,由於組件訂閱了context後只要context中某個值發生變化,即便沒有使用這個值也會致使組件從新渲染。
先不瞭解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傳遞,只關心本身訂閱的值,只有本身訂閱的值改變了纔去渲染,作到手術刀式的控制渲染。
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)
})
複製代碼
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} />
}
複製代碼