React實現了一套與瀏覽器無關的DOM系統,包括元素渲染、節點查詢、事件處理等機制。
## 一、ReactDOM
  自React v0.14開始,官方將與DOM相關的操作從React中剝離,組成單獨的react-dom庫,從而讓React能兼容更多的終端。在引入react-dom庫后,就能調用一個全局對象:ReactDOM,雖然在之前的章節中已多次使用該對象,但是都沒有給出過多的講解,本節將對其做重點分析。
  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()**
  當組件被渲染到頁面真實的DOM中后,就能通過findDOMNode()方法得到生成的DOM元素,然后就能完成諸如讀值、計算尺寸等操作。
  注意,findDOMNode()只能獲取已掛載的組件,并且不能用于函數組件。在組件的生命周期中,它只能存在于componentDidMount()和componentDidUpdate()兩個回調函數中,在其它地方調用會拋出一個錯誤,具體如下所示。
~~~js
class Btn extends React.Component {
render() {
ReactDOM.findDOMNode(this); //拋出錯誤
return <button>提交</button>;
}
componentDidMount() {
ReactDOM.findDOMNode(this); //<button>提交</button>
}
}
~~~
  在上面的示例中,this指向的是Btn組件實例,在將this傳給findDOMNode()方法后,得到了一個元素。有一點要注意,如果組件中的render()返回null或false,那么findDOMNode()只會返回null。
**2)createPortal()**
  在React v16中,新增了Portal特性,能讓組件渲染到父組件以外的DOM節點中。這個特性適用于需要跳出容器的場景,例如創建頁面內定制的彈框。
  在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"));
~~~
  在上面的render()方法中調用了ReactDOM.createPortal(),使得元素最終掛載到了元素中,而不是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()方法的配合過程。
~~~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>
}
}
~~~
  首先在組件的構造函數中調用React.createRef();再將返回值賦給this.myBtn,這樣就能在組件內部的任意位置使用該對象了;然后讓this.myBtn成為元素的ref屬性的值;最后在componentDidMount()中就能成功讀取到current屬性的值,從而完成了一次Refs式的訪問。
**2)回調函數**
  這個回調函數能接收一個參數(如下代碼所示),當組件被掛載時,參數的值為組件實例或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>
}
}
~~~
  與前一種使用方式最大的不同是解除了對React.createRef()方法的依賴,在回調函數中直接將其參數賦給this.myBtn,就能得到預期的結果,不用再調用一次current屬性。
**3)字符串**
  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>
}
}
~~~
  不過,官方已經不推薦這種寫法了,在未來的版本中有可能會被移除,因此建議改用回調函數的方式。
**4)使用場景**
  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組件的實例
}
}
~~~
  Container是Btn的父組件,在其render()方法中,通過回調函數將Btn組件的實例賦給了this.myBtn。
  由于函數組件沒有實例,因此不能對其添加ref屬性。
**5)子組件的DOM元素**
  在父組件中,如果要訪問子組件的某個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>
}
}
~~~
  在componentDidMount()方法中,調用了一次ReactDOM.findDOMNode(),從而得到了子組件所擁有的DOM元素。
## 三、Fragments
  JSX結構有一個限制,那就是在最外層必須用一個元素包裹,即使這是一個冗余的元素,也得加上。例如為一個元素掛載一組元素集合,如下所示。
~~~js
class Btns extends React.Component {
render() {
return (
<div>
<li>1</li>
<li>2</li>
<li>3</li>
</div>
);
}
}
~~~
  在頁面上渲染出的DOM會像下面這樣,其中\<div>元素在此處是沒有作用的。
~~~html
<ul id="container">
<div>
<li>1</li>
<li>2</li>
<li>3</li>
</div>
</ul>
~~~
  為了避免這種無意義的輸出,React引入了Fragments,其結構如下代碼所示。只需將最外層的元素的開始和結束標簽分別改成,就不用在DOM中增加額外的元素了。
~~~js
class Btns extends React.Component {
render() {
return (
<>
<li>1</li>
<li>2</li>
<li>3</li>
</>
);
}
}
~~~
**1)React.Fragment**
  最終會被編譯成React.Fragment組件的開始和結束標簽,也就是說,前者是后者的語法糖。下面的代碼和上一個Fragments的示例是等價的。
~~~js
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組件目前唯一可用的屬性。
*****
> 原文出處:
[博客園-React躬行記](https://www.cnblogs.com/strick/category/1455720.html)
[知乎專欄-React躬行記](https://zhuanlan.zhihu.com/pwreact)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020