Skip to content

recoil https://github.com/facebookexperimental/Recoil

fb 的实验性状态管理库,仅支持 hooks(对于 class 组件可以用 HOC 包装),和 mobax-react 有些相似,特色是支持 react 的 concurrent 模式(未来)

yarn add recoil

和 Context 和 useState 有些类似

RecoilRoot 提供原子(atom)的值的上下文(context), 必须是订阅 atom 的组件公共祖先节点。

可以有多个 RecoilRoot 也可以嵌套,atom 的值取决于最近的 RecoilRoot 上下文。

atom 表示一个状态(state),在整个应用中拥有唯一的 key。

atom() 函数返回一个可读写的 RecoilState 对象,(使用 isRecoilValue() 判断是否是 RecoilState 对象)

这个对象可以被订阅(useRecoilState),也可以被更新(更新后订阅者会 re-render)

selector() 也是返回一个 RecoilState 对象,用来计算派生值,计算属性 get 返回一个 promise 时具有记忆功能(缓存)

atomFamily() selectorFamily() 返回一个获取 RecoilState 的函数,根据参数来缓存值(相同参数将不会再次计算)

下面例子覆盖了绝大部分场景和 Api

src/store/counter.ts

tsx
import { atom, selector, selectorFamily } from 'recoil'
import { delay } from 'src/utils'

// 返回的 counter 是一个 RecoilState 对象,可以被订阅和更新。key 在整个应用中唯一
export const counter = atom({
  key: 'demo/counter',
  default: 0,
})

// 派生状态,计算属性 get 可以返回一个 Promise 并缓存其值。
// 泛型 number 表示返回值的类型
export const syncCounter = selector<number>({
  key: 'demo/sync-counter',
  get: async ({ get }) => {
    await delay(1000)
    return get(counter)
  },
  set: ({ set }, newVal) => set(counter, newVal), // 设置了 set 后才是可写的(writeable),相当于一层代理。
})

// atomFamily selectorFamily 常用来性能优化,它返回的 syncParamCounter 是一个函数,syncParamCounter(param) 返回 RecoilState 对象(根据参数进行缓存,参数不变时从缓存里取值)。
// 泛型 string 表示返回值的类型,number 表示 syncParamCounter 的参数类型
export const syncParamCounter = selectorFamily<string, number>({
  key: 'demo/sync-param-counter',
  // 注意这里是高阶函数
  get:
    (param) =>
    async ({ get }) => {
      await delay(3000)
      return `${param}-${get(counter)}`
    },
})

src/RecoilApp.tsx

tsx
import React, { memo, Suspense } from 'react'
import {
  isRecoilValue,
  useRecoilState,
  useRecoilStateLoadable,
  useRecoilValue,
  useRecoilValueLoadable,
  useSetRecoilState,
  useResetRecoilState,
} from 'recoil'

import { counter, syncCounter, syncParamCounter } from 'src/stores/recoil/counter'
import { sizeState } from './stores/recoil/size'

// 可以使用 memo 进行优化,这样父组件更新时,子组件不会 render
const RecoilSize = memo(() => {
  const [size, setSize] = useRecoilState(sizeState)
  // RecoilState 更新时会 render
  console.log(`TTT: RecoilSize -> size`, size)
  return <button onClick={() => setSize((val) => val + 1)}>+1({size})</button>
})

const RecoilCounter = () => {
  // const count = useRecoilValue(counter)
  // const setCount = useSetRecoilState(counter)
  // 需要注意 selector 没有定义 set 时是不可写的(只能使用 useRecoilValue 取值)
  const [count, setCount] = useRecoilState(counter) // 可读可写,包含上面功能
  const handleClick = () => {
    setCount((val) => val + 1) // 更新数据时,对于复杂对象的更新可以考虑引用 immer
  }
  return (
    <div>
      <p>
        <i>组件:RecoilCounter</i>
      </p>
      <span>{count}</span>
      <button onClick={handleClick}>+1</button>
    </div>
  )
}

const SyncCounter = () => {
  // 注意我们定义的 syncCounter 是一个 promise,所以这个组件是一个未 resolve 的组件(需要用 Suspense 包裹,好处是组件内部使用数据无需关注是异步还是同步)
  const syncCount = useRecoilValue(syncCounter)
  // 如果要取多个 promise 的值,可以一个类似 promise.all 的工具方法 waitForAll
  // const [value1, value2] = useRecoilValue(waitForAll([recoilStatePromise1, recoilStatePromise2]))
  const count = useRecoilValue(counter)
  const paramCount = useRecoilValue(syncParamCounter(count)) // 是一个根据 count 的缓存值,是一个“旧”值
  return (
    <div>
      <p>
        <i>组件:SyncCounter</i>
      </p>
      <span>{syncCount}</span> | <span>{paramCount}</span>
    </div>
  )
}

const RecoilApp = () => {
  const isR = isRecoilValue(counter) // 判断 counter 是不是一个 RecoilState
  // 另一个处理异步的方法(带 Loadable 后缀), 返回值包含两个属性 { state, contents } state 表示 promise 状态,contents 表示值
  const syncCount = useRecoilValueLoadable(syncCounter) // useRecoilStateLoadable
  const [count, setCount] = useRecoilState(counter)
  const setSize = useSetRecoilState(sizeState)
  return (
    <div>
      <p>
        <i>组件:RecoilApp</i>
      </p>
      <span>
        {syncCount.state} | {syncCount.state === 'hasValue' && syncCount.contents}
      </span>
      <button onClick={() => setCount((val) => val + 2)}>count+2({count})</button>
      <button onClick={() => setSize((val) => val + 2)}>size+2</button>
      <RecoilCounter />
      <Suspense fallback={<div>loading...</div>}>
        // SyncCounter 是一个未 resolve 的组件
        <SyncCounter />
      </Suspense>
      <RecoilSize />
    </div>
  )
}

export default RecoilApp

其他 useRecoilCallback

tsx
// 类似 useCallback 用于构造一个稳定的回调函数,进行复杂逻辑的状态更新
const handleSome = useRecoilCallback(
  ({ snapshot, gotoSnapshot, set, reset }) =>
    async () => {
      // snapshot 是 RecoilState 的不可变快照,用来同步全局状态、历史导航、
      snapshot.getLoadable // 返回 Loadable 对象(参考 useRecoilValueLoadable)
      snapshot.getPromise // 异步 selector 的求值
      snapshot.map(({ set }) => set(atom1, 42)) // 更新快照
      // gotoSnapshot
      // set 更新 RecoilState
      // reset
    },
  []
)

-waitForAll:并行请求所有依赖,并等待所有依赖变为可用。 -waitForAny:并行请求所有依赖项,并等待它们中的任何一个变得可用。 -waitForNone:并行请求所有依赖,但不等待它们中的任何一个。 -noWait:请求单个依赖,但不等待它变得可用。