问题描述
我在React中陷入了一种相当不舒服的境地,其中父元素必须管理随时间变化的大型(读取:昂贵的渲染)组件树中许多子对象的引用。我需要其引用的孩子也发生了变化,我需要加入生命周期的某些部分,以确保我需要跟踪的所有孩子都具有要引用的相应引用。
对于上下文:组件树很昂贵,因为它要渲染成语法突出显示的源文件中的数千个元素,并且我使用子引用来定位工具提示和滚动。我试图避免在这些表面的视觉更改上重新呈现组件树,并且仅在源文件内容更改时才这样做。重要的是,维护参考字典不应该引入新的渲染器。
至少,我们的组件看起来像这样:
type TProps = {
children_to_track: ChildProps[]
}
class Parent extends React.Component {
constructor(props) {
super(props);
// this.state = {};
}
render() {
return <div>
{/* lots of static children */}
{ this.props.children_to_track.map(childprops =>
<Child
{...childprops}
key={childprops.id}
ref={/* NEED REF */} />
) }
</div>
}
}
想到的几种解决方案是:
-
在
shouldComponentUpdate
(初始情况下为componentDidMount
)中,将指定子级的prop复制到状态中,并将它们与React ref配对,在此第一遍返回false。当此状态更改再次击中shouldComponentUpdate
时,返回true并重新渲染:type TState = { children_to_track: Array<[ChildProps,React.RefObject<any>]> }; shouldComponentRender(pprops: TProps,pstate: TState) { if(pprops.children_to_track !== this.props.children_to_track) { this.setState({ children_to_track: this.props.children_to_track.map(c => [c,React.createRef()]) }); return false; } else { return true; } }
-
使用
UNSAFE_componentWillUpdate
将ref的字典直接存储在渲染之前的对象属性中,并从该字典分配ref:UNSAFE_componentWillRender() { this.child_refs = this.props.children_to_track.map(_ => React.createRef()); }
-
类似于2,但更脏,直接在
render
中创建引用并将它们存储到对象的字典中。 -
使用callback refs。这是不理想的,因为如果组件在渲染之间保持不变,则不会调用这些对象。结果,我不再能够在每次更改道具时批量刷新参考字典,而不得不对它们进行区分并保留不会改变的参考。还与我不喜欢的和解语义紧密相关。
getSnapshotBeforeUpdate
和componentDidUpdate
都在render()
之后被调用,并且如果它们是用来存放新引用的,则为时已晚,无法阻止重新渲染。如果所有其他单一渲染解决方案最终都太令人讨厌,我只会求助于他们。
我倾向于1),但是在shouldComponentUpdate
中设置状态仍然感到很糟糕。有什么建议吗?
解决方法
最初我并没有想到只是将组件树包装在PureComponent
的状态children_to_track
中,因此我可以在父级中自由地重新渲染:
const Wrapper = React.memo(props => <div>
{/* lots of static children */}
{ props.children_to_track.map(([ch,ref]) =>
<Child {...ch}
key={ch.id}
ref={ref} />
) }
</div>);
class Parent extends React.Component<...> {
// ...
componentDidUpdate(pprops: TProps) {
if(pprops.children_to_track !== this.props.children_to_track)
this.setState({
children_to_track: this.props.children_to_track.map(ch => [ch,React.createRef()])
})
}
render() {
return <Wrapper children_to_track={this.state.children_to_track} />
}
}