React 深刻系列,深刻講解了React中的重點概念、特性和模式等,旨在幫助你們加深對React的理解,以及在項目中更加靈活地使用React。html
本篇是React深刻系列的最後一篇,將介紹開發React應用時,常常用到的模式,這些模式並不是都有官方名稱,因此有些模式的命名並不必定準確,請讀者主要關注模式的內容。前端
React 組件的數據流是由state和props驅動的,但對於input、textarea、select等表單元素,由於能夠直接接收用戶在界面上的輸入,因此破壞了React中的固有數據流。爲了解決這個問題,React引入了受控組件,受控組件指input等表單元素顯示的值,仍然是經過組件的state獲取的,而不是直接顯示用戶在界面上的輸入信息。react
受控組件的實現:經過監聽表單元素值的變化,改變組件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獲取表單元素的值,在一些場景下有着特有的做用(如設置表單元素的焦點)。redux
容器組件和展現組件是一組對應的概念,關注的是組件邏輯和組件展現的分離。邏輯由容器組件負責,展現組件聚焦在視圖層的展示上。在React 深刻系列2:組件分類中已對容器組件和展現組件做過詳細介紹,這裏再也不贅述。後端
高階組件是一種特殊的函數,接收組件做爲輸入,輸出一個新的組件。高階組件的主要做用是封裝組件的通用邏輯,實現邏輯的複用。在React 深刻系列6:高階組件中已經詳細介紹太高階組件,這裏也再也不贅述。bash
首先,這個模式的命名可能並不恰當。這個模式中,藉助React 組件的children屬性,實現組件間的解耦。經常使用在一個組件負責UI的框架,框架內部的組件能夠靈活替換的場景。app
一個示例:框架
// ModalDialog.js
export default function ModalDialog({ children }) {
return <div className="modal-dialog">{ children }</div>;
};
// App.js
render() {
<ModalDialog>
<SomeContentComp/>
</ModalDialog>
}
複製代碼
ModalDialog組件是UI的框,框內組件能夠靈活替換。ide
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實現的功能也能夠經過高階組件實現。**本例也能夠用高階組件實現,請讀者自行思考。
這種模式藉助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感興趣的同窗不妨去了解下。 購買地址: 噹噹 京東
歡迎關注個人公衆號:老幹部的大前端