我正在设法绕过useMemo
(或React.memo
)以优化某些组件渲染。
我遇到一个我无法解释的问题。
我有以下代码:
[...]
const [ cards, setCards ] = useState<Card[]>([])
function addCard(){
setCards([...cards, {name: 'card-' + cards.length, counter: 0, type: 'memo'}])
}
function onPressCard(index: number){
cards[index].counter += 1
setCards([...cards])
}
return (
[...]
{
cards.map((x, index) =>
<Card key={index} {...x} onPress={() => onPressCard(index)}/>
}
[...]
)
和卡定义为
const Card: FC<CardProps> = function({ name, counter, onPress }){
const counterRef = useRef(0)
const item = useMemo(() => {
counterRef.current +=1
return (
<RectButton onPress={onPress} style={[styles.card, { backgroundColor: 'lightcoral' }]}>
<Text style={styles.text}>{ name }</Text>
<Text style={styles.counter}> { `counter ${counter}` }</Text>
<Text style={styles.counter}>{ `render: ${counterRef.current}`}</Text>
</RectButton>
)
}, [name, counter])
return item
}
为什么当我按列表中的一项(最后一项除外)时,以下所有项都消失了?
编辑:定义为
的卡片也会发生同样的情况const areEqual = function(prevProps: Card, nextProps: Card){
return (
(prevProps.name === nextProps.name) &&
(prevProps.counter === nextProps.counter)
)
}
const Card = React.memo<CardProps>(({ name, counter, onPress }) => {
const counterRef = useRef(0)
counterRef.current +=1
return (
<RectButton onPress={onPress} style={[styles.card, { backgroundColor: 'lightcoral' }]}>
<Text style={styles.text}>{ name }</Text>
<Text style={styles.counter}> { `counter ${counter}` }</Text>
<Text style={styles.counter}>{ `render: ${counterRef.current}`}</Text>
</RectButton>
)
}, areEqual)
答案 0 :(得分:5)
问题在于,记忆成分包含对旧版本onPress
的引用。该旧onPress
的闭包中有一个旧版本的cards
。因此,点击按钮会调用旧功能,该功能会根据旧状态更新父级的状态,并且旧状态中包含的项更少。
解决此问题的一种方法是使用setCards的功能版本,以便使更新基于最新状态。另外,我更新了代码以不再更改旧卡:
function onPressCard(index: number){
setCards(oldCards => {
const newCards = [...oldCards];
newCards[index] = {...oldCards[index]};
newCards[index].counter += 1;
return newCards;
})
}
另一种选择是将onPress添加到useMemo的条件中,但是由于onPress函数一直在变化,因此最终不会真正从备忘录中获得任何收益。如果使用useCallback记住onPress本身,则可以对此进行改进:
const onPressCard = useCallback((index: number) => {
cards[index].counter += 1;
setCards([...cards]);
}, [cards])
// ...
const item = useMemo(() => {
counterRef.current +=1
return ( /* jsx omitted for brevity */ )
}, [name, counter, onPress])