<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
與PureComponent不同的是PureComponent只是進行淺對比props來決定是否跳過更新資料這個步驟,memo可以自己決定是否更新,但它是一個函陣列件而非一個類,但請不要依賴它來“阻止”渲染,因為這會產生 bug。
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中更新資料,會看到:
selected依然是false!
這是為什麼呢?前面不是把items中所有的selected都改為true了嗎?
我們再列印一下items看看:
似乎在InputNumber中的items依然是初始值。
對於這一現象,我個人理解為memo使用的memoization演演算法儲存了上一次渲染的items數值,由於InputNumber沒有重新渲染,所以在它的本地狀態中,items一直是初始值。
我們可以使用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
這樣再列印的時候我們會看到
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> )
顯示還是未選中
這是因為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相關的渲染不會出錯
在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的改變而重新整理。
在最後,我還是選擇了方案一解決這個問題。同時提醒自己,memo的使用要謹慎
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45