发布于 2026-01-05 3 阅读
0

使用 Redux-thunk 和 Redux-saga 实现异步 React 基础教程 我们正在构建的内容 入门指南 Redux-thunk 应用组件 Redux-saga 结束

使用 Redux-thunk 和 Redux-saga 实现 React 的异步基础

我们正在构建什么

入门

Redux-thunk

应用程序组件

Redux-saga

结束

最近我有很多空闲时间,所以决定稍微研究一下 React 和 Redux。如果你想用 Redux 编写易于维护的异步代码,你需要选择一个中间件,比如 redux-thunk 或 redux-saga。

我们正在构建什么

我喜欢猫,所以这个应用程序的功能是基于Cat API开发的。你可以从这里克隆/fork GitHub 代码库

应用程序界面大致如下:
catapi 应用

点击“获取猫咪”按钮,它会发送一个 HTTP GET 请求,返回一张随机的猫咪图片。点击“获取更多猫咪”按钮,它会返回一个包含 5 张随机猫咪图片的数组。
我知道这看起来不太美观,但我真的不想在 CSS 上浪费时间。如果您对完整的“项目”以及 CSS 文件感兴趣,请查看我上面提到的 GitHub 代码库。

fetchCats功能将使用 redux-thunk 实现,并fetchMoreCats使用 redux-saga 编写,以便我们可以对它们进行比较。

入门

create-react-app catapi_app
Enter fullscreen mode Exit fullscreen mode

我们先来安装一些依赖项。

npm i --save react-redux redux redux-logger redux-saga redux-thunk
Enter fullscreen mode Exit fullscreen mode

接下来,我们需要在……中设置redux index.js

//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './reducers/index'

const loggerMiddleware = createLogger()

const store = createStore(
    rootReducer,
    applyMiddleware(
        thunkMiddleware,
        loggerMiddleware ))

ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>, 
    document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

这段代码会运行失败,因为我们没有所需的参数rootReducer。所以我们继续执行其他步骤。

// ./reducers/index.js

import { combineReducers } from 'redux'
import fetchCatReducer from './fetchCatReducer'

export default combineReducers({
    cats: fetchCatReducer
})
Enter fullscreen mode Exit fullscreen mode

目前我们只有一个 reducer,但我喜欢使用 combineReducer,因为如果需要添加另一个 reducer,那就容易多了。

这段代码仍然会失败,因为现在我们缺少了句点fetchCatReducer

// ./reducers/fetchCatReducer.js

const fetchCatReducer = (state = [], action) => {
    switch(action.type) {
        case "FETCH_CATS_SUCCESS":
            return [
                ...action.payload,
                ...state
            ]
        case "FETCH_CATS_START":
            return state
        case "FETCH_CATS_ERROR":
            return state
        default:
        return state
    }
}

export default fetchCatReducer
Enter fullscreen mode Exit fullscreen mode

每当我们派发一个动作时,该动作就会执行fetchCatReducer,并相应地更新我们的状态。

  • "FETCH_CATS_SUCCESS"HTTP 请求成功,我们必须更新状态。
  • "FETCH_CATS_START"HTTP 请求已启动,此处适合显示例如忙碌指示器之类的信息(例如加载屏幕)。
  • "FETCH_CATS_ERROR"HTTP 请求失败。您可以显示错误信息或其他内容。

为了保持应用程序的简洁性,在发生错误"FETCH_CATS_START"或异常时,"FETCH_CATS_ERROR"我什么也不做,只是返回之前的状态。

Redux-thunk

目前,我们的应用程序什么也不做,因为我们需要一个 action creator 来触发一个 action,而这个 action 由我们的 reducer 来处理。

//./actions/fetchCats.js

/*Helper functions. remember, we have 3 action types so far,
 these functions return a plain object that has a 
type attribute that our reducer can handle.
in case of success request, 
the action has a payload property as well. 
That's the response cat from the server 
that we have requested*/

const fetchCatsError = () =>{
    return {type: "FETCH_CATS_ERROR"}
}

const fetchCatsStarted = () =>{
    return {type: "FETCH_CATS_START"}
}

const fetchCatsSuccess = (cat) => {
    return {type: "FETCH_CATS_SUCCESS", payload: cat}
}

// fetching a random cat starts now
const fetchCats = () => dispatch => {
    dispatch(fetchCatsStarted())

    fetch("https://api.thecatapi.com/v1/images/search",{
        headers: {
            "Content-Type": "application/json",
            "x-api-key": "YOUR_API_KEY"
        }
    })
    .then( catResponse => catResponse.json()) 
    .then( cat => dispatch(fetchCatsSuccess(cat)) )
    .catch( err => dispatch(fetchCatsError()))
}
Enter fullscreen mode Exit fullscreen mode

是的,要在 CAT API 上使用这个端点,你需要一个 API 密钥。
fetchCats它乍一看可能有点奇怪,但本质上是一个返回另一个带有参数的函数的函数dispatch。一旦你调用了 dispatch,控制流就会跳转到你的 reducer 来决定下一步的操作。在我们的例子中,只有在请求成功的情况下,我们才会更新应用程序状态。顺便说一下,这就是我安装的原因redux-logger。它会持续记录你的状态和操作的变化,这样更容易追踪发生了什么。

如果您更喜欢使用 Async/await 语法,那么您可以像这样实现上述函数:

const fetchCats =  () => async dispatch => {
    dispatch(fetchCatsStarted())
    try{
        const catResponse = await fetch("https://api.thecatapi.com/v1/images/search",{
            headers: {
                "Content-Type": "application/json",
                "x-api-key": "YOUR_API_KEY"
            }
        })

        const cat = await catResponse.json()
        dispatch(fetchCatsSuccess(cat))
    }catch(exc){
        dispatch(fetchCatsError())
    }
}
Enter fullscreen mode Exit fullscreen mode

应用程序组件

我不想这篇文章太长,所以省略了组件的实现部分。我会展示一下它的外观App.js,如果你想查看完整的代码,可以去GitHub上找。


//./components/App.js

import React, { Component } from 'react'
import Button from './proxy/Button'
import CatList from './CatList'
import '../css/App.css'
import { connect } from 'react-redux'
import fetchCats from '../actions/fetchCats'

class App extends Component {
    render() {
        return (
            <div className="App">
                <Button className="primary" text="Fetch cats" onClick={this.props.fetchCats}/>
                <Button className="secondary" text="Fetch more cats"/>
                <header className="App-header">
                    <CatList cats={this.props.cats}/>
                </header>
            </div>
        )
    }
}

const mapStateToProps = (state, ownProps) => ({
        cats: state.cats
})

export default connect(mapStateToProps, { fetchCats })(App);
Enter fullscreen mode Exit fullscreen mode

Redux-saga

Redux-saga 是一个 Redux 中间件,它使我们能够轻松地使用 Redux 实现异步代码。

要初始化它,我们需要index.js稍微调整一下。

//./index.js
...
import createSagaMiddleware from 'redux-saga'
import watchFetchMoreCatsSaga from './saga/fetchMoreCats'

//init
const sagaMiddleware = createSagaMiddleware()

//run
sagaMiddleware.run(watchFetchMoreCatsSaga)
...
Enter fullscreen mode Exit fullscreen mode

saga文件夹中,创建一个名为 . 的新文件fetchMoreCats

//./saga/fetchMoreCats

import { takeLatest, put } from "redux-saga/effects";

//Every time we dispatch an action 
//that has a type property "FETCH_MORE_CATS"
// call the fetchMoreCatsSaga function
export default function* watchFetchMoreCatsSaga(){
    yield takeLatest("FETCH_MORE_CATS", fetchMoreCatsSaga)
}

//query 5 cat image at the same time
function* fetchMoreCatsSaga(){
    yield put({type: "FETCH_MORE_CATS_SAGA_START"})

   const catResponse = yield fetch("https://api.thecatapi.com/v1/images/search?limit=5",{
        headers: {
            "Content-Type": "application/json",
            "x-api-key": "YOUR_API_KEY"
        }
    })

    const cats = yield catResponse.json()

    yield put({type: "FETCH_MORE_CATS_SAGA_SUCCESS", payload: cats})
}
Enter fullscreen mode Exit fullscreen mode

这些function*东西叫做生成器函数。如果你想了解更多信息,请点击这里

这个函数可以用例如 `get_event()`takeLatest来代替,但 `get_event()` 的一个很棒的特性是它只取最后一个“事件”。在我们的例子中,如果我们快速点击按钮 100 次,那么我们的应用程序就会发送 100 个请求,几乎相当于对 API 进行 DDoS 攻击 :D。所以,与其每次点击按钮都禁用它,我们可以使用 `get_event()` takeEverytakelatesttakeLatest

如您所见,通过调用该put函数,我们可以像之前一样触发操作dispatch。因此,让我们调整我们的代码./reducers/fetchCatReducer.js以处理新的 saga 操作。

//./reducers/fetchCatReducer.js

...
case "FETCH_MORE_CATS_SAGA_SUCCESS":
            return [
                ...action.payload,
                ...state
            ]
        case "FETCH_MORE_CATS_SAGA_START":
            return state
        case "FETCH_MORE_CATS_SAGA_ERROR":
            return state
...
Enter fullscreen mode Exit fullscreen mode

生成watchFetchMoreCatsSaga器函数会不断监听"FETCH_MORE_CATS"动作并调用我们的函数fetchMoreCatsSaga。因此,为了使其正常工作,我们需要首先触发该动作。

//./actions/fetchMoreCats.js

const fetchMoreCats = () => dispatch =>{
    dispatch({type: "FETCH_MORE_CATS"})
}

export default fetchMoreCats
Enter fullscreen mode Exit fullscreen mode

就是这样。每次我们调用时fetchMoreCats,它都会分发,该分发{type: "FETCH_MORE_CATS"}会“调用”我们的,而我们的又会调用watchFetchMoreCatsSagafetchMoreCatsSaga

所以我们需要将其导入fetchMoreCats到我们的程序中App.js,并在用户点击该按钮时调用它。

//App.js

...
import fetchMoreCats from '../actions/fetchMoreCats'

//put this button in the render method
<Button className="secondary" text="Fetch more cats" onClick={this.props.fetchMoreCats}/>

//we need to map that function to the props of the App


export default connect(mapStateToProps, { fetchCats, fetchMoreCats })(App);
Enter fullscreen mode Exit fullscreen mode

结束

如果您想了解更多信息:Saga 文档

如果您有任何疑问,请在评论区留言或给我发邮件。

文章来源:https://dev.to/bnorbertjs/async-react-basics-with-redux-thunk--redux-saga-4af7