前言
這篇文章是我在學(xué)習(xí)redux的過(guò)程中,在編碼的每一個(gè)階段編寫的學(xué)習(xí)筆記。項(xiàng)目地址:todo App。純r(jià)eact是在normal分支,結(jié)合redux的是在redux分支。react(15.6.1)和redux(3.7.2)都是當(dāng)前(2017.8.4)的最新版本。所有的數(shù)據(jù)是存儲(chǔ)在localstorage下的,點(diǎn)擊查看demo。因?yàn)槲业膗i是移動(dòng)端的,pc端沒(méi)有做處理,只能在移動(dòng)端或是chrome下按CTRL+SHIFT+M進(jìn)行查看。
演示

概念
action 來(lái)描述“發(fā)生了什么”
reducers 來(lái)根據(jù) action 更新 state
Store 就是把它們聯(lián)系到一起的對(duì)象
action
Action 本質(zhì)上是 JavaScript 普通對(duì)象。我們約定,action 內(nèi)必須使用一個(gè)字符串類型的 type 字段來(lái)表示將要執(zhí)行的動(dòng)作。
// action types
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';
const CHANGE_TAB = 'CHANGE_TAB';
const DELETE_TODO = 'DELETE_TODO';
// action constants
export const pageFilters = {
GO_LIST: 'GO_LIST',
GO_FORM: 'GO_FORM',
};
在 Redux 中的 action creators只是簡(jiǎn)單的返回一個(gè) action:
// action creators
// 添加一個(gè)todo任務(wù)
export function addTodo(newTodo) {
return { type: ADD_TODO, newTodo };
}
// 勾選完成/未完成的切換
export function toggleTodo(index) {
return { type: TOGGLE_TODO, index };
}
// 列表頁(yè)與表單頁(yè)的切換
export function changeTab(filter) {
return { type: CHANGE_TAB, filter };
}
// 刪除一個(gè)todo
export function deleteTodo(index) {
return { type: DELETE_TODO, index };
}
Reducer
Action 只是描述了有事情發(fā)生了這一事實(shí),并沒(méi)有指明應(yīng)用如何更新 state。而這正是 reducer 要做的事情。
先設(shè)計(jì)一個(gè)state tree,用來(lái)存放所有的state。
const stateTree = {
pageFilter: 'GO_LIST', //當(dāng)前頁(yè)
todos: [ //任務(wù)列表
{
text: '11',
place: '1',
time: '2:20',
content: 'dgusdguysdgisdhuis',
completed: false,
},
],
};
構(gòu)造一個(gè)初始化state
const initialState = {
pageFilter: pageFilters.GO_LIST,
todos: [],
};
創(chuàng)建reducer。通過(guò)action的type改變state
// reducer一定為純函數(shù),接受state和action,返回state。如果不存在state,則返回最初的state。
// (previousState, action) => newState
function todoApp(state = initialState, action) {
switch (action.type) {
case CHANGE_TAB:
// 這里不能直接修改state,要使用 Object.assign() 新建了一個(gè)副本
return Object.assign({}, state, {
filter: action.filter,
});
default:
return state;
}
}
寫一下對(duì)其他action的處理,值得注意的是,都不要在原來(lái)的state上進(jìn)行操作??!
function todoApp(state = initialState, action) {
switch (action.type) {
case CHANGE_TAB:
return Object.assign({}, state, {
filter: action.filter,
});
case ADD_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
action.newTodo,
],
});
case TOGGLE_TODO:
return Object.assign({}, state, {
todos: state.todos.map((todo, index) => {
if (action.index === index) {
return Object.assign({}, todo, {
completed: !todo.completed,
});
}
return todo;
}),
});
case DELETE_TODO:
return Object.assign({}, state, {
todos: [].concat(state.todos).splice(action.index, 1),
});
default:
return state;
}
}
除了CHANGE_TAB其他的action都是用來(lái)操作todos的,把它們拆分出來(lái),使代碼結(jié)構(gòu)看起來(lái)更加清晰。
// 操作state.todos
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state.todos,
action.newTodo,
];
case TOGGLE_TODO:
return state.todos.map((todo, index) => {
if (action.index === index) {
return Object.assign({}, todo, {
completed: !todo.completed,
});
}
return todo;
});
case DELETE_TODO:
return [].concat(state.todos).splice(action.index, 1);
default:
return state;
}
}
現(xiàn)在我們可以開(kāi)發(fā)一個(gè)函數(shù)來(lái)做為主 reducer,它調(diào)用多個(gè)子 reducer 分別處理 state 中的一部分?jǐn)?shù)據(jù),然后再把這些數(shù)據(jù)合成一個(gè)大的單一對(duì)象。主 reducer 并不需要設(shè)置初始化時(shí)完整的 state。初始時(shí),如果傳入 undefined, 子 reducer 將負(fù)責(zé)返回它們的默認(rèn)值。
function todoApp(state = {}, action) {
return {
filter: filter(state.filter, action),
todos: todos(state.todos, action),
};
}
調(diào)用redux的工具類combineReducers把子reducer整合在一起。
const todoApp = combineReducers({
filter,
todos,
});
Store
Store 有以下職責(zé):
- 維持應(yīng)用的 state;
- 提供 getState() 方法獲取 state;
- 提供 dispatch(action) 方法更新 state;
- 通過(guò) subscribe(listener) 注冊(cè)監(jiān)聽(tīng)器;
- 通過(guò) subscribe(listener) 返回的函數(shù)注銷監(jiān)聽(tīng)器。
創(chuàng)建一個(gè)store
import { createStore } from 'redux';
import todoApp from './reduces';
// Redux應(yīng)用只有一個(gè)單一的store
// 根據(jù)已有的reducer創(chuàng)建store
// createStore第二個(gè)參數(shù)是可選的, 用于設(shè)置 state 初始狀態(tài)
const store = createStore(todoApp);
發(fā)起action
// 打印初始state狀態(tài)
console.log(store.getState());
// 每次 state 更新時(shí),打印日志
// 注意 subscribe() 返回一個(gè)函數(shù)用來(lái)注銷監(jiān)聽(tīng)器
const unsubscribe = store.subscribe(() =>
console.log(store.getState()),
);
// 發(fā)起action
store.dispatch(changeTab(GO_FORM));
// 停止監(jiān)聽(tīng) state 更新
unsubscribe();
可以從console中看到state的變化。
搭配 React
react與redux并沒(méi)有什么依賴關(guān)系,只不過(guò)react允許使用state函數(shù)的形式來(lái)描述界面。
先安裝react-redux,使用<Provider>把根組件包起來(lái)
import { Provider } from 'react-redux';
render(
<Provider store={store}>
<Root />
</Provider>
,
document.getElementById('root'),
);
Provider中的任何一個(gè)組件,想要使用store中的數(shù)據(jù),就必須connect()對(duì)其封裝。
connect
connect() 接收四個(gè)參數(shù),它們分別是 mapStateToProps,mapDispatchToProps,mergeProps和options。
mapStateToProps 讀取state
在這里我們使用mapStateToProps這個(gè)函數(shù),將store中的數(shù)據(jù)作為props綁定到組件上。
// 構(gòu)造一個(gè)函數(shù)返回需要的state
const mapStateToProps = (state) => {
const { filter, todos } = state;
return {
filter,
todos,
};
};
// 使用connect將state作為props傳入root中
export default connect(mapStateToProps)(Root);
這時(shí),<Root/>中的就有了如下的props
{
dispatch:function dispatch(action),
filter:"GO_LIST",
todos:Array(0)
}
分發(fā)action
定義mapDispatchToProps()方法接受dispatch()方法,并返回一個(gè)回調(diào)函數(shù),用來(lái)分發(fā)action。
// 構(gòu)造一個(gè)函數(shù)來(lái)分發(fā)action
const mapDispatchToProps = dispatch => ({
goPages: (pageName) => {
dispatch(changeTab(pageName));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(Root);
這時(shí)再打印一下<Root/>里的props
{
filter:"GO_LIST",
goPages:pageName => {…},
todos:Array(0)
}
在調(diào)用時(shí),作為props傳遞給子組件,如:
<AddPage changeTab={() => goPages(pageFilters.GO_LIST)} data={this.state.data} />
相關(guān)資料
聽(tīng)說(shuō)你需要這樣了解 Redux(一)
聽(tīng)說(shuō)你需要這樣了解 Redux(二)