純手硬擼Redux

前言

當今無論做爲一個前端小白仍是一個資深的前端攻城獅。若是不掌握幾種前端框架(React,Vue,ng),都很差意思出去說本身是作前端。可是面對如此之多的前端框架,尤爲是ReactVue這種純負責UI展現的架子來講。有一件事是繞不開的就是前端的數據存儲問題。html

做爲業界層出不窮的數據處理框架Redux(React的數據存儲框架)又是不得不提起的。 Vue的數據處理通常用Vuex。可是他的設計思路都是基於Redux等前端

因此,有必要看看Redux是如何實現數據存儲,又如何使得存儲的數據被組件獲取,而且組件在觸發CRUD的時候,可以及時更新數據呢。react

咱們就按照Redux的實現原理來剖析一下這些數據存儲框架(Redux,Vuex)的原理。數據庫

接下里咱們會用代碼來實現createStore()(Redux的核心概念)、combineReducers()、連接Redux和React的connect()、Redux的異步中間件Redux Thunkredux

Redux

Redux是什麼

用淺顯的的話來歸納Redux的做用,其實Redux就是一個前端數據庫api

他將前端的全部數據(不論是從後臺返回的仍是前端本身用的數據,這些數據是你想共享的)存入一個變量。這個變量學名叫作Redux Store(是一個js對象)。而且該變量對外界是隻讀的。若是想要改變改變量中的數據,須要發送一個action來經過特定的修改機制來更新Store中的特定的值。bash

用後臺操做數據庫來類比:前端框架

//建立了一個名爲Store的數據庫 
//該操做等同於調用createStore()
CREATE DATABASE Store  

//建立一個名爲Persons的表,該表包含的字段爲Id,LastName,FirstName
//該操做等同在構建Store的時候,初始化state
CREATE TABLE Persons(Id int,LastName varchar(255),FirstName varchar(255))
複製代碼

store中的值發生變化的時候(state的值發生變化),你的程序可以察覺到。當Redux配合React使用的時候,當state的值發生變化,React組件可以接收到變化的消息,與該state有關係的組件就會按照最新的state來觸發re-render。app

如上圖展現,當store接收一個特定的action的時候,store須要一個指定的方式來更新store中特定的state。而這個特定的方式就是reducer的職責。它是一個js函數。用於規定,如何根據指定的key去更新store中指定的某些state。該函數在store建立的時候,作爲參數傳入到createStore()中。框架

//更新對應表中的指定字段
//reducer根據action來更新特定的state
UPDATE Persons SET FirstName = 'Fred' WHERE LastName = 'Wilson' 
複製代碼

code

針對store,咱們須要有一個大體的理解

  1. 獲取當前store的最新的state
  2. dispatch一個action,傳入reducer(),用於計算最新的state
  3. 對store的變化設置監聽函數

獲取最新的state

咱們建立了一個接收reducre,initialState做爲參數的函數。該實現方式能夠在調用createStore()以後,獲取到store,調用該對象的getState()獲取最新的state。

funtion createStore(reducer,initialState){
    var currentReducer = reducre;
    var currentState = iniitialState;
    return {
        getState(){
            return currentState;
        }
    }
}
複製代碼

Dispatch action

接下來,咱們要實現dispatch action.

funtion createStore(reducer,initialState){
    var currentReducer = reducre;
    var currentState = initialState;
    return {
        getState(){
            return currentState;
        }
        dispatch(action){
            currentState = currentReducer(currentState,action);
            return action;
        }
    }
}
複製代碼

dispatch()向指定的reducer中傳入action和當前的state,用於根據指定的方式來更新store中的值。

設置監聽函數

funtion createStore(reducer,initialState){
    var currentReducer = reducre;
    var currentState = initialState;
    var listener = ()={}
    return {
        getState(){
            return currentState;
        },
        dispatch(action){
            currentState = currentReducer(currentState,action);
            listener();
            return action;
        },
         subscribe(newListener) {
            listener = newListener;
        }
    }
    
}
複製代碼

當須要觸發一次action的時候,咱們能夠去調用已一個回調函數做爲參數的subscribe 如上代碼所示:利用subscibe()的參數來設置store中的listener,從而使每次action被觸發的時候,調用該函數,起到監聽數據的做用。

代碼實戰

咱們能夠利用redux官網的例子來驗證一下咱們本身的redux。

function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1
  case 'DECREMENT':
    return state - 1
  default:
    return state
  }
}
let store = createStore(counter)
store.subscribe(() =>
  console.log(store.getState())
  )
store.dispatch({ type: 'INCREMENT' })
store.dispatch({ type: 'INCREMENT' })
store.dispatch({ type: 'DECREMENT' })
複製代碼

CombineReducers

combineReducre的做用

在上面的例子中,我使用reducer來算數。從代碼上看,reducer()看起來就是一個switch(),根據不一樣的key去處理不一樣的狀況。

針對小的demo而已,這種處理方式很夠用,可是若是項目比較大的話,這種處理方式,就會使得reducer變得冗長,且不易管理。

針對上面的問題,combineReducer()就產生了,它能使將多個小的reducer合併成一個大的,最後供Redux使用。

編寫本身的combineReducer

以下是官網的一些簡單的示例:

// reducers.js
export default theDefaultReducer = (state = 0, action) => state;
export const firstNamedReducer = (state = 1, action) => state;
export const secondNamedReducer = (state = 2, action) => state;

 // Use ES6 object literal shorthand syntax to define the object shape
const rootReducer = combineReducers({
  theDefaultReducer,
  firstNamedReducer,
  secondNamedReducer
});
const store = createStore(rootReducer);
console.log(store.getState());
// {theDefaultReducer : 0, firstNamedReducer : 1, secondNamedReducer : 2}
複製代碼

定義函數

combineReducer接收由不少小的reducer構成的對象

function combineReducers(reducers){
    
}
複製代碼

返回一個空reducer

從上面的例子中看到,調用combineReducer返回了一個rootReducer,做爲createStore()的參數。也就是說,rootReducer其實就是一個簡單的Redux reducer函數。

function combineReducers(reducers){
    return function combination(state={},action){
        
    }
}

複製代碼

生成state

function combineReducers(reducers) {
  // 獲取全部子reducer對應的key的名稱
  const reducerKeys = Object.keys(reducers);
  return function combination(state = {}, action) {
   //零時變量
    const nextState = {}
    for (let i = 0; i < reducerKeys.length; i++) {
    //獲取當前的key
    const key = reducerKeys[i];
    // 對應的reducer
    const reducer = reducers[key]
    // 獲取未修改以前的state
    const previousStateForKey = state[key]
    // 經過redcuer計算以後的state
    const nextStateForKey = reducer(previousStateForKey, action)
    // 更新store
    nextState[key] = nextStateForKey;
    }
    return nextState;
        }
    }
複製代碼

和React進行數據溝通

Redux是一個獨立的前端數據管理庫。能夠和不少框架一塊兒使用,可是若是在React開發中,想使用Redux帶來的快感,能夠是配合react-redux一塊兒使用。

React Redux

React組件可以獲取到store

首先,咱們將react的root component內嵌到以store作爲props的react-redux組件中。這樣就可使得store可以在全部的react組件中可見

import { Provider } from 'react-redux';
const store = createStore(myReducer);
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
    document.getElementById('root')
)
複製代碼

在react組件中使用store

構建一個簡單的組件

let AddTodo = ({ todos }) => {
  // 省去部分代碼
}
複製代碼

react-redux有一個connect()用於將storeconnect到你的React組件中。

const mapStateToProps = (state) => {
  return {
    todos: state.todos
  }
}
const mapDispatchToProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch(toggleTodo(id))
} }
}
AddTodo = connect(mapStateToProps, mapDispatchToProps)(AddTodo)
複製代碼

connect()自動從store中拿對應的值,而且將拿到的值做爲props傳入到被connect的組件中。當store中的值發生變化,對應的props也會變化,從而觸發組件的從新渲染。

代碼實現connect

參數指定

connect()被調用的時候,會當即返回一個新的函數。

function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
      //something happens here
    } 
}
複製代碼

函數輸出結果

從上面的示例中,看到,connect()函數接受一個react組件作完參數,而且也返回了一個的組件。

function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    //返回了一個新的組件
    return class extends React.Component {
      render() {
        return (
          <WrappedComponent
            {...this.props}
            />
            ) }
        }
    }
}
複製代碼

若是對React開發比較熟悉的同窗,很快就會定位到,這是利用了React的Hoc。或者能夠參考,我寫的關於HOC的見解。有興趣的能夠研究一下,封裝一些公共組件頗有用。

向返回的新組件中添加參數

因爲使用connect()構建了一個的組件,因此須要像組件中傳入須要的數據,而這個數據的來源有3個地方。 使用connect()構建的新組件的數據來源有3個地方。

  1. 經過store.getState()獲取state存貯的值
  2. 調用mapDispatch(),獲取到觸發action的動做
  3. 獲取原始組件中的值
function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    return class extends React.Component {
      render() {
        return (
          <WrappedComponent
            {...this.props}//獲取原始組件的值
            {...mapStateToProps(store.getState(), this.props)}//獲取store存貯的值
            {...mapDispatchToProps(store.dispatch, this.props)}//觸發動做
          />
        ) }
    } 
  }
}
複製代碼

如何在組件中獲取到store的實例

從上面的代碼中,有的同窗可能存在疑問,爲何我沒有找到定義或者存儲store的地方,可是卻能夠直接使用。在進行react和redux數據關聯的時候,使用了Providerroot component進行包裝。其實這裏就是將store進行了全局註冊,你也能夠認爲是函數中的全局變量。具體如何實現的,能夠參考react中的Context

訂閱store

經過上述的操做,咱們如今已經能夠在咱們本身的組件獲取到store中的值,而且可以在組件中dispatch一些action來更新對應的state。 若是在實際項目中,須要用到針對某個事件變化,進行組件的強制渲染。能夠和該事件和store中的值,進行綁定,但凡觸發,就更新組件狀態。

function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    return class extends React.Component {
      render() {
        return (
          <WrappedComponent
            {...this.props}//獲取原始組件的值
            {...mapStateToProps(store.getState(), this.props)}//獲取store存貯的值
            {...mapDispatchToProps(store.dispatch, this.props)}//觸發動做
          />
        ) 
          
      }
      componentDidMount() {
        this.unsubscribe = store.subscribe(this.handleChange.bind(this))
      }
      componentWillUnmount() {
        this.unsubscribe()
        }
      handleChange() {
        this.forceUpdate()//利用react組件的事件,來強制從新渲染組件
        }
    } 
  }
}
複製代碼

Redux thunk Middleware

Redux是一個極其簡單的數據存貯狀態庫,因此在他本身的代碼實現中,只是針對了同步操做。可是對於前端來講,若是僅僅只是同步操做的話,一些後臺接口返回的數據就沒法存入到store中,這樣70%的數據就沒法享受使用redux的快感。

Redux Thunk Middleware完美解決了Redux異步數據存貯問題。(redux的異步處理,不僅是redux,thunk一種,有不少)

thunk的代碼實現

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    //若是action是一個函數,將會被處理,進行異步操做,同時能夠在異步的結果中獲取
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
複製代碼

這就是thunk的全部代碼。意不意外,驚不驚喜。

核心思路

其實核心代碼就是將Redux做爲一個中間件

if (typeof action === 'function') {
  return action(dispatch, getState, extraArgument);
}
複製代碼

Redux Thunk配置在你的項目中,每次dispatch一個action的時候,上面的代碼都會執行。若是被觸發的action是一個函數,上面的代碼就會去判斷,而且會調用以dispatch做爲參數的函數(也就是異步函數)。

總結:

  1. 當redux沒有配置中間件的時候,action老是一個簡單的js對象
  2. 當使用了中間件的時候,action能夠是一個對象也能夠是函數。

使用redux thunk

使用redux thunk建立的action格式以下:

function loadPostsAction() {
    return (dispatch, getState) => {
        // 進行異步處理
    }; 
}
複製代碼

調用上述函數,又返回了一個可以獲取到redux dispatch函數的函數。

使用異步action來處理異步數據。

function loadPostsAction() {
    return (dispatch, getState) => {
       get("/api/posts").then(
       (paylod)=>dispath({type:"success",paylod}),
       (err)=>dispath({typs:"err",})
       )
    }; 
}
複製代碼
相關文章
相關標籤/搜索