[英]Mutating child components based on parent component's state, without re-rendering
Goal 目标
I'm trying to manage mouseenter and mouseleave events from a parent component, for a collection of child components that keep getting re-rendered. 我正在尝试从父组件管理mouseenter和mouseleave事件,以获取不断重新呈现的子组件的集合。
I'm building a reusable component for the collection of listings, that does several things like pagination, and a few other things when a listing is hovered. 我正在为清单的集合构建一个可重用的组件,它可以完成诸如分页之类的几项工作,以及在悬停清单时执行的其他一些工作。
So to make this reusable, I have to maintain the state of the hovered listing from the parent CollectionComponent
, and mutate each individual listing component based on the state of the parent. 因此,要使其可重用,我必须维护父CollectionComponent
悬停列表的状态,并根据父状态对每个单独的列表组件进行突变。
Code 码
Here are the components I'm using (I stripped them all down to their most basic forms): 这是我正在使用的组件(我将它们剥离为最基本的形式):
Listings Component: 列表组件:
import React from 'react'
import $ from 'jquery'
import CollectionComponent from './CollectionComponent'
import Listing from './Listing'
export default class Listings extends React.Component {
constructor(props) {
super(props)
this.state = {
listings: this.props.listings,
}
}
render() {
return (<section className="listing-results">
{this.state.listings.map( listing =>
<CollectionComponent results={this.state.listings} IndividualResult={Listing} perPage={this.props.perPage} options={options}/>
)}
</section>)
}
}
Collection Component: 收集组件:
import React from 'react'
export default class CollectionComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
results: this.props.results,
hoveredId: null
}
}
componentDidMount() {
this.$listings = $('.result-card')
$(this.$listings).mouseenter(this.toggleInfoIn).mouseleave(this.toggleInfoOut)
}
toggleInfoIn = e => {
var { target } = e
var infoId = $(target).data('id')
this.setState({hoveredId: infoId})
}
toggleInfoOut = e => {
this.setState({hoveredId: null})
}
render() {
const {results, IndividualResult, perPage, options} = this.props
return (
<div className="paginated-results">
{this.state.results.map( result =>
<IndividualResult key={result.id} result={result} options={options}/>
)}
</div>
)
}
}
Individual Listing Component: 个别列表组件:
import React from 'react'
export default class Listing extends React.Component {
constructor(props) {
super(props)
}
render() {
const { listing, hoveredId } = this.props
return (
<div className="result-card" data-id={listing.id}>
<div className={hoveredId === listing.id ? 'hovered' : ''}>
Listing Content
</div>
</div>
)
}
}
I know I can probably structure the CollectionComponent
a little cleaner with a higher order component, but I'll leave that for refactoring later once I get it working properly with this basic setup. 我知道我可以用更高阶的组件来构造一个更干净的CollectionComponent
,但是一旦我在基本设置中正常使用它,我将把它留待以后重构。
Problem 问题
My problem is that every time I hover and change the state of the parent component, it re-renders the child components, because their props are dependent on the parent's state. 我的问题是,每次我悬停并更改父组件的状态时,它都会重新渲染子组件,因为它们的道具取决于父组件的状态。 Once this happens, the reference to my jQuery collection of listings is no longer valid. 一旦发生这种情况,对我的jQuery清单集合的引用将不再有效。 So the mouse events are attached to old DOM elements that no longer exist. 因此,鼠标事件将附加到不再存在的旧DOM元素上。
How can I structure this differently, so that either: 我如何以不同的方式构造它,以便:
I'd really like to avoid getting a new the jQuery collection every time the component updates. 我真的很想避免每次组件更新时都得到一个新的jQuery集合。
The behavior of hover should be confined to the individual listing component and not the Collections component. 悬停行为应仅限于单独的列表组件,而不是集合组件。
As the Collections
component maintains the state of currently hovered item, it is good idea to pass an handler as part of props and then render the list again based on the change in state set by the Collections
component. 当Collections
组件保持当前悬停的项目的状态时,最好将处理程序作为prop的一部分传递,然后根据Collections
组件设置的状态更改再次呈现列表。
Use react based event handlers where ever necessary which makes it for a controlled component. 如有必要,请使用基于React的事件处理程序,以使其成为受控组件。 It is not a good idea to put state in the DOM
where react can take care of it for you. 在DOM
中放置状态不是一个好主意,反应可以为您处理状态。
Listings 清单
import React from 'react'
export default class Listing extends React.Component {
constructor(props) {
super(props);
this.onMouseEnter = this.onMouseEnter.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
}
onMouseEnter() {
this.props.onMouseEnter({ listingId: this.props.listing.id });
}
onMouseLeave() {
this.props.onMouseLeave();
}
render() {
const { listing, hoveredId } = this.props
const listingId = listing.id;
const isHovered = this.props.hoveredId === listing.id;
return (
<div className="result-card" onMouseEnter={this.onMouseEnter} onMouseLeave={onMouseLeave}>
<div className={isHovered ? 'hovered' : ''}>
Listing Content
</div>
</div>
)
}
}
Collections 馆藏
import React from 'react'
export default class CollectionComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
results: this.props.results,
hoveredId: null
}
}
onMouseEnter({ listingId }) {
this.setState({ listingId });
}
onMouseLeave() {
this.setState({ listingId: null });
}
render() {
const {results, IndividualResult, perPage, options} = this.props
return (
<div className="paginated-results">
{this.state.results.map( result =>
<IndividualResult key={result.id} hoveredId={this.state.hoveredId} result={result} options={options} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}/>
)}
</div>
)
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.