React Hooks + Context打造簡易redux

HookReact 16.8的新特性,它可讓在不編寫class類組件的狀況下使用state以及其餘的React特性;而ContextReact16.3版本里面引入新的Context API,在以往React版本中存在一個Context API,那是一個幕後試驗性功能,官方提議避免使用,Redux的原理就是創建在舊的Context API。如今新的Context ApI提供了一個無需爲每層組件手動添加 props,就能在組件樹間進行數據傳遞的方法,爲數據通信另闢蹊徑。javascript

Context簡介

爲解決多層嵌套不一樣層級組件之間props數據傳遞,這種數據傳遞及其繁雜,並且後期不易進行維護,爲避免driling式數據通信,能夠採用redux進行數據通信。在新版本React 16.8.6Context爲咱們帶來新的通信方式。css

Context API組成部分

  • React.createContext函數:建立context上下文,參數是一個默認值(須要傳遞state數據),state能夠是Object、Array或者基本類型數據。html

  • Provider:由React.createContext建立返回對象的屬性。在Redux vs. The React Context API中比喻成構建組件樹中的電子總線比較形象。java

  • Consumer:由React.createContext建立返回對象的屬性。比喻接入電子總線獲取數據。react

Context vs redux

Contextcontext.Provider/Context.Consumerreduxprovider/connect很是類似。Context採用的是生產者消費者的模式,咱們能夠利用高階函數(Hoc)模擬實現一個reduxgit

redux是經過dispatch一個action去修改store數據;在React 16.8.6版本的React hooks提供的useredcuersuseContext爲咱們更方便經過Context+hooks的形式去打造一個屬於本身reduxgithub

Context 簡單例子

Context設計目的是爲了共享那些對於一個組件樹而言是「全局」的數據,例如當前認證的用戶、主題或首選語言。redux

  • Class.contextType

掛載在class上的 contextType 屬性會被重賦值爲一個由 React.createContext() 建立的 Context 對象。這能讓你使用 this.context 來消費最近 Context 上的那個值。你能夠在任何生命週期中訪問到它,包括render 函數中。api

  • Context.Consumer

讓你在函數式組件中完成訂閱 context。這須要函數做爲子元素(function as a child)這種作法。這個函數接收當前的 context 值,返回一個 React節點。傳遞給函數的value值等同於往上組件樹離這個 context最近的Provider 提供的 value 值。若是沒有對應的 Providervalue 參數等同於傳遞給createContext()defaultValue數組

// Context 可讓咱們無須明確地傳遍每個組件,就能將值深刻傳遞進組件樹。
// 爲當前的 theme 建立一個 context(「light」爲默認值)。
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 使用一個 Provider 來將當前的 theme 傳遞給如下的組件樹。
    // 不管多深,任何組件都能讀取這個值。
    // 在這個例子中,咱們將 「dark」 做爲當前的值傳遞下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

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

class ThemedButton extends React.Component {
  // 指定 contextType 讀取當前的 theme context。
  // React 會往上找到最近的 theme Provider,而後使用它的值。
  // 在這個例子中,當前的 theme 值爲 「dark」。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}
// 也能夠按照這種形式獲取
function ThemedButton(){
    return (
      <ThemeContext.Counsumer>
        {theme =>(
         <Button theme={theme} />
         )
  		}
       </ThemeContext.Counsumer>
      );
}
複製代碼

context的詳細用法能夠參考 Context文檔

React Hooks

React HooksReact 16.8.6版本爲函數式組件添加了在各生命週期中獲取stateprops的通道。可以讓您在不編寫類的狀況下使用 state(狀態) 和其餘 React 功能。再也不須要寫class組件,你的全部組件都將是Function。若是想了解更多關於React hooks信息能夠參考Hooks API 參考

基礎鉤子API

  • useState:獲取組件state狀態數據,第一個參數是保存的數據,第二參數是操做數據的方法,相似於setState。可用ES6的數組解構賦值來進行獲取。
  • useEffect: 網絡請求、訂閱某個模塊、DOM操做都是反作用,useEffect是專門用來處理反作用的。在class類組件中,componentDidMountcomponentDidUpdate生命週期函數是用來處理反作用的。
  • useContext:useContext能夠很方便去訂閱context的改變,並在合適的時候重渲染組件。例如上面的函數式組件中,經過Consumer的形式獲取Context的數據,有了useContext能夠改寫成下面:
function ThemedButton(){
    const value = useContext(ThemeContxet);
    return (
         <Button theme={value} /> ); } 複製代碼

useReducers API

若是習慣了redux經過reducer改變state或者props的形式,應該比較很好上手useReducersuseReducersuseContext是這篇文章比較重點的API

  • useReducersuseReducers能夠傳入三個參數,第一個是自定義reducer,第二參數是初始化默認值,第三個參數是一個函數,接受第二個參數進行計算獲取默認值(可選)。
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進行傳值。在這裏,咱們使用Context模擬實現Providerstore傳值,完整代碼能夠參考 simple-redux

封裝Provider組件

代碼中的storeContextReact.createContext()函數建立的Context對象。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數據管理

store文件,包括reducerContext的建立,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鉤子函數,返回更改statedispatch函數。而後把store數據和dispatch傳遞進封裝的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;
複製代碼

useConnect自定義Hook

useConext外包裹一層函數,更好模擬connect語法。這裏採用自定義React hooks的方法。定義一個自定hook函數useConnect,以下所示:

import {useConext} from 'react';
export default function useConnect(props){
    return useContext(props);
}
複製代碼

Child子組件

App.js的子組件Child中,在redux中經過connect高階函數來傳遞數據。這裏可使用自定義React hooks函數useConnect獲取statedispatch

import React,{useContext} from 'react';
import useConnect from './connect';
import {storeContext} from './store';
import DeepChild from './deepChild';
function Child() {
    const {state,dispatch}= useConnect(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數據。

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

運行效果

child子組件和DeepChild孫組件經過useConnect獲取頂層數據,最終運行效果以下所示:

demo

若是喜歡能夠給個贊~或星~喲
git地址:github.com/Harhao/simp…

參考文章
React中文文檔
[譯]2019 React Redux 徹底指南
[譯] Redux vs. React 的 Context API
React Hooks 解析(上):基礎
React Hooks 解析(下):進階

相關文章
相關標籤/搜索