React re 在添加新项目时渲染数组中的所有项目,尽管所有项目都使用唯一键
Posted
技术标签:
【中文标题】React re 在添加新项目时渲染数组中的所有项目,尽管所有项目都使用唯一键【英文标题】:React re renders all items in array when new items are added despite using unique keys for all items 【发布时间】:2019-03-01 17:24:33 【问题描述】:原帖
我有一个页面,它使用 Array.prototype.map 呈现包含在 redux 状态中的数组中的每个项目。该数组最初在调用 componentWillMount() 回调时填充,然后在用户请求更多数据时附加,即当用户滚动到页面底部并且有更多项目要获取时。我确保当使用 Array.prototype.concat() 附加数组时,redux 状态不会发生变化。然而,当更多的项目被添加到数组中时,看起来 react re 渲染了数组中的每个项目,而不仅仅是新项目,尽管为每个项目设置了一个唯一的键。我渲染组件的源代码如下:
this.props.state.list.map((item, index) =>
return (
<Grid className=classes.column key=item._id item xs=6 sm=6 md=4>
<Card className=classes.card>
<CardActionArea className=classes.cardAction>
<div className=classes.cardWrapper>
<div className=classes.outer>
<div className=classes.bgWrapper>
<img
className=[classes.bg, "lazyload"].join(" ")
data-sizes="auto"
data-src=genSrc(item.pictures[0].file)
data-srcset=genSrcSet(item.pictures[0].file)
alt=item.name
/>
</div>
</div>
<CardContent className=classes.cardContent>
<div className=classes.textWrapper>
<div className=classes.textContainer>
<Typography
gutterBottom
className=[classes.text, classes.title].join(" ")
variant="title"
color="inherit"
>
item.name
</Typography>
<Typography
className=[classes.text, classes.price].join(" ")
variant="subheading"
color="inherit"
>
item.minPrice === item.maxPrice
? `$$item.minPrice.toString()`
: `$$item.minPrice
.toString()
.concat(" - $", item.maxPrice.toString())`
</Typography>
</div>
</div>
</CardContent>
</div>
</CardActionArea>
</Card>
</Grid>
);
);
这是我的减速器,如果你认为它可能与此有关:
import
FETCH_PRODUCTS_PENDING,
FETCH_PRODUCTS_SUCCESS,
FETCH_PRODUCTS_FAILURE
from "../actions/products";
let defaultState =
list: [],
cursor: null,
calls: 0,
status: null,
errors: []
;
const reducer = (state = defaultState, action) =>
switch (action.type)
case FETCH_PRODUCTS_PENDING:
return
...state,
status: "PENDING"
;
case FETCH_PRODUCTS_SUCCESS:
return
...state,
calls: state.calls + 1,
cursor: action.payload.data.cursor ? action.payload.data.cursor : null,
list: action.payload.data.results
? state.list.concat(action.payload.data.results)
: state.list,
status: "READY"
;
case FETCH_PRODUCTS_FAILURE:
return
...state,
status: "FAIL",
call: state.calls + 1,
errors: [...state.errors, action.payload]
;
default:
return state;
;
export default reducer;
我从类似于我的问题的答案中读到,为从数组生成的每个组件设置一个 shouldComponentUpdate() 方法可能会起到作用,但没有明确的例子说明如何做到这一点。我非常感谢您的帮助。谢谢!
编辑 1
我还应该指定当新项目添加到数组中时,从数组呈现的组件完全卸载并重新安装在浏览器 DOM 中。理想情况下,我希望浏览器永远不要卸载现有组件,而只附加新渲染的组件。
编辑 2
这是呈现列表的组件的完整代码。
class Products extends React.Component
componentDidMount()
const fetchNext, fetchStart, filter = this.props
fetchStart(filter)
window.onscroll = () =>
const errors, cursor, status = this.props.state
if (errors.length > 0 || status === 'PENDING' || cursor === null) return
if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight)
fetchNext(filter, cursor)
render()
const classes, title = this.props
return (
<div className=classes.container>
<Grid container spacing=0>
this.props.state.status && this.props.state.status === 'READY' && this.props.state.list &&
this.props.state.list.map((item, index) =>
return (
<Grid className=classes.column key=item._id item xs=6 sm=6 md=4>
<Card className=classes.card>
<CardActionArea className=classes.cardAction>
<div className=classes.cardWrapper>
<div className=classes.outer>
<div className=classes.bgWrapper>
<img className=[classes.bg, 'lazyload'].join(' ') data-sizes='auto' data-src=genSrc(item.pictures[0].file) data-srcset=genSrcSet(item.pictures[0].file) alt=item.name />
</div>
</div>
<CardContent className=classes.cardContent>
<div className=classes.textWrapper>
<div className=classes.textContainer>
<Typography gutterBottom className=[classes.text, classes.title].join(' ') variant="title" color='inherit'>
item.name
</Typography>
<Typography className=[classes.text, classes.price].join(' ') variant='subheading' color='inherit'>
item.minPrice === item.maxPrice ? `$$item.minPrice.toString()` : `$$item.minPrice.toString().concat(' - $', item.maxPrice.toString())`
</Typography>
</div>
</div>
</CardContent>
</div>
</CardActionArea>
</Card>
</Grid>
)
)
</Grid>
</div>
)
【问题讨论】:
如果状态改变,一个组件和它的所有子组件都会重新渲染。这太正常了。但是,渲染并不意味着这些组件将再次卸载/安装或销毁/重新创建。这只会发生变化的部分。这与reconciliation 有关。所以,渲染并没有那么昂贵,DOM 操作才是。但是,如果您确定渲染所有这些组件会导致一些性能损失,那么您可以检查shouldComponentUpdate
(小心,有时触摸它会导致性能下降)或者您可以使用 PureComponent。
如果是这种情况,那么我应该了解更多信息:) 我认为在这种情况下只会发生重新渲染,而不是所有的 DOM 操作。我知道 React 会添加或删除子项,但我不认为在这种情况下所有 DOM 都会发生变化。
这也是我所期望的。我很确定我做错了什么。无论如何,再次感谢您的 cmets
当你说消失和重新出现时,你的意思是加载时从可见到消失吗?您是否对减速器上的状态字段做了任何会导致列表项不显示的事情?发布整个组件代码而不仅仅是列表渲染可能会有所帮助。
您可以写一个pure Component
来渲染您的地图项并将这些项作为道具传递给该组件,看看是否发生了同样的事情。更多参考reactjs.org/docs/optimizing-performance.html
【参考方案1】:
所以事实证明这是一个条件渲染导致渲染列表消失。在我的例子中,我设置它以便组件仅在所有正在进行的 fetch 操作都已完成时才呈现列表,因此列表当然会在 fetch 调用之间消失。
更改以下内容
this.props.state.status && this.props.state.status === 'READY' && this.props.state.list &&
this.props.state.list.map((item, index) =>
return <Item />
为此,取消对已完成获取状态的检查解决了我的问题。
this.props.state.list &&
this.props.state.list.map((item, index) =>
return <Item />
【讨论】:
以上是关于React re 在添加新项目时渲染数组中的所有项目,尽管所有项目都使用唯一键的主要内容,如果未能解决你的问题,请参考以下文章
React Native FlatList 不渲染数组中的项目