重新渲染组件和使用 memo() 的问题
Problem Of re-rendring the component and using memo()
问题是当我更新一个项目的数量时,父组件再次开始重新渲染我想了解做错了什么我已经使用备忘录停止重新渲染所以我应该怎么做我想修复重新渲染并且只更新阵列上的对象而不重新渲染,如果有人可以帮助我吗?
提前致谢
父组件
export const ProductsScreen = ({ }) => {
const [Products, setProducts] = useState([
// sample of object props
{
id: '1',
name: 'first item',
volume: 500,
price: 150.25,
total: 4,
qty: 0
}]
const renderProducts = ({ item }) => {
const { qty } = item
return (
<ProductListItems
product={item}
qty={qty}
onAddItem={onAddItem}
onRemoveItem={onRemoveItem} />
)
}
const updateObjectInArray = (newProduct) => {
return setProducts(
Products.map((item) => {
if (item.id !== newProduct.id) {
return item
}
return {
...item,
...newProduct
}
})
)
}
const onAddItem = productId => {
let product = Products.find(i => {
return i.id === productId
})
const { total, qty } = product
const newProduct = {
...product,
qty: qty + 1,
total: total - 1
}
updateObjectInArray(newProduct)
}
const onRemoveItem = productId => {
let product = Products.find(i => {
return i.id === productId
})
const { total, qty } = product
const newProduct = {
...product,
qty: qty - 1,
total: total + 1
}
updateObjectInArray(newProduct)
}
return (
console.log("RERENDERD"),
<View style={styles.container} >
<SafeAreaView style={{ flex: 1, }}>
<FlatList
contentContainerStyle={{ paddingVertical: 15 }}
showsVerticalScrollIndicator={false}
data={Products}
renderItem={renderProducts}
keyExtractor={item => item.id}
/>
</SafeAreaView>
</View >
)
};
子组件
const ProductListItems = ({
product,
qty,
onAddItem,
onRemoveItem
}) => {
return (
// design here
)
};
export default memo(ProductListItems, (prevState, nextState) => prevState.qty === nextState.qty);
添加和删除处理程序仅使用 React.useCallback 在挂载时创建,因为没有依赖关系,使用状态 setter 的回调来防止对产品的依赖。
renderItem 道具必须是 function that returns jsx 但由于 ProductListItems
是一个纯组件,只要道具没有改变,它就不会重新渲染。不确定 FlatList 是如何工作的,但如果只有一个项目发生变化,这应该会阻止每个项目呈现。
const { useState, useCallback, memo } = React;
//just for this demo, naive implementation of FlatList
const FlatList = memo(function FlatList({
data,
renderItem,
keyExtractor,
}) {
return (
<ul>
{data.map((item) => {
//strange need for renderItem to be a function that
// returns jsx but let's roll with it I'm sure there
// is a good reason for it
const content = renderItem({ item });
return (
<div key={keyExtractor(item)}>{content}</div>
);
})}
</ul>
);
});
//Make sure this component is pure so it won't re create jsx
// even if you didn't use memo the handlers would not cause
// a DOM re render (not sure about native)
const ProductListItems = memo(function ProductListItems({
product,
// removed qty, it is in product
onAddItem,
onRemoveItem,
}) {
console.log(
'render ProductListItems for id:',
product.id
);
return (
<li>
<pre>{JSON.stringify(product, undefined, 2)}</pre>
<button onClick={() => onAddItem(product.id)}>
Add
</button>
<button onClick={() => onRemoveItem(product.id)}>
Remove
</button>
</li>
);
});
//this is the code you could use, I removed some jsx but logic
// should be re usable
const ProductsScreen = () => {
const [products, setProducts] = useState([
{
id: 1,
total: 4,
qty: 0,
},
{
id: 2,
total: 6,
qty: 0,
},
]);
//when products change this render is
// needed but does not have to cause
// all items to be re rendered
console.log('render products screen');
//only create this function on mount so
// it won't re render all items
// passing a new handler to a sub component
// will cause virtual DOM compare to fail
// and React will re render that DOM
const changeQty = useCallback(
(direction) => (productId) =>
setProducts(
//use callback to state setter to prevent
// Products to be a dependency
(products) =>
products.map((product) =>
product.id !== productId
? product
: {
...product,
qty: product.qty + direction,
//had a logical error on next line (fixed now)
total: product.total + direction * -1,
}
)
),
[]
);
//both handlers depend on changeQty but that is only created
// on mount
const onAddItem = useCallback(changeQty(1), [changeQty]);
const onRemoveItem = useCallback(changeQty(-1), [
changeQty,
]);
//FlatList renderItem has to be a function
// https://reactnative.dev/docs/flatlist#required-renderitem
// no need to memoize anything as ProductListItems is a pure
// component
const renderItem = ({ item }) => (
<ProductListItems
product={item}
onAddItem={onAddItem}
onRemoveItem={onRemoveItem}
/>
);
return (
<FlatList
data={products}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
);
};
ReactDOM.render(
<ProductsScreen />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
**FlatList is a built-in list on react native**
import React from 'react';
import { SafeAreaView, View, FlatList, StyleSheet, Text, StatusBar } from 'react-native';
const DATA = [
{
id: '1',
title: 'First Item',
},
{
id: '2',
title: 'Second Item',
},
];
const App = () => {
const renderItem = ({ item }) => (
return(
<Item title={item.title} />
)
);
return (
<SafeAreaView>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
}
问题是当我更新一个项目的数量时,父组件再次开始重新渲染我想了解做错了什么我已经使用备忘录停止重新渲染所以我应该怎么做我想修复重新渲染并且只更新阵列上的对象而不重新渲染,如果有人可以帮助我吗? 提前致谢
父组件
export const ProductsScreen = ({ }) => {
const [Products, setProducts] = useState([
// sample of object props
{
id: '1',
name: 'first item',
volume: 500,
price: 150.25,
total: 4,
qty: 0
}]
const renderProducts = ({ item }) => {
const { qty } = item
return (
<ProductListItems
product={item}
qty={qty}
onAddItem={onAddItem}
onRemoveItem={onRemoveItem} />
)
}
const updateObjectInArray = (newProduct) => {
return setProducts(
Products.map((item) => {
if (item.id !== newProduct.id) {
return item
}
return {
...item,
...newProduct
}
})
)
}
const onAddItem = productId => {
let product = Products.find(i => {
return i.id === productId
})
const { total, qty } = product
const newProduct = {
...product,
qty: qty + 1,
total: total - 1
}
updateObjectInArray(newProduct)
}
const onRemoveItem = productId => {
let product = Products.find(i => {
return i.id === productId
})
const { total, qty } = product
const newProduct = {
...product,
qty: qty - 1,
total: total + 1
}
updateObjectInArray(newProduct)
}
return (
console.log("RERENDERD"),
<View style={styles.container} >
<SafeAreaView style={{ flex: 1, }}>
<FlatList
contentContainerStyle={{ paddingVertical: 15 }}
showsVerticalScrollIndicator={false}
data={Products}
renderItem={renderProducts}
keyExtractor={item => item.id}
/>
</SafeAreaView>
</View >
)
};
子组件
const ProductListItems = ({
product,
qty,
onAddItem,
onRemoveItem
}) => {
return (
// design here
)
};
export default memo(ProductListItems, (prevState, nextState) => prevState.qty === nextState.qty);
添加和删除处理程序仅使用 React.useCallback 在挂载时创建,因为没有依赖关系,使用状态 setter 的回调来防止对产品的依赖。
renderItem 道具必须是 function that returns jsx 但由于 ProductListItems
是一个纯组件,只要道具没有改变,它就不会重新渲染。不确定 FlatList 是如何工作的,但如果只有一个项目发生变化,这应该会阻止每个项目呈现。
const { useState, useCallback, memo } = React;
//just for this demo, naive implementation of FlatList
const FlatList = memo(function FlatList({
data,
renderItem,
keyExtractor,
}) {
return (
<ul>
{data.map((item) => {
//strange need for renderItem to be a function that
// returns jsx but let's roll with it I'm sure there
// is a good reason for it
const content = renderItem({ item });
return (
<div key={keyExtractor(item)}>{content}</div>
);
})}
</ul>
);
});
//Make sure this component is pure so it won't re create jsx
// even if you didn't use memo the handlers would not cause
// a DOM re render (not sure about native)
const ProductListItems = memo(function ProductListItems({
product,
// removed qty, it is in product
onAddItem,
onRemoveItem,
}) {
console.log(
'render ProductListItems for id:',
product.id
);
return (
<li>
<pre>{JSON.stringify(product, undefined, 2)}</pre>
<button onClick={() => onAddItem(product.id)}>
Add
</button>
<button onClick={() => onRemoveItem(product.id)}>
Remove
</button>
</li>
);
});
//this is the code you could use, I removed some jsx but logic
// should be re usable
const ProductsScreen = () => {
const [products, setProducts] = useState([
{
id: 1,
total: 4,
qty: 0,
},
{
id: 2,
total: 6,
qty: 0,
},
]);
//when products change this render is
// needed but does not have to cause
// all items to be re rendered
console.log('render products screen');
//only create this function on mount so
// it won't re render all items
// passing a new handler to a sub component
// will cause virtual DOM compare to fail
// and React will re render that DOM
const changeQty = useCallback(
(direction) => (productId) =>
setProducts(
//use callback to state setter to prevent
// Products to be a dependency
(products) =>
products.map((product) =>
product.id !== productId
? product
: {
...product,
qty: product.qty + direction,
//had a logical error on next line (fixed now)
total: product.total + direction * -1,
}
)
),
[]
);
//both handlers depend on changeQty but that is only created
// on mount
const onAddItem = useCallback(changeQty(1), [changeQty]);
const onRemoveItem = useCallback(changeQty(-1), [
changeQty,
]);
//FlatList renderItem has to be a function
// https://reactnative.dev/docs/flatlist#required-renderitem
// no need to memoize anything as ProductListItems is a pure
// component
const renderItem = ({ item }) => (
<ProductListItems
product={item}
onAddItem={onAddItem}
onRemoveItem={onRemoveItem}
/>
);
return (
<FlatList
data={products}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
);
};
ReactDOM.render(
<ProductsScreen />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
**FlatList is a built-in list on react native**
import React from 'react';
import { SafeAreaView, View, FlatList, StyleSheet, Text, StatusBar } from 'react-native';
const DATA = [
{
id: '1',
title: 'First Item',
},
{
id: '2',
title: 'Second Item',
},
];
const App = () => {
const renderItem = ({ item }) => (
return(
<Item title={item.title} />
)
);
return (
<SafeAreaView>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
}