5

我是 Recoil.js 的新手,我在应用程序中为登录用户提供了以下原子和选择器:

const signedInUserAtom = atom<SignedInUser | null>({
    key: 'signedInUserAtom',
    default: null
})

export const signedInUserSelector = selector<SignedInUser | null>({
    key: 'signedInUserSelector',
    get: ({ get }) => get(signedInUserAtom),
    set: ({ set, get }, newUserValue) => {
        // ... Do a bunch of stuff when a user signs in ...

        set(signedInUserAtom, newUserValue)
    }
})

所以基本上我用signedInUserSelector它来设置新用户。
现在,我想要一些函数来通过选择器设置用户,并在我的组件中使用它们,例如:

  export async function signInWithGoogleAccount() {
     const googleUser = async googleClient.signIn()
     // here I need to set the user atom like:
     //  const [user, setUser] = useRecoilState(signedInUserSelector)
     // setUser(googleUser)
  }

  export async function signInWithLocalAccount(email: string, password: string) {
     const localUser = async localClient.signIn(email, password)
     // here I need to set the user atom like:
     //  const [user, setUser] = useRecoilState(signedInUserSelector)
     // setUser(localUser)
  }

  export async function signOut() {
      await localClient.signOut()
      // here I need to set the user atom like:
     //  const [user, setUser] = useRecoilState(signedInUserSelector)
     // setUser(null)
  }

问题是因为这些函数没有在组件内部定义,所以我不能使用反冲钩子(比如useRecoilState访问选择器/原子)。

最后,我希望有任何组件能够做到:

function SignInFormComponent() {
  return <button onClick={signInWithGoogleAccount}>Sign In</button>
}

signInWithGoogleAccount但是,如果它不在组件中,我如何访问选择器/原子?

4

2 回答 2

7

正如我在另一个答案中指出的那样,您通常不想遇到这种情况,但是如果您最终确实需要更新 React Components 之外的原子,您可能会尝试使用Recoil Nexus

在您拥有 RecoilRoot 的同一个文件中,您将拥有类似的内容:

import React from 'react';
import { RecoilRoot } from "recoil"
import RecoilNexus from 'recoil-nexus'

export default function App() {
  return (
    <RecoilRoot>
      <RecoilNexus/>
      
      {/* ... */}
      
    </RecoilRoot>
  );
};


export default App;

然后,无论您需要在哪里读取/更新值:

import yourAtom from './yourAtom'
import { getRecoil, setRecoil } from 'recoil-nexus'

最终,您可以像这样获取和设置值:


const loading = getRecoil(loadingState)

setRecoil(loadingState, !loading)

而已!

免责声明:我是图书馆的作者。


检查此CodeSandbox以获取实时示例。

于 2021-09-28T12:13:02.317 回答
3

我认为唯一的方法(至少在几个月前)是一种 hack,您可以在其中包含一个非渲染组件,该组件使用反冲钩并从中导出提供的功能。

见:https ://github.com/facebookexperimental/Recoil/issues/289#issuecomment-777249693

下面是我自己的项目中实现这一点的文件,主要基于上面的链接。您需要做的就是将<RecoilExternalStatePortal />应用程序树中的任何位置放置在保证始终呈现的任何位置。

恕我直言,这似乎是 Recoil API 中的一个遗漏。

import React from 'react'
import { Loadable, RecoilState, RecoilValue, useRecoilCallback, useRecoilTransactionObserver_UNSTABLE } from 'recoil'

/**
 * Returns a Recoil state value, from anywhere in the app.
 *
 * Can be used outside of the React tree (outside a React component), such as in utility scripts, etc.

 * <RecoilExternalStatePortal> must have been previously loaded in the React tree, or it won't work.
 * Initialized as a dummy function "() => null", it's reference is updated to a proper Recoil state mutator when RecoilExternalStatePortal is loaded.
 *
 * @example const lastCreatedUser = getRecoilExternal(lastCreatedUserState);
 */
export function getRecoilState<T>(recoilValue: RecoilValue<T>): T {
  return getRecoilLoadable(recoilValue).getValue()
}

/** The `getLoadable` function from recoil. This shouldn't be used directly. */
let getRecoilLoadable: <T>(recoilValue: RecoilValue<T>) => Loadable<T> = () => null as any

/**
 * Sets a Recoil state value, from anywhere in the app.
 *
 * Can be used outside of the React tree (outside a React component), such as in utility scripts, etc.
 *
 * <RecoilExternalStatePortal> must have been previously loaded in the React tree, or it won't work.
 * Initialized as a dummy function "() => null", it's reference is updated to a proper Recoil state mutator when RecoilExternalStatePortal is loaded.
 *
 * @example setRecoilExternalState(lastCreatedUserState, newUser)
 */
export let setRecoilState: <T>(recoilState: RecoilState<T>, valOrUpdater: ((currVal: T) => T) | T) => void = () =>
  null as any

/**
 * Utility component allowing to use the Recoil state outside of a React component.
 *
 * It must be loaded in the _app file, inside the <RecoilRoot> component.
 * Once it's been loaded in the React tree, it allows using setRecoilExternalState and getRecoilExternalLoadable from anywhere in the app.
 *
 * @see https://github.com/facebookexperimental/Recoil/issues/289#issuecomment-777300212
 * @see https://github.com/facebookexperimental/Recoil/issues/289#issuecomment-777305884
 * @see https://recoiljs.org/docs/api-reference/core/Loadable/
 */
export function RecoilExternalStatePortal() {
  // We need to update the getRecoilExternalLoadable every time there's a new snapshot
  // Otherwise we will load old values from when the component was mounted
  useRecoilTransactionObserver_UNSTABLE(({ snapshot }) => {
    getRecoilLoadable = snapshot.getLoadable
  })

  // We only need to assign setRecoilExternalState once because it's not temporally dependent like "get" is
  useRecoilCallback(({ set }) => {
    setRecoilState = set

    return async () => {
      // no-op
    }
  })()

  return <></>
}
于 2021-08-26T23:23:59.890 回答