如何在 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 客户端的格式错误的身份验证标头错误
如何重置 Apollo 客户端的 useMutation 钩子
Apollo GraphQL - 如何将 RxJS 主题用作 Apollo 客户端的变量?