React 深刻系列,深刻講解了React中的重點概念、特性和模式等,旨在幫助你們加深對React的理解,以及在項目中更加靈活地使用React。
本篇是React深刻系列的最後一篇,將介紹開發React應用時,常常用到的模式,這些模式並不是都有官方名稱,因此有些模式的命名並不必定準確,請讀者主要關注模式的內容。html
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:高階組件中已經詳細介紹太高階組件,這裏也再也不贅述。app
首先,這個模式的命名可能並不恰當。這個模式中,藉助React 組件的children屬性,實現組件間的解耦。經常使用在一個組件負責UI的框架,框架內部的組件能夠靈活替換的場景。框架
一個示例:ide
// ModalDialog.js export default function ModalDialog({ children }) { return <div className="modal-dialog">{ children }</div>; }; // App.js render() { <ModalDialog> <SomeContentComp/> </ModalDialog> }
ModalDialog組件是UI的框,框內組件能夠靈活替換。函數
Render Props是把組件部分的渲染邏輯封裝到一個函數中,利用組件的props接收這個函數,而後在組件內部調用這個函數,執行封裝的渲染邏輯。ui
看一個官方的例子:
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進階之路》已上市,請你們多多支持!
連接:京東 噹噹