React Hooks + Context打造簡易redux

HookReact 16.8的新特性,它可以讓在不編寫class類組件的情況下使用state以及其他的React特性;而ContextReact16.3版本里面引入新的Context API,在以往React版本中存在一個(gè)Context API,那是一個(gè)幕后試驗(yàn)性功能,官方提議避免使用,Redux的原理就是建立在舊的Context API。現(xiàn)在新的Context ApI提供了一個(gè)無需為每層組件手動(dòng)添加 props,就能在組件樹間進(jìn)行數(shù)據(jù)傳遞的方法,為數(shù)據(jù)通訊另辟蹊徑。

Context簡介

為解決多層嵌套不同層級(jí)組件之間props數(shù)據(jù)傳遞,這種數(shù)據(jù)傳遞及其繁雜,而且后期不易進(jìn)行維護(hù),為避免driling式數(shù)據(jù)通訊,可以采用redux進(jìn)行數(shù)據(jù)通訊。在新版本React 16.8.6Context為我們帶來新的通訊方式。

Context API組成部分

  • React.createContext函數(shù):創(chuàng)建context上下文,參數(shù)是一個(gè)默認(rèn)值(需要傳遞state數(shù)據(jù)),state可以是Object、Array或者基本類型數(shù)據(jù)。

  • Provider:由React.createContext創(chuàng)建返回對(duì)象的屬性。在Redux vs. The React Context API中比喻成構(gòu)建組件樹中的電子總線比較形象。

  • Consumer:由React.createContext創(chuàng)建返回對(duì)象的屬性。比喻接入電子總線獲取數(shù)據(jù)。

Context vs redux

Contextcontext.Provider/Context.Consumerreduxprovider/connect非常相似。Context采用的是生產(chǎn)者消費(fèi)者的模式,我們可以利用高階函數(shù)(Hoc)模擬實(shí)現(xiàn)一個(gè)redux

redux是通過dispatch一個(gè)action去修改store數(shù)據(jù);在React 16.8.6版本的React hooks提供的useredcuersuseContext為我們更方便通過Context+hooks的形式去打造一個(gè)屬于自己redux

Context 簡單例子

Context設(shè)計(jì)目的是為了共享那些對(duì)于一個(gè)組件樹而言是“全局”的數(shù)據(jù),例如當(dāng)前認(rèn)證的用戶、主題或首選語言。

  • Class.contextType

掛載在class上的 contextType 屬性會(huì)被重賦值為一個(gè)由 React.createContext() 創(chuàng)建的 Context 對(duì)象。這能讓你使用 this.context 來消費(fèi)最近 Context 上的那個(gè)值。你可以在任何生命周期中訪問到它,包括render 函數(shù)中。

  • Context.Consumer

讓你在函數(shù)式組件中完成訂閱 context。這需要函數(shù)作為子元素(function as a child)這種做法。這個(gè)函數(shù)接收當(dāng)前的 context 值,返回一個(gè) React節(jié)點(diǎn)。傳遞給函數(shù)的value值等同于往上組件樹離這個(gè) context最近的Provider 提供的 value 值。如果沒有對(duì)應(yīng)的 Provider,value 參數(shù)等同于傳遞給createContext()defaultValue

// Context 可以讓我們無須明確地傳遍每一個(gè)組件,就能將值深入傳遞進(jìn)組件樹。
// 為當(dāng)前的 theme 創(chuàng)建一個(gè) context(“l(fā)ight”為默認(rèn)值)。
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 使用一個(gè) Provider 來將當(dāng)前的 theme 傳遞給以下的組件樹。
    // 無論多深,任何組件都能讀取這個(gè)值。
    // 在這個(gè)例子中,我們將 “dark” 作為當(dāng)前的值傳遞下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中間的組件再也不必指明往下傳遞 theme 了。
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 讀取當(dāng)前的 theme context。
  // React 會(huì)往上找到最近的 theme Provider,然后使用它的值。
  // 在這個(gè)例子中,當(dāng)前的 theme 值為 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}
// 也可以按照這種形式獲取
function ThemedButton(){
    return (
      <ThemeContext.Counsumer>
        {theme =>(
         <Button theme={theme} />
         )
        }
       </ThemeContext.Counsumer>
      );
}

context的詳細(xì)用法可以參考 Context文檔

React Hooks

React HooksReact 16.8.6版本為函數(shù)式組件添加了在各生命周期中獲取stateprops的通道??勺屇诓痪帉戭惖那闆r下使用 state(狀態(tài)) 和其他 React 功能。不再需要寫class組件,你的所有組件都將是Function。如果想了解更多關(guān)于React hooks信息可以參考Hooks API 參考。

基礎(chǔ)鉤子API

  • useState:獲取組件state狀態(tài)數(shù)據(jù),第一個(gè)參數(shù)是保存的數(shù)據(jù),第二參數(shù)是操作數(shù)據(jù)的方法,類似于setState??捎?code>ES6的數(shù)組解構(gòu)賦值來進(jìn)行獲取。
  • useEffect: 網(wǎng)絡(luò)請(qǐng)求、訂閱某個(gè)模塊、DOM操作都是副作用,useEffect是專門用來處理副作用的。在class類組件中,componentDidMountcomponentDidUpdate生命周期函數(shù)是用來處理副作用的。
  • useContext:useContext可以很方便去訂閱context的改變,并在合適的時(shí)候重渲染組件。例如上面的函數(shù)式組件中,通過Consumer的形式獲取Context的數(shù)據(jù),有了useContext可以改寫成下面:
function ThemedButton(){
    const value = useContext(ThemeContxet);
    return (
         <Button theme={value} />
      );
}

useReducers API

如果習(xí)慣了redux通過reducer改變state或者props的形式,應(yīng)該比較很好上手useReducersuseReducersuseContext是這篇文章比較重點(diǎn)的API。

  • useReducersuseReducers可以傳入三個(gè)參數(shù),第一個(gè)是自定義reducer,第二參數(shù)是初始化默認(rèn)值,第三個(gè)參數(shù)是一個(gè)函數(shù),接受第二個(gè)參數(shù)進(jìn)行計(jì)算獲取默認(rèn)值(可選)。
const [state,dispatch] = useReducer(reducer,initialValue)

下面是useReducers官方示例:

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'reset':
      return initialState;
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      // A reducer must always return a valid state.
      // Alternatively you can throw an error if an invalid action is dispatched.
      return state;
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, {count: initialCount});
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'reset'})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

簡易版Redux

redux具有Provider組件,通過store進(jìn)行傳值。在這里,我們使用Context模擬實(shí)現(xiàn)Providerstore傳值,完整代碼可以參考 simple-redux。

封裝Provider組件

代碼中的storeContextReact.createContext()函數(shù)創(chuàng)建的Context對(duì)象。this.props.store是模擬通過store傳值操作。

import React,{Component} from 'react';
import {storeContext} from './store';
export default class Provider extends Component{
    render(){
        return (
            <storeContext.Provider value={this.props.store}>
                {this.props.children}
            </storeContext.Provider>
        )
    }
}

store數(shù)據(jù)管理

store文件,包括reducer、Context的創(chuàng)建,initialStatereducers的定義。

import React from 'react';
export const storeContext = React.createContext();
export const initialState = {
    user:'kiwis',
    age:23
}
export const reducer = (state, action)=>{
    switch (action.type) {
      case 'CHANGENAME':
        return {user:'harhao',age:24}
      default:
        return initialState;
    }
}

App.js入口

在根組件App.js中,使用React hooksd的useReducer鉤子函數(shù),返回更改statedispatch函數(shù)。然后把store數(shù)據(jù)和dispatch傳遞進(jìn)封裝的Provider組件中。

import React,{useReducer} from 'react';
import Provider from './views/Provider';
import Child from './views/child';
import {initialState as store,reducer} from './views/store';
import './App.css';

function App() {
  const [state,dispatch] = useReducer(reducer,store);
  return (
    <div className="App">
      <Provider store={{state,dispatch}}>
        <Child/>
      </Provider>
    </div>
  );
}
export default App;

Child子組件

App.js的子組件Child中,通過useContext獲取傳遞的數(shù)據(jù)statedispatch。在redux中通過connect高階函數(shù)來傳遞數(shù)據(jù)。這里可以在useContext外包裹一層函數(shù),更好模擬實(shí)現(xiàn)與connect相似的語法。

import React,{useContext} from 'react';
import {storeContext} from './store';
import DeepChild from './deepChild';
function Child() {
    const {state,dispatch}= useContext(storeContext);
    return (
        <div className="child">
            <p>姓名:{state.user}</p>
            <p>年齡:{state.age}</p>
            <button onClick={()=>dispatch({type:'CHANGENAME'})}>changeName</button>
            <p>deep child:</p>
            <DeepChild/>
        </div>

    );
}

export default Child;

DeepChild(孫組件)

Child子組件中,引入DeepChild組件。通過useContext獲取頂層最近的state數(shù)據(jù)。

import React,{useContext} from 'react';
import {storeContext} from './store';
export default function DeepChild(){
    const {state} = useContext(storeContext);
    return (
        <div>
            {state.user}
        </div>
    )
}

運(yùn)行效果

child子組件和DeepChild孫組件通過useContext獲取頂層數(shù)據(jù),最終運(yùn)行效果如下所示:

demo

如果喜歡可以給個(gè)贊或星

git地址:https://github.com/Harhao/simple-redux

參考文章

React中文文檔

[譯]2019 React Redux 完全指南

[譯] Redux vs. React 的 Context API

React Hooks 解析(上):基礎(chǔ)

React Hooks 解析(下):進(jìn)階

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容