React 深刻系列7:React 經常使用模式

React 深刻系列,深刻講解了React中的重點概念、特性和模式等,旨在幫助你們加深對React的理解,以及在項目中更加靈活地使用React。html

本篇是React深刻系列的最後一篇,將介紹開發React應用時,常常用到的模式,這些模式並不是都有官方名稱,因此有些模式的命名並不必定準確,請讀者主要關注模式的內容。前端

1. 受控組件

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

2. 容器組件

容器組件和展現組件是一組對應的概念,關注的是組件邏輯和組件展現的分離。邏輯由容器組件負責,展現組件聚焦在視圖層的展示上。在React 深刻系列2:組件分類中已對容器組件和展現組件做過詳細介紹,這裏再也不贅述。後端

3. 高階組件

高階組件是一種特殊的函數,接收組件做爲輸入,輸出一個新的組件。高階組件的主要做用是封裝組件的通用邏輯,實現邏輯的複用。在React 深刻系列6:高階組件中已經詳細介紹太高階組件,這裏也再也不贅述。bash

4. Children傳遞

首先,這個模式的命名可能並不恰當。這個模式中,藉助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

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感興趣的同窗不妨去了解下。 購買地址: 噹噹 京東


歡迎關注個人公衆號:老幹部的大前端

alt
相關文章
相關標籤/搜索