React中操作Dom的四种方式
- 通过
‘Refs’
操作非受控组件《使用Ref》; - 通过事件处理器的默认参数
event
,获取EventTarget
《React事件系统》; - 通过原生JavaScript
Dom选择器
; - 通过
Dom操作插件
,例如Jquery
《React集成第三方库》;
为什么要操作Dom
在一般的网页应用中,像使用原生JavaScript、Jquery开发的应用中,Dom操作是很常见的。
而在典型的React数据流中,props
是父组件与子组件交互的唯一方式(单向数据流)。所以,为了修改子组件,你需要传递新的props
去重新渲染它。
然而,在某些情况下,我们需要脱离对React数据流动的依赖,去强制修改子组件。这些被修改的子组件,可能是一个React组件实例,也可能是实际的Dom元素。
Dom操作可以直接将改变呈现在当前document,而不会促使组件更新,不会导致周期函数的调用(特殊情况,Ref引用类组件实例)。但是,当组件更新时,这些操作也会参与“差分算法”
Refs
便是在这些情况下,React提供的推荐解决方式。值得注意的是,像event.target
、dom选择器
、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 />
不需要props
和state
的支撑,所以React没有理由去更新它,可以留给Dom操作插件
或选择器
自由的管理这部分Dom。 - 记得在组件卸载时移除插件注册的事件监听,避免内存泄漏。
- 另外,React推荐使用Refs或React event system来操作Dom,最好不要依赖
ID选择器
,因为相同的组件可能会被多次渲染,使得整个document
中存在多个元素具有相同id
属性。(详情可见《document中id属性不唯一时,id选择器如何工作》)
注意
- 在Props和State发生改变的时候,组件会发生更新,至于最终如何被更新,可以查看《差分算法》
- document维护着整棵React元素树,“差分算法” 便是基于document比较React元素树的算法;
- 通过
Refs
、event.target
、dom选择器
、Dom操作插件
对Dom节点的操作将保存在document中,并参与 “差分算法”,然而:组件更新
将会对该组件对应的React元素子树执行差异算法,如果新的状态和属性没有影响到这些Dom操作,那么操作将被保留,否则对Dom的操作会消失
或变得混乱
;组件卸载(路由)
,将会卸载组件,同时所包含的Dom节点会被销毁、子组件也会被卸载,所以之前对Dom的操作也会消失(除非你专门记录它:可以使用父组件state
或storage
)页面刷新
将会产生新的document,之前对Dom的操作将会消失(除非你专门记录它:只能使用storage
)。
- Element被创建后,被诸如
Node.insertBefore()
之类的操作插入document之前,是没有意义的,通过选择器document.querySelector()
或document.getElementById()
只能获得null
,所以在document渲染完成前,操作Dom是无意义的。