【面試官】 你必定用過React中的Redux,來講說吧

原文收錄在github 倉庫 (包含小demo) github地址:點這裏 react

1、Redux

1. redux的概述

通用的狀態管理輔助工具,習慣上咱們能夠結合ReactJs 來使用,在組件化開發過程當中,組件的數據狀態不得不集中化管理,這也是咱們使用Redux的緣由之一,是一個數據的容器。習慣上咱們稱之爲js庫ios

2 . 三大原則

  • 單一數據源,惟一的狀態倉庫
  • state是隻讀 (派發action)
  • 純函數執行修改數據的修改 (編寫 reducers)

3 . 組成部分

  • state
    • 服務端的數據
    • UI數據
    • app state
  • Action
  • Reducer
  • Store

Action

action顧名思義動做行動 行爲,一言以蔽之,它是把數據從應用傳到倉庫的一個動做,也就是這個數據倉庫git

  • JS對象es6

  • 格式github

    {
        type:ADD, // type 字段是約定,你們的約定,表示動做的名字,
        index:1,
        content:'這是一點內容'
    }
    複製代碼
    • 對象的type字段表示執行的動做;字符串常量
    • index 惟一的ID,主要是一個標識
    • content(或者其餘)JS對象什麼字段均可以啊,一個字段而已

在實際的開發中咱們習慣上是action建立函數typescript

const addAction = (params)=>{
    return {
        type:ADD,
        ...params
    }
}
複製代碼

Reducer

如今咱們依舊不說store 這個概念,如今動做有了,可是action它只是描述了一下這個動做,但並不知道咋更新數據,提到數據,咱們假使redux

{
    num:0
}
複製代碼

這個簡單的js對象就是數據axios

ACTION是個普通的對象;REDUCER是個普通的函數api

  • 說普通也不普通,js的函數而已app

    • function(a,b){
          console.log(a+b)
      }
      複製代碼
    • 可是沒那麼簡單

  • 乾淨簡單,

  • // 默認的數據
    const initData = {
        num:123
    }
    // reducer
    const counterReducer =(state=initData,action)=>{
        
      // 啥也不幹,返回傳進來的state(此時默認的initData)
       return state
    }
    
    複製代碼
  • 怎麼可能啥也不幹呢

import { addAction } from "./actions";
// 默認的數據
const initData = {
  num: 123
};
// reducer
const counterReducer = (state = initData, action) => {
  // 判斷傳入的動做是什麼類型
  switch (action.type) {
    case addAction:
      return Object.assign({}, state, {
        ...
      });
    default:
      return state;
  }

  // 啥也不幹,返回傳進來的state(此時默認的initData)
  // return state;
};


複製代碼

注意

  • 不能修改傳進來的數據
  • 在默認狀況下,必定得返回舊的state

Store

  • 這就是那個狀態倉庫,維持狀態
  • getState() 方法獲取state
  • 提供 dispatch ()方法發送action
  • 經過subscribe()來註冊監聽

獲取狀態

getState()
複製代碼

更新狀態

dispatch(action) 
複製代碼

也就是咱們說的派發一個動做

註冊監聽(訂閱)

subscribe(listener)
複製代碼

4 . 簡單案例

在這個時候,有個問題,前邊說的這一切,那咱們該怎麼來建立這個倉庫呢

yarn add redux
複製代碼

這個庫裏就有方法,也就是咱們常說的redux

構建action

import { ADD_TYPE } from './actionTypes'
const addAction = (params)=>{
    return {
        type:ADD_TYPE,
        ...params
    }
}

export {
    addAction
}
複製代碼

構建reducer

import { addAction } from "./actions";
// 默認的數據

// reducer
const counterReducer = (state = {num:123}, action) => {
  // 判斷傳入的動做是什麼類型
  switch (action.type) {
    case addAction:
      return Object.assign({}, state, action);
    default:
      return state;
  }

  // 啥也不幹,返回傳進來的state(此時默認的initData)
  // return state;
};

export {
    counterReducer
}
複製代碼

建立store

引入文件

import { createStore } from "redux";
import { counterReducer } from "./reducers";
複製代碼

createStore

const store = createStore(counterReducer);
export default store
複製代碼

派發action

const handleClick = ()=>{
    console.log(`點擊了按鈕`)
    const action = addAction({num:'456'})
    store.dispatch(action)
}
複製代碼

監聽

useEffect(() => {
        store.subscribe(()=>{
            console.log('-----',store.getState())
        })
    }, [])
複製代碼

訂閱狀態的變動

const render = ()=>{
    ReactDom.render( <App/>, document.querySelector('#root') ) }
// 上來的時候先渲染一次
render() 
// 訂閱變動,每當數據發生的變化的時候,就從新渲染
store.subscribe(render)
複製代碼

小結

經過一個簡單的案例,咱們知道一個簡易的流程:

  1. 首先構建一個action 返回一個對象必須有type屬性
  2. 構建reducer 響應action t經過return 把數據傳回store
  3. 利用redux這個庫來建立一個store 傳遞寫好的reducer
  4. 利用的$store.subscribe() 註冊監聽
  5. 能夠經過store.getState() 取值

二 、React-Redux

那在如上咱們使用的redux 這個庫看起來是沒有問題,可是

  • 首先要導入store
  • 而後註冊監聽
  • 而後組件銷燬的時候,咱們取消監聽

這一波流的操做在每一個組件都要走一遍,顯然是十分繁瑣和重複的,這就須要看誰能不能幫幫我,這就是react-redux 若是須要把redux整合到react 中來使用就須要react-redux

1. 什麼是react-redux

  • redux 官方出品

  • 可以更好的結合react 來管理數據

Provider 組件

  • 包裹在最外層的組件以外,這樣可使全部的子組件均可以拿到state
  • 接收store 做爲props 經過context 傳遞

connect 方法

  • 組件內部獲取store 中的state
  • 經過connect 增強

mapStateToProps(state,ownProps)

const mapStateToProps = (state, ownProps) => {
    console.log(state)
    return state
    // return {
    // prop: state.prop
    // }
}
複製代碼

mapDispathToProps(dispath,ownProps)

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    sendAction: () => {
      dispatch({
        type: "ADD_TYPE",
      });
    },
  };
};
複製代碼

2. 使用

  • 安裝相關的依賴

  • 構建store 和readucer

  • Provider組件實現

<>
      <Provider store = {store}>
        <List></List>
        <Detail></Detail>
      </Provider>
    </>
複製代碼
  • connect

combineReducers

  • 函數,接收一個參數
  • 拆分reducer
import { createStore, combineReducers } from "redux";
// import { counterReducer } from "./reducers";
// import rootReducer from './reducers/index'

import { infoReducer } from "./reducers/infoReducer";
import { listReducer } from "./reducers/listReducer";

const reducer = combineReducers({
  infoReducer,
  listReducer,
});

// 構建store
const store = createStore(reducer);
export default store;

複製代碼

建立組件

  • ComA A組件

    import React, { Component } from "react";
    import { connect } from "react-redux";
    class ComA extends Component {
      handleClick = () => {
        this.props.getInfo();
      };
    
      render() {
        return (
          <div>
            {/* <h3>{this.props.}</h3> */}
            <button onClick={this.handleClick}>獲取信息</button>
          </div>
        );
      }
    }
    
    const mapStateToProps = (state, ownProps) => {
      console.log(state.infoReducer);
      // return {
      //   prop: state.prop,
      // };
      // return state
      return {
        ...state.infoReducer,
      };
    };
    const mapDispatchToProps = (dispatch, ownProps) => {
      return {
        getInfo: () => {
          const actionCreator = {
            type: "GET_INFO",
          };
    
          dispatch(actionCreator);
        },
      };
    };
    export default connect(mapStateToProps, mapDispatchToProps)(ComA);
    
    複製代碼
  • ComB

    import React, { Component } from "react";
    import { connect } from "react-redux";
    
    class ComB extends Component {
      handleClick = () => {
        this.props.getList();
      };
      render() {
        return (
          <div>
            <button onClick={this.handleClick}>獲取列表</button>
          </div>
        );
      }
    }
    
    const mapStateToProps = (state, ownProps) => {
      console.log(state.listReducer)
      // return state
      return {
        ...state.listReducer
      }
    };
    
    const mapDispatchToProps = (dispatch, ownProps) => {
      return {
        getList: () => {
          const actionCreator = {
            type: "GET_LIST",
          };
          dispatch(actionCreator);
        },
      };
    };
    
    
    export default connect(mapStateToProps, mapDispatchToProps)(ComB);
    
    複製代碼

  • infoReducer.js

    const info = {
      name: "yayxs",
    };
    
    const infoReducer = (state = {}, action) => {
      switch (action.type) {
        case "GET_INFO":
          return {
            ...info,
          };
    
        default:
          return state;
      }
    };
    
    export {
      infoReducer
    }
    複製代碼
  • listReducer

    const listArr = [
      {
        id: 1,
        con: "耳機",
      },
    ];
    
    const listReducer = (state = {}, action) => {
      switch (action.type) {
        case "GET_LIST":
          return {
            listArr: [...listArr],
          };
    
        default:
          return state;
      }
    };
    
    export {
      listReducer
    }
    複製代碼

3、Redux-Saga

無論怎麼說,如上說起數據流操做只支持同步的操做,實現異步的話就須要中間件

1. 中間件

  • 自己就是一個函數
  • 應用在action 發佈出去以後

2 . 概述

  • 用來管理反作用,其中包括像異步操做 ,讓反作用的執行更加簡單
  • es6的語法,參考阮老師

3. createSagaMiddleware

其中源碼是這樣的

export default function createSagaMiddleware<C extends object>(options?: SagaMiddlewareOptions<C>): SagaMiddleware<C> export interface SagaMiddlewareOptions<C extends object = {}> {
  /** * Initial value of the saga's context. */
  context?: C
  /** * If a Saga Monitor is provided, the middleware will deliver monitoring * events to the monitor. */
  sagaMonitor?: SagaMonitor
  /** * If provided, the middleware will call it with uncaught errors from Sagas. * useful for sending uncaught exceptions to error tracking services. */
  onError?(error: Error, errorInfo: ErrorInfo): void
  /** * Allows you to intercept any effect, resolve it on your own and pass to the * next middleware. */
  effectMiddlewares?: EffectMiddleware[]
}
複製代碼

導入

import createSagaMiddleware from "redux-saga";
複製代碼

構建store

const store = createStore(sagaReducer, {}, applyMiddleware(sagaMiddleware));
複製代碼
  • 第一個參數是reducer
  • 第二個initState
  • 第三個參數:中間件

執行

sagaMiddleware.run(defSage);
複製代碼

4. 案例

saga 的輔助函數

  • takeEvery

  • takeLatest

  • throttle

  • SagaCom

handleClick = (type) => {
   
    switch (type) {
      case "takeEvery":
        this.props.dispatch({
          type: "takeEvery",
        });
        break;
      case "takeLatest":
        this.props.dispatch({
          type: "takeLatest",
        });
        break;

      case "throttle":
        this.props.dispatch({
          type: "throttle",
        });
        break;

      default:
        break;
    }
  };
複製代碼
  • sages/index.js
import {
  takeEvery,
  takeLatest,
  throttle,
  select,
  call,
} from "redux-saga/effects";

import axios from "axios";
export function* defSage() {
  yield takeEvery("takeEvery", function* () {
    const state = yield select((state) => state.payload);

    const res = yield call(
      axios.post,
      `http://rap2.taobao.org:38080/app/mock/249413/mock-api/v1/users/login`,
      {
        ...state,
      }
    );

    console.log(res);
  });
  // 最後的一次,取消正在運行中
  yield takeLatest("takeLatest", function* () {
    const state = yield select((state) => state.payload);

    const res = yield call(
      axios.post,
      `http://rap2.taobao.org:38080/app/mock/249413/mock-api/v1/users/login`,
      {
        ...state,
      }
    );

    console.log(res);
  });
  /** * 毫秒值 */
  yield throttle(0, "throttle", function* () {
    const state = yield select((state) => state.payload);

    const res = yield call(
      axios.post,
      `http://rap2.taobao.org:38080/app/mock/249413/mock-api/v1/users/login`,
      {
        ...state,
      }
    );

    console.log(res);
  });
}

複製代碼

effect 建立器

詳細的api 用法能夠參考官方文檔

  • select
  • call
  • take
  • put

業務流程

獲取數據

  • 當頁面一加載,而後發送一個獲取數據的action
  • reducer 匹配對應的action 若是是一部的action 直接把數據返回
  • 在saga 裏使用 takeEvery 來進行監聽
  • call方法調用異步請求,傳入請求的參數
  • put反作用發送action 成功或者是失敗
  • 在reducer 裏處理action

生命週期

  • componentDidMount獲取數據
  • componentWillUpdate 處理數據

4、思考

  1. Hooks API ,也就是函數式的組件怎麼監聽頁面數據的變化 ,而後執行刷新?
  2. redux-saga 中的輔助函數 takeEvery takeLatest throttle 在底層有什麼區別?

感謝你看到這,不妨給個星星,感謝

相關文章
相關標籤/搜索