文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何解决React.memo引起的bug问题

2023-06-29 10:25

关注

这篇文章主要介绍如何解决React.memo引起的bug问题,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

与PureComponent不同的是PureComponent只是进行浅对比props来决定是否跳过更新数据这个步骤,memo可以自己决定是否更新,但它是一个函数组件而非一个类,但请不要依赖它来“阻止”渲染,因为这会产生 bug。

一般memo用法:

import React from "react";function MyComponent({props}){    console.log('111);    return (        <div> {props} </div>    )};function areEqual(prevProps, nextProps) {    if(prevProps.seconds===nextProps.seconds){        return true    }else {        return false    }}export default React.memo(MyComponent,areEqual)

问题描述

我们在处理业务需求时,会用到memo来优化组件的渲染,例如某个组件依赖自身的状态即可完成更新,或仅在props中的某些数据变更时才需要重新渲染,那么我们就可以使用memo包裹住目标组件,这样在props没有变更时,组件不会重新渲染,以此来规避不必要的重复渲染。
下面是我创建的一个公共组件:

type Props = { inputDisable?: boolean // 是否一直展示输入框 inputVisible?: boolean value: any min: number max: number onChange: (v: number) => void}const InputNumber: FC<Props> = memo( (props: Props) => {   const { inputDisable, max, min, value, inputVisible } = props   const handleUpdate = (e: any, num) => {     e.stopPropagation()     props.onChange(num)   }   return (     <View className={styles.inputNumer}>       {(value !== 0 || inputVisible) && (         <>           <Image             className={styles.btn}             src={require(value <= min               ? '../../assets/images/reduce-no.png'               : '../../assets/images/reduce.png')}             onClick={e => handleUpdate(e, value - 1)}             mode='aspectFill'           />           <Input             value={value}             disabled={inputDisable}             alwaysEmbed             type='number'             cursor={-1}             onInput={e => handleUpdate(e, parseInt(e.detail.value ? e.detail.value : '0'), 'input')}           />         </>       )}       <Image         className={styles.btn}         src={require(max !== -1 && (value >= max || min > max)           ? '../../assets/images/plus-no.png'           : '../../assets/images/plus.png')}         onClick={e => handleUpdate(e, value + 1)}       />     </View>   ) }, (prevProps, nextProps) => {   return prevProps.value === nextProps.value && prevProps.min === nextProps.min && prevProps.max === nextProps.max })export default InputNumber

这个组件是一个自定义的数字选择器,在memo的第二个参数中设置我们需要的参数,当这些参数有变更时,组件才会重新渲染。
在下面是我们用到这个组件的场景。

type Props = {info: anyonUpdate: (items) => void}const CartBrand: FC<Props> = (props: Props) => {const { info } = propsconst [items, setItems] = useState<any>(  info.items.map(item => {  // selected默认为false    return { num:1, selected: false }  }))useEffect(() => {  getCartStatus()}, [])// 获取info.items中没有提供,但是展示需要的数据const getCartStatus = () => {  setTimeout(() => {    setItems(      info.items.map(item => {      //更新selected为true        return {num: 1, selected: true }      })    )  }, 1000)}return (  <View className={styles.brandBox}>    {items.map((item: GoodSku, index: number) => {      return (        <InputNumber          key={item.skuId}          inputDisable          min={0}          max={50}          value={item.num}          onChange={v => {            console.log(v, item.selected)          }}        />      )    })}  </View>)}export default CartBrand

这个组件的目的是展示props传过来的列表,但是列表中有些数据服务端没有给到,需要你再次通过另一个接口去获取,我用settimeout替代了获取接口数据的过程。为了让用户在获取接口的过程中不需要等待,我们先根据props的数据给items设置了默认值。然后在接口数据拿到后再更新items。
但几秒钟后我们在子组件InputNumber中更新数据,会看到:

如何解决React.memo引起的bug问题

selected依然是false!
这是为什么呢?前面不是把items中所有的selected都改为true了吗?
我们再打印一下items看看:

如何解决React.memo引起的bug问题

似乎在InputNumber中的items依然是初始值。
对于这一现象,我个人理解为memo使用的memoization算法存储了上一次渲染的items数值,由于InputNumber没有重新渲染,所以在它的本地状态中,items一直是初始值。

解决方法

方案一. 使用useRef + forceUpdate方案

我们可以使用useRef来保证items一直是最新的,讲useState换为useRef

  type Props = {  info: any  onUpdate: (items) => void}const CartBrand: FC<Props> = (props: Props) => {  const { info } = props  const items = useRef<any>(    info.items.map(item => {    // selected默认为false      return { num:1, selected: false }    })  )  useEffect(() => {    getCartStatus()  }, [])    // 获取info.items中没有提供,但是展示需要的数据  const getCartStatus = () => {    setTimeout(() => {      items.current = info.items.map(() => {        return { num: 1, selected: true }      })    }, 1000)  }  return (    <View className={styles.brandBox}>      {items.current.map((item: GoodSku, index: number) => {        return (          <InputNumber            key={item.skuId}            inputDisable            min={0}            max={50}            value={item.num}            onChange={v => {              console.log(v, items)            }}          />        )      })}    </View>  )}export default CartBrand

这样再打印的时候我们会看到

如何解决React.memo引起的bug问题

items中的selected已经变成true了
但是此时如果我们需要根据items中的selected去渲染不同的文字,会发现并没有变化。

  return (    <View className={styles.brandBox}>      {items.current.map((item: GoodSku, index: number) => {        return (          <View key={item.skuId}>            <View>{item.selected ? '选中' : '未选中'}</View>            <InputNumber              inputDisable              // 最小购买数量              min={0}              max={50}              value={item.num}              onChange={() => {                console.log('selected', items)              }}            />          </View>        )      })}    </View>  )

显示还是未选中

如何解决React.memo引起的bug问题

这是因为useRef的值会更新,但不会更新他们的 UI,除非组件重新渲染。因此我们可以手动更新一个值去强制让组件在我们需要的时候重新渲染。

const CartBrand: FC<Props> = (props: Props) => {  const { info } = props  // 定义一个state,它在每次调用的时候都会让组件重新渲染  const [, setForceUpdate] = useState(Date.now())  const items = useRef<any>(    info.items.map(item => {      return { num: 1, selected: false }    })  )  useEffect(() => {    getCartStatus()  }, [])const getCartStatus = () => {    setTimeout(() => {      items.current = info.items.map(() => {        return { num: 1, selected: true }      })      setForceUpdate()    }, 5000)  }  return (    <View className={styles.brandBox}>      {items.current.map((item: GoodSku, index: number) => {        return (          <View key={item.skuId}>            <View>{item.selected ? '选中' : '未选中'}</View>            <InputNumber              inputDisable              // 最小购买数量              min={0}              max={50}              value={item.num}              onChange={() => {                console.log('selected', items)              }}            />          </View>        )      })}    </View>  )}export default CartBrand

这样我们就可以使用最新的items,并保证items相关的渲染不会出错

方案2. 使用useCallback

在InputNumber这个组件中,memo的第二个参数,我没有判断onClick回调是否相同,因为无论如何它都是不同的。
参考这个文章:use react memo wisely
函数对象只等于它自己。让我们通过比较一些函数来看看:

function sumFactory() {return (a, b) => a + b;}const sum1 = sumFactory();const sum2 = sumFactory();console.log(sum1 === sum2); // => falseconsole.log(sum1 === sum1); // => trueconsole.log(sum2 === sum2); // => true

sumFactory()是一个工厂函数。它返回对 2 个数字求和的函数。
函数sum1和sum2由工厂创建。这两个函数对数字求和。但是,sum1和sum2是不同的函数对象(sum1 === sum2is false)。
每次父组件为其子组件定义回调时,它都会创建新的函数实例。在自定义比较函数中过滤掉onClick固然可以规避掉这种问题,但是这也会导致我们上述的问题,在前面提到的文章中,为我们提供了另一种解决思路,我们可以使用useCallback来缓存回调函数:

type Props = {  info: any  onUpdate: (items) => void}const CartBrand: FC<Props> = (props: Props) => {  const { info } = props  const [items, setItems] = useState(    info.items.map(item => {      return { num: 1, selected: false }    })  )  useEffect(() => {    getCartStatus()  }, [])  // 获取当前购物车中所有的商品的库存状态  const getCartStatus = () => {    setTimeout(() => {      setItems(        info.items.map(() => {          return { num: 1, selected: true }        })      )    }, 5000)  }  // 使用useCallback缓存回调函数  const logChange = useCallback(    v => {      console.log('selected', items)    },    [items]  )  return (    <View className={styles.brandBox}>      {items.map((item: GoodSku, index: number) => {        return (          <View key={item.skuId}>            <InputNumber              inputDisable              // 最小购买数量              min={0}              max={50}              value={item.num}              onChange={logChange}            />          </View>        )      })}    </View>  )}

相应的,我们可以把InputNumber的自定义比较函数去掉。

type Props = { inputDisable?: boolean // 是否一直展示输入框 inputVisible?: boolean value: any min: number max: number onChange: (v: number) => void}const InputNumber: FC<Props> = memo( (props: Props) => {   const { inputDisable, max, min, value, inputVisible } = props   const handleUpdate = (e: any, num) => {     e.stopPropagation()     props.onChange(num)   }   return (     <View className={styles.inputNumer}>       {(value !== 0 || inputVisible) && (         <>           <Image             className={styles.btn}             src={require(value <= min               ? '../../assets/images/reduce-no.png'               : '../../assets/images/reduce.png')}             onClick={e => handleUpdate(e, value - 1)}             mode='aspectFill'           />           <Input             value={value}             disabled={inputDisable}             alwaysEmbed             type='number'             cursor={-1}             onInput={e => handleUpdate(e, parseInt(e.detail.value ? e.detail.value : '0'), 'input')}           />         </>       )}       <Image         className={styles.btn}         src={require(max !== -1 && (value >= max || min > max)           ? '../../assets/images/plus-no.png'           : '../../assets/images/plus.png')}         onClick={e => handleUpdate(e, value + 1)}       />     </View>   ) })export default InputNumber

这样在items更新的时候,inputNumber也会刷新,不过在复杂的逻辑中,比如items的结构非常复杂,items中很多字段都会有高频率的改变,那这种方式会减弱InputNumber中memo的效果,因为它会随着items的改变而刷新。

以上是“如何解决React.memo引起的bug问题”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注编程网行业资讯频道!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯