React數據共享的三種方法


在典型的React應用中,數據是通過props屬性自上而下(由父及子)進行傳遞的。

 

但是,在實際開發中,我們發現有某些屬性是許多組件都需要的,那么通過組件樹逐層傳遞props就會特別繁瑣,且不容易維護。

 

所以總結以下三種不用逐層傳遞props,也可以獲得共享數據的方法:

 

1、Context

 

版本: 從React 16.3.0開始,加入了Context API。

 

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

API: 具體可查閱官方文檔https://zh-hans.reactjs.org/docs/context.html#reactcreatecontext

React.createContext

Context.Provider

Context.contextType

Context.Consumer

Context.displayName

  

 

應用場景: 很多不同層級的組件需要訪問同樣一些數據。 例如當前主題、locale等。

 

應用:Context共享當前主題

 

ThemeContext.js

 

import { createContext } from "react";

const ThemeContext = createContext();

ThemeContext.displayName = "themeContext";

export default ThemeContext;

App.js

 

import React, { Component } from "react";

import ThemeContext from "./ThemeContext";

import ClearButton from "./ClearButton";

class App extends Component {

  render() {

    return (

      <ThemeContext.Provider value="dark">

        <ClearButton />

      </ThemeContext.Provider>

    );

  }

}

ClearButton.js

 

import React, { Component } from 'react';

import ThemeContext from "./ThemeContext";

class ClearButton extends Component{

    componentDidMount(){

        console.log(this.context)

    }

    handleClear=()=>{

 

    }

    render() {

        return (

              <Tooltip placement="bottom" title="清空畫布">

                <span className="handlerbar-icon-box">

                  <a

                    className={`${this.context} top-icon-button clear-icon`}

                    onClick={this.handleClear}

                  ></a>

                </span>

              </Tooltip>

            )

          }

    );

  }

}

ClearButton.contextType = ThemeContext;

export default ClearButton;

  

掛載在class上的contextType屬性會被重新賦值為一個由React.createContext()創建的Context對象,這可以使我們使用this.context來拿到最近Context上的那個值,並且可以在任何生命周期中訪問到它,包括在render函數中。

 

如果你正在使用public class fields語法,你可以使用static這個類屬性來初始化你的contextType.如下:

 

class ClearButton extends Component{

  static contextType = ThemeContext; 

  render(){

    console.log(this.context)

    ....

  }

}

  

如果是函數式組件,你該怎么訂閱context呢?

可以使用Context.Consumer,他需要函數作為子元素, 這個函數接收當前的context值,返回一個React節點

 

 <ThemeContext.Consumer>

        {value => (

            <span>{value}</span>

        )}

 </ThemeContext.Consumer>

 

  

 

在React DevTools中可看到如上圖, themeContext就是我們為了好區分其他的Context而起的名字

ThemeContext.displayName = "themeContext"

React DevTools使用該字符串來確定context要顯示的內容。

 

應用:react-redux中的context

 

從React 16.3.0開始,Context API包含了2個特殊的組件<Provider>和<Consumer>。如果用過react-redux,會覺得其中的一個很熟悉。那就是<Provider>。因為react-redux也提供了一個叫<Provider>的組件。實際上,react-redux和react提供了兩個大致上一樣的東西。

 

react-redux是使用了React的Context特性使Redux的Store共享給嵌入的組件。react-redux中,唯一默認的Context對象實例被React.createContext()創建,被叫做ReactReduxContext. 

react-redux中Provider.js中部分代碼:

 

 

react-redux的<Provider>組件用<ReactReduxContext.Provider>把Redux的store數據放入Context 中,connect用<ReactReduxContext.Consumer>拿到這些值進行一些更新處理。

react-redux中ConnectAdvanced.js中部分代碼:

 

 

 

在使用Context 之前請先考慮下面2點:

 

Context如上所說主要的應用於很多不同層級的組件需要訪問同樣一些數據。 所以請謹慎使用,因為這會使組件的復用性變差。

 

如果你只是想避免層層傳遞一些屬性,組件組合(Component composition)有時候是一個比context 更好的解決方案。所以請繼續看第二種方法吧!

 

2、component composition

 

應用場景: 子組件與父組件解耦。

 

組件可以接收任意props,包括基本的數據類型(Number、String、Boolean、Array、Object)、React元素以及函數。

 

這句話很重要,尤其是props可以是React元素。要先有這個意識才能自己寫出來這樣的組件。

 

下面這張草圖是商城類的網站,可以先考慮一下要怎樣才能寫出比較好的組件。

 

 

 

這三個List,普通(例如首頁、分類等)的商品列表、訂單中的商品列表、購物車里的商品列表,每一個Item很像卻又有不一樣的地方。用組合組件的思路,我會封裝成3個組件,如下:

 

一個通用的商品Card組件,但是它的header、leftContent、rightContent、footer是可以作為React元素通過Props傳進來,正如我們在眾多的UI框架中看到的slot.

Stepper組件:用戶選擇、增減商品數量

 

Button組件:cancel、退貨按鈕等風格統一的按鈕。

Stepper、Button、checkBox作為React元素,通過rightContent、footer、leftContent的props屬性傳給Card組件。自從有了這個思路,我很鄙視我之前的做法,因為不是真正的可復用封裝。瞅一下以前的做法:那就是List里面嵌套Card,Card組件里面又硬編碼嵌套了Stepper組件,比如說現在要根據list組件里面的某個值,判斷Stepper組件的UI狀態,那么你可能就需要List通過props傳給Card,Card又通過props傳給Stepper組件。而通過把Stepper組件作為prop(react元素)動態的傳給Card,那么Stepper組件就會和Card處於同一級別的組件,List=>Card+Stepper這樣就不會出現props的多層傳遞。

 

3、render props

 

render prop是指一種在 React 組件之間使用一個值為函數的 prop 共享代碼的簡單技術,更具體地說,render prop 是一個用於告知組件需要渲染什么內容的函數 prop。

 

重要的是要記住,render prop 是因為模式才被稱為render prop ,你不一定要用名為render的 prop 來使用這種模式。事實上,任何被用於告知組件需要渲染什么內容的函數 prop 在技術上都可以被稱為 “render prop”。

 

<DataProvider render={data => (

<h1>Hello {data.target}</h1>

)}/>

<Mouse children={mouse => (

  <p>鼠標的位置是 {mouse.x},{mouse.y}</p>

)}/>

  

它和組件組合component composition不同的是:子組件需要在渲染前和父組件進行一些交流,例如子組件要分享父組件中的state。

 

使用 render prop 的庫有React Router

 

下面是官網的一個貓追逐鼠標的例子,貓追逐鼠標,自然要監聽鼠標的位置x,y。雖然Cat作為子組件硬編碼到Mouse中,但是它不是一個真正的可復用的封裝。我們可以提供一個帶有prop函數的<Mouse>組件,它能夠動態決定什么需要渲染的,並把state作為參數傳給prop函數。

 

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}>

 

      {/*

        Instead of providing a static representation of what <Mouse> renders,

        use the `render` prop to dynamically determine what to render.

      */}

      {this.props.render(this.state)}

    </div>

  );

}

}

 

class MouseTracker extends React.Component {

render() {

  return (

    <div>

      <h1>移動鼠標!</h1>

      <Mouse render={mouse => (

        <Cat mouse={mouse} />

      )}/>

    </div>

  );

}

}

  

可以使用帶有 render prop 的常規組件來實現大多數高階組件(HOC)

 

高階組件是一種基於React的組合特性而形成的設計模式,是一個參數是組件,返回值為新組件的函數。例如react-redux中的connectHOC就是一個高階組件,詳細的可以看react-redux源碼。

 

function withMouse(Component) {

return class extends React.Component {

  render() {

    return (

      <Mouse render={mouse => (

        <Component {...this.props} mouse={mouse} />

      )}/>

    );

  }

}

}

  

掌握了以上三種方法,會讓你開發React應用如魚得水。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM