React的性能调优 - state切片及其HOC高级组件的应用

前言

最近开始学习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的功能做功能增加

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值