redux 流程演繹

tenor發表於2018-10-14

從基礎開始深入,一步步的實現redux

初始需求是完成對統一狀態的資料的修改,想要達到的目的是修改狀態資料,即可完成對頁面顯示資料的修改,但是直接對公共資料的修改是不安全的

所以可以得出以下2個要點:

  • 1.統一管理的狀態資料是不能直接修改的
  • 2.需要提供一個資料來完成對狀態資料的修改,其他呼叫這個方法就可以

那要實現這個統一修改狀態資料的方法有哪些要注意的地方呢?

  • 1.需要告知這個函式修改的目標資料
  • 2.需要告知函式要將目標資料修改成如何

所以需要我們設計或者定義個方法,這個方法有以下的要求:

  • 1.引數需要傳遞個物件
  • 2.引數物件中必須要有個屬性為type,表示修改某個目標資料的標誌
  • 3.引數物件的其他屬性就是要將目標物件修改後的值
  • 4.方法的主要邏輯是根據不同type屬性值來完成對狀態資料的修改
  • 5.對狀態資料的修改時要注意修改之後返回的物件為新物件
return state = { ...state, titleState: { ...state.titleState, text: action.text } }
複製程式碼

注意: 在完成這個方法的呼叫之後,對資料更新完成後,需要重新呼叫render函式對資料進行渲染

保護狀態資料不被修改

到目前為止,我們可以對狀態資料進行修改了,但是有個問題是:

狀態資料暴露在全域性,這樣很容易被修改

解決方案是:建立一個容器將狀態放進去,這樣狀態資料就變成私有的,無法獲取到

但是使用函式的方式的話,外界要使用狀態資料和派發動作的方法就無法獲取到

  • 1.dispatch方法需要獲取到狀態資料,所以也放置到這個函式中,但是外面無法使用dispatch方法,需要將dispatch方法外放出去
  • 2.狀態資料物件在函式外也使用到了,但是並不能直接將這個物件給外界使用,不然就沒有任何意義了,所以可以克隆一個狀態資料物件給外界使用
克隆方式:
let getState = () => JSON.parse(JSON.stringify(state));
複製程式碼

狀態資料由外界定義

功能繼續優化,因為之前的做法有2個缺陷:

  • 缺陷1:狀態資料已經是私有的,但是這樣做是不合理的,因為狀態資料肯定不是函式自身就定義好了的,而是由函式外定義
  • 缺陷2:建立容器中的派發動作的方法裡面具體的type屬性的引數也不應該是函式自身來定義的,而是由外界來完成的,因為框架作為通用的是不可能定義這樣具體的type型別

針對這2個缺陷,我們需要做如下的操作:

  • 1.狀態資料在函式外定義 initState
  • 2.派發動作函式中的switch判斷type交由外界函式處理reducer
  • 3.建立容器時需要將這個reducer處理函式傳到容器中,因為容器中的派發動作函式需要使用到這個reducer處理函式
function createStore(reducer) {
    let state;
    function dispatch(action) {
        state = reducer(state, action);
    }
    dispatch({});
    let getState = () => JSON.parse(JSON.stringify(state));
    return {
        dispatch, getState
    }
}
複製程式碼
  • 4.reducer處理函式傳遞2個引數,在建立容器時狀態物件state為undefined,此時就會出錯,因此需要給reducer處理函式的state狀態函式物件傳遞一個初始值
reducer(state = initState, action)
複製程式碼
  • 5.注意:為了state狀態物件在處理的時候不為undefined,需要在建立容器函式中呼叫一次dispatch派發動作函式,並傳遞一個空物件,這樣,容器中的狀態物件就會被賦值成外界的initState狀態物件

事件釋出訂閱和取消事件訂閱

由於在事件完成派發操作之後都需要做一次重新渲染頁面的操作,所以這裡採用事件釋出訂閱的方式來完成這種後置回撥

容器函式中建立一個事件訂閱函式,用陣列將訂閱的函式儲存起來,在動作派發完成之後遍歷陣列,完成事件釋出

// 事件訂閱
let subscribe = (fn) => {
    listeners = [...listeners, fn];
}
複製程式碼
//事件釋出
function dispatch(action) {
    state = reducer(state, action);
    // 依次讓訂閱的函式執行
    listeners.forEach(item => item());
}
複製程式碼

同時要有一個取消事件訂閱的函式.

  • 1.在事件訂閱函式中,讓事件訂閱函式返回一個函式
  • 2.在返回的函式中進行取消事件訂閱的處理
  • 3.當呼叫這個函式的時候,則在陣列中刪除掉訂閱的這個函式
// 事件訂閱
let subscribe = (fn) => {
    listeners = [...listeners, fn];
    // 返回一個函式,用於取消事件訂閱
    return () => {
        listeners = listeners.filter(item => item != fn);
    }
}
複製程式碼

事件訂閱和取消訂閱的使用:

let unSubscribe = store.subscribe(() => {
    console.log("render done");
});
unSubscribe();
複製程式碼

redux 基礎庫

redux基礎庫的實現如下:

// redux 狀態容器
function createStore(reducer) {                                                                                     
    // 定義狀態和訂閱陣列
    let state, listeners = [];
    // 動作派發
    let dispatch = (action) => {
        state = reducer(state, action);
        listeners.forEach(item => item());
    }
    // 初始化狀態資料
    dispatch({});
    // 克隆狀態物件
    let getState = () => JSON.parse(JSON.stringify(state));

    // 事件訂閱
    let subscribe = (fn) => {
        listeners = [...listeners, fn];
        return () => {
            listeners = listeners.filter(item => item != fn);
        }
    }
    return {
        getState,
        dispatch,
        subscribe
    }
}
複製程式碼

redux 庫使用

redux庫在原生中的使用步驟:

  • 1.定義功能型別常量
const ADD = "add";
const MINUS = "minus";
複製程式碼
  • 2.定義初始狀態,建立reducer函式時將初始狀態賦給reducer函式引數state的預設值
let initState = { num: 0 };
function reducer(state = initState, action) {
    switch (action.type) {
        case ADD:
            return { ...state, num: state.num + action.count }
            break;
        case MINUS:
            return { ...state, num: state.num - action.count };
            break;
        default:
            return state;
    }
}
複製程式碼
  • 3.建立狀態容器,將reducer作為引數傳進去
let store = createStore(reducer);
複製程式碼
  • 4.預設渲染一次,在react的render方法中已經做了
function render() {
    window.number.innerHTML = store.getState().num;
}
render();
複製程式碼
  • 5。派發功能動作,修改狀態
window.add.addEventListener("click", function () {
    store.dispatch({ type: ADD, count: 3 });
});

window.minus.addEventListener("click", function () {
    store.dispatch({ type: MINUS, count: 2 });
});
複製程式碼
  • 6.派發完成動作後要訂閱事件,重新渲染頁面
store.subscribe(render);
store.subscribe(() => {
    console.log("render")
})
複製程式碼

相關文章