React 深入系列,深入講解了React中的重點概念、特性和模式等,旨在幫助大家加深對React的理解,以及在專案中更加靈活地使用React。
本篇是React深入系列的最後一篇,將介紹開發React應用時,經常用到的模式,這些模式並非都有官方名稱,所以有些模式的命名並不一定準確,請讀者主要關注模式的內容。
1. 受控元件
React 元件的資料流是由state和props驅動的,但對於input、textarea、select等表單元素,因為可以直接接收使用者在介面上的輸入,所以破壞了React中的固有資料流。為了解決這個問題,React引入了受控元件,受控元件指input等表單元素顯示的值,仍然是通過元件的state獲取的,而不是直接顯示使用者在介面上的輸入資訊。
受控元件的實現:通過監聽表單元素值的變化,改變元件state,根據state顯示元件最終要展示的值。一個簡單的例子如下:
class NameForm 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>
);
}
}
複製程式碼
和受控元件對應的概念是非受控元件,非受控元件通過ref獲取表單元素的值,在一些場景下有著特有的作用(如設定表單元素的焦點)。
2. 容器元件
容器元件和展示元件是一組對應的概念,關注的是元件邏輯和元件展示的分離。邏輯由容器元件負責,展示元件聚焦在檢視層的展現上。在React 深入系列2:元件分類中已對容器元件和展示元件作過詳細介紹,這裡不再贅述。
3. 高階元件
高階元件是一種特殊的函式,接收元件作為輸入,輸出一個新的元件。高階元件的主要作用是封裝元件的通用邏輯,實現邏輯的複用。在React 深入系列6:高階元件中已經詳細介紹過高階元件,這裡也不再贅述。
4. Children傳遞
首先,這個模式的命名可能並不恰當。這個模式中,藉助React 元件的children屬性,實現元件間的解耦。常用在一個元件負責UI的框架,框架內部的元件可以靈活替換的場景。
一個示例:
// ModalDialog.js
export default function ModalDialog({ children }) {
return <div className="modal-dialog">{ children }</div>;
};
// App.js
render() {
<ModalDialog>
<SomeContentComp/>
</ModalDialog>
}
複製程式碼
ModalDialog元件是UI的框,框內元件可以靈活替換。
5. Render Props
Render Props是把元件部分的渲染邏輯封裝到一個函式中,利用元件的props接收這個函式,然後在元件內部呼叫這個函式,執行封裝的渲染邏輯。
看一個官方的例子:
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{/*
* Mouse元件並不知道應該如何渲染這部分內容,
* 這部分渲染邏輯是通過props的render屬性傳遞給Mouse元件
*/}
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
複製程式碼
Mouse監聽滑鼠的移動,並將滑鼠位置儲存到state中。但Mouse元件並不知道最終要渲染出的內容,需要呼叫this.props.render方法,執行渲染邏輯。本例中,Cat元件會渲染到滑鼠移動到的位置,但完全可以使用其他效果來跟隨滑鼠的移動,只需更改render方法即可。由此可見,Mouse元件只關注滑鼠位置的移動,而跟隨滑鼠移動的介面效果,由使用Mouse的元件決定。這是一種基於切面程式設計的思想(瞭解後端開發的同學應該比較熟悉)。
使用這種模式,一般習慣將封裝渲染邏輯的函式賦值給一個命名為render的元件屬性(如本例所示),但這並不是必需,你也可以使用其他的屬性命名。
這種模式的變種形式是,直接使用React元件自帶的children屬性傳遞。上面的例子改寫為:
class Mouse extends React.Component {
// 省略
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{/*
* Mouse元件並不知道應該如何渲染這部分內容,
* 這部分渲染邏輯是通過props的children屬性傳遞給Mouse元件
*/}
{this.props.children(this.state)}
</div>
);
}
}
Mouse.propTypes = {
children: PropTypes.func.isRequired
};
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse>
{mouse => (
<Cat mouse={mouse} />
)}
</Mouse>
</div>
);
}
}
複製程式碼
注意children的賦值方式。
React Router 和 React-Motion 這兩個庫都使用到了Render Props模式。**很多場景下,Render Props實現的功能也可以通過高階元件實現。**本例也可以用高階元件實現,請讀者自行思考。
6. Provider元件
這種模式藉助React的context,把元件需要使用的資料儲存到context,並提供一個高階元件從context中獲取資料。
一個例子:
先建立MyProvider,將共享資料儲存到它的context中,MyProvider一般作為最頂層的元件使用,從而確保其他元件都能獲取到context中的資料:
import React from "react";
import PropTypes from "prop-types";
const contextTypes = {
sharedData: PropTypes.shape({
a: PropTypes.bool,
b: PropTypes.string,
c: PropTypes.object
})
};
export class MyProvider extends React.Component {
static childContextTypes = contextTypes;
getChildContext() {
// 假定context中的資料從props中獲取
return { sharedData: this.props.sharedData };
}
render() {
return this.props.children;
}
}
複製程式碼
然後建立高階元件connectData,用於從context中獲取所需資料:
export const connectData = WrappedComponent =>
class extends React.Component {
static contextTypes = contextTypes;
render() {
const { props, context } = this;
return <WrappedComponent {...props} {...context.sharedData} />;
}
};
複製程式碼
最後在應用中使用:
const SomeComponentWithData = connectData(SomeComponent)
const sharedData = {
a: true,
b: "react",
c: {}
};
class App extends Component {
render() {
return (
<MyProvider sharedData={sharedData}>
<SomeComponentWithData />
</MyProvider>
);
}
}
複製程式碼
Provider元件模式非常實用,在react-redux、mobx-react等庫中,都有使用到這種模式。
React 深入系列文章到此完結,希望能幫助大家更加深入的理解React,更加純熟的應用React。
我的新書《React進階之路》已上市,對React感興趣的同學不妨去了解下。 購買地址: 噹噹 京東
歡迎關注我的公眾號:老幹部的大前端