React 如何通过 Hooks 来封装比较复杂的数据?

138 天前
 HXHL

我的用例大概如下。比如拿 book 的数据,可能组件有时候需要一个数组 books 、有时候是一个数据结构 book 。 然后, 拿 books 的条件是 book list id(一个书单 id 下面的所有书)和 user id(用户名下书),两者可以都有,或者有一个。

然后拿 book 的条件是 book id 。

我是应该抽象成useBooks(bookListId?:string, userId?: string)useBook(bookId:string)两个 Hooks 还是直接返回一个 book store ,让组件自己拿数据? 这些 Hooks 感觉怎么怎么每次被调用时网络请求和数据更新的问题? 然后我还需要 createBook 、updateBook 之类的函数。

const { books, isLoading, createBook } = useBooks(xxx,xxx)

我之前用 swr 虽然感觉能凑合管理了,修改完数据就得 mutate 来刷新相关的 key 。但是总感觉不太舒服。

1480 次点击
所在节点    React
4 条回复
XCFOX
138 天前
你可能需要使用 zustand 或者 valtio 等状态管理库。

我个人是喜欢用 class 封装成 controller 的,然后在组件内使用 valtio 的 proxy 和 useSnapshot 来使用。

```ts
class BookController {
books: Record<string, Book> = {}

async getBooks() {
const books = await fetchBooks()
books.forEach((book) => {
this.books[book.id] = book
})
}

async createBook(book: Book) {
// ...
}
async updateBook(id: string, book: Book) {
// ...
}
}
```

在组件内使用:

```tsx
import { proxy, useSnapshot } from 'valtio'

const YourComponent = () => {
const controller = useMemo(() => proxy(new BookController()), [])
const { books } = useSnapshot(controller)

useEffect(() => {
controller.getBooks()
}, [controller])

const allBooks = Object.values(books)
const oneBook = books['bookId']

return (
<div>
{allBooks.map((book) => (
<div key={book.id}>{book.name}</div>
))}
<button onClick={() => controller.createBook(yourBook)}>创建</button>
</div>
)
}
```

valtio: https://github.com/pmndrs/valtio
zustand: https://github.com/pmndrs/zustand
theprimone
137 天前
@XCFOX #1 Jotai 呢?
XCFOX
137 天前
@theprimone #2
状态管理主要解决的问题有:
1. 跨组件状态传递
2. 组织 actionsa ,比如 createBook 、updateBook

zustand 强制在 create store 的时候组织 actions: https://docs.pmnd.rs/zustand/guides/updating-state

valtio 很自由,可以用你最熟悉的 js 语法组织 actions: https://valtio.pmnd.rs/docs/how-tos/how-to-organize-actions

Jotai 完全遵守 React Hooks 规则,本身没有组织 actions 的办法。需要使用 React 闭包 + useCallback 来封装 actions ,或者使用 Reducer 。
比如上面的 BookController 按 Jotai 的写法就会变成:
```ts
import { atom } from 'jotai'

const booksAtom = atom<Record<string, Book>>()

function useBooks() {
const [books, setBooks] = useAtom(booksAtom)

const getBooks = useCallback(async () => {
const books = await fetchBooks()
setBooks((prevBooks) => {
const nextBooks = { ...prevBooks }
books.forEach((book) => {
nextBooks[book.id] = book
})
return nextBooks
})
}, [setBooks])

const createBook = useCallback(
async (book: Book) => {
const newBook = await fetch('createBook', { method: 'POST', body: book })
setBooks((prevBooks) => ({ ...prevBooks, [newBook.id]: newBook }))
},
[setBooks]
)

const updateBook = useCallback(
async (id: string, book: Book) => {
// ...
},
[setBooks]
)

return { books, getBooks, createBook, updateBook }
}
```

因为我讨厌 useCallback 以及 Reducer ,所以不推荐 Jotai 。
theprimone
137 天前
@XCFOX #3 学习了,其实之前也看过这个 issue https://github.com/pmndrs/zustand/issues/483#issuecomment-876406137 一个组织整这么多状态管理也太秀了,不过后来看到个说法:

> Zustand 、Jotai 和 Valtio ,它们用起来分别非常像 Redux 、Recoil 和 MobX

之前有用过 zustand + immer 现在综合各方那我觉得还是这个方法比较合适啊 😂 更 React 一些

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1002839

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX