React实现了一套与浏览器无关的DOM系统,包括元素渲染、节点查询、事件处理等机制。

一、ReactDOM

  自React v0.14开始,官方将与DOM相关的操作从React中剥离,组成单独的react-dom库,从而让React能兼容更多的终端。在引入react-dom库后,就能调用一个全局对象:ReactDOM,虽然在之前的章节中已多次使用该对象,但是都没有给出过多的讲解,本节将对其做重点分析。

  ReactDOM只包含了unmountComponentAtNode()、findDOMNode()、createPortal()和render()等为数不多的几个方法,其中在第2篇中介绍了render()的功能,又在第4篇中提到用unmountComponentAtNode()方法移除DOM中已挂载的组件。接下来会讲解剩余的两个方法。

1)findDOMNode()

  当组件被渲染到页面真实的DOM中后,就能通过findDOMNode()方法得到生成的DOM元素,然后就能完成诸如读值、计算尺寸等操作。

  注意,findDOMNode()只能获取已挂载的组件,并且不能用于函数组件。在组件的生命周期中,它只能存在于componentDidMount()和componentDidUpdate()两个回调函数中,在其它地方调用会抛出一个错误,具体如下所示。

class Btn extends React.Component {
render() {
ReactDOM.findDOMNode(this); //抛出错误
return <button>提交</button>;
}
componentDidMount() {
ReactDOM.findDOMNode(this); //<button>提交</button>
}
}

  在上面的示例中,this指向的是Btn组件实例,在将this传给findDOMNode()方法后,得到了一个<button>元素。有一点要注意,如果组件中的render()返回null或false,那么findDOMNode()只会返回null。

2)createPortal()

  在React v16中,新增了Portal特性,能让组件渲染到父组件以外的DOM节点中。这个特性适用于需要跳出容器的场景,例如创建页面内定制的弹框。

  在React中使用Portal特性,需要调用ReactDOM上的一个新方法:createPortal()。此方法能接收2个参数,第一个是可渲染的React子元素,例如字符串、React元素数组等;第二个是DOM元素,也就是要挂载的容器。关于这个方法的具体使用可参考下面的示例。

class Btn extends React.Component {
render() {
return ReactDOM.createPortal(this.props.children, document.body);
}
}
ReactDOM.render(<Btn><p>按钮</p></Btn>, document.getElementById("container"));

  在上面的render()方法中调用了ReactDOM.createPortal(),使得<p>元素最终挂载到了<body>元素中,而不是id属性为“container”的元素。

二、Refs

  Refs是一种访问方式,通过它可读取render()方法内生成的组件实例和DOM元素,常用来处理元素的焦点、触发动画、集成第三方DOM库等。注意,在组件的生命周期中,要让Refs有效,得将其放在componentDidMount()和componentDidUpdate()两个回调函数中才行。虽然Refs能给某些场景带来便利,但是它破坏了React通过props传递数据的典型数据流,因此要尽量避免使用Refs。

  如果要使用Refs的功能,那么就得设置React元素的ref属性,它的值可以是对象、回调函数和字符串,下面会分别讲解ref属性的这三类值。

1)对象

  此处的对象是React.createRef()方法的返回值,包含一个current属性,而该属性指向的正是要读取的组件实例或DOM元素。下面的示例展示了ref属性和React.createRef()方法的配合过程。

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

  首先在组件的构造函数中调用React.createRef();再将返回值赋给this.myBtn,这样就能在组件内部的任意位置使用该对象了;然后让this.myBtn成为<button>元素的ref属性的值;最后在componentDidMount()中就能成功读取到current属性的值,从而完成了一次Refs式的访问。

2)回调函数

  这个回调函数能接收一个参数(如下代码所示),当组件被挂载时,参数的值为组件实例或DOM元素;当组件被卸载时,参数的值为null。

class Btn extends React.Component {
render() {
return (
<button ref={btn => { this.myBtn = btn }}>提交</button>
);
}
componentDidMount() {
let btn = this.myBtn;
console.log(btn); //<button>提交</button>
}
}

  与前一种使用方式最大的不同是解除了对React.createRef()方法的依赖,在回调函数中直接将其参数赋给this.myBtn,就能得到预期的结果,不用再调用一次current属性。

3)字符串

  ref属性的值还可以是字符串,例如下面代码中的"myBtn",通过this.refs.myBtn就能访问到想要的组件实例或DOM元素。

class Btn extends React.Component {
render() {
return <button ref="myBtn">提交</button>;
}
componentDidMount() {
let btn = this.refs.myBtn;
console.log(btn); //<button>提交</button>
}
}

  不过,官方已经不推荐这种写法了,在未来的版本中有可能会被移除,因此建议改用回调函数的方式。

4)使用场景

  ref属性不仅能像之前示例那样应用于DOM元素上,还能在类组件中使用ref属性,如下代码所示。

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组件的实例
}
}

  Container是Btn的父组件,在其render()方法中,通过回调函数将Btn组件的实例赋给了this.myBtn。

  由于函数组件没有实例,因此不能对其添加ref属性。

5)子组件的DOM元素

  在父组件中,如果要访问子组件的某个DOM元素,那么单靠ref属性是无法实现的,因为ref属性得到的只能是子组件的实例。不过,有一种间接的方式可以实现这个需求,那就是将ref属性和ReactDOM.findDOMNode()配合使用。下面套用上一节使用场景中的Btn和Container两个组件,代码只列出了修改部分,其余都已省略。

class Container extends React.Component {
componentDidMount() {
let btn = this.myBtn;
let dom = ReactDOM.findDOMNode(btn);
console.log(dom); //<button>提交</button>
}
}

  在componentDidMount()方法中,调用了一次ReactDOM.findDOMNode(),从而得到了子组件所拥有的DOM元素。

三、Fragments

  JSX结构有一个限制,那就是在最外层必须用一个元素包裹,即使这是一个冗余的元素,也得加上。例如为一个<ul>元素挂载一组元素集合,如下所示。

class Btns extends React.Component {
render() {
return (
<div>
<li>1</li>
<li>2</li>
<li>3</li>
</div>
);
}
}

  在页面上渲染出的DOM会像下面这样,其中<div>元素在此处是没有作用的。

<ul id="container">
<div>
<li>1</li>
<li>2</li>
<li>3</li>
</div>
</ul>

  为了避免这种无意义的输出,React引入了Fragments,其结构如下代码所示。只需将最外层的<div>元素的开始和结束标签分别改成<>和</>,就不用在DOM中增加额外的元素了。

class Btns extends React.Component {
render() {
return (
<>
<li>1</li>
<li>2</li>
<li>3</li>
</>
);
}
}

1)React.Fragment

  <>和</>最终会被编译成React.Fragment组件的开始和结束标签,也就是说,前者是后者的语法糖。下面的代码和上一个Fragments的示例是等价的。

class Btns extends React.Component {
render() {
return (
<React.Fragment>
<li>1</li>
<li>2</li>
<li>3</li>
</React.Fragment>
);
}
}

  如果要为Fragments添加Keys标识(即为其定义key属性),那么只能用React.Fragment组件包裹子元素。注意,key是React.Fragment组件目前唯一可用的属性。

最新文章

  1. noi-openjudge[4.7搜索]怀表问题
  2. c#中高效的excel导入sqlserver的方法
  3. 【JQuery】 ajax 无效的JSON基元
  4. 14-find 查找文件
  5. linux字符过滤
  6. verilog 双向IO实现
  7. 蓝桥杯比赛javaB组练习《四平方和》
  8. hbase参数配置和说明
  9. 使用 SetColorFilter 神奇地改变图片的颜色
  10. 学习熟悉箭头函数, 类, 模板字面量, let和const声明
  11. vue-cli按需加载,懒加载组件
  12. 如何破解QQ空间相册密码访问权限2019方法
  13. python ftp文件夹文件递归上传推送
  14. Extjs动态增删组件
  15. 服务器安装pip
  16. python抓取猫眼电影列表
  17. linux环境下 卸载 Oracle11G
  18. TFS二次开发系列索引
  19. Android的onCreateOptionsMenu()创建菜单Menu
  20. 如何在k8s集群里快速运行一个镜像?

热门文章

  1. android Notification分析—— 您可能会遇到各种问题
  2. jvm常用优化方案和方法
  3. Object-c学习笔记(1)
  4. Maven软件项目管理工具
  5. c#调用ffmpeg嵌入srt/ass字幕提示Unable to open xxx.srt......
  6. ASP.NET Core 配置 EF 框架服务 - ASP.NET Core 基础教程 - 简单教程,简单编程
  7. Folly: Facebook Open-source Library Readme.md 和 Overview.md(感觉包含的东西并不多,还是Boost更有用)
  8. WPF圆形环绕的Loading动画
  9. Android面HTTP协议发送get要求
  10. Android 5.0(L) ToolBar(替代ActionBar) 现实(四)