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

Giu*_*age 5 javascript reactjs react-native

当谈到状态集中化时,我知道如何使用 context api 和 Redux。但要恢复该状态,我们始终必须位于反应组件内。

访问不在反应组件内部的公共函数内的全局状态/变量的最佳策略是什么?

在环境变量中不是一个选项,因为该值在应用程序运行后会更改。出于安全原因,我不想放入 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'),
);
Run Code Online (Sandbox Code Playgroud)

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>
  );
};
Run Code Online (Sandbox Code Playgroud)

阿波罗客户端配置

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;
Run Code Online (Sandbox Code Playgroud)

Emi*_*ron 3

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} />;
}
Run Code Online (Sandbox Code Playgroud)

然后在应用程序中:

    <PersonalTokenProvider>
      <CustomApolloProvider>
        <App />
      </CustomApolloProvider>
    </PersonalTokenProvider>
Run Code Online (Sandbox Code Playgroud)

优点:

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

缺点:

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

使用本地存储

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

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()
});
Run Code Online (Sandbox Code Playgroud)

优点:

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

缺点:

  • 存在于 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()
});
Run Code Online (Sandbox Code Playgroud)

优点:

  • 可以轻松添加到您现有的实施中
  • 在应用程序的整个生命周期中只创建一个客户端

缺点:

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

反应式变量来管理令牌

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