V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
HXHL
V2EX  ›  React

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

  •  
  •   HXHL · 2023-12-23 12:49:22 +08:00 · 1779 次点击
    这是一个创建于 365 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我的用例大概如下。比如拿 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 。但是总感觉不太舒服。

    4 条回复    2023-12-24 22:24:17 +08:00
    XCFOX
        1
    XCFOX  
       2023-12-23 14:11:19 +08:00   ❤️ 2
    你可能需要使用 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
        2
    theprimone  
       363 天前
    @XCFOX #1 Jotai 呢?
    XCFOX
        3
    XCFOX  
       363 天前
    @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
        4
    theprimone  
       363 天前
    @XCFOX #3 学习了,其实之前也看过这个 issue https://github.com/pmndrs/zustand/issues/483#issuecomment-876406137 一个组织整这么多状态管理也太秀了,不过后来看到个说法:

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

    之前有用过 zustand + immer 现在综合各方那我觉得还是这个方法比较合适啊 😂 更 React 一些
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2636 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 07:11 · PVG 15:11 · LAX 23:11 · JFK 02:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.