359 | );
360 | }
361 | }
362 |
363 | Posts.propTypes = {
364 | posts: PropTypes.array.isRequired
365 | };
366 | ```
367 |
--------------------------------------------------------------------------------
/docs/advanced/Middleware.md:
--------------------------------------------------------------------------------
1 | # Middleware
2 |
3 | If you used server-side libraries like [Express](http://expressjs.com/) and [Koa](http://koajs.com/), you are familiar with a concept of *middleware*. In these frameworks, middleware is some code you can put between the framework receiving a request, and framework generating a response. For example, Express or Koa middleware may add CORS headers, logging, compression, and more. The best feature of middleware is that it’s composable in a chain. You can use multiple independent third-party middleware in a single project.
4 |
5 | Redux middleware solves different problems than Express or Koa middleware, but in a conceptually similar way. **It provides a third-party extension point between dispatching an action, and the moment it reaches the store.** People use Redux middleware for logging, crash reporting, talking to an asynchronous API, routing, and more.
6 |
7 | This article is divided in an in-depth intro to help you grok the concept, and [a few practical examples](#seven-examples) to show the power of middleware at the very end. You may find it helpful to switch back and forth between them, as you flip between feeling bored and inspired.
8 |
9 | >##### Note for the Impatient
10 |
11 | >You will find some practical advice on using middleware for asynchronous actions [in the next section](AsyncActions.md). However we strongly advise you to resist the urge to skip this article.
12 |
13 | >Middleware is the most “magical” part of Redux you are likely to encounter. Learning how it works and how to write your own is the best investment you can make into your productivity using Redux.
14 |
15 | >If you’re really impatient, skip ahead to [seven examples](#seven-examples) and come back.
16 |
17 | ## Understanding Middleware
18 |
19 | While middleware can be used for a variety of things, including asynchronous API calls, it’s really important that you understand where it comes from. We’ll guide you through the thought process leading to middleware, by using logging and crash reporting as examples.
20 |
21 | ### Problem: Logging
22 |
23 | One of the benefits of Redux is that it makes state changes predictable and transparent. Every time an action is dispatched, the new state is computed and saved. The state cannot change by itself, it can only change as a consequence of a specific action.
24 |
25 | Wouldn’t it be nice if we logged every action that happens in the app, together with the state computed after it? When something goes wrong, we can look back at our log, and figure out which action corrupted the state.
26 |
27 |
28 |
29 | How do we approach this with Redux?
30 |
31 | ### Attempt #1: Logging Manually
32 |
33 | The most naïve solution is just to log the action and the next state yourself every time you call [`store.dispatch(action)`](../api/Store.md#dispatch). It’s not really a solution, but just a first step towards understanding the problem.
34 |
35 | >##### Note
36 |
37 | >If you’re using [react-redux](https://github.com/gaearon/react-redux) or similar bindings, you likely won’t have direct access to the store instance in your components. For the next few paragraphs, just assume you pass the store down explicitly.
38 |
39 | Say, you call this when creating a todo:
40 |
41 | ```js
42 | store.dispatch(addTodo('Use Redux'));
43 | ```
44 |
45 | To log the action and state, you can change it to something like this:
46 |
47 | ```js
48 | let action = addTodo('Use Redux');
49 |
50 | console.log('dispatching', action);
51 | store.dispatch(action);
52 | console.log('next state', store.getState());
53 | ```
54 |
55 | This produces the desired effect, but you wouldn’t want to do it every time.
56 |
57 | ### Attempt #2: Wrapping Dispatch
58 |
59 | You can extract logging into a function:
60 |
61 | ```js
62 | function dispatchAndLog(store, action) {
63 | console.log('dispatching', action);
64 | store.dispatch(action);
65 | console.log('next state', store.getState());
66 | }
67 | ```
68 |
69 | You can then use it everywhere instead of `store.dispatch()`:
70 |
71 | ```js
72 | dispatchAndLog(store, addTodo('Use Redux'));
73 | ```
74 |
75 | We could end this here, but it’s not very convenient to import a special function every time.
76 |
77 | ### Attempt #3: Monkeypatching Dispatch
78 |
79 | What if we just replace the `dispatch` function on the store instance? Redux store is just a plain object with [a few methods](../api/Store.md), and we’re writing JavaScript, so we can just monkeypatch the `dispatch` implementation:
80 |
81 | ```js
82 | let next = store.dispatch;
83 | store.dispatch = function dispatchAndLog(action) {
84 | console.log('dispatching', action);
85 | let result = next(action);
86 | console.log('next state', store.getState());
87 | return result;
88 | };
89 | ```
90 |
91 | This is already closer to what we want! No matter where we dispatch an action, it is guaranteed to be logged. Monkeypatching never feels right, but we can live with this for now.
92 |
93 | ### Problem: Crash Reporting
94 |
95 | What if we want to apply **more then one** such transformation to `dispatch`?
96 |
97 | A different useful transformation that comes to my mind is reporting JavaScript errors in production. The global `window.onerror` event is not reliable because it doesn’t provide stack information in some older browsers, which is crucial to understand why an error is happening.
98 |
99 | Wouldn’t it be useful if, any time an error is thrown as a result of dispatching an action, we would send it to a crash reporting service like [Sentry](https://getsentry.com/welcome/) with the stack trace, the action that caused the error, and the current state? This way it’s much easier to reproduce the error in development.
100 |
101 | However, it is important that we keep logging and crash reporting separate. Ideally we want them to be different modules, potentially in different packages. Otherwise we can’t have an ecosystem of such utilities. (Hint: we’re slowly getting to what middleware is!)
102 |
103 | If logging and crash reporting are separate utilities, they might look like this:
104 |
105 | ```js
106 | function patchStoreToAddLogging(store) {
107 | let next = store.dispatch;
108 | store.dispatch = function dispatchAndLog(action) {
109 | console.log('dispatching', action);
110 | let result = next(action);
111 | console.log('next state', store.getState());
112 | return result;
113 | };
114 | }
115 |
116 | function patchStoreToAddCrashReporting(store) {
117 | let next = store.dispatch;
118 | store.dispatch = function dispatchAndReportErrors(action) {
119 | try {
120 | return next(action);
121 | } catch (err) {
122 | console.error('Caught an exception!', err);
123 | Raven.captureException(err, {
124 | extra: {
125 | action,
126 | state: store.getState()
127 | }
128 | });
129 | throw err;
130 | }
131 | };
132 | }
133 | ```
134 |
135 | If these functions are published as separate modules, we can later use them to patch our store:
136 |
137 | ```js
138 | patchStoreToAddLogging(store);
139 | patchStoreToAddCrashReporting(store);
140 | ```
141 |
142 | Still, this isn’t nice.
143 |
144 | ### Attempt #4: Hiding Monkeypatching
145 |
146 | Monkeypatching is a hack. “Replace any method you like”, what kind of API is that? Let’s figure out the essence of it instead. Previously, our functions replaced `store.dispatch`. What if they *returned* the new `dispatch` function instead?
147 |
148 | ```js
149 | function logger(store) {
150 | let next = store.dispatch;
151 |
152 | // Previously:
153 | // store.dispatch = function dispatchAndLog(action) {
154 |
155 | return function dispatchAndLog(action) {
156 | console.log('dispatching', action);
157 | let result = next(action);
158 | console.log('next state', store.getState());
159 | return result;
160 | };
161 | }
162 | ```
163 |
164 | We could provide a helper inside Redux that would apply the actual monkeypatching as an implementation detail:
165 |
166 | ```js
167 | function applyMiddlewareByMonkeypatching(store, middlewares) {
168 | middlewares = middlewares.slice();
169 | middlewares.reverse();
170 |
171 | // Transform dispatch function with each middleware.
172 | middlewares.forEach(middleware =>
173 | store.dispatch = middleware(store)
174 | );
175 | }
176 | ```
177 |
178 | We could use it to apply multiple middleware like this:
179 |
180 | ```js
181 | applyMiddlewareByMonkeypatching(store, [logger, crashReporter]);
182 | ```
183 |
184 | However, it is still monkeypatching.
185 | The fact that we hide it inside the library doesn’t alter this fact.
186 |
187 | ### Attempt #5: Removing Monkeypatching
188 |
189 | Why do we even overwrite `dispatch`? Of course, to be able to call it later, but there’s also another reason: so that every middleware can access (and call) the previously wrapped `store.dispatch`:
190 |
191 | ```js
192 | function logger(store) {
193 | // Must point to the function returned by the previous middleware:
194 | let next = store.dispatch;
195 |
196 | return function dispatchAndLog(action) {
197 | console.log('dispatching', action);
198 | let result = next(action);
199 | console.log('next state', store.getState());
200 | return result;
201 | };
202 | }
203 | ```
204 |
205 | It is essential to chaining middleware!
206 |
207 | If `applyMiddlewareByMonkeypatching` doesn’t assign `store.dispatch` immediately after processing the first middleware, `store.dispatch` will keep pointing to the original `dispatch` function. Then the second middleware will also be bound to the original `dispatch` function.
208 |
209 | But there’s also a different way to enable chaining. The middleware could accept the `next()` dispatch function as a parameter instead of reading it from the `store` instance.
210 |
211 | ```js
212 | function logger(store) {
213 | return function wrapDispatchToAddLogging(next) {
214 | return function dispatchAndLog(action) {
215 | console.log('dispatching', action);
216 | let result = next(action);
217 | console.log('next state', store.getState());
218 | return result;
219 | };
220 | }
221 | }
222 | ```
223 |
224 | It’s a [“we need to go deeper”](http://knowyourmeme.com/memes/we-need-to-go-deeper) kind of moment, so it might take a while for this to make sense. The function cascade feels intimidating. ES6 arrow functions make this [currying](https://en.wikipedia.org/wiki/Currying) easier on eyes:
225 |
226 | ```js
227 | const logger = store => next => action => {
228 | console.log('dispatching', action);
229 | let result = next(action);
230 | console.log('next state', store.getState());
231 | return result;
232 | };
233 |
234 | const crashReporter = store => next => action => {
235 | try {
236 | return next(action);
237 | } catch (err) {
238 | console.error('Caught an exception!', err);
239 | Raven.captureException(err, {
240 | extra: {
241 | action,
242 | state: store.getState()
243 | }
244 | });
245 | throw err;
246 | }
247 | }
248 | ```
249 |
250 | **This is exactly what Redux middleware looks like.**
251 |
252 | Now middleware takes the `next()` dispatch function, and returns a dispatch function, which in turn serves as `next()` to the middleware to the left, and so on. It’s still useful to have access to some store methods like `getState()`, so `store` stays available as the top-level argument.
253 |
254 | ### Attempt #6: Naïvely Applying the Middleware
255 |
256 | Instead of `applyMiddlewareByMonkeypatching()`, we could write `applyMiddleware()` that first obtains the final, fully wrapped `dispatch()` function, and returns a copy of the store using it:
257 |
258 | ```js
259 | // Warning: Naïve implementation!
260 | // That's *not* Redux API.
261 |
262 | function applyMiddleware(store, middlewares) {
263 | middlewares = middlewares.slice();
264 | middlewares.reverse();
265 |
266 | let dispatch = store.dispatch;
267 | middlewares.forEach(middleware =>
268 | dispatch = middleware(store)(dispatch)
269 | );
270 |
271 | return Object.assign({}, store, { dispatch });
272 | }
273 | ```
274 |
275 | The implementation of [`applyMiddleware()`](../api/applyMiddleware.md) that ships with Redux is similar, but **different in three important aspects**:
276 |
277 | * It only exposes a subset of [store API](../api/Store.md) to the middleware: [`dispatch(action)`](../api/Store.md#dispatch) and [`getState()`](../api/Store.
278 | md#getState).
279 |
280 | * It does a bit of trickery to make sure that if you call `store.dispatch(action)` from your middleware instead of `next(action)`, the action will actually travel the whole middleware chain again, including the current middleware. This is useful for asynchronous middleware, as we will see [later](AsyncActions.md).
281 |
282 | * To ensure that you may only apply middleware once, it operates on `createStore()` rather than on `store` itself. Instead of `(store, middlewares) => store`, its signature is `(...middlewares) => (createStore) => createStore`.
283 |
284 | ### The Final Approach
285 |
286 | Given this middleware we just wrote:
287 |
288 | ```js
289 | const logger = store => next => action => {
290 | console.log('dispatching', action);
291 | let result = next(action);
292 | console.log('next state', store.getState());
293 | return result;
294 | };
295 |
296 | const crashReporter = store => next => action => {
297 | try {
298 | return next(action);
299 | } catch (err) {
300 | console.error('Caught an exception!', err);
301 | Raven.captureException(err, {
302 | extra: {
303 | action,
304 | state: store.getState()
305 | }
306 | });
307 | throw err;
308 | }
309 | }
310 | ```
311 |
312 | Here’s how to apply it to a Redux store:
313 |
314 | ```js
315 | import { createStore, combineReducers, applyMiddleware } from 'redux';
316 |
317 | // applyMiddleware takes createStore() and returns
318 | // a function with a compatible API.
319 | let createStoreWithMiddleware = applyMiddleware(
320 | logger,
321 | crashReporter
322 | )(createStore);
323 |
324 | // Use it like you would use createStore()
325 | let todoApp = combineReducers(reducers);
326 | let store = createStoreWithMiddleware(todoApp);
327 | ```
328 |
329 | This is it! Now any actions dispatched to the store instance will flow through `logger` and `crashReporter`:
330 |
331 | ```js
332 | // Will flow through both logger and crashReporter middleware!
333 | store.dispatch(addTodo('Use Redux'));
334 | ```
335 |
336 | ## Seven Examples
337 |
338 | If your head boiled from reading the above section, imagine what it was like to write it. This part is meant to be a relaxation for you and me, and will help get your gears turning.
339 |
340 | Each function below is a valid Redux middleware. They are not equally useful, but at least they are equally fun.
341 |
342 | ```js
343 | /**
344 | * Logs all actions and states after they are dispatched.
345 | */
346 | const logger = store => next => action => {
347 | console.group(action.type);
348 | console.info('dispatching', action);
349 | let result = next(action);
350 | console.log('next state', store.getState());
351 | console.groupEnd(action.type);
352 | return result;
353 | };
354 |
355 | /**
356 | * Sends crash reports as state is updated and listeners are notified.
357 | */
358 | const crashReporter = store => next => action => {
359 | try {
360 | return next(action);
361 | } catch (err) {
362 | console.error('Caught an exception!', err);
363 | Raven.captureException(err, {
364 | extra: {
365 | action,
366 | state: store.getState()
367 | }
368 | });
369 | throw err;
370 | }
371 | }
372 |
373 | /**
374 | * Schedules actions with { meta: { delay: N } } to be delayed by N milliseconds.
375 | * Makes `dispatch` return a function to cancel the interval in this case.
376 | */
377 | const timeoutScheduler = store => next => action => {
378 | if (!action.meta || !action.meta.delay) {
379 | return next(action);
380 | }
381 |
382 | let intervalId = setTimeout(
383 | () => next(action),
384 | action.meta.delay
385 | );
386 |
387 | return function cancel() {
388 | clearInterval(intervalId);
389 | };
390 | };
391 |
392 | /**
393 | * Schedules actions with { meta: { raf: true } } to be dispatched inside a rAF loop frame.
394 | * Makes `dispatch` return a function to remove the action from queue in this case.
395 | */
396 | const rafScheduler = store => next => {
397 | let queuedActions = [];
398 | let frame = null;
399 |
400 | function loop() {
401 | frame = null;
402 | try {
403 | if (queuedActions.length) {
404 | next(queuedActions.shift());
405 | }
406 | } finally {
407 | maybeRaf();
408 | }
409 | }
410 |
411 | function maybeRaf() {
412 | if (queuedActions.length && !frame) {
413 | frame = requestAnimationFrame(loop);
414 | }
415 | }
416 |
417 | return action => {
418 | if (!action.meta || !action.meta.raf) {
419 | return next(action);
420 | }
421 |
422 | queuedActions.push(action);
423 | maybeRaf();
424 |
425 | return function cancel() {
426 | queuedActions = queuedActions.filter(a => a !== action)
427 | };
428 | };
429 | };
430 |
431 | /**
432 | * Lets you dispatch promises in addition to actions.
433 | * If the promise is resolved, its result will be dispatched as an action.
434 | * The promise is returned from `dispatch` so the caller may handle rejection.
435 | */
436 | const vanillaPromise = store => next => action => {
437 | if (typeof action.then !== 'function') {
438 | return next(action);
439 | }
440 |
441 | return Promise.resolve(action).then(store.dispatch);
442 | };
443 |
444 | /**
445 | * Lets you dispatch special actions with a { promise } field.
446 | *
447 | * This middleware will turn them into a single action at the beginning,
448 | * and a single success (or failure) action when the `promise` resolves.
449 | *
450 | * For convenience, `dispatch` will return the promise so the caller can wait.
451 | */
452 | const readyStatePromise = store => next => action => {
453 | if (!action.promise) {
454 | return next(action)
455 | }
456 |
457 | function makeAction(ready, data) {
458 | let newAction = Object.assign({}, action, { ready }, data);
459 | delete newAction.promise;
460 | return newAction;
461 | }
462 |
463 | next(makeAction(false));
464 | return action.promise.then(
465 | result => next(makeAction(true, { result })),
466 | error => next(makeAction(true, { error }))
467 | );
468 | };
469 |
470 | /**
471 | * Lets you dispatch a function instead of an action.
472 | * This function will receive `dispatch` and `getState` as arguments.
473 | *
474 | * Useful for early exits (conditions over `getState()`), as well
475 | * as for async control flow (it can `dispatch()` something else).
476 | *
477 | * `dispatch` will return the return value of the dispatched function.
478 | */
479 | const thunk = store => next => action =>
480 | typeof action === 'function' ?
481 | action(store.dispatch, store.getState) :
482 | next(action);
483 |
484 |
485 | // You can use all of them! (It doesn’t mean you should.)
486 | let createStoreWithMiddleware = applyMiddleware(
487 | rafScheduler,
488 | timeoutScheduler,
489 | thunk,
490 | vanillaPromise,
491 | readyStatePromise,
492 | logger,
493 | errorHandler
494 | )(createStore);
495 | let todoApp = combineReducers(reducers);
496 | let store = createStoreWithMiddleware(todoApp);
497 | ```
498 |
--------------------------------------------------------------------------------
/docs/advanced/NextSteps.md:
--------------------------------------------------------------------------------
1 | # Next Steps
2 |
3 | Sorry, but we’re still writing this doc.
4 | Stay tuned, it will appear in a day or two.
5 |
--------------------------------------------------------------------------------
/docs/advanced/README.md:
--------------------------------------------------------------------------------
1 | # 高级
2 |
3 | [基础章节](../basics/README.md)介绍了如何组织简单的 Redux 应用。在这一章节中,将要学习如何使用 AJAX 和路由。
4 |
5 | * [Middleware](Middleware.md)
6 | * [异步 Actions](Async Actions.md)
7 | * [异步数据流](AsyncFlow.md)
8 | * [搭配 React Router](UsageWithReactRouter.md)
9 | * [示例:Reddit API](ExampleRedditAPI.md)
10 | * [下一步](NextSteps.md)
11 |
--------------------------------------------------------------------------------
/docs/advanced/UsageWithReactRouter.md:
--------------------------------------------------------------------------------
1 | # Usage with React Router
2 |
3 | Sorry, but we’re still writing this doc.
4 | Stay tuned, it will appear in a day or two.
5 |
--------------------------------------------------------------------------------
/docs/api/README.md:
--------------------------------------------------------------------------------
1 | # API 文档
2 |
3 | Redux 的 API 非常少。Redux 定义了一系列的约定(contract)来让你来实现(例如 [reducers](../Glossary.md#reducer)),同时提供少量辅助函数来把这些约定整合到一起。
4 |
5 | 这一章会介绍所有的 Redux API。记住,Redux 只关心如何管理 state。在实际的项目中,你还需要使用 UI 绑定库如 [react-redux](https://github.com/gaearon/react-redux)。
6 |
7 | ### 顶级暴露的方法
8 |
9 | * [createStore(reducer, [initialState])](createStore.md)
10 | * [combineReducers(reducers)](combineReducers.md)
11 | * [applyMiddleware(...middlewares)](applyMiddleware.md)
12 | * [bindActionCreators(actionCreators, dispatch)](bindActionCreators.md)
13 | * [compose(...functions)](compose.md)
14 |
15 | ### Store API
16 |
17 | * [Store](Store.md)
18 | * [getState()](Store.md#getState)
19 | * [dispatch(action)](Store.md#dispatch)
20 | * [subscribe(listener)](Store.md#subscribe)
21 | * [getReducer()](Store.md#getReducer)
22 | * [replaceReducer(nextReducer)](Store.md#replaceReducer)
23 |
24 | ### 引入
25 |
26 | 上面介绍的所有函数都是顶级暴露的方法。都可以这样引入:
27 |
28 | #### ES6
29 |
30 | ```js
31 | import { createStore } from 'redux';
32 | ```
33 |
34 | #### ES5 (CommonJS)
35 |
36 | ```js
37 | var createStore = require('redux').createStore;
38 | ```
39 |
40 | #### ES5 (UMD build)
41 |
42 | ```js
43 | var createStore = Redux.createStore;
44 | ```
45 |
--------------------------------------------------------------------------------
/docs/api/Store.md:
--------------------------------------------------------------------------------
1 | # Store
2 |
3 | Store 就是用来维持应用所有的 [state 树](../Glossary.md#state) 的一个对象。
4 | 改变 store 内 state 的惟一途径是对它 dispatch 一个 [action](../Glossary.md#action)。
5 |
6 | Store 不是类。它只是有几个方法的对象。
7 | 要创建它,只需要把根部的 [reducing 函数](../Glossary.md#reducer) 传递给 [`createStore`](createStore.md)。
8 |
9 | >##### Flux 用户使用注意
10 |
11 | >如果你以前使用 Flux,那么你只需要注意一个重要的区别。Redux 没有 Dispatcher 且不支持多个 store。相反,只有一个单一的 store 和一个根级的 reduce 函数(reducer)。随着应用不断变大,你应该把根级的 reducer 拆成多个小的 reducers,分别独立地操作 state 树的不同部分,而不是添加新的 stores。这就像一个 React 应用只有一个根级的组件,这个根组件又由很多小组件构成。
12 |
13 | ### Store 方法
14 |
15 | - [`getState()`](#getState)
16 | - [`dispatch(action)`](#dispatch)
17 | - [`subscribe(listener)`](#subscribe)
18 | - [`getReducer()`](#getReducer)
19 | - [`replaceReducer(nextReducer)`](#replaceReducer)
20 |
21 | ## Store 方法
22 |
23 | ### [`getState()`](#getState)
24 |
25 | 返回应用当前的 state 树。
26 | 它与 store 的最后一个 reducer 返回值相同。
27 |
28 | #### 返回
29 |
30 | *(any)*: 应用当前的 state 树。
31 |
32 |
33 |
34 | ### [`dispatch(action)`](#dispatch)
35 |
36 | 分发 action。这是触发 state 变化的惟一途径。
37 |
38 | 会使用当前 [`getState()`](#getState) 的结果和传入的 `action` 以同步方式的调用 store 的 reduce 函数。返回值会被作为下一个 state。从现在开始,这就成为了 [`getState()`](#getState) 的返回值,同时变化监听器(change listener)会被触发。
39 |
40 | >##### Flux 用户使用注意
41 | >当你在 [reducer](../Glossary.md#reducer) 内部调用 `dispatch` 时,将会抛出错误提示“Reducers may not dispatch actions.(Reducer 内不能 dispatch action)”。这就相当于 Flux 里的 “Cannot dispatch in a middle of dispatch(dispatch 过程中不能再 dispatch)”,但并不会引起对应的错误。在 Flux 里,当 Store 处理 action 和触发 update 事件时,dispatch 是禁止的。这个限制并不好,因为他限制了不能在生命周期回调里 dispatch action,还有其它一些本来很正常的地方。
42 |
43 | >在 Redux 里,只会在根 reducer 返回新 state 结束后再会调用事件监听器,因此,你可以在事件监听器里再做 dispatch。惟一使你不能在 reducer 中途 dispatch 的原因是要确保 reducer 没有副作用。如果 action 处理会产生副作用,正确的做法是使用异步 [action 创建函数](../Glossary.md#action-creator)。
44 |
45 | #### 参数
46 |
47 | 1. `action` (*Object*†): 描述应用变化的普通对象。Action 是把数据传入 store 的惟一途径,所以任何数据,无论来自 UI 事件,网络回调或者是其它资源如 WebSockets,最终都应该以 action 的形式被 dispatch。按照约定,action 具有 `type` 字段来表示它的类型。type 也可被定义为常量或者是从其它模块引入。最好使用字符串,而不是 [Symbols](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Symbol) 作为 action,因为字符串是可以被序列化的。除了 `type` 字段外,action 对象的结构完全取决于你。参照 [Flux 标准 Action](https://github.com/acdlite/flux-standard-action) 获取如何组织 action 的建议。
48 |
49 | #### 返回
50 |
51 | (Object†): 要 dispatch 的 action。
52 |
53 | #### 注意
54 |
55 | † 使用 [`createStore`](createStore.md) 创建的 “纯正” store 只支持普通对象类型的 action,而且会立即传到 reducer 来执行。
56 |
57 | 但是,如果你用 [`applyMiddleware`](applyMiddleware.md) 来套住 [`createStore`](createStore.md) 时,middleware 可以修改 action 的执行,并支持执行 dispatch [intent(意图)](../Glossary.md#intent)。Intent 一般是异步操作如 Promise、Observable 或者 Thunk。
58 |
59 | Middleware 是由社区创建,并不会同 Redux 一起发行。你需要手动安装 [redux-thunk](https://github.com/gaearon/redux-thunk) 或者 [redux-promise](https://github.com/acdlite/redux-promise) 库。你也可以创建自己的 middleware。
60 |
61 | 想学习如何描述异步 API 调用?看一下 action 创建函数里当前的 state,执行一个有副作用的操作,或者以链式操作执行它们,参照 [`applyMiddleware`](applyMiddleware.md) 中的示例。
62 |
63 | #### 示例
64 |
65 | ```js
66 | import { createStore } from 'redux';
67 | let store = createStore(todos, ['Use Redux']);
68 |
69 | function addTodo(text) {
70 | return {
71 | type: 'ADD_TODO',
72 | text
73 | };
74 | }
75 |
76 | store.dispatch(addTodo('Read the docs'));
77 | store.dispatch(addTodo('Read about the middleware'));
78 | ```
79 |
80 |
81 |
82 | ### [`subscribe(listener)`](#subscribe)
83 |
84 | 添加一个变化监听器。每当 dispatch action 的时候就会执行,state 树中的一部分可能已经变化。你可以在回调函数里调用 [`getState()`](#getState) 来拿到当前 state。
85 |
86 | 这是一个底层 API。多数情况下,你不会直接使用它,会使用一些 React(或其它库)的绑定。如果你想让回调函数执行的时候使用当前的 state,你可以 [把 store 转换成一个 Observable 或者写一个定制的 `observeStore` 工具](https://github.com/rackt/redux/issues/303#issuecomment-125184409)。
87 |
88 | 如果需要解绑这个变化监听器,执行 `subscribe` 返回的函数即可。
89 |
90 | #### 参数
91 |
92 | 1. `listener` (*Function*): 每当 dispatch action 的时候都会执行的回调。state 树中的一部分可能已经变化。你可以在回调函数里调用 [`getState()`](#getState) 来拿到当前 state。store 的 reducer 应该是纯函数,因此你可能需要对 state 树中的引用做深度比较来确定它的值是否有变化。
93 |
94 | ##### 返回
95 |
96 | (*Function*): 一个可以解绑变化监听器的函数。
97 |
98 | ##### 示例
99 |
100 | ```js
101 | function select(state) {
102 | return state.some.deep.property;
103 | }
104 |
105 | let currentValue;
106 | function handleChange() {
107 | let previousValue = currentValue;
108 | currentValue = select(store.getState());
109 |
110 | if (previousValue !== currentValue) {
111 | console.log('Some deep nested property changed from', previousValue, 'to', currentValue);
112 | }
113 | }
114 |
115 | let unsubscribe = store.subscribe(handleChange);
116 | handleChange();
117 | ```
118 |
119 |
120 |
121 | ### [`getReducer()`](#getReducer)
122 |
123 | >##### 已过期
124 |
125 | >此 API 已[过期](https://github.com/rackt/redux/issues/350).
126 | >我们找到更好的方式来处理后会移除它。
127 |
128 | 返回 store 当前用来计算 state 的 reducer。
129 |
130 | 这是一个高级 API。只有在你需要实现 Redux 热加载机制的时候才可能用到它。
131 |
132 | #### 返回
133 |
134 | (*Function*): store 当前的 reducer.
135 |
136 |
137 |
138 | ### [`replaceReducer(nextReducer)`](#replaceReducer)
139 |
140 | >##### 已过期
141 |
142 | >此 API 已[过期](https://github.com/rackt/redux/issues/350).
143 | >我们找到更好的方式来处理后会移除它。
144 |
145 | 替换 store 当前用来计算 state 的 reducer。
146 |
147 | 这是一个高级 API。只有在你需要实现代码分隔,而且需要立即加载一些 reducer 的时候才可能会用到它。在实现 Redux 热加载机制的时候也可能会用到。
148 |
149 | #### 参数
150 |
151 | 1. `reducer` (*Function*) store 会使用的下一个 reducer。
152 |
--------------------------------------------------------------------------------
/docs/api/applyMiddleware.md:
--------------------------------------------------------------------------------
1 | # `applyMiddleware(...middlewares)`
2 |
3 | Middleware is the suggested way to extend Redux with custom functionality. Middleware lets you wrap the store’s [`dispatch`](Store.md#dispatch) method for fun and profit. The key feature of middleware is that it is composable. Multiple middleware can be combined together, where each middleware requires no knowledge of what comes before or after it in the chain.
4 |
5 | The most common use case for the middleware is to support asynchronous actions without much boilerplate code or a dependency on a library like [Rx](https://github.com/Reactive-Extensions/RxJS). It does so by letting you dispatch [async actions](../Glossary.md#async-action) in addition to normal actions.
6 |
7 | For example, [redux-thunk](https://github.com/gaearon/redux-thunk) lets the action creators invert control by dispatching functions. They would receive [`dispatch`](Store.md#dispatch) as an argument and may call it asynchronously. Such functions are called *thunks*. Another example of middleware is [redux-promise](https://github.com/acdlite/redux-promise). It lets you dispatch a [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) async action, and dispatches a normal action when the Promise resolves.
8 |
9 | Middleware is not baked into [`createStore`](createStore.md) and is not a fundamental part of the Redux architecture, but we consider it useful enough to be supported right in the core. This way, there is a single standard way to extend [`dispatch`](Store.md#dispatch) in the ecosystem, and different middleware may compete in expressiveness and utility.
10 |
11 | #### 参数
12 |
13 | * `...middlewares` (*arguments*): Functions that conform to the Redux *middleware API*. Each middleware receives [`Store`](Store.md)’s [`dispatch`](Store.md#dispatch) and [`getState`](Store.md#getState) functions as named arguments, and returns a function. That function will be given the `next` middleware’s dispatch method, and is expected to return a function of `action` calling `next(action)` with a potentially different argument, or at a different time, or maybe not calling it at all. The last middleware in chain will receive the real store’s [`dispatch`](Store.md#dispatch) method as the `next` parameter, thus closing the chain. So, the middleware signature is `({ getState, dispatch }) => next => action`.
14 |
15 | #### 返回
16 |
17 | (*Function*) A store enhancer that applies the given middleware. The store enhancer is a function that needs to be applied to `createStore`. It will return a different `createStore` which has the middleware enabled.
18 |
19 | #### Example: Using Thunk Middleware for Async Actions
20 |
21 | ```js
22 | import { createStore, combineReducers, applyMiddleware } from 'redux';
23 | import thunk from 'redux-thunk';
24 | import * as reducers from './reducers';
25 |
26 | // applyMiddleware supercharges createStore with middleware:
27 | let createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
28 |
29 | // We can use it exactly like “vanilla” createStore.
30 | let reducer = combineReducers(reducers);
31 | let store = createStoreWithMiddleware(reducer);
32 |
33 | function fetchSecretSauce() {
34 | return fetch('https://www.google.com/search?q=secret+sauce');
35 | }
36 |
37 | // These are the normal action creators you have seen so far.
38 | // The actions they return can be dispatched without any middleware.
39 | // However, they only express “facts” and not the “async flow”.
40 |
41 | function makeASandwich(forPerson, secretSauce) {
42 | return {
43 | type: 'MAKE_SANDWICH',
44 | forPerson,
45 | secretSauce
46 | };
47 | }
48 |
49 | function apologize(fromPerson, toPerson, error) {
50 | return {
51 | type: 'APOLOGIZE',
52 | fromPerson,
53 | toPerson,
54 | error
55 | };
56 | }
57 |
58 | function withdrawMoney(amount) {
59 | return {
60 | type: 'WITHDRAW',
61 | amount
62 | };
63 | }
64 |
65 | // Even without a middleware, you can dispatch an action:
66 | store.dispatch(withdrawMoney(100));
67 |
68 | // But what do you do when you need to start an asynchronous action,
69 | // such as an API call, or a router transition?
70 |
71 | // Meet thunks.
72 | // A thunk is a function that returns a function.
73 | // This is a thunk.
74 |
75 | function makeASandwichWithSecretSauce(forPerson) {
76 |
77 | // Invert control!
78 | // Return a function that accepts `dispatch` so we can dispatch later.
79 | // Thunk middleware knows how to turn thunk async actions into actions.
80 |
81 | return function (dispatch) {
82 | return fetchSecretSauce().then(
83 | sauce => dispatch(makeASandwich(forPerson, sauce)),
84 | error => dispatch(apologize('The Sandwich Shop', forPerson, error))
85 | );
86 | };
87 | }
88 |
89 | // Thunk middleware let me dispatch thunk async actions
90 | // as if they were actions!
91 |
92 | store.dispatch(
93 | makeASandwichWithSecretSauce('Me')
94 | );
95 |
96 | // It even takes care to return the thunk’s return value
97 | // from the dispatch, so I can chain Promises as long as I return them.
98 |
99 | store.dispatch(
100 | makeASandwichWithSecretSauce('My wife')
101 | ).then(() => {
102 | console.log('Done!');
103 | });
104 |
105 | // In fact I can write action creators that dispatch
106 | // actions and async actions from other action creators,
107 | // and I can build my control flow with Promises.
108 |
109 | function makeSandwichesForEverybody() {
110 | return function (dispatch, getState) {
111 | if (!getState().sandwiches.isShopOpen) {
112 |
113 | // You don’t have to return Promises, but it’s a handy convention
114 | // so the caller can always call .then() on async dispatch result.
115 |
116 | return Promise.resolve();
117 | }
118 |
119 | // We can dispatch both plain object actions and other thunks,
120 | // which lets us compose the asynchronous actions in a single flow.
121 |
122 | return dispatch(
123 | makeASandwichWithSecretSauce('My Grandma')
124 | ).then(() =>
125 | Promise.all([
126 | dispatch(makeASandwichWithSecretSauce('Me')),
127 | dispatch(makeASandwichWithSecretSauce('My wife'))
128 | ])
129 | ).then(() =>
130 | dispatch(makeASandwichWithSecretSauce('Our kids'))
131 | ).then(() =>
132 | dispatch(getState().myMoney > 42 ?
133 | withdrawMoney(42) :
134 | apologize('Me', 'The Sandwich Shop')
135 | )
136 | );
137 | };
138 | }
139 |
140 | // This is very useful for server rendering,
141 | // because I can wait to prefill the data before
142 | // sending synchronously rendering the app.
143 |
144 | store.dispatch(
145 | makeSandwichesForEverybody()
146 | ).then(() =>
147 | response.send(React.renderToString())
148 | );
149 |
150 | // I can also dispatch a thunk async action from a component
151 | // any times its props change to load the missing data.
152 |
153 | import { connect } from 'react-redux';
154 | import { Component } from 'react';
155 |
156 | class SandwichShop extends Component {
157 | componentDidMount() {
158 | this.props.dispatch(
159 | makeASandwichWithSecretSauce(this.props.forPerson)
160 | );
161 | }
162 |
163 | componentWillReceiveProps(nextProps) {
164 | if (nextProps.forPerson !== this.props.forPerson) {
165 | this.props.dispatch(
166 | makeASandwichWithSecretSauce(nextProps.forPerson)
167 | );
168 | }
169 | }
170 |
171 | render() {
172 | return
{this.props.sandwiches.join('mustard')}
173 | }
174 | }
175 |
176 | export default connect(
177 | SandwichShop,
178 | state => ({
179 | sandwiches: state.sandwiches
180 | })
181 | );
182 | ```
183 |
184 | #### 示例:自定义 Logger 日志 Middleware
185 |
186 | ```js
187 | import { createStore, applyMiddleware } from 'redux';
188 | import todos from './reducers';
189 |
190 | function logger({ getState }) {
191 | return (next) => (action) => {
192 | console.log('will dispatch', action);
193 |
194 | // Call the next dispatch method in the middleware chain.
195 | let returnValue = next(action);
196 |
197 | console.log('state after dispatch', getState());
198 |
199 | // This will likely be the action itself, unless
200 | // a middleware further in chain changed it.
201 | return returnValue;
202 | };
203 | }
204 |
205 | let createStoreWithMiddleware = applyMiddleware(logger)(createStore);
206 | let store = createStoreWithMiddleware(todos, ['Use Redux']);
207 |
208 | store.dispatch({
209 | type: 'ADD_TODO',
210 | text: 'Understand the middleware'
211 | });
212 | // (These lines will be logged by the middleware:)
213 | // will dispatch: { type: 'ADD_TODO', text: 'Understand the middleware' }
214 | // state after dispatch: ['Use Redux', 'Understand the middleware']
215 | ```
216 |
217 |
218 | #### 小贴士
219 |
220 | * Middleware only wraps the store’s [`dispatch`](Store.md#dispatch) function. Technically, anything a middleware can do, you can do manually by wrapping every `dispatch` call, but it’s easier to manage this in a single place and define action transformations on the scale of the whole project.
221 |
222 | * If you use other store enhancers in addition to `applyMiddleware`, make sure to put `applyMiddleware` before them in the composition chain because the middleware is potentially asynchronous. For example, it should go before [redux-devtools](https://github.com/gaearon/redux-devtools) because otherwise the DevTools won’t see the raw actions emitted by the Promise middleware and such.
223 |
224 | * Ever wondered what `applyMiddleware` itself is? It ought to be an extension mechanism more powerful than the middleware itself. Indeed, `applyMiddleware` is an example of the most poweful Redux extension mechanism called [store enhancers](../Glossary.md#store-enhancer). It is highly unlikely you’ll ever want to write a store enhancer yourself. Another example of a store enhancer is [redux-devtools](https://github.com/gaearon/redux-devtools). Middleware is less powerful than a store enhancer, but it is easier to write.
225 |
226 | * Middleware sounds much more complicated than it really is. The only way to really understand the middleware is to see how the existing middleware works, and try to write your own. The function nesting can be intimidating, but most of the middleware you’ll find are in fact 10-liners, and the nesting and composability is what makes the middleware system powerful.
227 |
--------------------------------------------------------------------------------
/docs/api/bindActionCreators.md:
--------------------------------------------------------------------------------
1 | # `bindActionCreators(actionCreators, dispatch)`
2 |
3 | Turns an object whose values are [action creators](../Glossary.md#action-creator), into an object with the same keys, but with every action creator wrapped into a [`dispatch`](Store.md#dispatch) call so they may be invoked directly.
4 |
5 | Normally you should just call [`dispatch`](Store.md#dispatch) directly on your [`Store`](Store.md) instance. If you use Redux with React, [react-redux](https://github.com/gaearon/react-redux) will provide you with the [`dispatch`](Store.md#dispatch) function so you can call it directly, too.
6 |
7 | The only use case for `bindActionCreators` is when you want to pass some action creators down to a component that isn’t aware of Redux, and you don’t want to pass [`dispatch`](Store.md#dispatch) or the Redux store to it.
8 |
9 | 为方便起见,你可以在第一个参数的位置传入一个函数,它又会返回一个函数。
10 | For convenience, you can also pass a single function as the first argument, and get a function in return.
11 |
12 | #### 参数
13 |
14 | 1. `actionCreators` (*Function* or *Object*): An [action creator](../Glossary.md#action-creator), or an object whose values action creators.
15 |
16 | 2. `dispatch` (*Function*): A [`dispatch`](Store.md#dispatch) function available on the [`Store`](Store.md) instance.
17 |
18 | #### 返回
19 |
20 | (*Function* or *Object*): An object mimicking the original object, but with each function immediately dispatching the action returned by the corresponding action creator. If you passed a function as `actionCreators`, the return value will also be a single function.
21 |
22 | #### 示例
23 |
24 | #### `TodoActionCreators.js`
25 |
26 | ```js
27 | export function addTodo(text) {
28 | return {
29 | type: 'ADD_TODO',
30 | text
31 | };
32 | }
33 |
34 | export function removeTodo(id) {
35 | return {
36 | type: 'REMOVE_TODO',
37 | id
38 | };
39 | }
40 | ```
41 |
42 | #### `SomeComponent.js`
43 |
44 | ```js
45 | import { Component } from 'react';
46 | import { bindActionCreators } from 'redux';
47 | import { connect } from 'react-redux';
48 |
49 | import * as TodoActionCreators from './TodoActionCreators';
50 | console.log(TodoActionCreators);
51 | // {
52 | // addTodo: Function,
53 | // removeTodo: Function
54 | // }
55 |
56 | class TodoListContainer extends Component {
57 | componentDidMount() {
58 | // Injected by react-redux:
59 | let { dispatch } = this.props;
60 |
61 | // 注意:这样做行不通:
62 | // TodoActionCreators.addTodo('Use Redux');
63 |
64 | // 你只是调用了创建 action 的方法。
65 | // 你必须要 dispatch action 而已。
66 |
67 | // 这样做行得通:
68 | let action = TodoActionCreators.addTodo('Use Redux');
69 | dispatch(action);
70 | }
71 |
72 | render() {
73 | // 由 react-redux 注入:
74 | let { todos, dispatch } = this.props;
75 |
76 | // 这是应用 bindActionCreators 比较好的场景:
77 | // 在子组件里,可以完全不知道 Redux 的存在。
78 |
79 | let boundActionCreators = bindActionCreators(TodoActionCreators, dispatch);
80 | console.log(boundActionCreators);
81 | // {
82 | // addTodo: Function,
83 | // removeTodo: Function
84 | // }
85 |
86 | return (
87 |
89 | );
90 |
91 | // An alternative to bindActionCreators is to pass
92 | // just the dispatch function down, but then your child component
93 | // needs to import action creators and know about them.
94 |
95 | // return ;
96 | }
97 | }
98 |
99 | export default connect(
100 | TodoListContainer,
101 | state => ({ todos: state.todos })
102 | )
103 | ```
104 |
105 | #### 小贴士
106 |
107 | * You might ask: why don’t we bind the action creators to the store instance right away, like in classical Flux? The problem is that this won’t work well with universal apps that need to render on the server. Most likely you want to have a separate store instance per request so you can prepare them with different data, but binding action creators during their definition means you’re stuck with a single store instance for all requests.
108 |
109 | * If you use ES5, instead of `import * as` syntax you can just pass `require('./TodoActionCreators')` to `bindActionCreators` as the first argument. The only thing it cares about is that the values of the `actionCreators` arguments are functions. The module system doesn’t matter.
110 |
--------------------------------------------------------------------------------
/docs/api/combineReducers.md:
--------------------------------------------------------------------------------
1 | # `combineReducers(reducers)`
2 |
3 | As your app grows more complex, you’ll want to split your [reducing function](../Glossary.md#reducer) into separate functions, each managing independent parts of the [state](../Glossary.md#state).
4 |
5 | The `combineReducers` helper function turns an object whose values are different reducing functions into a single
6 | reducing function you can pass to [`createStore`](createStore.md).
7 |
8 | The resulting reducer calls every child reducer, and gather their results into a single state object. The shape of the state object matches the keys of the passed `reducers`.
9 |
10 | >##### Flux 用户使用注意
11 |
12 | >This function helps you organize your reducers to manage their own slices of state, similar to how you would have different Flux Stores to manage different state. With Redux, there is just one store, but `combineReducers` helps you keep the same logical division between reducers.
13 |
14 | #### 参数
15 |
16 | 1. `reducers` (*Object*): An object whose values correspond to different reducing functions that need to be combined into one. One handy way to obtain it is to use ES6 `import * as reducers` syntax, but you can also construct this object manually. See the notes below for some rules every passed reducer must follow.
17 |
18 | #### 返回
19 |
20 | (*Function*): A reducer that invokes every reducer inside the `reducers` object, and constructs a state object with the same shape.
21 |
22 | #### 注意
23 |
24 | This function is mildly opinionated and is skewed towards helping beginners avoid common pitfalls. This is why it attempts to enforce some rules that you don’t have to follow if you write the root reducer manually.
25 |
26 | Any reducer passed to `combineReducers` must satisfy these rules:
27 |
28 | * For any action that is not recognized, it must return the `state` given to it as the first argument.
29 |
30 | * It may never return `undefined`. It is too easy to do this by mistake via an early `return` statement, so `combineReducers` throws if you do that instead of letting the error manifest itself somewhere else.
31 |
32 | * If the `state` given to it is `undefined`, it must return the initial state for this specific reducer. According to the previous rule, the initial state must not be `undefined` either. It is handy to specify it with ES6 optional arguments syntax, but you can also explicitly check the first argument for being `undefined`.
33 |
34 | While `combineReducers` attempts to check that your reducers conform to some of these rules, you should remember them, and do your best to follow them.
35 |
36 | #### 示例
37 |
38 | #### `reducers.js`
39 |
40 | ```js
41 | export function todos(state = [], action) {
42 | switch (action.type) {
43 | case 'ADD_TODO':
44 | return state.concat([action.text]);
45 | default:
46 | return state;
47 | }
48 | }
49 |
50 | export function counter(state = 0, action) {
51 | switch (action.type) {
52 | case 'INCREMENT':
53 | return state + 1;
54 | case 'DECREMENT':
55 | return state - 1;
56 | default:
57 | return state;
58 | }
59 | }
60 | ```
61 |
62 | #### `App.js`
63 |
64 | ```js
65 | import { createStore, combineReducers } from 'redux';
66 |
67 | import * as reducers from './reducers';
68 | console.log(reducers);
69 | // {
70 | // todos: Function,
71 | // counter: Function
72 | // }
73 |
74 | let reducer = combineReducers(reducers);
75 | let store = createStore(reducer);
76 | console.log(store.getState());
77 | // {
78 | // counter: 0,
79 | // todos: []
80 | // }
81 |
82 | store.dispatch({
83 | type: 'ADD_TODO',
84 | text: 'Use Redux'
85 | });
86 | console.log(store.getState());
87 | // {
88 | // counter: 0,
89 | // todos: ['Use Redux']
90 | // }
91 | ```
92 |
93 | #### 小贴士
94 |
95 | * This helper is just a convenience! You can write your own `combineReducers` that [works differently](https://github.com/acdlite/reduce-reducers), or even assemble the state object from the child reducers manually and write a root reducing function explicitly, like you would write any other function.
96 |
97 | * You may call `combineReducers` at any level of the reducer hierarchy. It doesn’t have to happen at the top. In fact you may use it again to split the child reducers that get too complicated into independent grandchildren, and so on.
98 |
--------------------------------------------------------------------------------
/docs/api/compose.md:
--------------------------------------------------------------------------------
1 | # `compose(...functions)`
2 |
3 | 从左到右来组合多个函数。
4 |
5 | 这是函数式编程中的方法,为了方便,被放到了 Redux 里。
6 | 当需要把多个 [store 增强器](../Glossary.md#store-enhancer) 依次执行的时候,需要用到它。
7 |
8 | #### 参数
9 |
10 | 1. (*arguments*): 需要合成的多个函数。每个函数都接收一个函数作为参数,然后返回一个函数。
11 |
12 | #### 返回
13 |
14 | (*Function*): 从左到右把接收到的函数合成后的最终函数。
15 |
16 | #### 示例
17 |
18 | 下面示例演示了如何使用 `compose` 增强 [store](Store.md),这个 store 与 [`applyMiddleware`](applyMiddleware.md) 和 [redux-devtools](https://github.com/gaearon/redux-devtools) 一起使用。
19 |
20 | ```js
21 | import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
22 | import thunk from 'redux-thunk';
23 | import * as reducers from '../reducers/index';
24 |
25 | let reducer = combineReducers(reducers);
26 | let middleware = [thunk];
27 |
28 | let finalCreateStore;
29 |
30 | // 生产环境中,我们希望只使用 middleware。
31 | // 而在开发环境中,我们还希望使用一些 redux-devtools 提供的一些 store 增强器。
32 | // UglifyJS 会在构建过程中把一些不会执行的死代码去除掉。
33 |
34 | if (process.env.NODE_ENV === 'production') {
35 | finalCreateStore = applyMiddleware(...middleware)(createStore);
36 | } else {
37 | finalCreateStore = compose(
38 | applyMiddleware(...middleware),
39 | require('redux-devtools').devTools(),
40 | require('redux-devtools').persistState(
41 | window.location.href.match(/[?&]debug_session=([^&]+)\b/)
42 | ),
43 | createStore
44 | );
45 |
46 | // 不使用 compose 来写是这样子:
47 | //
48 | // finalCreateStore =
49 | // applyMiddleware(middleware)(
50 | // devTools()(
51 | // persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))(
52 | // createStore
53 | // )
54 | // )
55 | // );
56 | }
57 |
58 | let store = finalCreateStore(reducer);
59 | ```
60 |
61 | #### 小贴士
62 |
63 | * `compse` 做的只是让你不使用深度右括号的情况下来写深度嵌套的函数。不要觉得它很复杂。
64 |
--------------------------------------------------------------------------------
/docs/api/createStore.md:
--------------------------------------------------------------------------------
1 | # `createStore(reducer, [initialState])`
2 |
3 | 创建一个 Redux [store](Store.md) 来以存放应用中所有的 state。
4 | 应用中应有且仅有一个 store。
5 |
6 | #### 参数
7 |
8 | 1. `reducer` *(Function)*: 接收两个参数,分别是当前的 state 树和要处理的 [action](../Glossary.md#action),返回新的 [state 树](../Glossary.md#state)。
9 |
10 | 2. [`initialState`] *(any)*: 初始时的 state。
11 | 在同构应用中,你可以决定是否把服务端传来的 state 水合(hydrate)后传给它,或者从之前保存的用户会话中恢复一个传给它。如果你使用 [`combineReducers`](combineReducers.md) 创建 `reducer`,它必须是一个普通对象,与传入的 keys 保持同样的结构。否则,你可以自由传入任何 `reducer` 可理解的内容。[TODO: Optimize]
12 |
13 | #### 返回
14 |
15 | ([*`Store`*](Store.md)): 保存了应用所有 state 的对象。改变 state 的惟一方法是 [dispatch](Store.md#dispatch) action。你也可以 [subscribe 监听](Store.md#subscribe) state 的变化,然后更新 UI。
16 |
17 | #### 示例
18 |
19 | ```js
20 | import { createStore } from 'redux';
21 |
22 | function todos(state = [], action) {
23 | switch (action.type) {
24 | case 'ADD_TODO':
25 | return state.concat([action.text]);
26 | default:
27 | return state;
28 | }
29 | }
30 |
31 | let store = createStore(todos, ['Use Redux']);
32 |
33 | store.dispatch({
34 | type: 'ADD_TODO',
35 | text: 'Read the docs'
36 | });
37 |
38 | console.log(store.getState());
39 | // ['Use Redux', 'Read the docs']
40 | ```
41 |
42 | #### 小贴士
43 |
44 | * 应用中不要创建多个 store!相反,使用 [`combineReducers`](combineReducers.md) 来把多个 reducer 创建成一个根 reducer。
45 |
46 | * 你可以决定 state 的格式。你可以使用普通对象或者 [Immutable](http://facebook.github.io/immutable-js/) 这类的实现。如果你不知道如何做,刚开始可以使用普通对象。
47 |
48 | * 如果 state 是普通对象,永远不要修改它!比如,reducer 里不要使用 `Object.assign(state, newData)`,应该使用 `Object.assign({}, state, newData)`。这样才不会覆盖旧的 `state`。也可以使用 [Babel 阶段 1](http://babeljs.io/docs/usage/experimental/) 中的 [ES7 对象的 spread 操作](https://github.com/sebmarkbage/ecmascript-rest-spread) 特性中的 `return { ...state, ...newData }`。
49 |
50 | * 对于服务端运行的同构应用,为每一个请求创建一个 store 实例,以此让 store 相隔离。dispatch 一系列请求数据的 action 到 store 实例上,等待请求完成后再在服务端渲染应用。
51 |
52 | * 当 store 创建后,Redux 会 dispatch 一个 action 到 reducer 上,来用初始的 state 来填充 store。你不需要处理这个 action。但要记住,如果第一个参数也就是传入的 state 如果是 `undefined` 的话,reducer 应该返回初始的 state 值。
53 |
--------------------------------------------------------------------------------
/docs/basics/Actions.md:
--------------------------------------------------------------------------------
1 | # Actions
2 |
3 | 首先,让我们来给 action 下个定义。
4 |
5 | **Actions** 是把数据从应用(译者注:这里之所以不叫 view 是因为这些数据有可能是服务器响应,用户输入或其它非 view 的数据 )传到 store 的有效载荷。它是 store 数据的**惟一**来源。用法是通过 [`store.dispatch()`](../api/Store.md#dispatch) 把 action 传到 store。
6 |
7 | 添加新 todo 任务的 action 是这样的:
8 |
9 | ```js
10 | {
11 | type: 'ADD_TODO',
12 | text: 'Build my first Redux app'
13 | }
14 | ```
15 |
16 | Action 本质是 JavaScript 普通对象。我们约定,action 内使用一个字符串类型的 `type` 字段来表示将要执行的动作。多数情况下,`type` 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
17 |
18 | ```js
19 | import { ADD_TODO, REMOVE_TODO } from '../actionTypes';
20 | ```
21 |
22 | >##### 样板文件使用提醒
23 |
24 | >使用单独的模块或文件来定义 action type 常量并不是必须的,甚至根本不需要定义。对于小应用来说,使用字符串做 action type 更方便些。不过,在大型应用中最多把它们显式地定义成常量。参照 [减少样板代码](../recipes/ReducingBoilerplate.md) 获取保持代码干净的实践经验。
25 |
26 | 除了 `type` 字段外,action 对象的结构完全取决于你。参照 [Flux 标准 Action](https://github.com/acdlite/flux-standard-action) 获取如何组织 action 的建议。
27 |
28 | 这时,我们还需要再添加一个 action type 来标记任务完成。因为数据是存放在数组中的,我们通过 `index` 来标识任务。实际项目中一般会在新建内容的时候生成惟一的 ID 做标识。
29 |
30 | ```js
31 | {
32 | type: COMPLETE_TODO,
33 | index: 5
34 | }
35 | ```
36 | **action 中传递的数据越少越好**。比如,这里传递 `index` 就比把整个任务对象传过去要好。
37 |
38 | 最后,再添加一个 action 类型来表示当前展示的任务状态。
39 |
40 | ```js
41 | {
42 | type: SET_VISIBILITY_FILTER,
43 | filter: SHOW_COMPLETED
44 | }
45 | ```
46 |
47 | ## Action 创建函数
48 |
49 | **Action 创建函数** 就是生成 action 的方法。“action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。
50 |
51 | 在 [传统的 Flux](http://facebook.github.io/flux) 实现中,当调用 action 创建函数时,一般会触发一个 dispatch,像这样:
52 |
53 | ```js
54 | function addTodoWithDispatch(text) {
55 | const action = {
56 | type: ADD_TODO,
57 | text
58 | };
59 | dispatch(action);
60 | }
61 | ```
62 | 不同的是,Redux 中的 action 创建函数是 **纯函数**,它没有任何副作用,只是返回 action 对象而已。
63 |
64 | ```js
65 | function addTodo(text) {
66 | return {
67 | type: ADD_TODO,
68 | text
69 | };
70 | }
71 | ```
72 |
73 | 这让代码更易于测试和移植。只需把 action 创建函数的结果传给 `dispatch()` 方法即可实例化 dispatch。
74 |
75 | ```js
76 | dispatch(addTodo(text));
77 | dispatch(completeTodo(index));
78 | ```
79 |
80 | 或者创建一个 **被绑定的 action 创建函数** 来自动 dispatch:
81 |
82 | ```js
83 | const boundAddTodo = (text) => dispatch(addTodo(text));
84 | const boundCompleteTodo = (index) => dispatch(CompleteTodo(index));
85 | ```
86 |
87 | 可以这样调用:
88 |
89 | ```
90 | boundAddTodo(text);
91 | boundCompleteTodo(index);
92 | ```
93 |
94 | store 里能直接通过 [`store.dispatch()`](../api/Store.md#dispatch) 调用 `dispatch()` 方法,但是多数情况下你会使用 [react-redux](http://github.com/gaearon/react-redux) 提供的 `connect()` 帮助器来调用。[`bindActionCreators()`](../api/bindActionCreators.md) 可以自动把多个 action 创建函数 绑定到 `dispatch()` 方法上。
95 |
96 | ## 源码
97 |
98 | ### `actions.js`
99 |
100 | ```js
101 | /*
102 | * action 类型
103 | */
104 |
105 | export const ADD_TODO = 'ADD_TODO';
106 | export const COMPLETE_TODO = 'COMPLETE_TODO';
107 | export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
108 |
109 | /*
110 | * 其它的常量
111 | */
112 |
113 | export const VisibilityFilters = {
114 | SHOW_ALL: 'SHOW_ALL',
115 | SHOW_COMPLETED: 'SHOW_COMPLETED',
116 | SHOW_ACTIVE: 'SHOW_ACTIVE'
117 | };
118 |
119 | /*
120 | * action 创建函数
121 | */
122 |
123 | export function addTodo(text) {
124 | return { type: ADD_TODO, text };
125 | }
126 |
127 | export function completeTodo(index) {
128 | return { type: COMPLETE_TODO, index };
129 | }
130 |
131 | export function setVisibilityFilter(filter) {
132 | return { type: SET_VISIBILITY_FILTER, filter };
133 | }
134 | ```
135 |
136 | ## 下一步
137 |
138 | 现在让我们 [开发一些 reducers](Reducers.md) 来指定发起 action 后 state 应该如何更新。
139 |
140 | >##### 高级用户建议
141 | >如果你已经熟悉这些基本概念且已经完成了这个示例,不要忘了看一下在 [高级教程](../advanced/README.md) 中的 [异步 actions] (../advanced/AsyncActions.md),你将学习如何处理 AJAX 响应和如何把 action 创建函数组合成异步控制流。
--------------------------------------------------------------------------------
/docs/basics/DataFlow.md:
--------------------------------------------------------------------------------
1 | # 数据流
2 |
3 | **严格的单向数据流**是 Redux 架构的设计核心。
4 |
5 | 这意味着应用中所有的数据都遵循相同的生命周期,这样可以让应用变得更加可预测且容易理解。同时也鼓励做数据范式化,这样可以避免使用多个,独立的无法相互引用的重复数据。
6 |
7 | 如何这些理由还不足以今你信服,读一下 [动机](../introduction/Motivation.md) 和 [Flux 案例](https://medium.com/@dan_abramov/the-case-for-flux-379b7d1982c6),这里面有更加详细的单向数据流优势分析。虽然 [Redux 就不是严格意义上的 [Flux](../introduction/Relation to Other Libraries.md),但它们有共同的设计思想。
8 |
9 | Redux 应用中数据的生命周期遵循下面 4 个步骤:
10 |
11 | 1. **调用** [`store.dispatch(action)`](../api/Store.md#dispatch)。
12 |
13 | action 就是一个描述“发生了什么”的普通对象。比如:
14 |
15 | ```js
16 | { type: 'LIKE_ARTICLE', articleId: 42 };
17 | { type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Megan' } };
18 | { type: 'ADD_TODO', text: 'Read the Redux docs.'};
19 | ```
20 |
21 | 可以把 action 理解成新闻的摘要。如 “玛丽喜欢42号文章。” 或者 “任务列表里添加了'学习 Redux 文档'”。
22 |
23 | 你可以在任何地方调用 [`store.dispatch(action)`](../api/Store.md#dispatch),包括组件中、XHR 回调中、甚至定时器中。
24 |
25 | 2. **Redux store 调用传入的 reducer 函数。**
26 |
27 | Store 会把两个参数传入 reducer,当前的 state 树和 action。例如,在这个 todo 应用中,根 reducer 可能接收这样的数据:
28 |
29 | ```js
30 | // 当前应用的 state(todos 列表和选中的过滤器)
31 | let previousState = {
32 | visibleTodoFilter: 'SHOW_ALL',
33 | todos: [{
34 | text: 'Read the docs.',
35 | complete: false
36 | }]
37 | };
38 |
39 | // 将要执行的 action(添加一个 todo)
40 | let action = {
41 | type: 'ADD_TODO',
42 | text: 'Understand the flow.'
43 | };
44 |
45 | // render 返回处理后的应用状态
46 | let nextState = todoApp(previousState, action);
47 | ```
48 |
49 | 注意 reducer 是纯函数。它应该是完全可预测的:多次传入相同的输入必须产生相同的输出。它不应做有副作用的操作,如 API 调用或路由跳转。这些应该在 dispatch action 前发生。
50 |
51 | 3. **根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。**
52 |
53 | 根 reducer 的结构完全由你决定。Redux 原生提供[`combineReducers()`](../api/combineReducers.md)辅助函数,来把根 reducer 拆分成多个函数,用于分别处理 state 树的一个分支。
54 |
55 | 下面演示 [`combineReducers()`](../api/combineReducers.md) 如何使用。假如你有一个 todos 列表,使用当前的选择过滤器来追踪两个 reducers(原文:and the currently selected filter setting to keep track of with two reducers):
56 |
57 | ```js
58 | function todos(state = [], action) {
59 | // 省略处理逻辑...
60 | return nextState;
61 | }
62 |
63 | function visibleTodoFilter(state = 'SHOW_ALL', action) {
64 | // 省略处理逻辑...
65 | return nextState;
66 | }
67 |
68 | let todoApp = combineReducers({
69 | todos,
70 | visibleTodoFilter
71 | });
72 | ```
73 |
74 | 当你触发 action 后,`combineReducers` 返回的 `todoApp` 会负责调用两个 reducer:
75 |
76 | ```js
77 | let nextTodos = todos(state.todos, action);
78 | let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action);
79 | ```
80 |
81 | 然后会把两个结果集合并成一个 state 树:
82 |
83 | ```js
84 | return {
85 | todos: nextTodos,
86 | visibleTodoFilter: nextVisibleTodoFilter
87 | };
88 | ```
89 |
90 | 虽然 [`combineReducers()`](../api/combineReducers.md) 是一个很方便的辅助工具,你也可以选择不用;你可以自行实现自己的根 reducer!
91 |
92 | 4. **Redux store 保存了根 reducer 返回的完整 state 树。**
93 |
94 | 这个新的树就是应用的下一个 state!所有订阅 [`store.subscribe(listener)`](../api/Store.md#subscribe) 的监听器都将被调用;监听器里可以调用 [`store.getState()`](../api/Store.md#getState) 获得当前 state。
95 |
96 | 现在,可以应用新的 state 来更新 UI。如果你使用了 [React Redux](https://github.com/gaearon/react-redux) 这类的绑定库,这时就应该调用 `component.setState(newState)` 来更新。
97 |
98 | ## 下一步
99 |
100 | 现在你已经理解了 Redux 如何工作,是时候[结合 React 开发应用](UsageWithReact.md)了。
101 |
102 | >##### 高级用户使用注意
103 | >如果你已经熟悉了基础概念且完成了这个教程,可以学习[高级教程](../advanced/README.md)中的[异步数据流](../advanced/AsyncFlow.md),你将学到如何使用 middleware 在 [异步 action](../advanced/AsyncActions.md) 到达 reducer 前处理它们。
104 |
--------------------------------------------------------------------------------
/docs/basics/ExampleTodoList.md:
--------------------------------------------------------------------------------
1 | # 示例: Todo 列表
2 |
3 | 这是我们在[基础教程](./README.md)里开发的迷你型的任务管理应用的完整源码。
4 |
5 | ## 入口文件
6 |
7 | #### `index.js`
8 |
9 | ```js
10 | import React from 'react';
11 | import { createStore } from 'redux';
12 | import { Provider } from 'react-redux';
13 | import App from './containers/App';
14 | import todoApp from './reducers';
15 |
16 | let store = createStore(todoApp);
17 |
18 | let rootElement = document.getElementById('root');
19 | React.render(
20 | // 为了解决 React 0.13 的问题,
21 | // 一定要把 child 用函数包起来。
22 |
23 | {() => }
24 | ,
25 | rootElement
26 | );
27 | ```
28 |
29 | ## Action 创建函数和常量
30 |
31 | #### `actions.js`
32 |
33 | ```js
34 | /*
35 | * action types
36 | */
37 |
38 | export const ADD_TODO = 'ADD_TODO';
39 | export const COMPLETE_TODO = 'COMPLETE_TODO';
40 | export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
41 |
42 | /*
43 | * 其它的常量
44 | */
45 |
46 | export const VisibilityFilters = {
47 | SHOW_ALL: 'SHOW_ALL',
48 | SHOW_COMPLETED: 'SHOW_COMPLETED',
49 | SHOW_ACTIVE: 'SHOW_ACTIVE'
50 | };
51 |
52 | /*
53 | * action 创建函数
54 | */
55 |
56 | export function addTodo(text) {
57 | return { type: ADD_TODO, text };
58 | }
59 |
60 | export function completeTodo(index) {
61 | return { type: COMPLETE_TODO, index };
62 | }
63 |
64 | export function setVisibilityFilter(filter) {
65 | return { type: SET_VISIBILITY_FILTER, filter };
66 | }
67 | ```
68 |
69 | ## Reducers
70 |
71 | #### `reducers.js`
72 |
73 | ```js
74 | import { combineReducers } from 'redux';
75 | import { ADD_TODO, COMPLETE_TODO, SET_VISIBILITY_FILTER, VisibilityFilters } from './actions';
76 | const { SHOW_ALL } = VisibilityFilters;
77 |
78 | function visibilityFilter(state = SHOW_ALL, action) {
79 | switch (action.type) {
80 | case SET_VISIBILITY_FILTER:
81 | return action.filter;
82 | default:
83 | return state;
84 | }
85 | }
86 |
87 | function todos(state = [], action) {
88 | switch (action.type) {
89 | case ADD_TODO:
90 | return [...state, {
91 | text: action.text,
92 | completed: false
93 | }];
94 | case COMPLETE_TODO:
95 | return [
96 | ...state.slice(0, action.index),
97 | Object.assign({}, state[action.index], {
98 | completed: true
99 | }),
100 | ...state.slice(action.index + 1)
101 | ];
102 | default:
103 | return state;
104 | }
105 | }
106 |
107 | const todoApp = combineReducers({
108 | visibilityFilter,
109 | todos
110 | });
111 |
112 | export default todoApp;
113 | ```
114 |
115 | ## 智能组件
116 |
117 | #### `containers/App.js`
118 |
119 | ```js
120 | import React, { Component, PropTypes } from 'react';
121 | import { connect } from 'react-redux';
122 | import { addTodo, completeTodo, setVisibilityFilter, VisibilityFilters } from '../actions';
123 | import AddTodo from '../components/AddTodo';
124 | import TodoList from '../components/TodoList';
125 | import Footer from '../components/Footer';
126 |
127 | class App extends Component {
128 | render() {
129 | // Injected by connect() call:
130 | const { dispatch, visibleTodos, visibilityFilter } = this.props;
131 | return (
132 |
52 |
53 | 在这个 todo 应用中,只应有一个智能组件,它存在于组件的最顶层。在复杂的应用中,也有可能会有多个智能组件。虽然你也可以嵌套使用智能组件,但应该尽可能的使用传递 props 的形式。
54 |
55 | ## 设计组件层次结构
56 |
57 | 还记得当初如何 [设计 reducer 结构](Reducers.md) 吗?现在就要定义与它匹配的界面的层次结构。其实这不是 Redux 相关的工作,[React 开发思想](https://facebook.github.io/react/docs/thinking-in-react.html)在这方面解释的非常棒。
58 |
59 | Our design brief is simple. We want to show a list of todo items. On click, a todo item is crossed out as completed. We want to show a field where user may add a new todo. In the footer, we want to show a toggle to show all / only completed / only incompleted todos.
60 |
61 | I see the following components (and their props) emerge from this brief:
62 |
63 | * **`AddTodo`** is an input field with a button.
64 | - `onAddClick(text: string)` is a callback to invoke when a button is pressed.
65 | * **`TodoList`** is a list showing visible todos.
66 | - `todos: Array` is an array of todo items with `{ text, completed }` shape.
67 | - `onTodoClick(index: number)` is a callback to invoke when a todo is clicked.
68 | * **`Todo`** is a single todo item.
69 | - `text: string` is the text to show.
70 | - `completed: boolean` is whether todo should appear crossed out.
71 | - `onClick()` is a callback to invoke when a todo is clicked.
72 | * **`Footer`** is a component where we let user change visible todo filter.
73 | - `filter: string` is the current filter: `'SHOW_ALL'`, `'SHOW_COMPLETED'` or `'SHOW_ACTIVE'`.
74 | - `onFilterChange(nextFilter: string)`: Callback to invoke when user chooses a different filter.
75 |
76 | These are all “dumb” components. They don’t know *where* the data comes from, or *how* to change it. They only render what’s given to them.
77 |
78 | If you migrate from Redux to something else, you’ll be able to keep all these components exactly the same. They have no dependency on Redux.
79 |
80 | Let’s write them! We don’t need to think about binding to Redux yet. You can just give them fake data while you experiment until they render correctly.
81 |
82 | ## 木偶组件
83 |
84 | 这就是普通的 React 组件,所以就不在详述。直接看代码:
85 |
86 | #### `components/AddTodo.js`
87 |
88 | ```js
89 | import React, { findDOMNode, Component, PropTypes } from 'react';
90 |
91 | export default class AddTodo extends Component {
92 | render() {
93 | return (
94 |
254 | );
255 | }
256 | }
257 | ```
258 |
259 | 渲染 `` 结果如下:
260 |
261 |
262 |
263 | 单独来看,并没有什么特别,现在把它和 Redux 连起来。
264 |
265 | ## 连接到 Redux
266 |
267 | We need to do two changes to connect our `App` component to Redux and make it dispatch actions and read state from the Redux store.
268 |
269 | First, we need to import `Provider` from [`react-redux`](http://github.com/gaearon/react-redux) we installed earlier, and **wrap the root component in ``** before rendering.
270 |
271 | #### `index.js`
272 |
273 | ```js
274 | import React from 'react';
275 | import { createStore } from 'redux';
276 | import { Provider } from 'react-redux';
277 | import App from './containers/App';
278 | import todoApp from './reducers';
279 |
280 | let store = createStore(todoApp);
281 |
282 | let rootElement = document.getElementById('root');
283 | React.render(
284 | // The child must be wrapped in a function
285 | // to work around an issue in React 0.13.
286 |
287 | {() => }
288 | ,
289 | rootElement
290 | );
291 | ```
292 |
293 | This makes our store instance available to the components below. (Internally, this is done via React [undocumented “context” feature](http://www.youtube.com/watch?v=H7vlH-wntD4), but it’s not exposed directly in the API so don’t worry about it.)
294 |
295 | Then, we **wrap the components we want to connect to Redux with `connect()` function from [`react-redux`](http://github.com/gaearon/react-redux)**. Try to only do this for a top-level component, or route handlers. While technically you can `connect()` any component in your app to Redux store, avoid doing this too deeply because it will make the data flow harder to trace.
296 |
297 | **Any component wrapped with `connect()` call will receive a [`dispatch`](../api/Store.md#dispatch) function as a prop, and any state it needs from the global state.** The only argument to `connect()` is a function we call a **selector**. This function takes the global Redux store’s state, and returns the props you need for the component. In the simplest case, you can just return the `state` given to you, but you may also wish to transform it first.
298 |
299 | To make performant memoized transformations with composable selectors, check out [reselect](https://github.com/faassen/reselect). In this example, we won’t use it, but it works great for larger apps.
300 |
301 | #### `containers/App.js`
302 |
303 | ```js
304 | import React, { Component, PropTypes } from 'react';
305 | import { connect } from 'react-redux';
306 | import { addTodo, completeTodo, setVisibilityFilter, VisibilityFilters } from '../actions';
307 | import AddTodo from '../components/AddTodo';
308 | import TodoList from '../components/TodoList';
309 | import Footer from '../components/Footer';
310 |
311 | class App extends Component {
312 | render() {
313 | // Injected by connect() call:
314 | const { dispatch, visibleTodos, visibilityFilter } = this.props;
315 | return (
316 |