redux-thunk vs redux-saga vs redux-logger vs redux-devtools-extension vs redux-devtools
Redux工具和中间件
redux-thunkredux-sagaredux-loggerredux-devtools-extensionredux-devtools类似的npm包:
Redux工具和中间件

Redux是一个流行的JavaScript状态管理库,广泛用于React应用程序。为了增强Redux的功能,社区开发了各种工具和中间件,帮助开发者更有效地管理和调试应用状态。以下是一些常见的Redux工具和中间件:

  • redux-devtools:一个强大的调试工具,允许开发者实时查看和回溯Redux状态变化。它提供时间旅行调试功能,使状态管理更加透明和可控。
  • redux-devtools-extension:这是redux-devtools的浏览器扩展,集成了调试工具,使其更易于使用和配置。它提供了一个用户友好的界面,方便开发者监控状态变化。
  • redux-logger:一个中间件,自动记录每个Redux动作及其导致的状态变化。它帮助开发者理解状态如何随着动作的分发而变化,便于调试和优化代码。
  • redux-saga:一个基于生成器的中间件,专注于处理复杂的异步操作和副作用。它通过编写saga函数来管理异步流程,使代码更具可读性和可维护性。
  • redux-thunk:一个简单的中间件,允许动作创建者返回函数而不是动作对象。它使得处理异步操作(如API调用)更加方便,特别适合简单的异步逻辑。
npm下载趋势
3 年
GitHub Stars 排名
统计详情
npm包名称
下载量
Stars
大小
Issues
发布时间
License
redux-thunk9,572,03717,74126.8 kB12 年前MIT
redux-saga1,259,99722,5046.25 kB441 个月前MIT
redux-logger992,6685,741-589 年前MIT
redux-devtools-extension828,93713,487-2655 年前MIT
redux-devtools85,11314,309-2295 年前MIT
功能对比: redux-thunk vs redux-saga vs redux-logger vs redux-devtools-extension vs redux-devtools

调试能力

  • redux-thunk:

    redux-thunk不提供专门的调试工具,但它允许开发者在动作中编写异步逻辑,结合redux-devtools可以方便地调试异步操作。

  • redux-saga:

    redux-saga本身不提供调试界面,但通过结构化的saga函数,开发者可以更清晰地管理异步流程,减少调试难度。结合redux-devtools,可以更有效地调试异步操作。

  • redux-logger:

    redux-logger自动记录每个动作及其导致的状态变化,帮助开发者理解状态如何随着动作的分发而变化。它提供了详细的日志,便于调试和分析。

  • redux-devtools-extension:

    redux-devtools-extension作为浏览器扩展,提供了一个用户友好的界面,使调试过程更加直观和便捷。它集成了redux-devtools的所有功能,方便开发者在浏览器中直接使用。

  • redux-devtools:

    redux-devtools提供强大的调试功能,允许开发者实时查看状态变化,进行时间旅行调试,回溯状态历史。这对于定位和修复状态管理中的问题非常有帮助。

异步处理

  • redux-thunk:

    redux-thunk允许动作创建者返回函数,从而实现简单的异步操作,如API调用。它适合处理简单的异步逻辑,易于理解和使用。

  • redux-saga:

    redux-saga专注于处理复杂的异步操作和副作用,提供了一个结构化的方式来管理异步流程,特别适合需要协调多个异步操作的场景。

  • redux-logger:

    redux-logger记录所有动作,包括异步动作的开始和完成,帮助开发者跟踪异步操作的状态变化。

  • redux-devtools-extension:

    redux-devtools-extension同样不处理异步逻辑,但提供了一个平台,帮助开发者观察异步操作对状态的影响。

  • redux-devtools:

    redux-devtools不处理异步逻辑,但它可以帮助开发者调试异步操作引起的状态变化。

代码复杂度

  • redux-thunk:

    redux-thunk相对简单,易于理解和使用,适合快速实现异步操作,代码复杂度较低。

  • redux-saga:

    redux-saga引入了生成器函数和saga概念,可能增加一定的学习曲线和代码复杂度,但对于复杂的异步逻辑,它提供了更清晰的结构。

  • redux-logger:

    redux-logger作为中间件,集成简单,但会增加日志输出,可能需要在生产环境中禁用。

  • redux-devtools-extension:

    redux-devtools-extension作为浏览器扩展,使用起来非常方便,几乎不增加代码复杂度。

  • redux-devtools:

    redux-devtools不会增加代码复杂度,但需要在项目中集成和配置。它的使用对代码结构没有影响。

代码示例

  • redux-thunk:

    redux-thunk集成示例

    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import rootReducer from './reducers';
    
    const store = createStore(rootReducer, applyMiddleware(thunk));
    
  • redux-saga:

    redux-saga集成示例

    import { createStore, applyMiddleware } from 'redux';
    import createSagaMiddleware from 'redux-saga';
    import rootReducer from './reducers';
    import rootSaga from './sagas';
    
    const sagaMiddleware = createSagaMiddleware();
    const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
    sagaMiddleware.run(rootSaga);
    
  • redux-logger:

    redux-logger集成示例

    import { createStore, applyMiddleware } from 'redux';
    import rootReducer from './reducers';
    import logger from 'redux-logger';
    
    const store = createStore(rootReducer, applyMiddleware(logger));
    
  • redux-devtools-extension:

    redux-devtools-extension集成示例

    import { createStore } from 'redux';
    import rootReducer from './reducers';
    import { composeWithDevTools } from 'redux-devtools-extension';
    
    const store = createStore(rootReducer, composeWithDevTools());
    
  • redux-devtools:

    redux-devtools集成示例

    import { createStore } from 'redux';
    import rootReducer from './reducers';
    import { composeWithDevTools } from 'redux-devtools-extension';
    
    const store = createStore(rootReducer, composeWithDevTools());
    
如何选择: redux-thunk vs redux-saga vs redux-logger vs redux-devtools-extension vs redux-devtools
  • redux-thunk:

    选择redux-thunk,如果你的异步逻辑相对简单,且希望快速实现异步操作而不引入复杂性。

  • redux-saga:

    选择redux-saga,如果你的应用有复杂的异步逻辑和副作用,需要更结构化和可维护的解决方案。

  • redux-logger:

    选择redux-logger,如果你需要自动记录每个动作及其对状态的影响,帮助你理解状态变化的过程。

  • redux-devtools-extension:

    选择redux-devtools-extension,如果你希望在浏览器中使用调试工具,享受更直观的界面和更便捷的操作。

  • redux-devtools:

    选择redux-devtools,如果你需要一个强大的调试工具,能够实时查看和回溯状态变化。它适合需要深入分析状态管理的项目。

redux-thunk的README

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 the default export.

More Details: Importing the thunk middleware

If you're using ES modules:

import thunk from 'redux-thunk' // no changes here 😀

If you use Redux Thunk 2.x in a CommonJS environment, don’t forget to add .default to your import:

- const thunk = require('redux-thunk')
+ const thunk = require('redux-thunk').default

Additionally, since 2.x, we also support a UMD build for use as a global script tag:

const ReduxThunk = window.ReduxThunk

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