从hr口中了解react的状态管理库(mobx, recoil), 立马过来学习之recoil

276 阅读6分钟

这几天在找工作,昨天晚上联系了一个字节的面试岗位hr,然后推荐了react的两个状态管理库。感觉。 913478211132775178.jpg

我们都知道react-redux状态管理的编码方式比较复杂,所以得知这两个状态管理库后,立刻来学习。xdm,学无止境啊,加油。

这里贴一下两个库的中文文档地址

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属性时,使用useRecoilState和useSetRecoilState的区别.gif 如果不需要使用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

未出现错误 image.png 出现错误 image.png

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>
      )
    }

image.png 正确的方式

    // 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>
      )
    }

image.png

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>
      )
    }

image.png 正确的方式

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>
  )
}

image.png

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>
      )
    }

useRecoilCallback测试.gif

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
      }
    })

image.png

    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>
      )
    }

以上发送的请求接口都是通过myjson来创建的。平常开发学习时,使用这个网站来做网络请求,真的是舒服,嘻嘻。

以上就是参考官网学习了一些常用的api,再接再厉。