亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何解決React.memo引起的bug問題

發布時間:2022-03-08 13:45:44 來源:億速云 閱讀:222 作者:小新 欄目:開發技術

這篇文章主要介紹如何解決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: any
    onUpdate: (items) => void
    }
    
    const CartBrand: FC<Props> = (props: Props) => {
    const { info } = props
    const [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); // => false
    
    console.log(sum1 === sum1); // => true
    
    console.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問題”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    岢岚县| 阿克| 津南区| 奉贤区| 黑山县| 建宁县| 峡江县| 巴林左旗| 澎湖县| 文昌市| 郓城县| 墨玉县| 尼玛县| 静安区| 曲水县| 涡阳县| 汶川县| 霍山县| 遂溪县| 承德县| 宁武县| 罗源县| 顺昌县| 临武县| 那坡县| 田阳县| 洪江市| 遂昌县| 阿拉善右旗| 广河县| 璧山县| 星子县| 晋州市| 博罗县| 铁岭市| 亚东县| 顺昌县| 沙田区| 松江区| 赫章县| 古交市|