React踩坑笔记 —— React中的Dom操作

4,051 阅读4分钟

React中操作Dom的四种方式

  1. 通过‘Refs’操作非受控组件《使用Ref》
  2. 通过事件处理器的默认参数event,获取EventTarget《React事件系统》
  3. 通过原生JavaScriptDom选择器
  4. 通过Dom操作插件,例如Jquery《React集成第三方库》

为什么要操作Dom

在一般的网页应用中,像使用原生JavaScript、Jquery开发的应用中,Dom操作是很常见的。

而在典型的React数据流中,props是父组件与子组件交互的唯一方式(单向数据流)。所以,为了修改子组件,你需要传递新的props去重新渲染它。

然而,在某些情况下,我们需要脱离对React数据流动的依赖,去强制修改子组件。这些被修改的子组件,可能是一个React组件实例,也可能是实际的Dom元素。

Dom操作可以直接将改变呈现在当前document,而不会促使组件更新,不会导致周期函数的调用(特殊情况,Ref引用类组件实例)。但是,当组件更新时,这些操作也会参与“差分算法”

Refs便是在这些情况下,React提供的推荐解决方式。值得注意的是,像event.targetdom选择器dom操作插件也是能够使用的方式,但是它们存在局限性,比如:

  • 只能够操作实际Dom元素;
  • 相同组件可以被多次渲染,id选择器无法正常工作。

Refs既能够操作实际Dom元素,又能够操作React组件实例(注意:class组件可以被使用,而function无状态组件不能被使用,因为它无法被实例化)


什么时候操作Dom

这里有一些典型的使用情况:

  • 焦点获取、文本选择、媒体播放等动态行为;
  • 触发强制动画;
  • 集成第三方库;

对于任何可声明性完成的事情避免使用Refs 例如,在Dialog组件中通过传递isOpen属性,来代替暴露open()close()方法。


须知

  • React不会知道React以外的方式对Dom做出的改变,它基于自己内部的表现来决定如何更新,如果一个Dom节点同时被React以外的方式操作,那么React将变的混乱,并且无从恢复。
    • 比如:用React以外的方式对Dom内容修改,在props或state发生改变——组件更新后被覆盖;
    • 比如:用React以外的方式对Dom内容的修改依赖于组件更新前的状态,组件更新后没有达到预期效果;
  • 为了解决这个问题,你可以通过状态改变来记录每一步操作,以便在更新后重现它。
  • 最干脆的方式就是,不让React有理由去更新这个Dom,从而阻止组件更新带来的冲突。例如空的<div />,这个<div />不需要propsstate的支撑,所以React没有理由去更新它,可以留给Dom操作插件选择器自由的管理这部分Dom。
  • 记得在组件卸载时移除插件注册的事件监听,避免内存泄漏。
  • 另外,React推荐使用RefsReact event system来操作Dom,最好不要依赖ID选择器,因为相同的组件可能会被多次渲染,使得整个document中存在多个元素具有相同id属性。(详情可见《document中id属性不唯一时,id选择器如何工作》

注意

  • 在Props和State发生改变的时候,组件会发生更新,至于最终如何被更新,可以查看《差分算法》
  • document维护着整棵React元素树,“差分算法” 便是基于document比较React元素树的算法;
  • 通过Refsevent.targetdom选择器Dom操作插件对Dom节点的操作将保存在document中,并参与 “差分算法”,然而:
    • 组件更新将会对该组件对应的React元素子树执行差异算法,如果新的状态和属性没有影响到这些Dom操作,那么操作将被保留,否则对Dom的操作会消失变得混乱
    • 组件卸载(路由),将会卸载组件,同时所包含的Dom节点会被销毁、子组件也会被卸载,所以之前对Dom的操作也会消失(除非你专门记录它:可以使用父组件statestorage
    • 页面刷新将会产生新的document,之前对Dom的操作将会消失(除非你专门记录它:只能使用storage)。
  • Element被创建后,被诸如Node.insertBefore()之类的操作插入document之前,是没有意义的,通过选择器document.querySelector()document.getElementById()只能获得null,所以在document渲染完成前,操作Dom是无意义的。