如何使用 apollo-hooks 为 react-native 创建实用程序

Posted

技术标签:

【中文标题】如何使用 apollo-hooks 为 react-native 创建实用程序【英文标题】:How to create utils with apollo-hooks for react-native 【发布时间】:2019-11-21 01:22:12 【问题描述】:

我正在 react-native/apollo-hooks/graphql 上使用两个令牌(accessToken、refreshToken)进行身份验证流程。并为 updateTokens 创建了 apollo-hook。这个钩子我需要在不同的组件中使用,因为每次用户想要做任何动作,比如发帖或评论时,我都需要调用 updateTokens apollo 钩子。因此我需要将 updateTokens 的所有逻辑放到一个单独的工具中,因为我不想重复自己并将相同的代码放在任何地方。但是当我用 apollo 钩子制作单独的 util 文件时,它不起作用。谁能解释一下我该怎么做!

import React from 'react'
import  View, Button, Text  from 'react-native'
import  useMutation  from 'react-apollo-hooks'
import gql from 'graphql-tag'
import * as Keychain from 'react-native-keychain'

const UPDATE_TOKENS = gql`
  mutation UpdateTokens($accessToken: String!, $refreshToken: String!) 
    updateTokens(data:  accessToken: $accessToken, refreshToken: $refreshToken ) 
      user 
        name
        phone
      
      accessToken
      refreshToken
    
  
`

const HomeScreen = ( navigation ) => 
  const update_tokens = useMutation(UPDATE_TOKENS)

  const updateTokens = (accessToken, refreshToken) => 
    console.log('accessToken', accessToken)
    console.log('refreshToken', refreshToken)
    update_tokens(
      variables:  accessToken, refreshToken ,
      update: async (cache,  data ) => 
        const accessToken = data.updateTokens.accessToken
        const refreshToken = data.updateTokens.refreshToken
        await Keychain.setGenericPassword(accessToken, refreshToken)
      
    ).then(() => console.log('We have new credentials'))
  

  const getCredentials = async () => 
    const tokens = await Keychain.getGenericPassword()
    console.log('tokens', tokens)
    const keyChainAccessToken = tokens.username
    const keyChainRefreshToken = tokens.password
    console.log('keyChainAccessToken', keyChainAccessToken)
    console.log('keyChainRefreshToken', keyChainRefreshToken)
    updateTokens(keyChainAccessToken, keyChainRefreshToken)
  

  return (
    <View style= flex: 1, alignItems: 'center', justifyContent: 'center' >
      <Text>Home Screen</Text>
      <Button title="updateTokens" onPress=getCredentials />
      <Button title="getCredentials" onPress=checkCredentials />
      <Button title="SignOut" onPress=userSignOut />
    </View>
  )

export  HomeScreen 

我可以像这样把它放到单独的文件中,然后在不同的地方使用它:

import  useMutation  from 'react-apollo-hooks'
import gql from 'graphql-tag'
import * as Keychain from 'react-native-keychain'
import jwtDecode from 'jwt-decode'

const UPDATE_TOKENS = gql`
  mutation UpdateTokens($refreshToken: String!, $refreshTokenId: String!) 
    updateTokens(refreshToken: $refreshToken, refreshTokenId: $refreshTokenId) 
      user 
        name
        phone
      
      accessToken
      refreshToken
    
  
`

const updateCredentials = () => 
  const update_tokens = useMutation(UPDATE_TOKENS)
  const updateTokens = (refreshToken, refreshTokenId) => 
    console.log('refreshToken', refreshToken)
    console.log('refreshTokenId', refreshTokenId)
    update_tokens(
      variables:  refreshToken, refreshTokenId ,
      update: async (cache,  data ) => 
        const newAccessToken = data.updateTokens.accessToken
        const newRefreshToken = data.updateTokens.refreshToken
        const user = data.updateTokens.user
        await Keychain.setGenericPassword(newAccessToken, newRefreshToken)
      
    ).then(() => console.log('We have new credentials'))
  

  const getCredentials = async () => 
    try 
      const tokens = await Keychain.getGenericPassword()
      console.log('tokens', tokens)
      const keychainAccessToken = tokens.username
      const keychainRefreshToken = tokens.password
      const currentTime = Date.now() / 1000
      const decodeAccessToken = jwtDecode(keychainAccessToken)
      console.log('decodeAccessToken', decodeAccessToken)
      const keychainRefreshTokenId = decodeAccessToken.refreshTokenId
      console.log('keychainRefreshTokenId', keychainRefreshTokenId)

      if (decodeAccessToken.exp > currentTime) 
        console.log('Credentials is still valid')
       else if (decodeAccessToken.exp < currentTime) 
        console.log('Go to Update!')
        updateTokens(keychainRefreshToken, keychainRefreshTokenId)
      
     catch (err) 
      throw new Error('Invalid credentials')
    
  

export  updateCredentials 

【问题讨论】:

【参考方案1】:

对我来说,我不会为了刷新令牌而这样做。我为它做了一个中间件。

如何设置中间件:

import  createUploadLink  from 'apollo-upload-client';
const getClient = ( userStore:  token, refreshToken: refresh_token , lang , dispatch) => 
    const locale = lang || 'en';

    const uploadLink = createUploadLink(
        uri: 'http://localhost:4000'
    );

    const client = new ApolloClient(
        link: ApolloLink.from([
            getTokensMiddleware(token, refresh_token, locale, dispatch),
            uploadLink
        ]),
        cache: new InMemoryCache()
    );

    return client;
;

中间件:

const getTokensMiddleware = (token, refresh_token, locale, dispatch) => 
    return setContext(async (req,  headers, ...others ) => 
        if (!token || !refresh_token) return ;
        var decoded = jwtDecode(token);
        const isExpired = decoded.exp <= Date.now() / 1000 + 120;
        var decodedRefresh = jwtDecode(refresh_token);
        const isRefreshJWTExpired = decodedRefresh.exp <= Date.now() / 1000;
        if(isRefreshJWTExpired) return ;
        if (!isExpired) 
            return 
                ...others,
                headers: 
                    ...headers,
                    Authorization: token ? `Bearer $token` : '',
                    locale
                
            ;
        
        return new Promise((success, fail) => 
            refreshToken(refresh_token)
                .then(response => 
                    if (response.ok) 
                        return response.json();
                     else 
                        fail(response);
                    
                )
                .then(json => 
                    const  token  = json.data.refreshToken;
                    dispatch(type: "login", payload: json.data.refreshToken)
                    success(
                        ...others,
                        headers: 
                            ...headers,
                            Authorization: token ? `Bearer $token` : '',
                            locale
                        
                    );
                );
        );
    );
;

你的刷新功能:

const refreshToken = refreshToken => 
    const data = 
        operation: 'RefreshTokenMutation',
        query:
            'mutation RefreshTokenMutation($email: String!, $refreshToken: String!)   refreshToken(email: $email, refreshToken: $refreshToken)     token    refreshToken    user       id      email      username      displayname      role         title        permissions        __typename            __typename        __typename  ',
        variables:  email: 'admin_email', refreshToken: refreshToken 
    ;
    return fetch('http://localhost:4000/', 
        method: 'POST',
        headers:  'content-type': 'application/json' ,
        body: JSON.stringify(data)
    );
;

如何使用

const AppPage = () => 
    const  state, dispatch  = useStore(); //a hook for getting context containing useReducer
    return (
        <I18nextProvider i18n=i18n>
            <Helmet>
                <title>My Project</title>
                <meta name="description" content="My project" />
            </Helmet>
            <ApolloProvider client=getClient(state, dispatch)>
                <LocaleProvider locale=state.antdLocale>
                    <App />
                </LocaleProvider>
            </ApolloProvider>
        </I18nextProvider>
    );
;

【讨论】:

非常感谢您的出色回答,但您能否解释一下在创建中间件时您从何处获得此参数: userStore: token, refreshToken: refresh_token , lang , dispatch?在我的情况下,它必须来自钥匙串存储? 这些变量仅供我自己使用,您可以使用自己的方式。它们来自用于获取和更新令牌状态的 useReducer 钩子。 您能否再回答一个问题。 Apollo setContext 何时工作?在应用程序的初始化时刻还是之后?因为在我的情况下,我需要将令牌保存到 setContext,但是当应用程序初始化时,钥匙串存储没有任何令牌,因为用户尚未登录。他只有在登录后才能获得令牌。当他注销时,令牌正在擦除。但我的东西 setContext 是从一开始就开始工作的。对吗? getTokensMiddleware 是生成中间件的函数。它在您设置 ApolloClient 时启动。 getClient 是生成 apollo 客户端的函数。我们使用令牌(状态)在应用程序运行期间生成一个新客户端。更新令牌(状态)后,它将生成一个新客户端。更新了 ans 示例。 你把 useReducer 钩子放在哪里?你能给我一个例子吗?【参考方案2】:

如果你还想这样做,你可以用 HOC 的方式来做。

import React from 'react';
import YOUR_FUNCTIONS from '~/functions_location';

const withRefreshToken = () => (Component) => 
    return <Component ...YOUR_FUNCTIONS />
;

export 
    withRefreshToken

之后你可以这样做:

export default withRefreshToken(HomeScreen);

【讨论】:

以上是关于如何使用 apollo-hooks 为 react-native 创建实用程序的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Typescript 为 React 设置 Material-UI?

如何使用 React、Jest 和 React-testing-library 为带有令牌的 api 调用编写单元测试?

如何使用 Firebase 托管为 React SPA 提供 404 页面?

如何使用 createBottomTabNavigator 为 React Navigation 过渡设置动画?

如何使用 React-Native 和 Reanimated 2 为 svg 设置动画道具变换

如何将 React 组件导出为 NPM 包以在单独的项目中使用?