redux-thunk README

The project's README from github.com/reduxjs/redux-thunk. As one markdown file: README.md.

Redux Thunk

Thunk middleware for Redux. It allows writing functions with logic inside that can interact with a Redux store's dispatch and getState methods.

For complete usage instructions and useful patterns, see the Redux docs Writing Logic with Thunks page.

GitHub Workflow Status npm version npm downloads

Installation and Setup

Redux Toolkit

If you're using our official Redux Toolkit package as recommended, there's nothing to install - RTK's configureStore API already adds the thunk middleware by default:

import { configureStore } from '@reduxjs/toolkit'

import todosReducer from './features/todos/todosSlice'
import filtersReducer from './features/filters/filtersSlice'

const store = configureStore({
  reducer: {
    todos: todosReducer,
    filters: filtersReducer,
  },
})

// The thunk middleware was automatically added

Manual Setup

If you're using the basic Redux createStore API and need to set this up manually, first add the redux-thunk package:

npm install redux-thunk

yarn add redux-thunk

The thunk middleware is a named export.

More Details: Importing the thunk middleware

If you're using ES modules:

import { thunk } from 'redux-thunk'

If you use Redux Thunk in a CommonJS environment:

const { thunk } = require('redux-thunk')

Then, to enable Redux Thunk, use applyMiddleware():

import { createStore, applyMiddleware } from 'redux'
import { thunk } from 'redux-thunk'
import rootReducer from './reducers/index'

const store = createStore(rootReducer, applyMiddleware(thunk))

Injecting a Custom Argument

Since 2.1.0, Redux Thunk supports injecting a custom argument into the thunk middleware. This is typically useful for cases like using an API service layer that could be swapped out for a mock service in tests.

For Redux Toolkit, the getDefaultMiddleware callback inside of configureStore lets you pass in a custom extraArgument:

import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './reducer'
import { myCustomApiService } from './api'

const store = configureStore({
  reducer: rootReducer,
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware({
      thunk: {
        extraArgument: myCustomApiService,
      },
    }),
})

// later
function fetchUser(id) {
  // The `extraArgument` is the third arg for thunk functions
  return (dispatch, getState, api) => {
    // you can use api here
  }
}

If you need to pass in multiple values, combine them into a single object:

const store = configureStore({
  reducer: rootReducer,
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware({
      thunk: {
        extraArgument: {
          api: myCustomApiService,
          otherValue: 42,
        },
      },
    }),
})

// later
function fetchUser(id) {
  return (dispatch, getState, { api, otherValue }) => {
    // you can use api and something else here
  }
}

If you're setting up the store by hand, the named export withExtraArgument() function should be used to generate the correct thunk middleware:

const store = createStore(reducer, applyMiddleware(withExtraArgument(api)))

Why Do I Need This?

With a plain basic Redux store, you can only do simple synchronous updates by dispatching an action. Middleware extends the store's abilities, and lets you write async logic that interacts with the store.

Thunks are the recommended middleware for basic Redux side effects logic, including complex synchronous logic that needs access to the store, and simple async logic like AJAX requests.

For more details on why thunks are useful, see:

You may also want to read the Redux FAQ entry on choosing which async middleware to use.

While the thunk middleware is not directly included with the Redux core library, it is used by default in our @reduxjs/toolkit package.

Motivation

Redux Thunk middleware 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:

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:

function incrementIfOdd() {
  return (dispatch, getState) => {
    const { counter } = getState()

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

    dispatch(increment())
  }
}

What’s a thunk?!

A thunk is a function that wraps an expression to delay its evaluation.

// 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 as a humorous past-tense version of "think".

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:

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 in this context is a function that can be dispatched to perform async
// activity and can dispatch actions and read state.
// This is an action creator that returns a thunk:
function makeASandwichWithSecretSauce(forPerson) {
  // We can invert control here by returning a function - the "thunk".
  // When this function is passed to `dispatch`, the thunk middleware will intercept it,
  // and call it with `dispatch` and `getState` as arguments.
  // This gives the thunk function the ability to run some logic, and still interact with the store.
  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 partner')).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)

License

MIT

redux-thunk (reduxjs/redux-thunk)

Searching Database…

这是什么?一个用于 Redux 的小型中间件,它让 action 创建函数可以返回一个函数,而不是普通的 action 对象。这个函数可以执行异步逻辑,或者决定是否以及何时派发 action。
它是 Redux 自带的一部分,还是需要单独安装的东西?它是一个独立的包,并不包含在 Redux 核心里。不过如果你用的是 Redux Toolkit(官方推荐的配置方式),它的 configureStore 默认已经包含 thunk,所以很多时候你不用安装任何东西就能用上它。
怎么把它添加到我的项目里?如果用 Redux Toolkit,什么都不用做——configureStore 默认就加上了它。在使用经典的 createStore API 时,安装 redux-thunk 包(中间件是一个具名导出),然后通过 applyMiddleware() 启用它。
除了 Redux 本身,用它还需要别的东西吗?不需要。这个包没有任何运行时依赖——它只是接入你 store 的中间件,任何已经在跑 Redux 的项目都可以直接使用,ES 模块或 CommonJS 都行。
为什么只是想异步做点事情还需要中间件?一个基础版的 Redux store 只能通过派发 action 做简单的同步更新;中间件则扩展了 store 的能力。对于基本的副作用,thunk 是官方推荐的中间件——比如需要访问 store 的复杂同步逻辑,以及 AJAX 请求这类简单的异步操作。
thunk 里面的那个函数到底能做什么?它会接收到 store 的 dispatch 和 getState 方法作为参数,所以可以延迟派发某个 action、只在满足特定条件时才派发,或者在异步工作运行时读取当前状态。
一个 thunk 能等另一个 thunk 执行完吗?可以——内层函数返回什么,dispatch 本身就返回什么。thunk 通常返回 Promise,并通过互相派发来编排异步控制流程。
我能把自己的依赖(比如一个 API 服务)注入到每个 thunk 里吗?可以,从 2.1.0 开始支持:在 Redux Toolkit 里通过 getDefaultMiddleware 传入自定义的 extraArgument,或者手动用 withExtraArgument() 来构建中间件。README 中特别提到这在测试时可以换成一个 mock 服务。
为什么叫 'thunk' 这个名字?在编程中,thunk 指的是一种把表达式包起来、推迟其求值的函数。这个名字最初是 'think' 的过去式的幽默说法。
What are similar projects?redux-saga, q, zustand, redux-form, core-js, normalizr, nuqs, fetch
Creator
reduxjs
Size
3.3 MB
Stars
17.7K
Age
11y
Commits · 90 Days