React中refs的理解

Refs提供了一种方式,允许我们访问DOM节点或在render方法中创建的React元素。

描述

在典型的React数据流中,props是父组件与子组件交互的唯一方式,要修改一个子组件,你需要使用新的props来重新渲染它,但是在某些情况下,你需要在典型数据流之外强制修改子组件,被修改的子组件可能是一个React组件的实例,也可能是一个DOM元素,对于这两种情况React都提供了解决办法。
避免使用refs来做任何可以通过声明式实现来完成的事情,通常在可以使用propsstate的情况下勿依赖refs,下面是几个适合使用refs的情况:

  • 管理焦点、文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方DOM库。

使用

React提供的这个ref属性,表示为对组件真正实例的引用,其实就是ReactDOM.render()返回的组件实例,需要区分一下渲染组件与渲染原生DOM元素,渲染组件时返回的是组件实例,而渲染DOM元素时,返回是具体的DOM节点,Reactref3种用法。

字符串

ref可以直接设置为字符串值,这种方式基本不推荐使用,或者在未来的React版本中不会再支持该方式。这主要是因为使用字符串导致的一些问题,例如当ref定义为string时,需要React追踪当前正在渲染的组件,在reconciliation阶段,React Element创建和更新的过程中,ref会被封装为一个闭包函数,等待commit阶段被执行,这会对React的性能产生一些影响等。

class InputOne extends React.Component { componentDidMount() { this.refs.inputRef.value = 1; } render() { return <input ref="inputRef" />; } }

回调

React支持给任意组件添加特殊属性,ref属性接受一个回调函数,其在组件被加载或卸载时会立即执行。

  • 当给HTML元素添加ref属性时,ref回调接收了底层的DOM元素作为参数。
  • 当给组件添加ref属性时,ref回调接收当前组件实例作为参数。
  • 当组件卸载的时候,会传入null
  • ref回调会在componentDidMountcomponentDidUpdate等生命周期回调之前执行。

Callback Ref我们通常会使用内联函数的形式,那么每次渲染都会重新创建,由于React会清理旧的ref然后设置新的,因此更新期间会调用两次,第一次为null,如果在Callback中带有业务逻辑的话,可能会出错,可以通过将Callback定义成类成员函数并进行绑定的方式避免。

class InputTwo extends React.Component { componentDidMount() { this.inputRef.value = 2; } render() { return <input ref={(element) =>this.inputRef = element} />; } }

API创建

React v16.3中经0017-new-create-ref提案引入了新的React.createRefAPI,当ref被传递给render中的元素时,对该节点的引用可以在refcurrent属性中被访问,ref的值根据节点的类型而有所不同:

  • ref属性用于HTML元素时,构造函数中使用React.createRef()创建的ref接收底层DOM元素作为其current属性。
  • ref属性用于自定义class组件时,ref对象接收组件的挂载实例作为其current属性。
  • 不能在函数组件上使用ref属性,因为他们没有实例。

对比新的CreateRefCallback Ref,并没有压倒性的优势,只是希望成为一个便捷的特性,在性能上会会有微小的优势,Callback Ref采用了组件Render过程中在闭包函数中分配ref的模式,而CreateRef则采用了Object Ref

class InputThree extends React.Component { constructor(props) { super(props); this.inputRef = React.createRef(); } componentDidMount() { this.inputRef.current.value = 3; } render() { return <input ref={this.inputRef} />; } }

示例

<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>React</title> </head> <body> <div id="root"></div> </body> <script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> class InputOne extends React.Component { componentDidMount() { this.refs.inputRef.value = 1; } render() { return <input ref="inputRef" />; } } class InputTwo extends React.Component { componentDidMount() { this.inputRef.value = 2; } render() { return <input ref={(element) =>this.inputRef = element} />; } } class InputThree extends React.Component { constructor(props) { super(props); this.inputRef = React.createRef(); } componentDidMount() { this.inputRef.current.value = 3; } render() { return <input ref={this.inputRef} />; } } var vm = ReactDOM.render( <> <InputOne /> <InputTwo /> <InputThree /> </>, document.getElementById("root") ); </script> </html>

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://zhuanlan.zhihu.com/p/40462264 https://www.jianshu.com/p/4e2357ea1ba1 https://juejin.cn/post/6844903809274085389 https://juejin.cn/post/6844904048882106375 https://segmentfault.com/a/1190000008665915 https://zh-hans.reactjs.org/docs/refs-and-the-dom.html