Recoil
RecoilRoot
一个组件。如果我们想要在项目中每个组件中都是用recoil管理的数据,我们就需要使用它包裹整个项目的根组件。
atom
定义共享数据的state
import { atom, useRecoilState } from 'recoil' import React from 'react' const counter = atom({ key: 'myCounter', // 定义默认值 default: 0 }) export default function Test4() { // 对atom提供的state进行读写。 const [count, setCount] = useRecoilState(counter) const incrementByOne = () => setCount(count + 1) return ( <div> Count: {count} <br /> <button onClick={incrementByOne}>Increment</button> </div> ) }
selector
是一个纯函数。如果只提供get方法,那么他将是只读的,如果还提供了set方法,那么它是可读写的,返回RecoilState类型的值。
- get方法,返回一个修饰后的state。他接受一个对象作为参数,其中为get, getCallback。
- set方法, 修改state中定义的属性。他的参数一为一个对象,其中有get, set, reset。参数二是修改传入的值。
const proxySelector = selector({ key: 'ProxySelector', // 直接传入想要获取的auto,而不是auto中定义的key值字符串 get: ({ get }) => get(counter), set: ({ set }, newValue) => set(counter, newValue) }) export default function Test4() { // 对selector提供的state进行读写。 const [count, setCount] = useRecoilState(proxySelector) const incrementByOne = () => setCount(count + 1) return ( <div> Count: {count} <br /> <button onClick={incrementByOne}>Increment</button> </div> ) }
useRecoilState
返回一个数组,第一个元素是 state 的值,第二个元素是一个 setter 函数,调用该函数时会更新为给定 state 的值。
这个api和useState hook十分相似。
- 该方法的setter函数如果传递一个值,那么将覆盖原来的值。
- 如果传入一个函数,该函数接收原来的值作为参数,然后返回新值。注意:不管是哪种修改,都是将原值覆盖。
useRecoilValue
返回给定 Recoil state 的值。如果指向读取state的值,那么就应该使用这个hook。
useSetRecoilState
返回一个 setter 函数,用来更新可写 Recoil state 的值。如果你只想修改state,那么就应该使用这个hook。
可以传给此 setter 函数一个新的值,也可以传入一个更新函数,此函数接受上一次的值作为其参数。我们来测试一下,当我们只想要修改state属性时,使用useRecoilState和useSetRecoilState的区别:
// 使用useRecoilState import React from 'react' import { useRecoilState } from 'recoil' import { personState } from '../../recoilStore/index' export default function UseStateTest() { const [person, setPerson] = useRecoilState(personState) const handleClick = () => { setPerson(100) } console.log('=====看其页面是否重新渲染====引用state,但是未使用') return ( <div> <div>使用useRecoilState</div> <button onClick={handleClick}>修改</button> </div> ) }
// 使用useSetRecoilState import React from 'react' import { useSetRecoilState } from 'recoil' import { personState } from '../../recoilStore/index' export default function NotUseStateTest() { const setPerson = useSetRecoilState(personState) console.log('=====看其页面是否重新渲染====不引用state') return ( <div> <button onClick={() => { setPerson(2) }} > 修改 </button> </div> ) }
如果不需要使用state,而只是想要修改state,就应该使用useSetRecoilState,而不是useRecoilState。
useResetRecoilState
返回一个函数,用来把给定 state 重置为其初始值。函数中不需要传递任何值。
import {todoListState} from "../atoms/todoListState"; const TodoResetButton = () => { const resetList = useResetRecoilState(todoListState); return <button onClick={resetList}>Reset</button>; };
useRecoilStateLoadable
此钩子可用于读取异步 selector 的值。为获取到指定状态值,此钩子将隐含地订阅对应组件。
const userInfo = atom({ key: 'userInfo', default: {} }) const userInfoSelector = selector({ key: 'userInfoSelector', get: async () => { const result = await axios('http://myjson.dit.upm.es/api/bins/fo6v') return result.data }, set: ({ set }, newValue) => set(userInfo, newValue) }) function AsyncSelector() { // 返回一个loadable和一个更新state的函数组成的元组 const [userInfoLoadable, setUserInfo] = useRecoilStateLoadable(userInfoSelector) // 当返回正确的值后,修改state if (userInfoLoadable.state === 'hasValue') { setUserInfo(userInfoLoadable.contents) } const userInfos = useRecoilValue(userInfo) return ( <div> <p>异步</p> <Suspense fallback={<div>loading..</div>}> <p>{userInfos.name}</p> <p>{userInfos.age}</p> </Suspense> </div> ) }
useRecoilValueLoadable
此 hook 用来读取异步 selector 的值。使用此 hook 会使组件隐式地订阅给定的 state。
它会返回一个 Loadable
对象。
import React, { Suspense, useEffect } from 'react' import { atom, selector, useRecoilValue, useRecoilValueLoadable } from 'recoil' import axios from 'axios' const userInfoSelector = selector({ key: 'userInfoSelector', get: async () => { const result = await axios('http://myjson.dit.upm.es/api/bins/fo6v') return result.data } }) function AsyncSelector() { const userInfoLoadable = useRecoilValueLoadable(userInfoSelector) console.log('======', userInfoLoadable) return ( <div> <p>异步</p> <Suspense fallback={<div>loading..</div>}> {userInfoLoadable.state === 'hasValue' ? ( <> <p>{userInfoLoadable.contents.name}</p> <p>{userInfoLoadable.contents.age}</p> </> ) : null} </Suspense> </div> ) } export default AsyncSelector
未出现错误
出现错误
atomFamily
默认情况下,我们不能给atom中的default传递参数,来指定我们想要使用的默认值。如果让外界传入,我们就可以使用atomFamily
来做到。其实我个人感觉这个api没啥用。
const myAtom = atom({ key: 'myAtom', default: (params) => params }) function Test10() { const myA = useRecoilValue(myAtom({ name: 'zh' })) return ( <div> <h2>测试默认情况下是否可以给atom函数传递参数</h2> <p>{myA.name}</p> </div> ) }
正确的方式
// const myAtom = atom({ // key: 'myAtom', // default: (params) => params // }) const myAtom = atomFamily({ key: 'myAtom', default: (params) => params }) function Test10() { const myA = useRecoilValue(myAtom({ name: 'zh' })) return ( <div> <h2>测试默认情况下是否可以给atom函数传递参数</h2> <p>{myA.name}</p> </div> ) }
selectorFamily
默认情况下,我们不能在使用selector的情况下给getter,setter方法传递参数,如果想要传递参数,我们就需要使用这个api。其他内容都和selector一样。
const myAtom = atom({ key: 'myAtom', default: 2 }) const myAtomSelector = selector({ key: 'myAtomSelector', get: (index) => ({ get }) => { let _myAtom = get(myAtom) return `${_myAtom}-${index}` } }) function Test9() { const myAtomSele = useRecoilValue(myAtomSelector(9)) // 默认情况下不能给selector中的方法传递参数 return ( <div> <h2>测试默认情况下是否可以给get方法传递参数</h2> <p>{myAtomSele}</p> </div> ) }
正确的方式
const myAtom = atom({ key: 'myAtom', default: 2 }) // const myAtomSelector = selector({ // key: 'myAtomSelector', // get: // (index) => // ({ get }) => { // let _myAtom = get(myAtom) // return `${_myAtom}-${index}` // } // }) const myAtomSelector = selectorFamily({ key: 'myAtomSelector', get: (index) => ({ get }) => { let _myAtom = get(myAtom) return `${_myAtom}-${index}` } }) function Test9() { const myAtomSele = useRecoilValue(myAtomSelector(9)) return ( <div> <h2>测试默认情况下是否可以给get方法传递参数</h2> <p>{myAtomSele}</p> </div> ) }
useRecoilCallback
这个钩子类似于 useCallback()
,但将为你的回调提供一个 API,以便与 Recoil 状态一起工作。这个钩子可以用来构造一个回调,这个回调可以访问 Recoil 状态的只读 Snapshot
,并且能够异步更新当前的 Recoil 状态。
就是在当异步操作拿到数据后,我们就可以更新state。
const itemsInCartState = atom({ key: 'itemsInCartState', default: {} }) const itemsInCart = selector({ key: 'itemsInCart', get: async () => { const res = await axios('http://myjson.dit.upm.es/api/bins/fo6v') return res.data } }) function Test11() { // 返回一个函数 const logCartItems = useRecoilCallback(({ snapshot, set }) => async () => { // 异步获取异步数据 const numItemsInCart = await snapshot.getPromise(itemsInCart) // 获取到数据后更新state set(itemsInCartState, numItemsInCart) }) const items = useRecoilValue(itemsInCartState) return ( <div> {items.name ? <p>{items.name}</p> : <p>暂无数据</p>} <button onClick={logCartItems}>获取数据</button> </div> ) }
waitForAll
这个api就是可以让我们并发去获取异步数据。但是在他内部不能直接书写网络请求。因为他的参数就只能是一个包含若干RecoilValue值的数组 或者 一个对象,对象属性值都是RecoilValue值。
const friendsInfoQuery = selector({ key: 'FriendsInfoQuery', // 这种写法会报错。 get: async ({ get }) => { const friends = waitForAll([ await axios('http://myjson.dit.upm.es/api/bins/albj'), await axios('http://myjson.dit.upm.es/api/bins/fo6v') ]) return friends } })
function myDBQuery(params) { return axios('http://myjson.dit.upm.es/api/bins/' + params) } // 异步请求的selector const userInfoQuery = selectorFamily({ key: 'UserInfoQuery', get: (params) => async () => { const response = await myDBQuery(params) if (response.error) { throw response.error } return response.data } }) let paramss = ['albj', 'fo6v'] // 并发获取异步数据 const friendsInfoQuery = selector({ key: 'FriendsInfoQuery', get: async ({ get }) => { const friends = get(waitForAll(paramss.map((item) => userInfoQuery(item)))) return friends } }) function AllRequest() { const value = useRecoilValue(friendsInfoQuery) return ( <div> <p>异步</p> <Suspense fallback={<div>loading...</div>}> {value.map((item) => ( <div key={item.name}> <h1>{item.name}</h1> <p>{item.age}</p> </div> ))} </Suspense> </div> ) }