【问题标题】:How to manipulate a global state outside of a React component using Recoil?如何使用 Recoil 操作 React 组件之外的全局状态?
【发布时间】:2021-05-12 09:11:50
【问题描述】:

我正在使用 Recoil,我想从实用程序函数中访问组件(get/set)之外的存储。

更一般地说,人们如何编写可重用的函数来使用 Recoil 操纵全局状态?使用 Redux,我们可以直接向 store 发送事件,但我还没有找到 Recoil 的替代方案。

使用钩子是一种很好的开发者体验,但很难将在组件中定义的函数转换为外部实用函数,因为钩子只能在组件中使用。

【问题讨论】:

  • Recoil 没有“全局”状态。您有位于 Recoil 组件图下方的原子。唯一将 Recoil 与 React 联系在一起的是 RecoilRoot 组件。但也许你可以给出一些你想要实现的伪代码,这样我就可以更好地理解这个问题以及你想要做什么。
  • 确实如此,即使只使用一个RecoilRoot,也可能会认为它是全局存储。

标签: reactjs store dispatch recoiljs


【解决方案1】:

一些没有任何 npm 包和复杂事物的小技巧,但我不确定这样做是否是好方法 :) 但效果很好。

在某些 HOC(高阶组件)中定义 ref 和命令式Hanle

  1. 组件外部(在组件声明之上)
// typescript version
export const errorGlobalRef = createRef<{
  setErrorObject: (errorObject: ErrorTypes) => void;
}>();

// javascript version
export const errorGlobalRef = createRef();
  1. 内部组件
const [errorObject, setErrorObject] = useRecoilState(errorAtom);

//typescript version
useImperativeHandle(errorGlobalRef, () => {
  return {
    setErrorObject: (errorObject: ErrorTypes) => setErrorObject(errorObject),
  };
});

//javascritp version
useImperativeHandle(errorGlobalRef, () => {
  return {
    setErrorObject: (errorObject) => setErrorObject(errorObject),
  };
});

并在您想要的地方导入和使用;) 就我而言:

//axios.config.ts
instance.interceptors.response.use(
  (response) => {
    return response;
  },
  async function (error) {
    const originalRequest = error.config;
    if (error?.response?.data) {
      const { data } = error.response;
      if (data) {
        // set recoil state
        errorGlobalRef.current?.setErrorObject({
          error: data.error,
          message: typeof data.message === 'string' ? [data.message] : data.message,
          statusCode: data.statusCode,
        });
      }
    }
    return Promise.reject(error);
  }
);

【讨论】:

    【解决方案2】:

    您可以使用 recoil-nexus,这是一个小型软件包,其代码类似于 Vadorequest 的答案。

    https://www.npmjs.com/package/recoil-nexus

    // Loading example
    import { loadingState } from "../atoms/loadingState";
    import { getRecoil, setRecoil } from "recoil-nexus";
    
    export default function toggleLoading() {
      const loading = getRecoil(loadingState);
      setRecoil(loadingState, !loading);
    }

    【讨论】:

    • 谢谢!我还没有尝试过,但是有一个合适的(和维护的)替代方案真是太好了。
    【解决方案3】:

    我设法调整了https://github.com/facebookexperimental/Recoil/issues/289#issuecomment-777249693 答案并使其与 Next.js 框架一起工作。 (见下面的用法示例)

    此解决方法允许将 Recoil Root 用作一种全局状态。不过,只有只有一个 RecoilRoot 组件时,它才能正常工作。

    // RecoilExternalStatePortal.tsx
    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 = getRecoilExternalLoadable(lastCreatedUserState);
     */
    export let getRecoilExternalLoadable: <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 setRecoilExternalState: <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 }) => {
        getRecoilExternalLoadable = snapshot.getLoadable;
      });
    
      // We only need to assign setRecoilExternalState once because it's not temporally dependent like "get" is
      useRecoilCallback(({ set }) => {
        setRecoilExternalState = set;
    
        return async () => {
    
        };
      })();
    
      return <></>;
    }
    
    

    使用 Next.js 框架的配置示例:

    // pages/_app.tsx
    
    import {
      NextComponentType,
      NextPageContext,
    } from 'next';
    import { Router } from 'next/router';
    import React from 'react';
    import { RecoilRoot } from 'recoil';
    import { RecoilExternalStatePortal } from '../components/RecoilExternalStatePortal';
    
    type Props = {
      Component: NextComponentType<NextPageContext>; // Page component, not provided if pageProps.statusCode is 3xx or 4xx
      err?: Error; // Only defined if there was an error
      pageProps: any; // Props forwarded to the Page component
      router?: Router; // Next.js router state
    };
    
    /**
     * This file is the entry point for all pages, it initialize all pages.
     *
     * It can be executed server side or browser side.
     *
     * @see https://nextjs.org/docs/advanced-features/custom-app Custom _app
     * @see https://nextjs.org/docs/basic-features/typescript#custom-app TypeScript for _app
     */
    const App: React.FunctionComponent<Props> = (props): JSX.Element => {
      const { Component, pageProps} = props;
    
      return (
          <RecoilRoot>
            <Component {...pageProps} />
            <RecoilExternalStatePortal />
          </RecoilRoot>
      );
    };
    
    // Anywhere, e.g: src/utils/user.ts
    
    const createUser = (newUser) => {
      setRecoilExternalState(lastCreatedUserState, newUser)
    }
    

    【讨论】:

    • 你能解释一下用法吗?
    • @Pranta 您可以在非反应元素(函数等)中使用setRecoilExternalState(lastCreatedUserState, newUser)。这更清楚吗?这是我写的应用程序的一个实际示例:github.com/Vadorequest/rwa-faunadb-reaflow-nextjs-magic/…
    • 嘿,在getServersideprops中好像不行
    • 我不希望它在 getServerSideProps 中工作,因为尚未定义 RecoilRoot 组件。你的 Recoil 状态此时甚至不存在,是吗?如果您在 _app.js 中定义 &lt;RecoilRoot&gt; 并从页面调用 getServerSideProps,也许它会起作用,但我不确定这是否会起作用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-05-05
    • 2021-01-01
    • 1970-01-01
    • 2021-11-10
    • 2021-03-16
    • 1970-01-01
    • 2021-05-12
    相关资源
    最近更新 更多