redux簡單實現與分析

2018不下雪發表於2018-04-12

隨著單頁開發日趨複雜,管理不斷變化的state非常困難。在React中,資料在元件中是單向流動的。資料從一個方向父元件流向子元件(通過props),但是,兩個非父子關係的元件(或者稱作兄弟元件)之間的通訊就比較麻煩(可以通過父給子傳遞方法,子通過這個方法修改父的資料,後父傳給另一個子元件來實現)。Redux的出現就是為了解決state裡的資料問題

1.Redux概念解析

1.1 Store

  • Store就是儲存資料的地方,你可以把它看成一個容器。整個應用只能有一個Store
  • Redux 提供createStore這個函式,用來生成Store
import { createStore } from 'redux';
const store = createStore(fn);
//createStore函式接受另一個函式作為引數,返回新生成的Store物件。
複製程式碼

1.2 State

Store物件包含所有資料。如果想得到某個時點的資料,就要對Store生成快照。這種時間點的資料集合,就叫做State。 當前時刻的State,可以通過store.getState()拿到。

import { createStore } from 'redux';
const store = createStore(fn);
const state = store.getState();
複製程式碼

Redux 規定, 一個 State 對應一個 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什麼樣,反之亦然。

1.3 Action

State的變化,會導致View的變化。但是,使用者接觸不到 State,只能接觸到View 。Action 就是 View 發出的通知,表示State 應該怎樣變化,它會運送資料到 Store。 Action是一個物件。其中的type屬性是必須的,表示 Action 的名稱。

const action = {
  type: 'ADD_TODO',
  payload: '學習redux'
};
複製程式碼

1.4 Action Creator

View要傳送多少種訊息,就會有多少種 Action。如果都手寫,會很麻煩。可以定義一個函式來生成 Action,這個函式就叫 Action Creator。

const ADD_TODO = '新增 TODO';
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

const action = addTodo('學習Redux');
複製程式碼

上面程式碼中,addTodo函式就是一個 Action Creator。

1.5 store.dispatch()

store.dispatch()是 View 發出 Action 的唯一方法。

import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
  type: 'ADD_TODO',
  payload: '學習Redux'
});
複製程式碼

上面程式碼中,store.dispatch接受一個 Action 物件作為引數,將它傳送出去。 結合 Action Creator,這段程式碼可以改寫如下。

store.dispatch(addTodo('學習Redux'))
複製程式碼

1.6 Reducer

Store 收到 Action 以後,必須給出一個新的 State,這樣 View 才會發生變化。 這種 State 的計算過程就叫做 Reducer。 Reducer 是一個純函式,它接受 當前 State 和Action作為引數,返回一個新的 State。

const reducer = function (state, action) {
  return new_state;
};
複製程式碼

2.redux 簡單實現

知道了上面redux的基本概念,我們來實現一個簡單的redux,相信你可以更瞭解redux。

2.1簡單起步

function createStore(reducer) {
   
}
let store=createStore(reducer);
複製程式碼

reducer是我們定的,我們想讓state怎樣改,reducer中這是,這是我們自己寫的。為什麼傳入的是reducer?因為createStore是建立state的集合store的,而reducer

Reducer 是一個純函式,它接受 當前 State 和Action作為引數,返回一個新的 State。

所以createStore內部是通過Reducer返回的state。

2.2 將reducer中的state覆蓋createStore的state

reducer返回一個新的state。我們怎樣將返回的state覆蓋store中對應的state呢?state不能直接修改,只能通過dispatch派發action讓reducer修改
如果一不小心state='',則state將被清空,為了避免錯誤的修改。修改state只能通過dispatch。

dispatch不是傳送action的嗎?
對,dispatch內部是通過reducer使用action返回的新state賦值給舊的state

function createStore(reducer) {
    let state;//此時預設還是undefined
    function dispatch(action) {//派發
        state=reducer(state,action);
    }
    dispatch({});//目的是第一次時將預設狀態覆蓋掉自身狀態
}
function reducer(state={title:'標題'},action) {
    switch (action.type){
       
    }
    return state;
}
複製程式碼

2.3 getState

getState是唯一獲得state的方法,但是getState獲得的事原state的深拷貝物件,防止被篡改。

function createStore(reducer) {
    let state;
    function dispatch(action) {
        state=reducer(state,action);
    }
    dispatch({});
    let getState=()=>JSON.parse(JSON.stringify(state));
    return(getState,dispatch)
}
function reducer(state={title:'標題'},action) {
    switch (action.type){
       
    }
    return state;
}
複製程式碼

2.4完善reducer

function createStore(reducer) {
    let state;
    function dispatch(action) {
        state=reducer(state,action);
    }
    dispatch({});
    let getState=()=>JSON.parse(JSON.stringify(state));
    return(getState,dispatch)
}

//以下需要使用者自己寫
const CHANGE_TITLE='change_title'
function reducer(state={title:'標題'},action) {
    switch (action.type){
        case CHANGE_TITLE://{type:CHANGE_TITLE,content:'xx'}
            return {...state,title:action.content}
    }
    return state;
}
let store=createStore(reducer);
function render() {
    document.querySelector('.title').innerHTML=store.getState().title
}
render();
複製程式碼

2.5呼叫dispatch

setTimeout(function () {
    store.dispatch({type:HANGE_TITLE,content:'住'});
    render();
},2000)
setTimeout(function () {
    store.dispatch({type:HANGE_TITLE,content:'住'});
    render();
},2000)
複製程式碼

現在我們呼叫了兩次setTimeout,需要兩次render方法。如果有多個,需要多次呼叫render。現在我們通過釋出訂閱將render,或其他的方法訂閱。每次dispatch後都呼叫這些方法。

2.6釋出訂閱 subscribe

function createStore(reducer) {
    let state;
    function dispatch(action) {
        state=reducer(state,action);
        listeners.forEach(item=>item());
    }
    let listeners=[]//存放所有的監聽函式
    let subscribe=(fn)=>{
        listeners.push(fn);
    }
    dispatch({});
    let getState=()=>JSON.parse(JSON.stringify(state));
    return(getState,dispatch,subscribe)
}

let store=createStore(reducer);
store.subscribe(render);//訂閱render方法
//也可以是其他方法
store.subscribe(function(){
    alert(1111)
);
function render() {
    document.querySelector('.title').innerHTML=store.getState().title
}
render();
setTimeout(function () {
    store.dispatch({type:HANGE_TITLE,content:'住'});
},2000)
setTimeout(function () {
    store.dispatch({type:HANGE_TITLE,content:'住'});
},2000)
複製程式碼

2.7 釋出訂閱 unSubscribe

上面方法中有一次我們訂閱了alert(1111)的方法,如果我們想讓第一次setTimeout執行這個方法,第二次就不需要了。這時候就需要unSubscribe
執行subscrib時,返回一個unSubscribe的方法

let unSubscribe=store.subscribe(function(){
    alert(1111)
);
複製程式碼
function createStore(reducer) {
    let state;
    function dispatch(action) {
        state=reducer(state,action);
        listeners.forEach(item=>item());
    }
    let listeners=[]//存放所有的監聽函式
    let subscribe=(fn)=>{
        listeners.push(fn);
        return ()=>{//取消繫結的函式,呼叫可以刪除函式
            listeners=listeners.filter(item=>item!==fn);
        }
    }
    dispatch({});
    let getState=()=>JSON.parse(JSON.stringify(state));
    return(getState,dispatch,subscribe)
}
store.subscribe(render);
let unSubscribe=store.subscribe(function(){
    alert(1111)
);
function render() {
    document.querySelector('.title').innerHTML=store.getState().title
}
render();
setTimeout(function () {
    store.dispatch({type:HANGE_TITLE,content:'住'});
+    unSubscribe()
},2000)
setTimeout(function () {
    store.dispatch({type:HANGE_TITLE,content:'住'});
},2000)
複製程式碼

相關文章