recoil实战+踩坑总结

1,527 阅读2分钟

facebook 的全局状态管理,相对于常用的全局状态管理redux/mobx有可用的地方

虽说都是hook,但上手+学习成本有点高

用作实践笔记和总结,踩了坑给大家一起分享

0、安装

Npm 即可

1、最外侧RecoilRoot

import {RecoilRoot} from 'recoil';

function AppRoot() {
  return (
    <RecoilRoot>
      <ComponentThatUsesRecoil />
    </RecoilRoot>
  );
}

2、常见钩子

  • useRecoilState

用于读写

  • useRecoilValue

用于读 [atom|selector]

  • useSetRecoilState

仅用作设置

  • useRecoilCallback

只读不订阅 数据变化也不会导致当前组件重渲染。

import {atom, useRecoilCallback} from 'recoil';

const itemsInCart = atom({
  key: 'itemsInCart',
  default: 0,
});

function CartInfoDebug() {
  const logCartItems = useRecoilCallback(({snapshot}) => async () => {
    const numItemsInCart = await snapshot.getPromise(itemsInCart);
    console.log('Items in cart: ', numItemsInCart);
  });

  return (
    <div>
      <button onClick={logCartItems}>Log Cart Items</button>
    </div>
  );
}
  • selector

主要注意的是,selector 可能会被重复执行多次,所以其结果会被缓存,它应该是一个纯函数,相同的输入参数和依赖项,其得到的值应该是一样的。

同样,在使用异步状态时,也需要相同的输入,得到的值是相同的。相同的查询参数,只会执行一次异步查询。

import {atom, selector, useRecoilState, DefaultValue} from 'recoil';

const tempFahrenheit = atom({
  key: 'tempFahrenheit',
  default: 32,
});
  • 同步
const tempCelsius = selector({
  key: 'tempCelsius',
  get: ({get}) => ((get(tempFahrenheit) - 32) * 5) / 9,
  set: ({set}, newValue) =>
    set(
      tempFahrenheit,
      newValue instanceof DefaultValue ? newValue : (newValue * 9) / 5 + 32
    ),
});
  • 异步
const a = selector({
  key: 'a',
  get: async({get})=>{
		  const aws = await axios.get(...);
      return aws + get(tempFahrenheit)
  }
})

function TempCelsius() {
  const [tempF, setTempF] = useRecoilState(tempFahrenheit);
  const [tempC, setTempC] = useRecoilState(tempCelsius);
  const resetTemp = useResetRecoilState(tempCelsius);

  const addTenCelsius = () => setTempC(tempC + 10);
  const addTenFahrenheit = () => setTempF(tempF + 10);
  const reset = () => resetTemp();

  return (
    <div>
      Temp (Celsius): {tempC}
      <br />
      Temp (Fahrenheit): {tempF}
      <br />
      <button onClick={addTenCelsius}>Add 10 Celsius</button>
      <br />
      <button onClick={addTenFahrenheit}>Add 10 Fahrenheit</button>
      <br />
      <button onClick={reset}>>Reset</button>
    </div>
  );
}

3、总结一下

- 与redux差别

  • 经常在开发时redux对象很深,一但调用一个修改的action 就会重新生成新的state,导致不少其他也在state的里的模块会被重新渲染
function reducerTest(state = initialState, action) {
  switch (action.type) {
    case 'ADD':
      const {info} = state
      const newInfo = {
      	...info,
        age: info.age+1
      }
      return { ...state,info:newInfo }
    default:
      return state
}}
  • recoil会将粒度化的很细,当一个被改时 并不会造成其他变动,因为每个都是atom
export const projectInfo = atom({key:'1',default:{}})

export const projectChangeLog = atom({key:'1',default:[]})

export const projectName = atom({key:'1',default:''})

- atom是异步!!!

- atom&atomfamily

开始不是很能理解这俩,看上去atomfamily是atom的集合?那为什么不用对象/map映射呢?

// 对象映射
export const mock = {
  "1":atom({key:'1',default:''}),
  "2":atom({key:'2',default:''}),
}
// atomfamily
export const family = atomfamily({
  key:'family',
  default: (xx)=>xx+'haha'	// 返回字符串会自动包装为一个atom对象
})

通过实际研究下来 找到了不同的应用场景

  • 通过对象映射:能做缓存+同步调用+增删数据(修改对象即可)
/*
** 增+查
** 利用对象缓存所有atom,也可用map
*/
export const getAtomById = (id) => {
  if(!mock[id]){
     mock[id] = atom({
     	key: 'id',
     	default: ''
     })
  }
  return mock[id]
}
/* 
** 改
** 通过useSetRecoilState(atom)获取到修改函数
** 调用函数即可类似useState的hook
*/
useSetRecoilState(getAtomById(id))(oldValue=>oldValue+'xxx')
  • atomfamily:规范化+在组件/自定义hook中通过api调用
import {family} from './store/xxx.js'
// family是上个代码段定义的atomfamily 传入id获取对应atom
const myAtom = family(id)
// 在进行其他操作
const [nowAtom,setNowAtom] = useRecoilState(myAtom)