組件(Component)由若干個React元素組成,包含屬性、狀態和生命周期等部分,滿足獨立、可復用、高內聚和低耦合等設計原則,每個React應用程序都是由一個個的組件搭建而成,即組成React應用程序的最小單元正是組件。
## 一、構建
  目前推崇的構建組件的方式總共有兩種:類和函數,而用React.createClass()構建組件的方式已經過時,本節也不會對其做講解。
**1)類組件**
  通過ES6新增的類構建而成的組件必須繼承自React.Component,并且需要定義render()方法。此方法用于組件的輸出,即組件的渲染內容,如下代碼所示。注意,render()是一個純函數,不會改變組件的狀態,并且其返回值有多種,包括React元素、布爾值、數組等。
~~~js
class Btn extends React.Component {
render() {
return <button>提交</button>;
}
}
~~~
**2)函數組件**
  使用函數構建的組件只關注用戶界面的展示,既無狀態,也無生命周期。其功能相當于類組件的render()方法,但能接收一個屬性對象(props),下面是一個簡單的函數組件。
~~~js
function Btn(props) {
return <button>{props.text}</button>;
}
~~~
  與類組件不同,函數組件在調用時不會創建新實例。
## 二、state(組件狀態)
  組件中的state用于記錄其內部狀態,這類有狀態組件會隨著state的變化修改其最終的呈現。
**1)初始化**
  在組件的構造函數constructor()中可以像下面這樣,通過this.state初始化組件的內部狀態,其中this.state必須是一個對象。
~~~js
class Btn extends React.Component {
constructor() {
super();
this.state = {
text: "提交"
};
}
render() {
return <button>{this.state.text}</button>;
}
}
~~~
  注意,在初始化之前要先調用super(),因為ES6對兩個類的this的初始化順序做了規定,先父類,再子類,所以super()方法要在使用this之前調用。
  如果要讀取this.state中的數據,那么可以像上面的代碼那樣通過成員訪問運算符得到。但如果要更新this.state中的數據,那么就得用setState()方法,而不是用運算符。
**2)setState()**
  此方法能接收2個參數,第一個是函數或對象,第二個是可選的回調函數,會在更新之后觸發。下面用示例講解第一個參數的兩種情況(省略了構造函數以及初始化的代碼),當它是函數時,能接收2個參數,第一個是當前的state,第二個是組件的props(將在下一節講解),此處函數的功能是交替變換按鈕的文本。
~~~js
class Btn extends React.Component {
change() {
this.setState((state, props) => {
return { text: state.text == "點擊" ? "提交" : "點擊" };
});
}
render() {
return <button onClick={this.change.bind(this)}>{this.state.text}</button>;
}
}
~~~
  當setState()方法的第一個參數是對象時,可以將要更新的數據傳遞進來,就像下面這樣(省略了render()方法)。
~~~js
class Btn extends React.Component {
change() {
this.setState({ text: "點擊" });
}
}
~~~
  setState()是一個異步方法,React會將多個setState()方法合并成一個調用,也就是說,在調用setState()后,不能馬上反映出狀態的變化。例如this.state.text的值原先是“提交”,在像下面這樣更新狀態后,打印出的值仍然是“提交”。
~~~js
this.setState({text: "點擊"});
console.log(this.state.text); //"提交"
~~~
  setState()方法在將新數據合并到當前狀態之后,就會自動調用render()方法,驅動組件重新渲染。由此可知,在render()方法中不允許調用setState()方法,以免造成死循環。
  在后面的生命周期一節中會講解組件在各個階段可用的回調函數。其中有些回調函數得在render()方法被執行后再被調用,因此在它們內部通過this.state得到的將是更新后的內部狀態。
## 三、props(組件屬性)
  props(properties的縮寫)能接收外部傳遞給組件的數據,當組件作為React元素使用時,props就是一個由元素屬性所組成的對象。以Btn組件為例,它的props的結構如下所示,其中children是一個特殊屬性,后續將會單獨講解。
~~~js
<Btn name="strick" digit={0}>提交</Btn>
props = { name: "strick", digit: 0, children: "提交" }
~~~
**1)讀取**
  每個組件都會有一個構造函數,而它的參數正是props。由于React組件相當于一個純函數,因此props不能被修改,它的屬性都是只讀的,像下面這樣賦值勢必會引起組件的副作用,因而React會馬上終止程序,直接拋出錯誤。
~~~js
class Btn extends React.Component {
constructor(props) {
super(props);
props.name = "freedom"; //錯誤
}
}
~~~
  因為有此限制,所以若要修改props中的某個屬性,通常會先將它賦給state,再通過state更新數據,如下所示。
~~~js
class Btn extends React.Component {
constructor(props) {
super(props);
this.state = {
name: props.name
};
}
}
~~~
  在構造函數之外,可通過this.props訪問到傳遞進來的數據。
**2)defaultProps**
  組件的靜態屬性defaultProps可為props指定默認值,例如為組件設置默認的name屬性,當props.name缺省時,就能用該值,如下所示。
~~~js
class Btn extends React.Component {
constructor(props) {
super(props);
}
render() {
return <button>{this.props.name}</button>;
}
}
Btn.defaultProps = {
name: "freedom"
};
~~~
**3)children**
  每個props都會包含一個特殊的屬性:children,表示組件的內容,即所包裹的子組件。例如下面這個Btn組件,其props.children的值為“搜索”。
~~~js
<Btn>搜索</Btn>
~~~
  children可以是null、字符串或對象等數據類型,并且當組件的內容是多個子組件時,children還能自動變成一個數組。
  官方通過React.Children給出了專門處理children的輔助方法,例如用于遍歷的forEach(),如下代碼所示,其余方法可參考表1。
~~~js
React.Children.forEach(props.children, child => {
console.log(child);
});
~~~
:-: 
:-: 表1 輔助方法
  當children是數組時,React.Children中的map()和forEach()兩個方法與數組中的功能類似,并且count()方法的返回值與數組的length屬性相同。但當children是其它類型時,用React.Children中的輔助方法會比較保險,例如children為一個子組件“strick”,調用count()方法得到的值為1,而調用length屬性得到的值為6,這與當前子組件的數量不符。
**4)校驗屬性**
  自React v15.5起,官方棄用了React.PropTypes,改用prop-types庫。此庫能校驗props中屬性的類型,例如將Btn組件的age屬性限制為數字,可以像下面這樣設置。
~~~js
Btn.propTypes = {
age: PropTypes.number
}
~~~
  在引入該庫后,就會有一個全局對象PropTypes。除了數字類型之外,PropTypes還提供了其它類型的校驗,具體對應關系可參考表2。
:-: 
:-: 表2 對應關系
  當組件的屬性是對象或數組時,PropTypes能校驗其成員的類型,例如要求數組的成員都得是字符串、對象的某個屬性必須是布爾值,可以像下面這樣操作。
~~~js
Btn.propTypes = {
names: PropTypes.arrayOf(PropTypes.string),
person: PropTypes.shape({ isMan: PropTypes.bool })
}
~~~
  PropTypes還能在其任意屬性后加isRequired標記,例如PropTypes.number.isRequired表示必須傳數字類型的屬性,并且不能缺省。下面示例中的school屬性,不限制類型,只要有值就行。
~~~js
Btn.propTypes = {
school: PropTypes.any.isRequired
}
~~~
  此節只列出了prop-types庫中的部分功能,其余功能可參考官方文檔。
**5)數據流**
  在React中,組件之間的數據是自頂向下單向流動,即父組件通過props將數據傳遞給子組件(如圖3所示),以此實現它們之間的對話和聯系。
:-: 
:-: 圖3 單向數據流
  舉個簡單的例子,有兩個組件Container和Btn,其中Container是父組件,Btn是子組件,Container組件會將它的text屬性傳遞給Btn組件,以此完成數據的流動。為了便于觀察,省略了兩個組件的構造函數,具體如下所示。
~~~js
class Btn extends React.Component {
render() {
return <button>{this.props.text}</button>;
}
}
class Container extends React.Component {
render() {
return <Btn text="提交" />;
}
}
~~~
## 四、列表和Keys
  在組件中渲染列表數據是非常常見的需要,例如輸出多個按鈕,如下代碼所示。
~~~js
class Btns extends React.Component {
constructor(props) {
super(props);
}
render() {
const list = this.props.names.map(value => <button>{value}</button>);
return <div>{list}</div>;
}
}
ReactDOM.render(
<Btns names={[1,2,3]}>按鈕列表</Btns>,
document.getElementById("container")
);
~~~
  在上面的render()方法中,先通過map()方法遍歷傳遞進來的names屬性,再為這個數組的每個元素加上標簽,最后得到元素列表list后,將其作為返回值輸出。不過,此時會收到一個要求為列表中的子元素添加key屬性的警告,如圖4所示。
:-: 
圖4 key屬性的警告
  在React中,Keys會作為元素的身份標識,能夠幫助React識別出發生變化的元素,從而只渲染這些元素。每個元素的key屬性在當前列表中要保持唯一性,即在其兄弟元素之間要獨一無二,如下代碼所示(省略了組件的構造函數)。
~~~js
class Btns extends React.Component {
render() {
const list1 = this.props.names.map(value => <button key={value}>{value}</button>);
const list2 = this.props.names.map(value => <button key={value}>{value}</button>);
return (
<div>
<section>{list1}</section>
<section>{list2}</section>
</div>
);
}
}
~~~
  在上面的render()方法中,有兩個元素列表:list1和list2,雖然它們包含相同key屬性的元素,但分別被嵌到了兩個元素中,從而將兩者隔離,達成了key屬性唯一的目標。由此可知,key屬性不是全局唯一的。
  注意,一般不建議用數組的索引作為key屬性的值,因為一旦數組中元素的位置發生變化,其索引也會跟著改變,不利于渲染優化。
  關于在何時設置key屬性,有個簡單的規則可以參考,那就是當元素位于map()方法內時,需要為該元素添加key屬性。
*****
> 原文出處:
[博客園-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