如何在 Apollo 客户端的初始化中使用来自 React 的全局数据?

Posted

技术标签:

【中文标题】如何在 Apollo 客户端的初始化中使用来自 React 的全局数据?【英文标题】:How to use the global data from React inside Apollo client's initialization? 【发布时间】: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 已更新 【参考方案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();

【讨论】:

以上是关于如何在 Apollo 客户端的初始化中使用来自 React 的全局数据?的主要内容,如果未能解决你的问题,请参考以下文章

如何修复来自 Apollo 客户端的格式错误的身份验证标头错误

如何在来自客户端的 graphql 查询中添加 hmac?

如何重置 Apollo 客户端的 useMutation 钩子

Apollo GraphQL - 如何将 RxJS 主题用作 Apollo 客户端的变量?

如何在本地网络上部署带有 apollo 客户端和 apollo-server 后端的 React 应用程序

使用 Apollo 客户端的片段组合:约定和样板