隨著單頁開發日趨複雜,管理不斷變化的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)
複製程式碼