一个可预测的全局状态管理的 JS 库
A JS library for predictable global state management
redux
=========================
[![build status](https://img.shields.io/travis/gaearon/redux.svg?style=flat-square)](https://travis-ci.org/gaearon/redux)
[![npm version](https://img.shields.io/npm/v/redux.svg?style=flat-square)](https://www.npmjs.com/package/redux)
An experiment in fully hot-reloadable Flux.
**The API might change any day.**
_**Don't use in production just yet.**_
## Why another Flux framework?
Read **[The Evolution of Flux Frameworks](https://medium.com/@dan_abramov/the-evolution-of-flux-frameworks-6c16ad26bb31)** for some context.
### Design Goals
* Hot reloading of everything.
* A hook for the future devtools to "commit" a state, and replay actions on top of it during hot reload.
* No wrapper calls in your stores and actions. Your stuff is your stuff.
* Super easy to test things in isolation without mocks.
* I don't mind action constants. Seriously.
* Keep Flux lingo. No cursors or observables in core.
* Have I mentioned hot reloading yet?
## Demo
```
git clone https://github.com/gaearon/redux.git redux
cd redux
npm install
npm start
```
## What does it look like?
### Actions
```js
// Still using constants...
import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes';
// But action creators are pure functions returning actions
export function increment() {
return {
type: INCREMENT_COUNTER
};
}
export function decrement() {
return {
type: DECREMENT_COUNTER
};
}
// Can also be async if you return a function
export function incrementAsync() {
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
};
}
// Could also read state of a store in the callback form
export function incrementIfOdd() {
return (dispatch, { counter }) => {
if (counter % 2 === 0) {
return;
}
dispatch(increment());
};
}
```
### Stores
```js
// ... too, use constants
import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes';
// what's important is that Store is a pure function,
// and you can write it anyhow you like.
// the Store signature is (state, action) => state,
// and the state shape is up to you: you can use primitives,
// objects, arrays, or even ImmutableJS objects.
export default function counter(state = 0, action) {
// this function returns the new state when an action comes
switch (action.type) {
case INCREMENT_COUNTER:
return state + 1;
case DECREMENT_COUNTER:
return state - 1;
default:
return state;
}
// BUT THAT'S A SWITCH STATEMENT!
// Right. If you hate 'em, see the FAQ below.
}
```
### Components
#### Dumb Components
```js
// The dumb component receives everything using props:
import React, { PropTypes } from 'react';
export default class Counter {
static propTypes = {
increment: PropTypes.func.isRequired,
decrement: PropTypes.func.isRequired,
counter: PropTypes.number.isRequired
};
render() {
const { increment, decrement, counter } = this.props;
return (
Clicked: {counter} times
{' '}
{' '}
);
}
}
```
#### Smart Components
```js
// The smart component may observe stores using ``,
// and bind actions to the dispatcher with `bindActionCreators`.
import React from 'react';
import { Connector, bindActionCreators } from 'redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';
// You can optionally specify `select` for finer-grained subscriptions
// and retrieval. Only when the return value is shallowly different,
// will the child component be updated.
function select(state) {
return { counter: state.counter };
}
export default class CounterApp {
render() {
return (
{({ counter, dispatch }) =>
/* Yes this is child as a function. */
}
);
}
}
```
#### Decorators
The `@connect` decorator lets you create smart components less verbosely:
```js
import React from 'react';
import { connect, bindActionCreators } from 'redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';
@connect(state => ({
counter: state.counter
}))
export default class CounterApp {
render() {
const { counter, dispatch } = this.props;
// Instead of `bindActionCreators`, you may also pass `dispatch` as a prop
// to your component and call `dispatch(CounterActions.increment())`
return (
);
}
}
```
#### Initializing Redux
The simplest way to initialize a Redux instance is to give it an object whose values are your Store functions, and whose keys are their names. You may `import *` from the file with all your Store definitions to obtain such an object:
```js
import { createRedux, Provider } from 'redux';
import * as stores from '../stores/index';
const redux = createRedux(stores);
```
Then pass `redux` as a prop to `` component in the root component of your app, and you're all set:
```js
export default class App {
render() {
return (
{() =>
}
);
}
}
```
#### Running the same code on client and server
The `redux` instance returned by `createRedux` also has the `dispatch(action)`, `subscribe()` and `getState()` methods that you may call outside the React components.
You may optionally specify the initial state as the second argument to `createRedux`. This is useful for hydrating the state you received from running Redux on the server:
```js
// server
const redux = createRedux(stores);
redux.dispatch(MyActionCreators.doSomething()); // fire action creators to fill the state
const state = redux.getState(); // somehow pass this state to the client
// client
const initialState = window.STATE_FROM_SERVER;
const redux = createRedux(stores, initialState);
```
#### Additional customization
There is also a longer way to do the same thing, if you need additional customization.
This:
```js
import { createRedux } from 'redux';
import * as stores from '../stores/index';
const redux = createRedux(stores);
```
is in fact a shortcut for this:
```js
import { createRedux, createDispatcher, composeStores } from 'redux';
import thunkMiddleware from 'redux/lib/middleware/thunk';
import * as stores from '../stores/index';
// Compose all your Stores into a single Store function with `composeStores`:
const store = composeStores(stores);
// Create a Dispatcher function for your composite Store:
const dispatcher = createDispatcher(
store,
getState => [thunkMiddleware(getState)] // Pass the default middleware
);
// Create a Redux instance using the dispatcher function:
const redux = createRedux(dispatcher);
```
Why would you want to write it longer? Maybe you're an advanced user and want to provide a custom Dispatcher function, or maybe you have a different idea of how to compose your Stores (or you're satisfied with a single Store). Redux lets you do all of this.
`createDispatcher()` also gives you the ability to specify middleware -- for example, to add support for promises. [Learn more](https://github.com/gaearon/redux/blob/master/docs/middleware.md) about how to create and use middleware in Redux.
When in doubt, use the shorter option!
## FAQ
### How does hot reloading work?
* http://webpack.github.io/docs/hot-module-replacement.html
* http://gaearon.github.io/react-hot-loader/