從基礎開始深入,一步步的實現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")
})
複製程式碼