mirror of
https://github.com/idanoo/GoScrobble.git
synced 2024-11-26 02:15:15 +00:00
324 lines
8.3 KiB
Markdown
324 lines
8.3 KiB
Markdown
|
Redux Thunk
|
|||
|
=============
|
|||
|
|
|||
|
Thunk [middleware](https://redux.js.org/advanced/middleware) for Redux.
|
|||
|
|
|||
|
[![build status](https://img.shields.io/travis/reduxjs/redux-thunk/master.svg?style=flat-square)](https://travis-ci.org/reduxjs/redux-thunk)
|
|||
|
[![npm version](https://img.shields.io/npm/v/redux-thunk.svg?style=flat-square)](https://www.npmjs.com/package/redux-thunk)
|
|||
|
[![npm downloads](https://img.shields.io/npm/dm/redux-thunk.svg?style=flat-square)](https://www.npmjs.com/package/redux-thunk)
|
|||
|
|
|||
|
```js
|
|||
|
npm install --save redux-thunk
|
|||
|
```
|
|||
|
|
|||
|
## Note on 2.x Update
|
|||
|
|
|||
|
Most tutorials today assume Redux Thunk 1.x so you might run into an issue when running their code with 2.x.
|
|||
|
**If you use Redux Thunk 2.x in CommonJS environment, [don’t forget to add `.default` to your import](https://github.com/reduxjs/redux-thunk/releases/tag/v2.0.0):**
|
|||
|
|
|||
|
```diff
|
|||
|
- var ReduxThunk = require('redux-thunk')
|
|||
|
+ var ReduxThunk = require('redux-thunk').default
|
|||
|
```
|
|||
|
|
|||
|
If you used ES modules, you’re already all good:
|
|||
|
|
|||
|
```js
|
|||
|
import ReduxThunk from 'redux-thunk' // no changes here 😀
|
|||
|
```
|
|||
|
|
|||
|
Additionally, since 2.x, we also support a [UMD build](https://unpkg.com/redux-thunk/dist/redux-thunk.min.js):
|
|||
|
|
|||
|
```js
|
|||
|
var ReduxThunk = window.ReduxThunk.default
|
|||
|
```
|
|||
|
|
|||
|
As you can see, it also requires `.default` at the end.
|
|||
|
|
|||
|
## Why Do I Need This?
|
|||
|
|
|||
|
If you’re not sure whether you need it, you probably don’t.
|
|||
|
|
|||
|
**[Read this for an in-depth introduction to thunks in Redux.](http://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559)**
|
|||
|
|
|||
|
## Motivation
|
|||
|
|
|||
|
Redux Thunk [middleware](https://github.com/reactjs/redux/blob/master/docs/advanced/Middleware.md) allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods `dispatch` and `getState` as parameters.
|
|||
|
|
|||
|
An action creator that returns a function to perform asynchronous dispatch:
|
|||
|
|
|||
|
```js
|
|||
|
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
|
|||
|
|
|||
|
function increment() {
|
|||
|
return {
|
|||
|
type: INCREMENT_COUNTER
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
function incrementAsync() {
|
|||
|
return dispatch => {
|
|||
|
setTimeout(() => {
|
|||
|
// Yay! Can invoke sync or async actions with `dispatch`
|
|||
|
dispatch(increment());
|
|||
|
}, 1000);
|
|||
|
};
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
An action creator that returns a function to perform conditional dispatch:
|
|||
|
|
|||
|
```js
|
|||
|
function incrementIfOdd() {
|
|||
|
return (dispatch, getState) => {
|
|||
|
const { counter } = getState();
|
|||
|
|
|||
|
if (counter % 2 === 0) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
dispatch(increment());
|
|||
|
};
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## What’s a thunk?!
|
|||
|
|
|||
|
A [thunk](https://en.wikipedia.org/wiki/Thunk) is a function that wraps an expression to delay its evaluation.
|
|||
|
|
|||
|
```js
|
|||
|
// calculation of 1 + 2 is immediate
|
|||
|
// x === 3
|
|||
|
let x = 1 + 2;
|
|||
|
|
|||
|
// calculation of 1 + 2 is delayed
|
|||
|
// foo can be called later to perform the calculation
|
|||
|
// foo is a thunk!
|
|||
|
let foo = () => 1 + 2;
|
|||
|
```
|
|||
|
|
|||
|
The term [originated](https://en.wikipedia.org/wiki/Thunk#cite_note-1) as a humorous past-tense version of "think".
|
|||
|
|
|||
|
## Installation
|
|||
|
|
|||
|
```
|
|||
|
npm install --save redux-thunk
|
|||
|
```
|
|||
|
|
|||
|
Then, to enable Redux Thunk, use [`applyMiddleware()`](https://redux.js.org/api-reference/applymiddleware):
|
|||
|
|
|||
|
```js
|
|||
|
import { createStore, applyMiddleware } from 'redux';
|
|||
|
import thunk from 'redux-thunk';
|
|||
|
import rootReducer from './reducers/index';
|
|||
|
|
|||
|
// Note: this API requires redux@>=3.1.0
|
|||
|
const store = createStore(
|
|||
|
rootReducer,
|
|||
|
applyMiddleware(thunk)
|
|||
|
);
|
|||
|
```
|
|||
|
|
|||
|
## Composition
|
|||
|
|
|||
|
Any return value from the inner function will be available as the return value of `dispatch` itself. This is convenient for orchestrating an asynchronous control flow with thunk action creators dispatching each other and returning Promises to wait for each other’s completion:
|
|||
|
|
|||
|
```js
|
|||
|
import { createStore, applyMiddleware } from 'redux';
|
|||
|
import thunk from 'redux-thunk';
|
|||
|
import rootReducer from './reducers';
|
|||
|
|
|||
|
// Note: this API requires redux@>=3.1.0
|
|||
|
const store = createStore(
|
|||
|
rootReducer,
|
|||
|
applyMiddleware(thunk)
|
|||
|
);
|
|||
|
|
|||
|
function fetchSecretSauce() {
|
|||
|
return fetch('https://www.google.com/search?q=secret+sauce');
|
|||
|
}
|
|||
|
|
|||
|
// These are the normal action creators you have seen so far.
|
|||
|
// The actions they return can be dispatched without any middleware.
|
|||
|
// However, they only express “facts” and not the “async flow”.
|
|||
|
|
|||
|
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
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
// Even without middleware, you can dispatch an action:
|
|||
|
store.dispatch(withdrawMoney(100));
|
|||
|
|
|||
|
// But what do you do when you need to start an asynchronous action,
|
|||
|
// such as an API call, or a router transition?
|
|||
|
|
|||
|
// Meet thunks.
|
|||
|
// A thunk is a function that returns a function.
|
|||
|
// This is a thunk.
|
|||
|
|
|||
|
function makeASandwichWithSecretSauce(forPerson) {
|
|||
|
|
|||
|
// Invert control!
|
|||
|
// Return a function that accepts `dispatch` so we can dispatch later.
|
|||
|
// Thunk middleware knows how to turn thunk async actions into actions.
|
|||
|
|
|||
|
return function (dispatch) {
|
|||
|
return fetchSecretSauce().then(
|
|||
|
sauce => dispatch(makeASandwich(forPerson, sauce)),
|
|||
|
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
|
|||
|
);
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
// Thunk middleware lets me dispatch thunk async actions
|
|||
|
// as if they were actions!
|
|||
|
|
|||
|
store.dispatch(
|
|||
|
makeASandwichWithSecretSauce('Me')
|
|||
|
);
|
|||
|
|
|||
|
// It even takes care to return the thunk’s return value
|
|||
|
// from the dispatch, so I can chain Promises as long as I return them.
|
|||
|
|
|||
|
store.dispatch(
|
|||
|
makeASandwichWithSecretSauce('My wife')
|
|||
|
).then(() => {
|
|||
|
console.log('Done!');
|
|||
|
});
|
|||
|
|
|||
|
// In fact I can write action creators that dispatch
|
|||
|
// actions and async actions from other action creators,
|
|||
|
// and I can build my control flow with Promises.
|
|||
|
|
|||
|
function makeSandwichesForEverybody() {
|
|||
|
return function (dispatch, getState) {
|
|||
|
if (!getState().sandwiches.isShopOpen) {
|
|||
|
|
|||
|
// You don’t have to return Promises, but it’s a handy convention
|
|||
|
// so the caller can always call .then() on async dispatch result.
|
|||
|
|
|||
|
return Promise.resolve();
|
|||
|
}
|
|||
|
|
|||
|
// We can dispatch both plain object actions and other thunks,
|
|||
|
// which lets us compose the asynchronous actions in a single flow.
|
|||
|
|
|||
|
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')
|
|||
|
)
|
|||
|
);
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
// This is very useful for server side rendering, because I can wait
|
|||
|
// until data is available, then synchronously render the app.
|
|||
|
|
|||
|
store.dispatch(
|
|||
|
makeSandwichesForEverybody()
|
|||
|
).then(() =>
|
|||
|
response.send(ReactDOMServer.renderToString(<MyApp store={store} />))
|
|||
|
);
|
|||
|
|
|||
|
// I can also dispatch a thunk async action from a component
|
|||
|
// any time its props change to load the missing data.
|
|||
|
|
|||
|
import { connect } from 'react-redux';
|
|||
|
import { Component } from 'react';
|
|||
|
|
|||
|
class SandwichShop extends Component {
|
|||
|
componentDidMount() {
|
|||
|
this.props.dispatch(
|
|||
|
makeASandwichWithSecretSauce(this.props.forPerson)
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
componentDidUpdate(prevProps) {
|
|||
|
if (prevProps.forPerson !== this.props.forPerson) {
|
|||
|
this.props.dispatch(
|
|||
|
makeASandwichWithSecretSauce(this.props.forPerson)
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
render() {
|
|||
|
return <p>{this.props.sandwiches.join('mustard')}</p>
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
export default connect(
|
|||
|
state => ({
|
|||
|
sandwiches: state.sandwiches
|
|||
|
})
|
|||
|
)(SandwichShop);
|
|||
|
```
|
|||
|
|
|||
|
## Injecting a Custom Argument
|
|||
|
|
|||
|
Since 2.1.0, Redux Thunk supports injecting a custom argument using the `withExtraArgument` function:
|
|||
|
|
|||
|
```js
|
|||
|
const store = createStore(
|
|||
|
reducer,
|
|||
|
applyMiddleware(thunk.withExtraArgument(api))
|
|||
|
)
|
|||
|
|
|||
|
// later
|
|||
|
function fetchUser(id) {
|
|||
|
return (dispatch, getState, api) => {
|
|||
|
// you can use api here
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
To pass multiple things, just wrap them in a single object and use destructuring:
|
|||
|
|
|||
|
```js
|
|||
|
const store = createStore(
|
|||
|
reducer,
|
|||
|
applyMiddleware(thunk.withExtraArgument({ api, whatever }))
|
|||
|
)
|
|||
|
|
|||
|
// later
|
|||
|
function fetchUser(id) {
|
|||
|
return (dispatch, getState, { api, whatever }) => {
|
|||
|
// you can use api and something else here
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
## License
|
|||
|
|
|||
|
MIT
|