React官方状态管理Recoil-api详细用法

2,658 阅读7分钟

如果你一点都不清楚Recoil,建议先查看一下官方文档,本文主要是讲解一下常用api的使用。

Recoil作为facebook官方提出的状态管理,与react结合度还是很不错。

核心概念

使用Recoil,您可以创建一个数据流图,数据从atoms(共享状态)通过selectors(纯函数)一直注入到React组件。atoms是组件可以预订的状态单位。selectors可以同步或异步转换此状态

atoms

atoms是状态单位,它们是可更新可订阅的,当atoms被更新时,每个订阅的组件都将使用新值重新呈现(重新render)。

它们也可以在运行时创建,可以使用atoms来代替React本地组件状态。如果多个组件使用相同的原子,则所有这些组件共享其状态。

// 使用方式
const listState = atom({
    key:'list',
    default:[]
})

原子需要一个唯一的key,++该key可用于调试,持久性以及某些高级API,这些API可让您查看所有atoms的图++。确保key在全局上是唯一的,像React组件状态一样,它们也具有默认值

在组件中读取 atom 使用 useRecoilValue 可以读取。 读取和写入使用 useRecoilState

// 使用方式和hooks相同
function App(){
    // 获取atom
    const list = useRecoilValue(listState);
    // 获取和设置
    const [list,setList] = useRecoilState(listState)
}

同时,default是可以固定值、异步的Promise、或者另一个atom

function atom<T>({
  key: string,
  default: T | Promise<T> | RecoilValue<T>,

  dangerouslyAllowMutability?: boolean,
}): RecoilState<T>

// Promise 的写法
const listState = atom({
  key: 'list',
  default: (async () => {
    const res = await fetch('http://api.manster.me/getCampaignData').then(res => res.json());
    return res;
  })(),
});


// 引用其他atom
const aState  = atom({
  key:'a',
  default:'aaa'
})
const bState = atom({
  key:'b',
  default:aState
})

通常,你需要使用以下 hook 来与 atom 搭配使用。

  • useRecoilState():当你同时需要对 atom 进行读写时,使用此 hook。使用此 hook 会使组件订阅 atom。
  • useRecoilValue():当你仅需要读取 atom 时,使用此 hook。使用此 hook 会使组件订阅 atom。
  • useSetRecoilState():当你仅需要写入 atom 时,使用此 hook。
  • useResetRecoilState():需将 atom 重置为默认值时,使用此 hook。

selector

官方定义:selector 是个function或者 代表Recoil中的派生状态。 您可以将它们视为“纯函数”,而没有副作用,对于给定的一组依赖值,该副作用始终返回相同的值。 如果仅提供get函数,则selector为只读,并返回RecoilValueReadOnly对象。 如果还提供了一个set,它将返回一个可写的RecoilState对象

function selector<T>({
  key: string, // 唯一key

  get: ({  
    get: GetRecoilValue // 从其他atom或selector获取值,所有传入该函数的 atom 或 selector 将会隐式地被添加到此 selector 的一个依赖列表中。如果这个 selector 的任何一个依赖发生改变,这个 selector 就会重新计算值。
  }) => T | Promise<T> | RecoilValue<T>,

  set?: (
    {
      get: GetRecoilValue, // 一个用来从其他 atom 或 selector 获取值的函数。该函数【不会】为 selector 订阅给定的 atom 或 selector。
      set: SetRecoilState, // 一个用来设置 Recoil 状态的函数。第一个参数是 Recoil 的 state,第二个参数是新的值。新值可以是一个更新函数,或一个 DefaultValue 类型的对象,用以传递更新操作
      reset: ResetRecoilState,
    },
    newValue: T | DefaultValue,
  ) => void,

  dangerouslyAllowMutability?: boolean,
})

具体使用方法

const aState = atom({
  key: 'a',
  default: 'a',
});
const bState = atom({
  key: 'b',
  default: 'b',
});

const gsSelector = selector({
  key: 'getandset',
  get: ({ get }) => {
    let a = get(aState);
    let b = get(bState);
    return a + b;
  },
  set: ({ get, set }, newValue) => {
    // 这个get不会做依赖收集
    const c = get(cState);
    set(bState, newValue);
  },
});

function Test() {
  const [gs, setgs] = useRecoilState(gsSelector); // 写法类似于atom,可以直接写入;
  const add = ()=>{
    setgs('+1')
  }
  console.log('gs', gs);
  return <div>
    <Button onClick={add}>add</Button>
  </div>;
}


异步可以这么写

const myQuery = selector({
  key: 'MyDBQuery',
  get: async () => {
    const response = await fetch(getMyRequestUrl());
    return response.json();
  },
});

核心api

RecoilRoot

用法:

<RecoilRoot>
  <App/>
</RecoilRoot>

Recoil的上下文,可以有多个。atomselector中的key是在RecoilRoot的全局性唯一的。多个嵌套的RecoilRoot内层的会覆盖外层的。

atom 、selector

用法:上面都介绍了

atom 即 Recoil state。atom的初始值在 default 参数设置,值可以是 确定值、Promise、其他 atom、 selector。

selector 是纯函数,用于计算 Recoil state 的派生数据并做缓存。selector是 Recoil derived state (派生状态),它不仅仅可以从 atom衍生数据,也可以从其他 selector 衍生数据。 selector 的属性 get 的回调函数 get 的参数*(参数为 atom或者selector)*会成为 selector 的依赖 ,当依赖改变的时候,selector 就会重新计算得到新的衍生数据并更新缓存。如果依赖不变就用缓存值。selector 的返回值由 get 方法返回,可以返回计算出的确定的值,也可以是 Promise、其他 selector、atom, 如果 atom和selector的值是 Promise ,那么表示状态值是异步获取的,Recoil 提供了专门的 api 用于获取异步状态,并支持和 React suspense 结合使用显示异步状态

设置和获取 Recoil state 的 hooks API :

读 Recoil state

  • useRecoilValue 读取同步状态
  • useRecoilValueLoadable 读取异步状态,读取的状态分为hasValue、loading、hasError三个状态,并且返回的是一个loadable对象,具有以下字段

state:表示 selector 的状态。可选的值有 'hasValue''hasError''loading'contents:此值代表 Loadable 的结果。如果状态为 hasValue,则值为实际结果;如果状态为 hasError,则会抛出一个错误对象;如果状态为 loading,则值为 Promise。

用法:

// 读取同步状态
const alist = useRecoilValue(listState)

// 读取异步状态 
const listState = atom({
  key: 'list',
  default: (async () => {
    const res = await fetch('http://api.manster.me/getCampaignData').then(res => res.json());
    return res;
  })(),
});


const listLoadable = useRecoilValueLoadable(listState);
  switch (listLoadable.state) {
    case 'hasValue':
      return <div>...something...</div>;
    case 'loading':
      return <div>Loading...</div>;
    case 'hasError':
      throw listLoadable.msg;
}
// 如果不使用 React Suspense 可以使用 useRecoilValueLoadable 来替代。 

写 Recoil state

  • useSetRecoilState 写状态,返回一个 setter 函数,用来更新可写 Recoil state 的值,返回一个可以用来异步改变 state 的 setter 函数。可以传给此 setter 函数一个新的值,也可以传入一个更新函数,此函数接受上一次的值作为其参数。

用法

const setA = useSetRecoilState(aState);

setA('newValue')

setA((preState)=>{
  return preState+'new'
});


  • useResetRecoilState 重置状态,重置为default

用法:

// 一个操作性的hook
useResetRecoilState(todoListState)

读和写 Recoil state

  • useRecoilState 读写state

用法:

  const [tempF, setTempF] = useRecoilState(tempFahrenheit);
  const [tempC, setTempC] = useRecoilState(tempCelcius);
 
  setTempC('newValue');
  setTempF((preValue)=>{
    return newValue  
  });
  • useRecoilStateLoadable 异步读写state

用法:

// 读取
const [userNameLoadable, setUserName] = useRecoilStateLoadable(userNameState);
  switch (userNameLoadable.state) {
    case 'hasValue':
      return <div>{userNameLoadable.contents}</div>;
    case 'loading':
      return <div>Loading...</div>;
    case 'hasError':
      throw userNameLoadable.contents;
  }

setUserName('newValue')

setUserName((pre)=>{
    return 'newValue'
})

  • useRecoilCallback 读写state但是不更新组件

用法:

// 可以用于预加载数据,但是不刷新组件,
// 出于性能考虑,您可能希望在呈现之前获取数据,这样查询就可以在我们开始渲染的时候进行。React文档给出了一些例子,这种模式也适用于Recoil。
我们改变上面的例子,当用户点击改变用户的按钮时,就开始获取下一个用户信息:
 
function CurrentUserInfo() {
  const currentUser = useRecoilValue(currentUserInfoQuery);
  const friends = useRecoilValue(friendsInfoQuery);

  const changeUser = useRecoilCallback(({snapshot, set}) => userID => {
    snapshot.getLoadable(userInfoQuery(userID)); // pre-fetch user info
    set(currentUserIDState, userID); // change current user to start new render
  });

  return (
    <div>
      <h1>{currentUser.name}</h1>
      <ul>
        {friends.map(friend =>
          <li key={friend.id} onClick={() => changeUser(friend.id)}>
            {friend.name}
          </li>
        )}
      </ul>
    </div>
  );
}

其中 useRecoilState = useRecoilValue + useSetRecoilState,后两个就是获取 Recoil state 的只读状态值和设置状态值。

useResetRecoilState 用于重置 Recoil state 为初始状态,也就是 default 值。

useRecoilStateLoadable 用于获取异步状态和设置状态,useRecoilValueLoadable 只读异步状态

useRecoilCallback:上面的几个 api 都会让组件去订阅 state,当 state 改变组件就会更新,但是使用 useRecoilCallback 可以不用更新组件useRecoilCallback 允许组件不订阅 Recoil state 的情况下获取和设置 Recoil state,也就说组件可以获取 state 但是不用更新,反过来组件不必更新自身才能获取新 state,将读取 state 延迟到我们需要的时候而不是在组件 monted 的时候。

工具类API

atomFamily

和 atom 一样,但是可以支持传参数

const myAtomFamily = atomFamily({
  key: ‘MyAtom’,
  default: param => defaultBasedOnParam(param),
});

selectorFamily

和 selector 的写法一样,但是支持我们传递参数

const myMultipliedState = selectorFamily({
  key: 'MyMultipliedNumber',
  get: (multiplier) => ({get}) => {
    return get(myNumberState) * multiplier;
  },

  // optional set
  set: (multiplier) => ({set}, newValue) => {
    set(myNumberState, newValue / multiplier);
  },
});

waitForAll

并发api,参数会被依赖收集。所有异步都返回才执行。直接返回结果,没有中间态。类似于Promise.all

用法:会全部返回之后再 return 相当于 自动async(配合Suspense)

// 在 selector 中

const fetchAll = selector({
  key: 'fetchAll',
  get: ({ get }) => {
    const list = get(
      waitForAll([confirmListSelector, offlineListSelector, ingListSelector, endListSelector]),
    );
    return list;
  },
});

waitForAny

并发api,有一个异步返回可用值,就直接返回,后续的也许返回。会被执行多次。返回一个 【状态管理器】

// 第一次返回
[    {state:'hasValue'},    {state:'loading'}]
// 第二次返回
[    {state:'hasValue'},    {state:'hasValue'}]

waitForNone

每一个依赖项的状态都会返回,所以会执行多次。各种loading 和 hasValue

以上三个wait 使用方法都是一样。

noWait

selector 的 api,功能和 useRecoilValueLoadable 一样,只不是useRecoilValueLoadable是一个hooks。moWait是一个selector的helper。

功能就是获取异步依赖项的当前状态 loading 或者 hasValue。

const noWaitSelector = selector({
  key: 'noWaitSelector',
  get: ({ get }) => {
    const result = get(noWait(p1Selector));
    return result;
  },
});