🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
&emsp;&emsp;React实现了一套与浏览器无关的DOM系统,包括元素渲染、节点查询、事件处理等机制。 ## 一、ReactDOM &emsp;&emsp;自React v0.14开始,官方将与DOM相关的操作从React中剥离,组成单独的react-dom库,从而让React能兼容更多的终端。在引入react-dom库后,就能调用一个全局对象:ReactDOM,虽然在之前的章节中已多次使用该对象,但是都没有给出过多的讲解,本节将对其做重点分析。 &emsp;&emsp;ReactDOM只包含了unmountComponentAtNode()、findDOMNode()、createPortal()和render()等为数不多的几个方法,其中在[第2篇](https://www.cnblogs.com/strick/p/10508932.html)中介绍了render()的功能,又在[第4篇](https://www.cnblogs.com/strick/p/10570368.html)中提到用unmountComponentAtNode()方法移除DOM中已挂载的组件。接下来会讲解剩余的两个方法。 **1)findDOMNode()** &emsp;&emsp;当组件被渲染到页面真实的DOM中后,就能通过findDOMNode()方法得到生成的DOM元素,然后就能完成诸如读值、计算尺寸等操作。 &emsp;&emsp;注意,findDOMNode()只能获取已挂载的组件,并且不能用于函数组件。在组件的生命周期中,它只能存在于componentDidMount()和componentDidUpdate()两个回调函数中,在其它地方调用会抛出一个错误,具体如下所示。 ~~~js class Btn extends React.Component { render() { ReactDOM.findDOMNode(this); //抛出错误 return <button>提交</button>; } componentDidMount() { ReactDOM.findDOMNode(this); //<button>提交</button> } } ~~~ &emsp;&emsp;在上面的示例中,this指向的是Btn组件实例,在将this传给findDOMNode()方法后,得到了一个元素。有一点要注意,如果组件中的render()返回null或false,那么findDOMNode()只会返回null。 **2)createPortal()** &emsp;&emsp;在React v16中,新增了Portal特性,能让组件渲染到父组件以外的DOM节点中。这个特性适用于需要跳出容器的场景,例如创建页面内定制的弹框。 &emsp;&emsp;在React中使用Portal特性,需要调用ReactDOM上的一个新方法:createPortal()。此方法能接收2个参数,第一个是可渲染的React子元素,例如字符串、React元素数组等;第二个是DOM元素,也就是要挂载的容器。关于这个方法的具体使用可参考下面的示例。 ~~~js class Btn extends React.Component { render() { return ReactDOM.createPortal(this.props.children, document.body); } } ReactDOM.render(<Btn><p>按钮</p></Btn>, document.getElementById("container")); ~~~ &emsp;&emsp;在上面的render()方法中调用了ReactDOM.createPortal(),使得元素最终挂载到了元素中,而不是id属性为“container”的元素。 ## 二、Refs &emsp;&emsp;Refs是一种访问方式,通过它可读取render()方法内生成的组件实例和DOM元素,常用来处理元素的焦点、触发动画、集成第三方DOM库等。注意,在组件的生命周期中,要让Refs有效,得将其放在componentDidMount()和componentDidUpdate()两个回调函数中才行。虽然Refs能给某些场景带来便利,但是它破坏了React通过props传递数据的典型数据流,因此要尽量避免使用Refs。 &emsp;&emsp;如果要使用Refs的功能,那么就得设置React元素的ref属性,它的值可以是对象、回调函数和字符串,下面会分别讲解ref属性的这三类值。 **1)对象** &emsp;&emsp;此处的对象是React.createRef()方法的返回值,包含一个current属性,而该属性指向的正是要读取的组件实例或DOM元素。下面的示例展示了ref属性和React.createRef()方法的配合过程。 ~~~js class Btn extends React.Component { constructor(props) { super(props); this.myBtn = React.createRef(); } render() { return <button ref={this.myBtn}>提交</button>; } componentDidMount() { let btn = this.myBtn.current; console.log(btn); //<button>提交</button> } } ~~~ &emsp;&emsp;首先在组件的构造函数中调用React.createRef();再将返回值赋给this.myBtn,这样就能在组件内部的任意位置使用该对象了;然后让this.myBtn成为元素的ref属性的值;最后在componentDidMount()中就能成功读取到current属性的值,从而完成了一次Refs式的访问。 **2)回调函数** &emsp;&emsp;这个回调函数能接收一个参数(如下代码所示),当组件被挂载时,参数的值为组件实例或DOM元素;当组件被卸载时,参数的值为null。 ~~~js class Btn extends React.Component { render() { return ( <button ref={btn => { this.myBtn = btn }}>提交</button> ); } componentDidMount() { let btn = this.myBtn; console.log(btn); //<button>提交</button> } } ~~~ &emsp;&emsp;与前一种使用方式最大的不同是解除了对React.createRef()方法的依赖,在回调函数中直接将其参数赋给this.myBtn,就能得到预期的结果,不用再调用一次current属性。 **3)字符串** &emsp;&emsp;ref属性的值还可以是字符串,例如下面代码中的"myBtn",通过this.refs.myBtn就能访问到想要的组件实例或DOM元素。 ~~~js class Btn extends React.Component { render() { return <button ref="myBtn">提交</button>; } componentDidMount() { let btn = this.refs.myBtn; console.log(btn); //<button>提交</button> } } ~~~ &emsp;&emsp;不过,官方已经不推荐这种写法了,在未来的版本中有可能会被移除,因此建议改用回调函数的方式。 **4)使用场景** &emsp;&emsp;ref属性不仅能像之前示例那样应用于DOM元素上,还能在类组件中使用ref属性,如下代码所示。 ~~~js class Btn extends React.Component { render() { return <button>提交</button>; } } class Container extends React.Component { render() { return <Btn ref={btn => { this.myBtn = btn }}>提交</Btn>; } componentDidMount() { let btn = this.myBtn; console.log(btn); //Btn组件的实例 } } ~~~ &emsp;&emsp;Container是Btn的父组件,在其render()方法中,通过回调函数将Btn组件的实例赋给了this.myBtn。 &emsp;&emsp;由于函数组件没有实例,因此不能对其添加ref属性。 **5)子组件的DOM元素** &emsp;&emsp;在父组件中,如果要访问子组件的某个DOM元素,那么单靠ref属性是无法实现的,因为ref属性得到的只能是子组件的实例。不过,有一种间接的方式可以实现这个需求,那就是将ref属性和ReactDOM.findDOMNode()配合使用。下面套用上一节使用场景中的Btn和Container两个组件,代码只列出了修改部分,其余都已省略。 ~~~js class Container extends React.Component { componentDidMount() { let btn = this.myBtn; let dom = ReactDOM.findDOMNode(btn); console.log(dom); //<button>提交</button> } } ~~~ &emsp;&emsp;在componentDidMount()方法中,调用了一次ReactDOM.findDOMNode(),从而得到了子组件所拥有的DOM元素。 ## 三、Fragments &emsp;&emsp;JSX结构有一个限制,那就是在最外层必须用一个元素包裹,即使这是一个冗余的元素,也得加上。例如为一个元素挂载一组元素集合,如下所示。 ~~~js class Btns extends React.Component { render() { return ( <div> <li>1</li> <li>2</li> <li>3</li> </div> ); } } ~~~ &emsp;&emsp;在页面上渲染出的DOM会像下面这样,其中\<div>元素在此处是没有作用的。 ~~~html <ul id="container"> <div> <li>1</li> <li>2</li> <li>3</li> </div> </ul> ~~~ &emsp;&emsp;为了避免这种无意义的输出,React引入了Fragments,其结构如下代码所示。只需将最外层的元素的开始和结束标签分别改成,就不用在DOM中增加额外的元素了。 ~~~js class Btns extends React.Component { render() { return ( <> <li>1</li> <li>2</li> <li>3</li> </> ); } } ~~~ **1)React.Fragment** &emsp;&emsp;最终会被编译成React.Fragment组件的开始和结束标签,也就是说,前者是后者的语法糖。下面的代码和上一个Fragments的示例是等价的。 ~~~js class Btns extends React.Component { render() { return ( <React.Fragment> <li>1</li> <li>2</li> <li>3</li> </React.Fragment> ); } } ~~~ &emsp;&emsp;如果要为Fragments添加Keys标识(即为其定义key属性),那么只能用React.Fragment组件包裹子元素。注意,key是React.Fragment组件目前唯一可用的属性。 ***** > 原文出处: [博客园-React躬行记](https://www.cnblogs.com/strick/category/1455720.html) [知乎专栏-React躬行记](https://zhuanlan.zhihu.com/pwreact) 已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎浏览。 ![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200) 推荐一款前端监控脚本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不仅能监控前端的错误、通信、打印等行为,还能计算各类性能参数,包括 FMP、LCP、FP 等。