redux与redux-thunk的使用


redux

  • redux是一个可预测的状态容器,(区别于Wordpress framework - Redux Framework)
  • 它可以帮助您编写在不同环境(客户端,服务器和本地)中运行一致并且易于测试的应用程序。 最重要的是,它提供了出色的开发者体验,例如结合时间旅行调试器进行实时代码编辑。
  • 你可以结合react一起使用redux。或者其他任何的视图库,redux很小,包括依赖仅仅2kb。
  • 安装
1
2
3
4
5
6
npm install --save redux
# 补充包
# 也许你还要和react一起使用,并且需要开发工具
npm install --save react-redux
npm install --save-dev redux-devtools

  • 例子
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
37
38
39
40
41
42
import { createStore } from 'redux'

/**
* 这是一个reducer,它是一个具有(state,action)=>状态签名的纯函数。
* 它描述了动作如何将状态转换为下一个状态。
* 状态的形状取决于你:它可以是一个基元,一个数组,一个对象,
* 甚至是Immutable.js数据结构。 唯一重要的部分是你应该
* 不改变状态对象,但如果状态改变则返回一个新的对象。
* 在这个例子中,我们使用`switch`语句和字符串,但是你可以使用一个帮助器
* 遵循不同的约定(如功能图),如果它适合你的项目。
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}

// 创建一个持有应用状态的Redux store。
// 他的api { subscribe, dispatch, getState }.
let store = createStore(counter)

// 你可以通过subscribe()来更新ui来响应状态的改变。
// 通常你会使用视图绑定库 (比如:React-Redux) 比起直接用 subscribe() 。
// 但是,将当前状态保存在localStorage中也可能非常方便。

store.subscribe(() =>
console.log(store.getState())
)

// 改变内部状态的唯一方法是发送一个action。
// 这些动作可以被序列化,记录或存储,并在以后重播。
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })

您不需要直接改变状态,而是使用称为操作的简单对象指定想要发生的突变。然后编写一个称为reducer的特殊函数来决定每个操作如何转换整个应用程序的状态。

如果您用过Flux,那么您需要了解一个重要的差异。 Redux没有分派器或支持许多store。相反,只有一个store具有单一根reducer函数。随着您的项目的扩展,您不必添加store,而是将根reducer分解为更小的reducer,并独立运行于状态树的不同部分。这就好像React应用程序中只有一个根组件,但它由许多小组件组成。

这种体系结构可能看起来像一个反应器应用程序的矫枉过正,但这种模式的美妙之处在于它如何适应大型复杂的应用程序。它还支持非常强大的开发者工具,因为可以追踪引发它的操作的每个变异。您可以记录用户会话并通过重播每一个动作来重现它们。

中间件redux-thunk

  • 用途
    redux thunk 中间件允许你写入一个action创建者通过返回一个函数来代替action。thunk通常被用于延迟一个action的分发。或分发一些条件可以预测的action。内部函数接收store方法dispatch和getState作为参数。一个thunk函数包含一个表达式来延迟他的计算。
    - 安装
1
2
npm install --save redux-thunk

  • 启用redux-thunk,使用applyMiddleware()
1
2
3
4
5
6
7
8
9
10
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';

// Note: redux@>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);

  • 例子如下:
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
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';

function increment() {
return {
type: INCREMENT_COUNTER
};
}
// 异步
function incrementAsync() {
return dispatch => {
setTimeout(() => {
// 用dispatch可以处理同步和异步的action
dispatch(increment());
}, 1000);
};
}
// 一个action创建者返回一个函数去实现条件分发
function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();

if (counter % 2 === 0) {
return;
}

dispatch(increment());
};
}

  • 作用
    任何来自内部函数的返回值都将作为dispatch本身的返回值。 这对于编写一个异步控制流来说非常方便,因为thunk动作创建者相互调度并返回Promise以等待对方的完成:
  • 示例代码
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

// Note: redux@>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);

function fetchSecretSauce() {
return fetch('https://www.google.com/search?q=secret+sauce');
}

// 正常action的创建
// 他们返回的action可以在没有任何中间件的情况下派发
// 然而他们只能表达事实,而不是异步流程。

function makeASandwich(forPerson, secretSauce) {
return {
type: 'MAKE_SANDWICH',
forPerson,
secretSauce
};
}

function apologize(fromPerson, toPerson, error) {
return {
type: 'APOLOGIZE',
fromPerson,
toPerson,
error
};
}

function withdrawMoney(amount) {
return {
type: 'WITHDRAW',
amount
};
}

// 甚至没有中间件你可以分发action.
store.dispatch(withdrawMoney(100));

// 当你需要使用异步操作比如api的调用或路由转换
// thunk函数返回一个函数
function makeASandwichWithSecretSauce(forPerson) {

// 反转控制
// 返回一个接受`dispatch`的函数,以便稍后分发。
// Thunk 中间件知道如何将异步action转换为action
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
};
}

// Thunk 中间件让我们分发thunk异步action
// 就好像自己的action一样
store.dispatch(
makeASandwichWithSecretSauce('Me')
);

// 它甚至需要关心thunk的返回值
// 对于dispatch, 我们只有声明一个promise对象并返回他们.

store.dispatch(
makeASandwichWithSecretSauce('My wife')
).then(() => {
console.log('Done!');
});

// 事实上,我们通过编写dispacth的action。
// 来自其他action创建者创建的action和 异步actiuon,
// 我们可以通过promise构建控制流

function makeSandwichesForEverybody() {
return function (dispatch, getState) {
if (!getState().sandwiches.isShopOpen) {

// 你不必返回promise对象,这是很便利的
// 因此调用者可以始终使用.then()函数来获取异步分发的结果
return Promise.resolve();
}

// 我们可以分发普通对象action和其他thunk,
// 这使得我们可以在单个流程中组合异步操作

return dispatch(
makeASandwichWithSecretSauce('My Grandma')
).then(() =>
Promise.all([
dispatch(makeASandwichWithSecretSauce('Me')),
dispatch(makeASandwichWithSecretSauce('My wife'))
])
).then(() =>
dispatch(makeASandwichWithSecretSauce('Our kids'))
).then(() =>
dispatch(getState().myMoney > 42 ?
withdrawMoney(42) :
apologize('Me', 'The Sandwich Shop')
)
);
};
}

// 这对服务端渲染非常有用,因为我们可以等待服务器的响应。
// 知道服务端响应的数据完成然后才开始同步渲染应用。
store.dispatch(
makeSandwichesForEverybody()
).then(() =>
response.send(ReactDOMServer.renderToString(
  • 注入自定义参数
    自2.1.0以来,Redux Thunk支持使用withExtraArgument函数注入自定义参数:
1
2
3
4
5
6
7
8
9
10
11
12
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument(api))
)

// 然后
function fetchUser(id) {
return (dispatch, getState, api) => {
// 你可以在这里使用api
}
}

  • 要传递多个东西,只需将它们包装在单个对象中并使用解构:
1
2
3
4
5
6
7
8
9
10
11
12
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument({ api, whatever }))
)

// 然后
function fetchUser(id) {
return (dispatch, getState, { api, whatever }) => {
// 你可以在这里使用api和其他东西
}
}