如何从Store取数且不触发更新?React组件订阅选中项难题
看起来你遇到的问题是:子组件Product需要获取当前选中项的数量,但如果让每个子组件自己订阅这个状态,很可能会导致不必要的重复渲染,甚至浪费订阅资源,对吧?毕竟你的父组件用了React.PureComponent,本来是想做渲染优化的,结果子组件的订阅可能把这个优化给冲没了。
给你几个实用的解决方案,你可以根据自己的场景选:
方案一:父组件统一订阅,通过props传递选中数量
这是最直接的方式,把订阅逻辑提到父组件里,父组件拿到选中数量后,通过props传给每个Product子组件。这样子组件只需要接收props,不需要自己订阅,还能享受PureComponent的优化。
修改后的父组件代码大概是这样:
export default class ProductGrid extends React.PureComponent<Props, State> { state = { selectedCount: 0 }; subscription = null; componentDidMount() { // 这里替换成你实际的订阅逻辑,比如从状态管理库或者事件总线 this.subscription = someSelectedStore.subscribe(() => { const count = someSelectedStore.getSelectedItemCount(); this.setState({ selectedCount: count }); }); } componentWillUnmount() { // 别忘了取消订阅,防止内存泄漏 if (this.subscription) { this.subscription.unsubscribe(); } } renderProducts() { return this.props.products.map(product => ( // 把选中数量通过props传给子组件 <Product {...product} selectedCount={this.state.selectedCount} /> )); } render() { return <div>{this.renderProducts()}</div>; } }
子组件Product如果是PureComponent的话,只有当product本身或者selectedCount变化时,才会重新渲染,完美保留优化效果。
方案二:用React Context传递选中状态(适合复杂场景)
如果你的组件层级比较深,或者多个组件都需要用到选中数量,用Context会更方便,不用一层层传递props。配合React.memo还能保证子组件只有在必要时渲染。
首先创建一个Context:
const SelectedCountContext = React.createContext(0);
然后在父组件里提供Context的值:
export default class ProductGrid extends React.PureComponent<Props, State> { state = { selectedCount: 0 }; subscription = null; componentDidMount() { this.subscription = someSelectedStore.subscribe(() => { this.setState({ selectedCount: someSelectedStore.getSelectedItemCount() }); }); } componentWillUnmount() { this.subscription?.unsubscribe(); } render() { return ( <SelectedCountContext.Provider value={this.state.selectedCount}> <div>{this.renderProducts()}</div> </SelectedCountContext.Provider> ); } renderProducts() { return this.props.products.map(product => ( <Product {...product} /> )); } }
最后子组件用useContext获取值,并用memo包裹:
import React, { useContext } from 'react'; const Product = React.memo((props) => { const selectedCount = useContext(SelectedCountContext); // 这里写你的子组件渲染逻辑,比如展示选中数量 return ( <div className="product-item"> <h3>{props.name}</h3> <p>当前已选商品总数:{selectedCount}</p> </div> ); }); export default Product;
这样只有当selectedCount变化时,所有订阅了这个Context的Product组件才会重新渲染,父组件的PureComponent优化依然生效。
方案三:精准传递单个商品的选中状态(如果只需要自身状态)
如果你的子组件其实不需要知道总数,只需要知道自己是否被选中,那可以在父组件里维护选中的商品ID集合,然后给每个Product传递isSelected的props。这样子组件的渲染会更精准,只有自己的选中状态变化时才会重新渲染。
父组件修改:
export default class ProductGrid extends React.PureComponent<Props, State> { state = { selectedIds: new Set() }; subscription = null; componentDidMount() { this.subscription = someSelectedStore.subscribe(() => { this.setState({ selectedIds: someSelectedStore.getSelectedIds() }); }); } componentWillUnmount() { this.subscription?.unsubscribe(); } renderProducts() { return this.props.products.map(product => ( <Product {...product} isSelected={this.state.selectedIds.has(product.id)} /> )); } render() { return <div>{this.renderProducts()}</div>; } }
这种方式的渲染性能是最优的,因为只有被选中/取消选中的那个Product才会重新渲染,其他的都不会动。
为什么不推荐子组件自己订阅?
主要有两个问题:
- 重复订阅浪费资源:每个Product组件都单独订阅,假设你有100个商品,就会有100个订阅,这完全没必要,父组件一次订阅就够了。
- 破坏PureComponent优化:即使Product是PureComponent,只要订阅的状态变化,它就会重新渲染,不管props有没有变化,这样PureComponent的优化就白做了,性能反而下降。
内容的提问来源于stack exchange,提问作者corvid

