【问题标题】:How to use the global data from React inside Apollo client's initialization?如何在 Apollo 客户端的初始化中使用来自 React 的全局数据?
【发布时间】:2021-10-23 10:27:19
【问题描述】:

说到状态集中,我知道如何使用 context api 和 Redux。但是要恢复这种状态,我们总是必须在 react 组件中。

在一个不在 React 组件内部的通用函数中访问全局状态/变量的最佳策略是什么?

在环境变量中不是一个选项,因为该值在应用程序运行后更改。而且出于安全原因,我不想放入 cookie 或本地存储。

索引.ts

import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloProvider } from 'react-apollo';
import apolloClient from './services/apollo';

import { PersonalTokenProvider } from './providers/personal-token';
import './index.css';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <PersonalTokenProvider>
      <ApolloProvider client={apolloClient}>
        <App />
      </ApolloProvider>
    </PersonalTokenProvider>
  </React.StrictMode>,
  document.getElementById('root'),
);

PresonalToken 上下文提供者

import React, { useState } from 'react';

interface ProviderProps {
  children: JSX.Element[] | JSX.Element;
}

export const PersonalTokenContext = React.createContext({});

export const PersonalTokenProvider: React.FC<ProviderProps> = (
  props: ProviderProps,
) => {
  const [token, setToken] = useState<string | null>(null);

  const { children } = props;

  return (
    <PersonalTokenContext.Provider value={{ token, setToken }}>
      {children}
    </PersonalTokenContext.Provider>
  );
};

apollo 客户端配置

import { useContext } from 'react';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { PersonalTokenContext } from '../providers/personal-token';

//cant do this
const {token} = useContext(PersonalTokenContext)

const httpLink = new HttpLink({
  uri: 'https://api.github.com/graphql',
  headers: {
    authorization: `Bearer ${token}`,
  },
});

const client = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache(),
});

export default client;

【问题讨论】:

  • 我想你错过了useSelector, react-redux.js.org/api/hooks ?
  • 老兄,我在玩 github graphQL api,我想让用户插入个人令牌,之后它必须可供 ApolloClient 使用。我不认为创建一个数据库只是为了保存它是一种选择。
  • 表单组件将 update a token context,自定义 apollo 提供程序(带有中间件链接)将使用该上下文来设置正确的标题。
  • @EmileBergeron 我真的不想为此使用 redux,我会更新我的问题,以便您可以看到我在做什么,我想我明白了中间件的意义
  • @EmileBergeron 已更新

标签: javascript reactjs react-native


【解决方案1】:

Pure React Apollo 客户端初始化

有多种方法可以模拟单例来从 React 中管理 Apollo 客户端。这是使用useRef 在进行 GraphQL 查询时始终拥有最新令牌和使用useMemo 只创建一次客户端的一种方法。

import {
  ApolloClient,
  createHttpLink,
  InMemoryCache,
  ApolloProvider
} from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

// The name here doesn't really matters.
export default function CustomApolloProvider(props) {
  const { token } = useContext(PersonalTokenContext);
  const tokenRef = useRef();

  // Whenever the token changes, the component re-renders, thus updating the ref.
  tokenRef.current = token;

  // Ensure that the client is only created once.
  const client = useMemo(() => {
    const authLink = setContext((_, { headers }) => ({
      headers: {
        ...headers,
        authorization: tokenRef.current ? `Bearer ${tokenRef.current}` : '',
      }
    }));

    const httpLink = createHttpLink({
      uri: 'https://api.github.com/graphql',
    });

    return new ApolloClient({
      link: authLink.concat(httpLink),
      cache: new InMemoryCache(),
    });
  }, [])

  return <ApolloProvider client={client} {...props} />;
}

然后在应用中:

    <PersonalTokenProvider>
      <CustomApolloProvider>
        <App />
      </CustomApolloProvider>
    </PersonalTokenProvider>

优点:

  • 完全在 React 内部,这意味着它可以使用从不同位置更改的其他钩子和数据,例如翻译库中的语言环境代码等。
  • 每个已安装的应用程序一个客户端,这意味着,如果需要卸载应用程序,此解决方案将确保正确清理。
  • 易于添加单元/集成测试

缺点:

  • 实施起来有点复杂。
  • 如果设置不正确,最终可能会创建多个 Apollo 客户端,从而丢失之前的缓存等。

使用本地存储

Apollo documentation 建议使用本地存储来管理身份验证令牌。

import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({
  uri: '/graphql',
});

const authLink = setContext((_, { headers }) => {
  // get the authentication token from local storage if it exists
  const token = localStorage.getItem('token');
  // return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  }
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});

优点:

  • 很容易添加到您现有的实施中
  • 在应用的整个生命周期内只创建一个客户端
  • 本地存储是存储选项卡、刷新等全局数据的好地方。

缺点:

  • 存在于 React 之外,因此当令牌更改等时应用不会重新渲染。
  • 单元测试可能更难/更复杂。

使用模块作用域变量

在模块的根目录使用一个简单的变量就足够了,你甚至不再需要令牌上下文了。

import {
  ApolloClient,
  createHttpLink,
  InMemoryCache,
  makeVar
} from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

// module scoped var for the token:
let token;

// custom module setter:
export const setToken = (newToken) => token = newToken;

const httpLink = createHttpLink({
  uri: '/graphql',
});

// Apollo link middleware gets called for every query.
const authLink = setContext((_, { headers }) => ({
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  }
));

export const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});

优点:

  • 很容易添加到您现有的实施中
  • 在应用的整个生命周期内只创建一个客户端

缺点:

  • 存在于 React 之外,因此当令牌更改等时应用不会重新渲染。
  • 单元测试可能更难/复杂
  • 用户刷新页面或关闭应用时丢失。

用于管理令牌的反应式变量

juanireyes 建议 Apollo Reactive variables,但它们是针对特定用例的,完全没有必要像我们在这里想要的那样在全局范围内管理令牌。它类似于上面的模块范围变量建议,但有额外的步骤

【讨论】:

    【解决方案2】:

    如果您尝试使用 Apollo,我个人建议您使用更新后的库:@apollo/client。然后您可以使用Reactive Variables 从多个位置访问状态。然后您可以尝试在您的提供程序文件中使用类似这样的内容来访问token 变量:

    import React, { useState } from 'react';
    import { makeVar } from '@apollo/client';
    
    interface ProviderProps {
      children: JSX.Element[] | JSX.Element;
    }
    
    export const tokenVar = makeVar<string | null>(null);
    
    export const PersonalTokenContext = React.createContext({});
    
    export const PersonalTokenProvider: React.FC<ProviderProps> = (
      props: ProviderProps,
    ) => {
      const [token, setToken] = useState<string | null>(null);
    
      useEffect(() => {
       tokenVar(token)
      }, [token]);
    
      const { children } = props;
    
      return (
        <PersonalTokenContext.Provider value={{ token, setToken }}>
          {children}
        </PersonalTokenContext.Provider>
      );
    };
    

    最后,您可以从任何地方调用tokenVar() 或使用useReactiveVar 挂钩访问令牌值。

    【讨论】:

      【解决方案3】:

      您可以从组件外部访问 Redux 存储的内容。我知道这样做的两种方法:

      getState

      从声明它的文件中导入 store,并使用 getState 方法访问整个状态:

      import { store } from '../myReduxConfig.js';
      
      const myFunc = () => {
        const reduxData = store.getState();
      }
      

      订阅

      如果您需要在 redux 存储更改时再次运行该函数,请从您声明它的文件中导入该存储,并为您的函数订阅它:

      import { store } from '../myReduxConfig.js';
      
      store.subscribe(myFunc);
      
      const myFunc = () => {
        const reduxData = store.getState();
      }
      

      【讨论】:

        猜你喜欢
        • 2021-12-09
        • 2019-06-18
        • 2017-10-13
        • 2018-02-25
        • 2019-08-29
        • 2023-03-19
        • 1970-01-01
        • 2020-11-13
        • 1970-01-01
        相关资源
        最近更新 更多