说到 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.js 和 BookTable.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 (代码见下方),然后在 SearchBar 和 BookTable 中调用这个 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 setSpreadOut 和 getSpreadIn 即可解决,它们的用法和 useSpreadOut 、 useSpreadIn 一致。不同之处在于,setSpreadOut 和 getSpreadIn 可以直接在函数内调用。
写在最后
好啦,相信大家通过这个例子已经对 Spreado 及其用法有了一定的了解。如果有任何的疑问或想法,欢迎在 spreado/issues 留言,中英文都可以。如果有时间和兴趣,欢迎直接提交代码,具体可以参考开发引导。如果觉得小工具有帮助,请给 GitHub repo react-easier/spreado 点个 ⭐️,这也是我们不断前行的动力 😃。