Appearance
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:请求单个依赖,但不等待它变得可用。