如何更新 Recoil.js 外部组件中的原子(状态)? (反应)
Posted
技术标签:
【中文标题】如何更新 Recoil.js 外部组件中的原子(状态)? (反应)【英文标题】:How to update atoms (state) in Recoil.js outside components ? (React) 【发布时间】:2021-10-26 22:20:35 【问题描述】:我是 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
中的选择器/原子不在组件中,我该如何访问它?
【问题讨论】:
【参考方案1】:我认为唯一的方法(至少在几个月前)是一种 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 <></>
【讨论】:
【参考方案2】:正如我在another answer 中指出的那样,您通常不想遇到这种情况,但是如果您最终确实需要更新 React 之外的原子组件,您可以尝试@ 987654322@.
在您拥有 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 以获取实时示例。
【讨论】:
老兄,太棒了,你救了我的一天以上是关于如何更新 Recoil.js 外部组件中的原子(状态)? (反应)的主要内容,如果未能解决你的问题,请参考以下文章