React 的核心思想是一切皆組件
在 React 中,一切皆組件。組件可以將 UI 切分為一系列獨立的、可重用的部件,這樣使我們可以專注於構建每一個單獨的部件。
理解 React 中各種各樣的組件對於更好的學習 React 變得尤為重要,如 UI 組件、容器組件、無狀態組件、函數式組件,對 react 了解不夠深入的人通常對於這些概念理解不夠清晰,很難寫出高質量的 react 組件,本文是我對於 React 的組件的一些理解。
Component#
Compoent 在這裡指 React.Component,大多數人一開始學習 React 了解最多的便是 Component。使用 Component 可以非常方便的定義一個組件。
使用 ES6 的 class 來定義一個組件如下:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
PureComponent#
PureComponent 在這裡指 React.PureComponent,PureComponent 與 Component 幾乎完全相同,但是 PureComponent 通過 props 和 state 的淺對比來實現 shouldComponentUpdate()。
class Welcome extends React.PureComponent {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
在 React 的生命週期中,當組件的 props 和 state 發生改變時,會執行組件的 shouldComponentUpdate 函數,當該函數返回為 true 時組件重新渲染,返回為 false 則不重新渲染。使用 Component 定義的組件中,shouldComponentUpdate 默認返回為 true,通常當組件遭遇性能問題時,可以在 shouldComponent 中對新舊屬性和狀態進行相等判斷,來減少不必要的重新渲染。
PureComponent 是 react v15.3.0 中新加入的特性,默認實現了 shouldComponentUpdate 中對於新舊屬性和狀態的相等比較,減少不必要的重新渲染來提升性能。
注意:PureComponent 的 shouldComponentUpdate 只會對對象進行淺比較,當對象層級較深或者結構複雜時,可能會出現對象深層數據已改變而視圖沒有更新的問題,使用時需要注意。
函數式組件#
上述使用 Component 和 PureComponent 定義的組件稱為聲明式組件。在 react 中,還可以使用函數定義一個組件,稱為函數式組件。如下:
const Text = ({ children = 'Hello World!' }) =>
<p>{children}</p>
函數式組件又稱無狀態(stateless)組件,在組件中不能對狀態(state)進行操作,使用函數式組件使 react 的代碼更具有可讀性,可以減少諸如 class、constructor 等冗餘代碼,有利於組件重用。
函數式組件具有以下特點:
- 函數式組件不會被實例化,提升了整體渲染性能。
函數式組件沒有 class 聲明,僅使用 render 方法實現,不存在組件實例化的過程,因此不需要分配多餘的內存,從而提升了渲染性能。
- 函數式組件不能訪問 this 對象。
函數式組件內部不能訪問 this.state,無法操作狀態。
3. 函數式組件沒有生命週期。
4. 函數式組件只接受 props 和 context 參數,沒有副作用。
UI 組件和容器組件#
在結合 redux 管理數據流的應用中,redux 將組件分為 UI 組件(presentational component)和容器組件(container component)。UI 組件又稱為展示型組件。在早期的 Redux 的版本中,作者將上述兩種組件定義為智能(Smart)組件和木桶(Dumb)組件,兩者的區別是看是否有數據操作。Dumb 組件對應 UI 組件,Smart 組件對應容器組件。UI 組件只負責 UI 的呈現,不負責業務邏輯;內部沒有狀態,數據由 this.props 參數提供;容器組件負責處理業務邏輯,不負責 UI 呈現;有內部狀態;使用 Redux 的 API 連接 UI 組件。
通常在項目的文件結構中,components 文件夾包含 UI 組件,containers 文件夾包含容器組件。
純組件#
提到純組件,先來回顧一下函數式編程中的 “純函數”,純函數由三大原則構成:
- 給定相同的輸入,總是返回相同的輸出;
- 函數執行過程中不會產生副作用(side effect);
- 沒有額外的狀態依賴;
在 react 中,使用純組件可以提高 Virtual DOM 的執行效率,那麼,怎樣的組件才是純組件呢?
首先,PureComponent 並不是純組件,因為在組件中存在生命週期,並且擁有內部狀態 state,可以產生副作用。
通常,函數式組件、UI 組件、無狀態組件這類僅接受 props 參數進行數據渲染的組件可以稱為純組件,它們接受相同的輸入參數 props,返回相同的輸出,且不具有副作用。
受控組件#
在 HTML 中,像 input,textarea, 和 select 這類表單元素會維持自身狀態,並根據用戶輸入進行更新。但在 React 中,可變的狀態通常保存在組件的狀態屬性中,並且只能用 setState (). 方法進行更新。
在 react 中,使用 state 來控制表單元素的數據的顯示,同時控制表單元素輸入引起值的變化並更新 state 來進行表單元素數據的更新,其值由 React 控制的輸入表單元素稱為 “受控組件”。
考慮如下代碼:
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
上述代碼是一個受控組件的示例,由於 value 屬性是在我們的表單元素上設置的,因此顯示的值將始終為 React 數據源上 this.state.value 的值。由於每次按鍵都會觸發 handleChange 來更新當前 React 的 state,所展示的值也會隨著不同用戶的輸入而更新。
非受控組件#
在大多數情況下,我們推薦使用 受控組件 來實現表單。 在受控組件中表單數據由 React 組件處理。如果讓表單數據由 DOM 處理時,替代方案為使用非受控組件。
要編寫一個非受控組件,而非為每個狀態更新編寫事件處理程序,你可以 使用 ref 從 DOM 獲取表單值。
考慮如下代碼:
class Form extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={(input) => this.input = input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
由於非受控組件將真實數據保存在 DOM 中,因此在使用非受控組件時,更容易同時集成 React 和非 React 代碼。如果你想快速而隨性,這樣做可以減小代碼量。否則,你應該使用受控組件。
通常,不建議使用非受控組件。
高階組件#
高階組件(HOC)是 react 中對組件邏輯進行重用的高級技術。但高階組本身並不是 React API。它只是一種模式,這種模式是由 react 自身的組合性質必然產生的。
具體而言,高階組件類似於高階函數,且該函數接受一個 React 組件作為參數,並返回一個新的組件。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
你或許沒聽過高階組件之前已經使用過了,在 react 的一些第三方庫,如 react-redux 的 connect,react-router-dom 的 withRouter 等等。
高階組件可以讓代碼更具有重用性、邏輯性與抽象特性。它可以對 render 方法做劫持,也可以控制 props 與 state。
考慮以下代碼:
export default function Form(Comp) {
return class WrapperComp extends Component {
constructor(props) {
super(props);
this.state = {
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(key, val) {
this.setState({
[key]: val
})
}
render() {
return <Comp {...this.props} handleChange={this.handleChange} state={this.state}/>
}
}
}
上述代碼是一個高階組件,為了解決使用表單組件時頻繁的編寫時間處理函數來管理表單數據的問題。使用方法如下:
class Login extends Component {
constructor(props) {
super(props);
this.state = {
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(key, val) {
this.setState({
[key]: val
})
}
render() {
return (
<div className="login">
<input
type="text"
value={this.props.state.user}
onChange={(e) => this.props.handleChange('user', e.target.value)}
/>
<input
type="password"
value={this.props.state.psw}
onChange={(e) => this.props.handleChange('psw', e.target.value)}
/>
</div>
)
}
}
export default Form(Login);
這樣,對於需要在很多地方使用表單的組件,可以提供組件重用度,減少樣板代碼。