Redux 是一个用于 JavaScript 应用程序的预测性状态容器,它可以让您的应用行为一致且易于测试。Redux 可以帮助您管理各种状态,并确保状态变更是可控的和可预测的。但是,Redux 自身只提供了最基本的 API,如 createStore()、dispatch() 和 getState()。为了扩展 Redux 并处理异步操作,社区开发了许多中间件,Redux-thunk、Redux-promise 和 Redux-saga 就是其中的代表。
在本文中,我们将深入研究这三个流行的 Redux 中间件,弄清楚它们的工作原理以及它们如何与 Redux 一起工作。同时,我们将从 Redux 源码的角度出发,逐步学习这些中间件的实现原理。
Redux-thunk
在 Redux-thunk 中间件诞生之前,处理异步操作的最基本方法是在 Action 中返回一个函数,而不是一个对象。这个函数的参数是 dispatch() 方法。
举个例子,我们在 Redux 中定义了一个 Action:
-- -------------------- ---- ------- ------ ----- --------- - -------- -- - ------ ---------- -- - ---------- ----- -------------- --- ------ ------------------- ---------- -- - ---------- ----- --------------------- -------- ---- --- -- ------------ -- - ---------- ----- ------------------- -------- ----- --- --- -- --
在上面的代码中,fetchUser() 返回了一个函数,这个函数接收 dispatch() 方法作为参数。这个函数会异步获取用户数据,并在获取数据成功或失败后分别调用 dispatch() 方法来分发相应的 Action。
这种模式称为 thunk:一个返回函数的函数。Redux-thunk 就是一个中间件,可以将这种模式嵌入到 Redux 中。
实现原理
Redux-thunk 本质上是一个函数,它的作用是将异步操作延迟到 Action Creator 中。当一个 Action Creator 返回一个函数时,Redux-thunk 会执行这个函数并传入 dispatch() 方法和 getState() 方法。这个函数会在内部执行异步操作,并在完成后调用 dispatch() 方法来分发相应的 Action。
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === "function") { return action(dispatch, getState); } return next(action); };
以上是 Redux-thunk 中间件的代码实现。它接收一个 store 对象,并返回一个函数,这个函数负责处理 Action。当 store.dispatch() 方法被调用时,这个函数会被调用,并先检查传入的 action 是否为函数。如果 action 是函数,thunk 中间件会立刻执行这个函数,并将 store.dispatch() 和 store.getState() 作为参数传入。
案例分析
假设我们有一个 todoList 应用,用户可以在应用中添加、完成、删除待办事项。每当用户进行这些操作时,我们需要在数据库中同步数据。
我们可以使用 Redux-thunk 捕获用户添加待办事项的操作,并在操作完成后调用 API 同步数据。
-- -------------------- ---- ------- ------ ----- ---- -------- ------ ----- ------- - ------ -- ----- ---------- -- - ---------- ----- ------------------ --- --- - ----- ------------------- - ---- --- ---------- ----- ------------------- -------- - ---- - --- - ----- ------- - ---------- ----- ------------------- -------- - ----- - --- - --
在上面的代码中,我们使用了 async 函数来处理异步操作,并在操作完成后调用 dispatch() 方法来分发相应的 Action。
需要注意的是,Redux-thunk 只是一种“手动”处理异步操作的方式,因此我们需要自己管理异步操作的状态。同时,由于异步 Action 及其对应的 reducer 可能会非常复杂,因此在处理异步操作时仍需要谨慎。
Redux-promise
Redux-promise 是另一个处理异步 Action 的 Redux 中间件。它在传递给 dispatch() 方法的 Action 中提供了一个序列化的 Promise。
这个 Promise 表示异步操作的结果,可以在 reducer 中进行处理。
实现原理
与 Redux-thunk 类似,Redux-promise 也是一个函数,它的作用是将 Promise 嵌入到 Redux 中。当一个 Action Creator 返回一个 Promise 时,Redux-promise 会等待这个 Promise 完成,并分发相应的 Action。
const promise = ({ dispatch }) => next => action => { if (typeof action.then === 'function') { return action.then(dispatch); } return next(action); };
以上是 Redux-promise 中间件的代码实现,与 Redux-thunk 非常相似。当 store.dispatch() 方法被调用时,这个函数会被调用,并先检查传入的 action 是否为 Promise。如果 action 是 Promise,Redux-promise 中间件会立刻等待这个 Promise 完成,并将完成后的结果作为参数调用 dispatch() 方法进行 Action 分发。
案例分析
假设我们有一个 weather app 应用,我们需要在异步获取天气数据后,将数据作为 payload 发送到 reducer 中。
我们可以使用 Redux-promise 在 Action 中返回一个 Promise,然后在 reducer 中处理异步 Action。
export const fetchWeather = () => ({ type: 'FETCH_WEATHER', payload: axios.get('/weather') });
在上面的代码中,我们定义了一个 Action Creator,它返回了一个包含 axios.get() 的 Promise。当这个 Promise 完成后,Redux-promise 中间件会调用 dispatch() 方法来分发相应的 Action,并将完成的结果传递给 reducer。
比起 Redux-thunk,Redux-promise 更加简单,并且可以根据返回的 Promise 自动处理异步 Action。因此,与 Redux-thunk 不同,我们不需要在 Action Creator 自己处理异步操作,也不需要在 reducer 中额外的处理。但相应的,Redux-promise 的灵活性则变得比较有限。
Redux-saga
Redux-saga 是一个中间件,可以用于处理异步操作和副作用。相比之前的 Redux-thunk 和 Redux-promise,Redux-saga 提供了更为灵活和强大的解决方案。
Redux-saga 是一个基于 Generator 的库,允许我们使用类似同步代码的方式编写异步代码,并且允许我们在 Redux 应用程序中进行复杂的控制流程和较为轻松的测试。
实现原理
Redux-saga 允许我们使用 Generator 函数来编写 saga。saga 是一段代码,在 saga 中会监听特定的 action,一旦符合则会触发一些异步操作。
-- -------------------- ---- ------- --------- --------------------- - --- - ----- ---- - ----- ------------------- ----------------------- ----- ----- ----- --------------------- -------- - ---- - --- - ----- ------- - ----- ----- ----- ------------------- -------- - ----- - --- - - --------- -------- - ----- ----------------------- --------------- -
在上面的代码中,我们定义了一个名为 fetchUserSaga 的 Generator 函数,用于处理 action 类型为 FETCH_USER 的异步操作。在 saga 中,我们使用 call() 方法来调用 api.fetchUser() 方法,该方法返回 Promise。如果 Promise 成功执行,则使用 put() 方法来分发 FETCH_USER_SUCCESS。
在 mySaga 中,我们使用 takeEvery() 方法来监听 FETCH_USER action,当收到匹配的 DialogflowAction 后使用 fork 来执行相应的 saga。
案例分析
假设我们有一个 gallery 应用,用户可以在应用中浏览相片。当用户选择相片时,应用会自动开始加载相应的数据。我们可以使用 Redux-saga 来处理这个过程,并在完成后将数据作为 payload 分发给 reducer。
-- -------------------- ---- ------- --------- ------------------------ - --- - ----- ----- ----- ---------------------- --- ----- ----------- - ----- -------------------------- -------------------------- ----- ----- ----- ----------------------------- -------- ----------- --- - ----- ------- - ----- ----- ----- --------------------------- -------- ----- --- - - --------- ----------------------- - ----- ------------------------------- ------------------ - ------ ------- --------- ---------- - ----- ----- ------------------------ --- -
在上面的代码中,我们定义了一个 Generator 方法 fetchGalleryData 用于处理 FETCH_GALLERY_DATA action,当收到匹配的 action 时,我们使用 try/catch 代码块包裹整个异步操作代码。在代码块内部,我们使用 put() 方法来分发 LOADING_GALLERY_DATA action,告诉应用已开始加载数据,同时使用 call() 方法来等待 api.fetchGalleryData() 方法返回数据。
当 api.fetchGalleryData() 执行后,我们使用 put() 方法来分发 FETCH_GALLERY_DATA_SUCCESS action,并将 fetchGalleryData 的结果作为 payload 传递给 action。如果异常发生,则触发 FETCH_GALLERY_DATA_ERROR action,并提供错误信息。
与 Redux-thunk 和 Redux-promise 不同,Redux-saga 允许我们在应用程序中执行真正的异步操作。这个库不仅提供了灵活和强大的解决方案,而且可以更容易地进行测试和重构。
结论
在本文中,我们通过讨论 Redux-thunk、Redux-promise 和 Redux-saga,深入了解了使用中间件处理异步操作的方法。同时,我们也了解了这些中间件在 Redux 源码中的实现原理。
在实际开发中,我们可以根据业务场景选择不同的中间件,对于较为简单的异步操作,我们可以使用 Redux-thunk 或 Redux-promise,对于较为复杂的异步操作,我们可以使用 Redux-saga 来处理相关逻辑。在使用中间件时,我们也需要谨慎思考,确保异步操作的状态正确,以避免出现困难和错误。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/670f77c65f5512810264abe8