Ref: Redux中文文檔javascript
Ref: React 讀書會 - B團 - Level 19 Redux 深刻淺出html
Ref: React+Redux 分享會前端
Ruan Yifeng, Redux 架構: 教程一(有代碼)、教程二、教程三java
Redux 入門教程 #1 課程介紹react
Redux 入門教程 #3 什麼是 Reduxgithub
Redux 入門教程 #4 建立頁面mongodb
Redux 入門教程 #6 使用 react-reduxredux
(1) 首先,複習 Flux:[React] 07 - Flux: react communicates with mongodb
詳見Flux章節和連接。
(2) 以後,讓咱們開始對 Redux 認真學習。
2015年,Redux 出現,將 Flux 與函數式編程結合一塊兒,很短期內就成爲了最熱門的前端架構。
* 是什麼?
- actions
- components
- constants
- reducers
- store
管理數據的狀態容器,單獨的js庫。
* 解決什麼問題?
A如何把參數傳給B?react的單向流很差處理。
或者,使用嵌入式寫法,好比A觸發事件,B監聽事件。
但嵌入式寫法不推薦。
- 代碼結構
- 組件之間的通訊
- 用戶的使用方式複雜
- 不一樣身份的用戶有不一樣的使用方式(好比普通用戶和管理員)
- 多個用戶之間能夠協做
- 與服務器大量交互,或者使用了WebSocket
- View要從多個來源獲取數據
- 某個組件的狀態,須要共享
- 某個狀態須要在任何地方均可以拿到
- 一個組件須要改變全局狀態
- 一個組件須要改變另外一個組件的狀態
第一步,從老阮開始。設計一個以下的"加減計數器"。
[1] ----------------------------------------------------------------------------------------------------------------------------------------> Store & State
兩個重要的功能:
(1)建立惟一的一個store。
(2)獲取該store裏面的state。
import { createStore } from 'redux';
const store = createStore(reducer); // 1.接受另外一個函數做爲參數,返回新生成的 Store 對象
const state = store.getState(); // 2.獲得當前時刻的State,一個state對應一個view
Jeff: 在flux中的state計算問題放在了reducer中,reducer與store經過createStore()維持關係。
[2] ----------------------------------------------------------------------------------------------------------------------------------------> Action
用戶操做 on view --> action --> state
# 想要的結果
const action = {
type: 'ADD_TODO', // Action的名稱,是必須的;Action自己是個對象
payload: 'Learn Redux' // Action的貨物,不是必須的;
};
使用 Action Creator,避免所有「手寫定義」帶來的麻煩,返回以上的結果。
const ADD_TODO = '添加 TODO';
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
const action = addTodo('Learn Redux');
[3] ----------------------------------------------------------------------------------------------------------------------------------------> store.dispatch()
經過store.dispatch (...),view 發出 action (參數) to store.
store.dispatch({
type : 'ADD_TODO',
payload: 'Learn Redux'
});
或者,對象經過函數生成:
store.dispatch( // <--- 推薦
addTodo('Learn Redux')
);
[4] ----------------------------------------------------------------------------------------------------------------------------------------> Reducer
Store 收到 Action 之後,必須給出一個新的 State,View 才能發生變化。
Reducer:新State 的計算過程,based on old state。
const defaultState = 0;
const reducer = (state = defaultState, action) => {
/**
* reducer 函數裏面不能改變 State,必須返回一個全新的對象
*/
switch (action.type) {
case 'ADD':
return state + action.payload;
default:
return state;
}
};
....................................................
const state = reducer(1, {
type: 'ADD',
payload: 2
});
由於createStore(reducer),每當store.dispatch
發送過來一個新的 Action,就會自動調用 Reducer,獲得新的 State。
reducer 做爲參數,爲 "action們" 提供趁手的工具。
const actions = [
{ type: 'ADD', payload: 0 },
{ type: 'ADD', payload: 1 },
{ type: 'ADD', payload: 2 }
];
/**
* action是數據,打算使用reducer這個工具來處理,最後返回一個新的狀態
*/
const total = actions.reduce(reducer, 0); // 3
另外:最好把 State 對象設成只讀,這樣你無法改變它,要獲得新的 State,惟一辦法就是生成一個新對象。
// State 是一個對象
function reducer(state, action) {
return Object.assign({}, state, { thingToChange });
// 或者
return { ...state, ...newState };
}
// State 是一個數組
function reducer(state, action) {
return [...state, newItem];
}
[5] ---------------------------------------------------------------------------------------------------------------------------------------- listener
store改變,提醒 listener,觸發view改變。
解除 listener 居然是利用其"返回值"。
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
);
unsubscribe();
Store 提供了三個方法:
store 的三個部分,以下:
import { createStore } from 'redux';
let { subscribe, dispatch, getState } = createStore(reducer);
let store = createStore(todoApp, window.STATE_FROM_SERVER)
// 第二個參數表示:整個應用的狀態初始值,會覆蓋 Reducer 函數的默認初始值
【createStore內部實現暫不深究】
三種 Action 分別改變 State 的三個屬性。
const chatReducer = (state = defaultState, action = {}) => {
const { type, payload } = action;
switch (type) {
............................................................. ADD_CHAT:chatLog屬性
case ADD_CHAT:
return Object.assign({}, state, {
chatLog: state.chatLog.concat(payload)
});
............................................................. CHANGE_STATUS:statusMessage屬性
case CHANGE_STATUS:
return Object.assign({}, state, {
statusMessage: payload
});
............................................................. CHANGE_USERNAME:userName屬性
case CHANGE_USERNAME:
return Object.assign({}, state, {
userName: payload
});
default: return state;
}
};
const chatReducer = (state = defaultState, action = {}) => {
return {
chatLog: chatLog(state.chatLog, action), // 函數名字重複也礙眼,參數也不太想多寫
statusMessage: statusMessage(state.statusMessage, action),
userName: userName(state.userName, action)
}
};
理由: 這種拆分與 React 應用的結構相吻合:一個 React 根組件由不少子組件構成。這就是說,子組件與子 Reducer 徹底能夠對應。
combineReducers
方法,專門用於拆分這種寫法有一個前提,就是 State 的屬性名必須與子 Reducer 同名。
import { combineReducers } from 'redux';
/**
* 先找個地兒,定義各個子 Reducer 函數,
* 而後用這個方法,將它們合成一個大的 Reducer
*/
const chatReducer = combineReducers({
chatLog,
statusMessage,
userName
})
export default todoApp;
import { combineReducers } from 'redux'
import * as reducers from './reducers'
const reducer = combineReducers(reducers)
怎麼改變狀態?
如何觸發界面更新?
這些都是Store的活兒!
* 首先,用戶發出 Action --> store。
store.dispatch(action);
* 而後,Store 自動調用 Reducer,而且傳入兩個參數:[當前 State] & [收到的 Action],返回新的 State 。
let nextState = todoApp(previousState, action); // 產生新狀態
* State 一旦有變化,Store 就會調用監聽函數。
/* 設置監聽函數 */ store.subscribe(listener);
/* 若是使用的是 React,出發方式以下 */
function listerner() { let newState = store.getState(); // 得到新狀態 component.setState(newState); // 根據新狀態更新界面 }
Clicked: 3 times
const render = () => ReactDOM.render(
/* 自定義Counter控件的使用 */ <Counter value ={store.getState()} # 得到new state onIncrement={() => store.dispatch({ type: 'INCREMENT' })} onDecrement={() => store.dispatch({ type: 'DECREMENT' })} />, rootEl )
在複雜的狀況下,state會是如何?這裏的例子有點過於簡單,沒有表現。
export default (state = 0, action) => { # 產生新狀態
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
class Counter extends Component {
constructor(props) { super(props); this.incrementAsync = this.incrementAsync.bind(this); this.incrementIfOdd = this.incrementIfOdd.bind(this); }
////////////////////////////////////////////////////////
incrementIfOdd() { if (this.props.value % 2 !== 0) { this.props.onIncrement() } } incrementAsync() { setTimeout(this.props.onIncrement, 1000) }
////////////////////////////////////////////////////////
render() { const { value, onIncrement, onDecrement } = this.props return ( <p> Clicked: {value} times {' '} <button onClick={onIncrement}> + </button> {' '} <button onClick={onDecrement}> - </button> {' '} <button onClick={this.incrementIfOdd}> Increment if odd </button> {' '} <button onClick={this.incrementAsync}> Increment async </button> </p> ) } }