Spreado - 基于 Redux + SWR 的集成

1,362 阅读4分钟

说到 React,不可避免的需要提及状态管理和数据拉取这个话题。在小型项目中,使用 React 内置的 useState useReducer useContext 等 Hook 来进行状态管理就足够了。但是面对复杂的项目,可能就需要引入第三方状态管理库了。其中较常见的状态管理库有 Redux Mobx ,数据拉取库有 React-Query SWR 等。

今天我将教大家如何在项目中使用 Spreado/Redux/SWR 进行状态管理。

初识 Spreado

Spreado 一共有四个 API,我们只需要了解下面这两个就可以轻松在项目中进行状态管理了

  • useSpreadOut - 设置要共享的值
  • useSpreadIn - 获取共享的值

不要被标题中的 redux 所欺骗,事实上我们不需要编写任何的 reducer action dispatch。Spreado 相当于一个适配器,抹平了 Redux MobX 状态管理库之间的 API 差异。如果只想简单的在组件间共享数据,但是又不想写一堆 redux 的模板代码,那么 Spreado 是一个很好的选择。同时,由于 redux 是作为 Spreado 的 peer dependency 而存在,当遇到极个别 Spreado 无法覆盖的场景时,我们也可以直接使用 redux 来实现。

SWR 简介

SWR 全称是 stale-while-revalidate ,是一款小巧的数据拉取库。在过去,我需要将从接口获取到的数据存在 redux 中以在不同组件中共享,重新获取数据时需要手动更新数据,同时还要控制请求过程中的 loading、error 等状态,SWR 让这一切变得更简单了。

在项目中使用 Spreado

接下来我会以一个图书查询页面为例,展示如何使用 Spreado。该页面包含两部分,搜索栏和图书列表。修改搜索栏中的条件时将请求接口获取数据,接口返回的数据在列表中进行展示。

首先我们使用 CRA 创建项目,并安装 Spreado 相关的依赖包。

npx create-react-app spreado-redux-swr
cd spreado-redux-swr

npm install --save react-redux redux swr spreado

项目创建完成后,修改 App.js,添加 Spreado 所需的配置。

import {Provider as ReduxProvider} from 'react-redux';
import {combineReducers, createStore} from 'redux';
import {SpreadoSetupProvider} from 'spreado';
import {spreadoReduxReducerPack, SpreadoSetupForReduxSwr} from 'spreado/for-redux-swr';
import {SearchBar} from './Book/SearchBar'
import {BookTable} from './Book/BookTable'

const store = createStore(combineReducers(spreadoReduxReducerPack));
const spreadoSetup = new SpreadoSetupForReduxSwr({store});

function App() {
  return (
    <ReduxProvider store={store}>
      <SpreadoSetupProvider setup={spreadoSetup}>
        <div className="App">
          <SearchBar />
          <BookTable />
        </div>
      </SpreadoSetupProvider>
    </ReduxProvider>
  );
}

export default App;
// ./Book/SearchBar.js
import React, {useState} from 'react'
import useSWR from 'swr'

export function SearchBar() {
  const [bookType, setBookType] = useState("")
  
  // TODO: {} 为获取图书列表的占位符,稍后会替换
  const {data, error} = {}
  
  function handleBookTypeChange(e) {
    setBookType(e.target.value)
  }
  
  if (!data && !error) {
    return <div>loading...</div>
  }

  return (
    <div>
      <select onChange={handleBookTypeChange}>
        <option value="">所有</option>
        <option value="fiction">小说类</option>
        <option value="non-fiction">非小说类</option>
      </select>
      <div>总数量: {data?.totalCount || 0}</div>
    </div>
  )
}
// ./Book/BookTable.js
import React from 'react'

export function BookTable() {
  // TODO: {} 为获取图书列表的占位符,稍后会替换
  const {data, error} = {}

  if (!data && !error) {
    return <div>loading...</div>
  }

  return (
    <table>
      <tr><th>书名</th><th>类型</th></tr>
      {
        data.map(book => (
          <tr><td>{book.name}</td><td>{book.type}</td></tr>
        ))
      }
    </table>
  )
}

接下来我们需要创建一个文件 bookHooks.js,将查询和获取数据的 Hook 放在该文件中,以便复用。

// ./Books/bookHooks.js
import { useSpreadOut } from "spreado";
import { useSpreadIn } from "spreado";
import useSWR from "swr";

export const INDEX_OF_BOOKS_QUERY = "INDEX_OF_BOOKS_QUERY"

export function useBooksQuerySpreadOut(bookType="") {
  return useSpreadOut(
    INDEX_OF_BOOKS_QUERY,
    useSWR(['/api/books', bookType], (url, bookType) => {
      // TODO: 获取并返回图书列表
    })
  );
}

export function useBooksQuerySpreadIn() {
  return useSpreadIn(INDEX_OF_BOOKS_QUERY, {})
}

我们修改一下 SearchBar.jsBookTable.js以在搜索条件改变时,实时请求接口并且将获取到的数据显示到表格中。

// ./Book/SearchBar.js
...
+ import {useBooksQuerySpreadOut} from './bookHooks'

export function SearchBar() {
  const [bookType, setBookType] = useState("")
- const {data, error} = {}
+ const {data, error} = useBooksQuerySpreadOut(bookType)

  ...
}
// ./Book/BookTable.js
...
+ import {useBooksQuerySpreadIn} from './bookHooks'

export function BookTable() {
- const {data, error} = {}
+ const {data, error} = useBooksQuerySpreadIn()

  ...
}

至此,一个简单的图书查询页面就完成了。

这时,有些长得比较帅的小伙伴可能就会问了,直接将获取图书列表的方法封装成一个公用 hook useGetBooks (代码见下方),然后在 SearchBarBookTable 中调用这个 hook 获取数据不就好了吗,为什么要使用 Spreado 呢?

// ./Books/bookHooks.js
import useSWR from "swr";

+ export function useGetBooks(bookType="") {
+  return useSWR(['/api/books', bookType], (url, bookType) => {
+    // TODO: 获取并返回图书列表
+  })
+ }

仔细观察上面 Spreado 的例子,你会发现,BookTable 组件不需要关心用户的查询参数是什么,只需使用 useBooksQuerySpreadIn 获取数据即可。

如果直接使用 useGetBooks,那么在 BookTable 中获取数据时就需要传入与 SearchBar 相同的查询参数。这需要我们将查询参数存到 redux 或更高一层的父组件中,这无疑增加了应用程序的复杂度。

对于确实需要在组件之间共享数据的情况,使用 Spreado 的另外两个 API setSpreadOutgetSpreadIn 即可解决,它们的用法和 useSpreadOutuseSpreadIn 一致。不同之处在于,setSpreadOutgetSpreadIn 可以直接在函数内调用。

写在最后

好啦,相信大家通过这个例子已经对 Spreado 及其用法有了一定的了解。如果有任何的疑问或想法,欢迎在 spreado/issues 留言,中英文都可以。如果有时间和兴趣,欢迎直接提交代码,具体可以参考开发引导。如果觉得小工具有帮助,请给 GitHub repo react-easier/spreado 点个 ⭐️,这也是我们不断前行的动力 😃。