Redux 三大原则
单一数据源。 整个应用的 state 被储存在一棵 object tree 中,它只有一个单一的 store 。
State 是只读的。 惟一改变 state 的办法就是触发 action,action 是一个描述要发生什么的对象。
纯函数的形式来执行修改。 为了描述 action 如何改变 state tree ,你需要编写 reducers。
同 Flux 的关系
Redux 没有 dispatcher 的概念,它依赖纯函数来替代事件处理器。Redux 设想你永远不会变动你的数据。
细节有所不同:分别体现在 reducer 和 state 上。
Action
Actions 是把数据从应用传到 store 的有效载荷。它是 store 数据的惟一来源。
Actions 表明有事情发生了,包含了更新 state 所需要的足够多的信息。
Action 本质是 JavaScript 普通对象。
1
2
3
4
5
6
7
8
{
type: 'ADD_TODO',
text: 'Build my first Redux app'
}
{
type: 'DELETE_TODO',
index: 5
}
Action 创建函数
1
2
3
4
5
6
function addTodo(text) {
return {
type: ADD_TODO,
text
};
}
Reducer
reducer 就是一个函数,接收旧的 state 和 action,返回新的 state。
1
(previousState, action) => newState
不要修改 state。
在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。
1
2
3
4
5
6
7
8
9
10
{
visibilityFilter: 'SHOW_ALL',
todos: [{
text: 'Consider using Redux',
completed: true,
}, {
text: 'Keep all state in a single tree',
completed: false
}]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
});
case ADD_TODO:
return Object.assign({}, state, {
todos: [...state.todos, {
text: action.text,
completed: false
}]
});
case COMPLETE_TODO:
return Object.assign({}, state, {
todos: [
...state.todos.slice(0, action.index),
Object.assign({}, state.todos[action.index], {
completed: true
}),
...state.todos.slice(action.index + 1)
]
});
default:
return state;
}
}
combineReducers
一个函数来做为主 reducer,它调用多个子 reducer 分别处理 state 中的一部分数据,然后再把这些数据合成一个大的单一对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
};
}
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [...state, {
text: action.text,
completed: false
}];
case COMPLETE_TODO:
return [
...state.slice(0, action.index),
Object.assign({}, state[action.index], {
completed: true
}),
...state.slice(action.index + 1)
];
default:
return state;
}
}
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter;
default:
return state;
}
}
最后,Redux 提供了 combineReducers() 工具类来做上面 todoApp 做的事情,这样就能消灭一些样板代码了
1
2
3
4
5
6
7
8
import { combineReducers } from 'redux';
const todoApp = combineReducers({
visibilityFilter,
todos
});
export default todoApp;
Store
Store 就是把action and reducers联系到一起的对象。
Store 有以下职责:
维持应用的 state;
提供 getState() 方法获取 state;
提供 dispatch(action) 方法更新 state;
通过 subscribe(listener) 注册监听器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createStore } from 'redux';
import todoApp from './reducers';
let store = createStore(todoApp);
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
);
// 发起一系列 action
store.dispatch(addTodo('Learn about actions'));
store.dispatch(addTodo('Learn about reducers'));
store.dispatch(addTodo('Learn about store'));
store.dispatch(completeTodo(0));
store.dispatch(completeTodo(1));
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED));
// 停止监听 state 更新
unsubscribe();
数据流
调用 store.dispatch(action)。
Redux store 调用传入的 reducer 函数。
根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
Redux store 保存了根 reducer 返回的完整 state 树。
With React
Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。
Redux 还是和 React 和 Deku 这类框架搭配起来用最好,因为这类框架允许你以 state 函数的形式来描述界面,Redux 通过 action 的形式来发起 state 变化。
1
npm install --save react-redux
只在最顶层组件里使用 Redux。
内部组件应该像木偶一样保持“呆滞”,所有数据都通过 props 传入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './containers/App'
import todoApp from './reducers'
let store = createStore(todoApp)
let rootElement = document.getElementById('root')
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class App extends Component {
render() {
// Injected by connect() call:
const { dispatch, visibleTodos, visibilityFilter } = this.props;
return (
<div>
<AddTodo
onAddClick={text =>
dispatch(addTodo(text))
} />
<TodoList
todos={this.props.visibleTodos}
onTodoClick={index =>
dispatch(completeTodo(index))
} />
<Footer
filter={visibilityFilter}
onFilterChange={nextFilter =>
dispatch(setVisibilityFilter(nextFilter))
} />
</div>
);
}
}
// Which props do we want to inject, given the global state?
function select(state) {
return {
visibleTodos: selectTodos(state.todos, state.visibilityFilter),
visibilityFilter: state.visibilityFilter
};
}
// Wrap the component to inject dispatch and state into it
export default connect(select)(App);
异步action 及 middleware
1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──────────────┐
┌─────────────┐ ┌──▶│ subReducer 1 │
┌───▶│Middleware 1 │ │ └──────────────┘
│ └─────────────┘ │ │
│ │ │ ▼
┌─────────────┐ │ │ ┌───────────────┐ ┌──────────┐ │ ┌──────────────┐
│ action' │────┘ ▼ ┌──▶│store.dispatch │───▶│ reducer │───┘ │ subReducer m │
└─────────────┘ ┌─────────────┐ │ └───────────────┘ └──────────┘ └──────────────┘
│Middleware n │ │ │
└─────────────┘ │ │
│ │ ▼
│ │ ┌──────────────┐
└──────────┘ │ state │
plain action └──────────────┘
通过使用指定的 middleware,action creator 除了返回 action 对象外还可以返回函数。这时,这个 action creator 就成为了 thunk。
当 action creator 返回函数时,这个函数会被 Redux Thunk middleware 执行。这个函数并不需要保持纯净;它还可以带有副作用,包括执行异步 API 请求。这个函数还可以 dispatch action,就像 dispatch 前面定义的同步 action 一样。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import { createStore, applyMiddleware } from 'redux';
import { selectReddit, fetchPosts } from './actions';
import rootReducer from './reducers';
const loggerMiddleware = createLogger();
const createStoreWithMiddleware = applyMiddleware(
thunkMiddleware, // 允许我们 dispatch() 函数
loggerMiddleware // 一个很便捷的 middleware,用来打印 action 日志
)(createStore);
const store = createStoreWithMiddleware(rootReducer);
store.dispatch(selectReddit('reactjs'));
store.dispatch(fetchPosts('reactjs')).then(() =>
console.log(store.getState())
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
export function fetchPosts(reddit) {
// Thunk middleware 知道如何处理函数。
// 这里把 dispatch 方法通过参数的形式参给函数,
// 以此来让它自己也能 dispatch action。
return function (dispatch) {
// 首次 dispatch:更新应用的 state 来通知
// API 请求发起了。
dispatch(requestPosts(reddit));
// thunk middleware 调用的函数可以有返回值,
// 它会被当作 dispatch 方法的返回值传递。
// 这个案例中,我们返回一个等待处理的 promise。
// 这并不是 redux middleware 所必须的,但是我们的一个约定。
return fetch(`http://www.reddit.com/r/${reddit}.json`)
.then(response => response.json())
.then(json =>
// 可以多次 dispatch!
// 这里,使用 API 请求结果来更新应用的 state。
dispatch(receivePosts(reddit, json))
);
// 在实际应用中,还需要
// 捕获网络请求的异常。
};
}
redux-thunk 支持异步的 middleware 包装了 store 的 dispatch() 方法,以此来让你 dispatch 一些除了 action 以外的其他内容,如函数。你所使用的任何 middleware 都可以以自己的方式解释你 dispatch 的任何内容,并继续传递 actions 给下一个 middleware。
1
2
3
4
5
6
7
8
9
10
function thunkMiddleware(_ref) {
var dispatch = _ref.dispatch;
var getState = _ref.getState;
return function (next) {
return function (action) {
return typeof action === 'function' ? action(dispatch, getState) : next(action);
};
};
}