如何解决React.memo引起的bug问题(BUG,memo,react,开发技术)

时间:2024-04-27 21:05:22 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

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

    一般memo用法:

    importReactfrom"react";functionMyComponent({props}){console.log('111);return(<div>{props}</div>)};functionareEqual(prevProps,nextProps){if(prevProps.seconds===nextProps.seconds){returntrue}else{returnfalse}}exportdefaultReact.memo(MyComponent,areEqual)

    问题描述

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

    typeProps={inputDisable?:boolean//是否一直展示输入框inputVisible?:booleanvalue:anymin:numbermax:numberonChange:(v:number)=>void}constInputNumber:FC<Props>=memo((props:Props)=>{const{inputDisable,max,min,value,inputVisible}=propsconsthandleUpdate=(e:any,num)=>{e.stopPropagation()props.onChange(num)}return(<ViewclassName={styles.inputNumer}>{(value!==0||inputVisible)&&(<><ImageclassName={styles.btn}src={require(value<=min?'../../assets/images/reduce-no.png':'../../assets/images/reduce.png')}onClick={e=>handleUpdate(e,value-1)}mode='aspectFill'/><Inputvalue={value}disabled={inputDisable}alwaysEmbedtype='number'cursor={-1}onInput={e=>handleUpdate(e,parseInt(e.detail.value?e.detail.value:'0'),'input')}/></>)}<ImageclassName={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)=>{returnprevProps.value===nextProps.value&&prevProps.min===nextProps.min&&prevProps.max===nextProps.max})exportdefaultInputNumber

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

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

    这个组件的目的是展示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

    typeProps={info:anyonUpdate:(items)=>void}constCartBrand:FC<Props>=(props:Props)=>{const{info}=propsconstitems=useRef<any>(info.items.map(item=>{//selected默认为falsereturn{num:1,selected:false}}))useEffect(()=>{getCartStatus()},[])//获取info.items中没有提供,但是展示需要的数据constgetCartStatus=()=>{setTimeout(()=>{items.current=info.items.map(()=>{return{num:1,selected:true}})},1000)}return(<ViewclassName={styles.brandBox}>{items.current.map((item:GoodSku,index:number)=>{return(<InputNumberkey={item.skuId}inputDisablemin={0}max={50}value={item.num}onChange={v=>{console.log(v,items)}}/>)})}</View>)}exportdefaultCartBrand

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

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

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

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

    显示还是未选中

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

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

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

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

    方案2. 使用useCallback

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

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

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

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

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

    typeProps={inputDisable?:boolean//是否一直展示输入框inputVisible?:booleanvalue:anymin:numbermax:numberonChange:(v:number)=>void}constInputNumber:FC<Props>=memo((props:Props)=>{const{inputDisable,max,min,value,inputVisible}=propsconsthandleUpdate=(e:any,num)=>{e.stopPropagation()props.onChange(num)}return(<ViewclassName={styles.inputNumer}>{(value!==0||inputVisible)&&(<><ImageclassName={styles.btn}src={require(value<=min?'../../assets/images/reduce-no.png':'../../assets/images/reduce.png')}onClick={e=>handleUpdate(e,value-1)}mode='aspectFill'/><Inputvalue={value}disabled={inputDisable}alwaysEmbedtype='number'cursor={-1}onInput={e=>handleUpdate(e,parseInt(e.detail.value?e.detail.value:'0'),'input')}/></>)}<ImageclassName={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>)})exportdefaultInputNumber

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

     </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
    本文:如何解决React.memo引起的bug问题的详细内容,希望对您有所帮助,信息来源于网络。
    上一篇:Block 动画 第三种下一篇:

    6 人围观 / 0 条评论 ↓快速评论↓

    (必须)

    (必须,保密)

    阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18