這樣使用React Context,提升你的工做效率

寫在前面

無論你是寫React的一兩年的程序員老司機,仍是剛剛接觸React的小白程序員,若是你想掌握React而且使用它來解決各類各樣的需求,那麼你就必定須要掌握React Context。html

在我沒有掌握Context以前,老是感受這種技術是多餘的,在項目中使用它徹底是多此一舉,甚至能夠說是捨近求遠。可是當我結合一些具體的業務深刻學習它以後——嗯~,Context真香。react

Context是什麼?該怎麼使用?能不能用在class組件和function組件中?有哪些具體的業務場景?程序員

我結合本身的學習心得,在這篇博客中跟你們一一分享。天太熱,準備好西瓜,我們開始吧!api

Context是什麼

Context直譯過來就是「上下文」的意思,咱們能夠把它理解成代碼所處的環境,若是你的一段代碼在某一個「上下文」中,那麼能夠在代碼中訪問這個「上下文」中的一些變量和方法,即便這些變量和方法沒有在你的這段代碼中進行定義。markdown

作一個類比,對於一個學生來說,他的上下文就是學校,那麼他可使用學校圖書館中的資源,即便這些資源不是他本身的。ide

在React中也是同樣,咱們能夠給一個React組件指定一個上下文,在這個上下文中定義一些變量和方法,那麼在這個組件中就能夠直接使用這些變量和方法了。函數

若是咱們給全部的組件中都設置成一樣的上下文,會怎麼樣呢?固然是全部的組件就均可以共享一樣的一批數據,這就是Redux庫所提供的功能。oop

該怎麼使用Context

瞭解了Context以後,咱們要怎麼使用Context呢?學習

咱們先假設一個使用場景:以下圖所示,項目中有App根組件,它下面有一個Page組件,Page組件中又分別有Header, Body, Footer三個組件。ui

image.png

這些組件中有不一樣的內容,項目中要有一個能夠一鍵更改主題顏色的功能。具體場景以下圖所示:

這個時候,咱們可使用Context一步步來完成這些需求。若是你想直接查看demo代碼,能夠點擊:codesandbox.io/s/react-con…

首先咱們須要定義一個Context對象,在項目中新建ContextData.js文件,代碼以下:

import React, { createContext } from "react";
export const ContextData = createContext("light");
複製代碼

這裏使用createContext這個api建立的Context對象,在建立的時候,咱們須要傳給它一個默認值。這個默認值基本沒有什麼做用,後面在使用的時候,會把它給覆蓋掉。不過它也不是一無可取,後面咱們會講到會在什麼狀況下用到了

Context對象建立以後,若是咱們在控制檯中把它打印出來,能夠看到它有這些屬性:

{
  $$typeof: xxxx,
  **Consumer**: xxxx,
  **Provider**: xxxx,
  _calculateChangedBits: null,
  _currentRenderer: {},
  _currentRenderer2: null,
  _currentValue: "light",
  _currentValue2: "light",
  _threadCount: 0
}
複製代碼

要着重介紹ConsumerProvider屬性,這兩個屬性是兩個React組件,翻譯過來就是「生產者組件」和「消費者組件」。

Consumer生產者組件一般是一個父組件,咱們定義的其餘組件要放在它的裏面,成爲它的子節點或後代節點。Consumer組件用來提供共享的數據,咱們能夠理解成它定義一個上下文,這樣一來,他的後代節點就可使用上下文中的變量和方法了。

Provider消費者節點一般是一個或多個子節點,這些節點可使用生產者節點提供的上下文中的數據。

Context對象定義以後,咱們就能夠在其餘組件中引入並使用它,咱們在App組件中使用它,代碼以下:

import React from 'react';
import Page from "./Page.jsx";
import { ContextData } from './ContextData';  // 引入Context對象

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      themeColor: "orange"
    }
  }

  changeThemeColor = (colorName) => {
    this.setState({
      themeColor: colorName
    })
  }

  render() {
	  // 定義上下文中的數據,這裏不只能夠定義變量,還能夠定義方法
    const contextValue = {
      changeThemeColor: this.changeThemeColor,
      themeColor: this.state.themeColor
    }

    return (
      <ContextData.Provider value={contextValue}> <div> <p>Please select your theme color</p> <button onClick={() => { this.changeThemeColor("red") }}>Red</button> <button onClick={() => { this.changeThemeColor("green") }}>Green</button> <button onClick={() => { this.changeThemeColor("yellow") }}>Yellow</button> </div> {/* 這裏須要把Page組件包裹進來 */} <Page /> </ContextData.Provider>
    );
  }
}
export default App;
複製代碼

在上述代碼中咱們能夠看到,須要使用<ContextData.Provider value={contextValue}>組件包裹其餘組件,這裏須要指定value屬性,這個屬性就是上下文中的數據,它的子元素中所共享的變量就是這個屬性。

同時咱們也把Page組件做爲<ContextData.Provider value={contextValue}>組件的子節點,這樣在Page節點及其子結點中使用上下文中的數據了。Page組件的代碼以下:

import React, { Component } from "react";
import Header from './Header.jsx';
import Body from './Body.jsx';
import Footer from './Footer.jsx';

class Page extends Component {

  constructor(props) {
    super(props);
  }

  render() {
    return (<> <Header /> <Body /> <Footer /> </>);
  }
}

export default Page;
複製代碼

咱們能夠直接在Page組件中使用上下文的數據,可是這裏沒有,而是引入了子節點,咱們在它的子節點中使用上下文數據是同樣的。這裏以Header組件爲例,代碼以下:

import React, { Component } from "react";
import { ContextData } from "./ContextData.js";  // 這裏要引入Context對象

class Header extends Component {
	// 這一行代碼很重要,經過class的靜態屬性contextType,咱們能夠把上下文數據綁定到該class實例的context屬性
  static contextType = ContextData; 

  constructor(props) {
    super(props);
  }
  
  render() {
		// 經過this.context來使用上下文中的數據
    return <div style={{ backgroundColor: this.context.themeColor }}> This is the Header component, the whole theme color is {this.context.themeColor} </div>
  }
}

export default Header;
複製代碼

Header是class形式定義的組件,可是如今愈來愈推崇使用function組件,那麼該如何在function組件中使用上下文中的變量呢?這裏咱們能夠參考Body組件中的代碼:

import React from "react";
import { ContextData } from "./ContextData.js";  // 這裏要引入Context對象

const Body = () => {
	// 這裏須要使用消費者組件包裹,而且須要一個函數做爲子元素
  return <ContextData.Consumer> { contextValue => { const { themeColor, changeThemeColor } = contextValue; return <button onClick={() => { changeThemeColor("blue") }} style={{ backgroundColor: themeColor }} > Blue Color </button> } } </ContextData.Consumer>;
}

export default Body;
複製代碼

function組件中沒有靜態屬性contextType,須要使用消費者組件<ContextData.Consumer>結合一個函數的形式來獲取上下文中的數據,而且這個函數要返回一個React節點。

傳遞給函數的value值等價於組件樹上方離這個context最近的 Provider 提供的 value 值。若是沒有對應的Provider,value參數等同於傳遞給createContext()的defaultValue。

咱們還能夠看到一個有趣的事情,在Body組件中,咱們不只可使用上下文問中的變量,還可使用上下文中的方法,來修改上下文中的變量,這一點能夠支持在後代節點中,修改祖先節點中的數據。

在function組件中,除了上述使用<ContextData.Consumer>以外,還有另一種方式上下文數據的方式,那就是使用useContext這一鉤子,咱們能夠參考Footer組件中的代碼:

import React, { useContext } from "react";
import { ContextData } from "./ContextData.js";  // 這裏要引入Context對象

const Footer = () => {
  const context = useContext(ContextData);  // 把整個context對象傳給useContext 

	// 而後就能夠直接訪問上下文中的變量和方法了
  const handleChangeThemeColor = () => {
    context.changeThemeColor("gray")
  }

  return <div onClick={handleChangeThemeColor} style={{ color: context.themeColor }}> This is the footer text </div>
}

export default Footer;
複製代碼

兩種方式比較下來,我比較推薦第二種,寫法更簡潔,並且如今React官方也是比較鼓勵使用Hooks。

寫在最後

關於Context的學習心得就分享結束了,若是你想了解更多,能夠查看React的官方文檔,寫得仍是很是清楚的:

Context文檔:zh-hans.reactjs.org/docs/contex…

useContext文檔: zh-hans.reactjs.org/docs/hooks-…

相關文章
相關標籤/搜索