前言
最近开始学习React,跟着Kent学,有很多干货,这里分享通过state切片以提高性能以及通过HOC抽象逻辑
一、background
1.1 实际问题
有时候,即便使用了memo,但是由于太多components跟新了,导致其它components的联动跟新,于是出现了性能问题
1.2 example
不妨假设我们有两个组件,一个是Grid,另外一个是Cell,在Grid中有多个Cell
function AppProvider({children}) {
const [state, dispatch] = React.useReducer(appReducer, {
grid: initialGrid,
})
return (
<AppStateContext.Provider value={state}>
<AppDispatchContext.Provider value={dispatch}>
{children}
</AppDispatchContext.Provider>
</AppStateContext.Provider>
)
}
function useAppState() {
const context = React.useContext(AppStateContext)
if (!context) {
throw new Error('useAppState must be used within the AppProvider')
}
return context
}
function Grid() {
const dispatch = useAppDispatch()
const [rows, setRows] = useDebouncedState(50)
const [columns, setColumns] = useDebouncedState(50)
const updateGridData = () => dispatch({type: 'UPDATE_GRID'})
return (
<AppGrid
onUpdateGrid={updateGridData}
rows={rows}
handleRowsChange={setRows}
columns={columns}
handleColumnsChange={setColumns}
Cell={Cell}
/>
)
}
Grid = React.memo(Grid)
function Cell({row, column}) {
const state = useAppState()
const cell = state.grid[row][column]
const dispatch = useAppDispatch()
const handleClick = () => dispatch({type: 'UPDATE_GRID_CELL', row, column})
return (
<button
className="cell"
onClick={handleClick}
style={{
color: cell > 50 ? 'white' : 'black',
backgroundColor: `rgba(0, 0, 0, ${cell / 100})`,
}}
>
{Math.floor(cell)}
</button>
)
}
Cell = React.memo(Cell)
function App() {
return (
<div className="grid-app">
<AppProvider>
<Grid />
</AppProvider>
</div>
)
}
期待的是单个Cell跟新只会React对应Cell的re-render,但是上面的例子无法完成需求,原因是Cell中的state是通过useAppState获取的,而useAppState本身是调用获取context中的state的,因为state是通过context来统一管理,每个Cell跟新都会触发state的跟新,而每个Cell用的都是使用Context中的state,于是一旦state跟新所有Cell都会跟新
二、 提出方法
2.1 方案1
最直接的方法当然是避免使用context处理全局转态,但是对于很多项目来说这个改动可能非常大,这个方法实现起来可能会很复杂
2.2 方案2
上面的问题在于state被Cell组件调用,这导致了state的跟新会出发所有Cell组件的跟新;
那么,可以把state抽离出来吗?
不妨吧API设计如下,我们给Cell直接传递cell,而不是在Cell组件中使用state来获取
function Cell({cell, row, column}) {
const dispatch = useAppDispatch()
const handleClick = () => dispatch({type: 'UPDATE_GRID_CELL', row, column})
return (
<button
className="cell"
onClick={handleClick}
style={{
color: cell > 50 ? 'white' : 'black',
backgroundColor: `rgba(0, 0, 0, ${cell / 100})`,
}}
>
{Math.floor(cell)}
</button>
)
}
Cell = React.memo(Cell)
可是,Grid组件中获取的Cell方法签名是
<Cell row={row} column={column} />
显然,我们需要中间件
,也就是题目提到的state切片
2.3 使用state切片
我们的Cell方法签名不能改变,那么就是
function Cell({row, column}) {
const dispatch = useAppDispatch()
const handleClick = () => dispatch({type: 'UPDATE_GRID_CELL', row, column})
return (
<Comp />
)
}
Cell = React.memo(Cell)
但是,我们可以returnCell({cell, row, column})
, 具体来说就是
Grid = React.memo(Grid)
function Cell({row, column}) {
const state = useAppState()
const cell = state.grid[row][column]
return <CellImpl cell={cell} row={row} column={column} />
}
Cell = React.memo(Cell)
function CellImpl({cell, row, column}) {
const dispatch = useAppDispatch()
const handleClick = () => dispatch({type: 'UPDATE_GRID_CELL', row, column})
return (
<button
className="cell"
onClick={handleClick}
>
{Math.floor(cell)}
</button>
)
}
CellImpl = React.memo(CellImpl)
这样,分两次memo,第一次是对Cell的memo,每次state的跟新都会触发Cell的跟新,但是由于Cell返回的也是一个memo的组件,而这个组件只会在cell, row, 或者column改变时才会触发re-render,其中cell也就是state.grid[row][column]
的单个cell,这样就符合了我们对单个cell改变只会重新渲染当个cell的需求了
三、HOC
其实上面对Cell组件的包裹,就是React中的HOC,通过HOC给组件赋予更多功能,修改如下
Cell本身这个组件设计就是接受三个参数的cell, row, column
function Cell({cell, row, column}) {
const dispatch = useAppDispatch()
const handleClick = () => dispatch({type: 'UPDATE_GRID_CELL', row, column})
return (
<button
className="cell"
onClick={handleClick}
>
{Math.floor(cell)}
</button>
)
}
于是需要一个HOC把接收两个参数的Cell组件包裹起来
function withStateSlice(Comp) {
return function Wrapper({row, column}) {
const state = useAppState()
const cell = state.grid[row][column]
return <MemoComp cell={cell} row={row} column={column}/>
}
}
然后加上memo就完成需求了
function withStateSlice(Comp) {
const MemoComp = React.memo(Comp)
function Wrapper({row, column}) {
const state = useAppState()
const cell = state.grid[row][column]
return <MemoComp cell={cell} row={row} column={column}/>
}
return React.memo(Wrapper)
}
Cell = withStateSlice(Cell)
总结
这里通过state切片,把context的全局state处理到另外一个组件先做memo,在传递到单个组件中,已实现避免了全局变量的跟新导致全部组件的跟新;同时也介绍了HOC的功能做功能增加