React.memo, useMemo, useCallback

2024-01-12  本文已影响0人  小冰山口

当遇到一个需求, 每次渲染一个页面时, 如果数据源没有发生变化, 实际上页面是不用重新渲染的, 但是在RN中, 有没有一个方法是可以去满足这个需求的呢?

在函数式组件中:
需要用到React.memo
以下是原始的代码:

 export default withFloatingButton(
    (props: Props) => {
      console.log('render...')
      const theme = useContext(ThemeContext)
      const styles = theme === 'dark' ? darkStyles : lightStyles
      return (
        <View style={styles.avarta}>
          <Image 
            style={styles.img}
            source={{uri: `${props.info.avarta}`}}
          >
          </Image>
          <Text style={styles.titleTxt}>
            {`${props.info.name}`}
          </Text>
          <View style={styles.descContent}>
            <Text style={styles.descTxt}>
              {`${props.info.desc}`}
            </Text>
          </View>
        </View>
      )
    }
  )

但这样写的话, 如果props的值重新设置的话, 这个组件就会重新渲染一遍, 但是, 重新设置的props的值事实上并不一定是变化的. 那这时候重新渲染就没有意义了, 如果这时候用React.memo包一层的话, 情况就会不同:

export default React.memo(
  withFloatingButton(
    (props: Props) => {
      console.log('render...')
      const theme = useContext(ThemeContext)
      const styles = theme === 'dark' ? darkStyles : lightStyles
      return (
        <View style={styles.avarta}>
          <Image 
            style={styles.img}
            source={{uri: `${props.info.avarta}`}}
          >
          </Image>
          <Text style={styles.titleTxt}>
            {`${props.info.name}`}
          </Text>
          <View style={styles.descContent}>
            <Text style={styles.descTxt}>
              {`${props.info.desc}`}
            </Text>
          </View>
        </View>
      )
    }
  )
, (preProps: Props, nextProps: Props) => {
  return JSON.stringify(preProps.info) === JSON.stringify(nextProps.info)
})

这段代码的意思是, 将原先的整个函数, 作为参数传进入React.memo的第一个参数, 那第二个参数也是一个函数:

(preProps: any, nextProps: any) => {
  return xxx /// bool类型
}

这个函数的意思是将原先的preProps和新的nextProps进行比较, 来决定是否渲染, return的这个返回值, 如果是true(表示数据一样), 则不重新渲染, 如果是false(表示数据不同), 需要重新渲染.

那么在类组件中有没有类似的方法呢?
也是有的

shouldComponentUpdate(nextProps: Readonly<Props>): boolean

需要去实现这个方法, 在这个方法中, 有点类似于React.memo的第二个参数, 需要是当前的this.props和新的nextProps进行比较:

  shouldComponentUpdate(nextProps: Readonly<Props>): boolean {
    return JSON.stringify(nextProps.info) !== JSON.stringify(this.props.info)
  }

区别在于, 当两者不相同时, 则返回true, 表示需要重新渲染. 当两者相同时, 则返回false, 表示不需要重新渲染. 这是跟React.memo刚好相反的地方.

好了, 现在来聊一下如何使用useMemo
假设现在有这样一个场景

image.png
需要算出列表的某一项的总计, 那么事实上, 这个列表如何展示, 其实跟这个计算并没有什么关系, 比如, 列表也可以这样展示
image.png
但是, 如果常规的写法的话, 就会将数值再重新计算一遍, 那这个其实是很没有必要的, 试想一下, 假设这个列表有几千条数据呢? 所以, 我们需要将这个列表的数据缓存起来, 这个就用到了useMemo

使用前:

   const calculateAmount = () => {
     return data.map((item) => item.amount).reduce((pre, cur) => pre + cur)
   }

使用后:

  // useMemo
  const calculateAmount = useMemo(
    () => {
    return data.map((item) => item.amount).reduce((pre, cur) => pre + cur)
  }, [data])

但其实上面的还不够完善, 我们仅仅只是数据需要缓存吗? 其实整个底部合计的view都是可以缓存的. 因为当数据没有发生变化时, 整个view都是不变的.

那么就可以写成这样:

  // useMemo
  const totalAmountView = useMemo(
    () => {
      const totalAmount = data.map((item) => item.amount).reduce((pre, cur) => pre + cur)
      return (
        <View style={styles.bottomView}>
          <Text style={styles.titleTxt}>
            {`${totalAmount}`}
          </Text>
          <Text style={styles.titleTxt}>
            {`费用总计: `}
          </Text>
        </View>
      )
    }
  , [data])

最后我们来聊一下useCallback
比如在上面的列表中, 其实cell的点击事件是并不需要每次都创建一遍的. 那么如何缓存这些点击事件的函数呢, 这时候就要用到useCallback了.

  const didClickCell = useCallback(
    (item: UserData, index: number) => {
      console.log(`点击了第${index + 1}个cell, 名称是${item.name}`)
    }
  , [])

这样, 在onPress属性中就可以这样写:

        onPress={() => {
          didClickCell(item, index)
        }}

那可以直接在属性中写一个函数吗? 也是可以的. 这时, useCallback就要改一下, 改成一个返回值是一个函数的写法:

  const didClickCellNew = useCallback(
    (item: UserData, index: number) => () => {
      console.log(`点击了第${index + 1}个cell, 名称是${item.name}`)
    }
  , [])

这样, 在填写onPress属性时, 就可以直接写函数了:

onPress={ didClickCellNew(item, index) }
上一篇下一篇

猜你喜欢

热点阅读