维护React引用字典,同时避免重新渲染

问题描述

我在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>
    }
}

想到的几种解决方案是:

  1. 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;
     }
    }
    
  2. 使用UNSAFE_componentWillUpdate将ref的字典直接存储在渲染之前的对象属性中,并从该字典分配ref:

    UNSAFE_componentWillRender() {
     this.child_refs = this.props.children_to_track.map(_ => React.createRef());
    }
    
  3. 类似于2,但更脏,直接在render中创建引用并将它们存储到对象的字典中。

  4. 使用callback refs。这是不理想的,因为如果组件在渲染之间保持不变,则不会调用这些对象。结果,我不再能够在每次更改道具时批量刷新参考字典,而不得不对它们进行区分并保留不会改变的参考。还与我不喜欢的和解语义紧密相关。

getSnapshotBeforeUpdatecomponentDidUpdate都在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} />
  }
}

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...