React Native Context,如何在多个嵌套文件和组件之间共享上下文

Posted

技术标签:

【中文标题】React Native Context,如何在多个嵌套文件和组件之间共享上下文【英文标题】:React Native Context, how to share context beetwing multiple nested files and components 【发布时间】:2020-11-08 19:31:57 【问题描述】:

我对本机反应很陌生,而且我坚持在不同文件中的组件之间传递上下文 基本上我正在按照反应导航 auth-flow https://reactnavigation.org/docs/auth-flow/ 构建登录流程 我的场景如下:

在 App.js 中 带有 Login/Register/Home 的堆栈屏幕,根据登录状态显示 Login/Register 或 Home 主屏幕由一个抽屉组件组成,使用自定义抽屉和两个组件(主页和关于)

//VARIOUS IMPORT    
const Drawer = createDrawerNavigator();

const HeaderOption = () => (
  headerShown: false,
  // animationTypeForReplace: state.isSignout ? 'pop' : 'push',
);

const AppStack = createStackNavigator();

const AuthContext = createContext();

//THE DRAWER FOR HOME
function DrawerNavigator(props) 
  return (
    <Drawer.Navigator
      initialRouteName="Home"
      drawerContent=(props) => MyDrawer(props)
    >
      <Drawer.Screen name="Home" component=Home />
      <Drawer.Screen name="About" component=About />
    </Drawer.Navigator>
  );

//MAIN APP
export default function App( navigation ) 
  const [state, dispatch] = useReducer(
    (prevState, action) => 
      switch (action.type) 
        case 'RESTORE_TOKEN':
          return 
            ...prevState,
            userToken: action.token,
            isLoading: false,
          ;
        case 'SIGN_IN':
          return 
            ...prevState,
            isSignout: false,
            userToken: action.token,
          ;
        case 'SIGN_OUT':
          return 
            ...prevState,
            isSignout: true,
            userToken: null,
          ;
      
    ,
    
      isLoading: true,
      isSignout: false,
      userToken: null,
    
  );

  useEffect(() => 
    // Fetch the token from storage then navigate to our appropriate place
    const bootstrapAsync = async () => 
      let userToken;

      try 
        userToken = await AsyncStorage.getItem('userToken');
       catch (e) 
      

     
      dispatch( type: 'RESTORE_TOKEN', token: userToken );
    ;

    bootstrapAsync();
  , []);

  const authContext = useMemo(
    () => (
      signIn: async (data) => 
        // LOGIN PROCEDURE

        dispatch( type: 'SIGN_IN', token: 'dummy-auth-token' );
      ,
      signOut: () => dispatch( type: 'SIGN_OUT' ),
      signUp: async (data) => 
        // SUBSCRIBE PROCEDURE

        dispatch( type: 'SIGN_IN', token: 'dummy-auth-token' );
      ,
    ),
    []
  );

  if (state.isLoading) 
    // We haven't finished checking for the token yet
    return (
      <View>
        <Text>Loading</Text>
      </View>
    );
  
  return (
    <AuthContext.Provider value=authContext>
      <NavigationContainer>
        <AppStack.Navigator initialRouteName="Login">
          state.userToken == null ? (
            <>
              <AppStack.Screen
                name="Login"
                component=LoginScreen
                options=HeaderOption
              />
              <AppStack.Screen
                name="Register"
                component=RegisterScreen
                options=HeaderOption
              />
            </>
          ) : (
            <AppStack.Screen
              name="HomeApp"
              component=DrawerNavigator
              options=HeaderOption
            />
          )
        </AppStack.Navigator>
      </NavigationContainer>
    </AuthContext.Provider>
  );

在 LoginScreen.js 中 有效的登录屏幕(如果未登录,则会在应用启动时显示)

//import

export default function LoginScreen(props) 
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const  signIn  = useContext(AuthContext);

  return (
    <View
      style=
        flex: 1,

        backgroundColor: Constants.MAIN_GREEN,
      
    >
      <View style= ...styles.container >
        <StatusBar hidden=true />
        <View style= ...styles.logoContainer >
          <Image
            style=styles.logoIcon
            source=require('../assets/logo_popeating_amp.png')
          />
        </View>

        <View style= ...styles.inputContainer >
          <Image
            style=styles.inputIcon
            source=require('../assets/mail.png')
          />
          <TextInput
            autoFocus=true
            placeholder="Email address"
            onChangeText=(email) => setEmail(email)
            value=email
            label="Email"
            style=styles.inputs
            keyboardType='email-address'
          />
        </View>
        <View style= ...styles.inputContainer >
          <Image
            style=styles.inputIcon
            source=require('../assets/password.png')
          />
          <TextInput
            placeholder="Password"
            onChangeText=(password) => setPassword(password)
            value=password
            secureTextEntry=true
            label="Password"
            style=styles.inputs
          />
        </View>
        <TouchableHighlight
          style=[styles.buttonContainer, styles.loginButton]
          onPress=() => signIn( email, password )
          underlayColor=Constants.HI_COLOR
        >
          <Text style=styles.loginText>LOGIN</Text>
        </TouchableHighlight>

        <TouchableHighlight
          style=styles.buttonContainer
          onPress=() => props.navigation.navigate('HomeApp')
          underlayColor=Constants.HI_COLOR
        >
          <Text>Forgot your password?</Text>
        </TouchableHighlight>

        <TouchableHighlight
          style=styles.buttonContainer
          onPress=() => props.navigation.navigate('Register')
          underlayColor=Constants.HI_COLOR
        >
          <Text>Register</Text>
        </TouchableHighlight>
      </View>
    </View>
  );




const styles = StyleSheet.create(
  //styles
);

在 DrawerContent.js 中 主页的抽屉,其中包含指向主页的链接、指向关于的链接、指向注销的链接

在 Home.js 中 主页面是Drawer的初始路由

每次我尝试启动应用程序时 错误是 未处理的承诺拒绝:ReferenceError:找不到变量:AuthContext

似乎 LoginScreen 无法访问 AuthContext,我怎样才能让 AuthContext 可用于文件之间的其他组件?

【问题讨论】:

【参考方案1】:

您可以将上下文创建放在单独的文件中

//AuthContext.js
const AuthContext = createContext();
export default AuthContext;

在 app.js 中,您可以简单地导入并使用它

import AuthContext from './AuthContext.js';

你也可以对 login.js 做同样的事情 然后它将按预期工作。

【讨论】:

谢谢!它适用于登录,但如果我对 DrawerContent 执行相同操作,我会收到错误,无效的挂钩调用。 Hooks 只能在函数组件内部调用。 由于错误表明您的抽屉内容是一个类组件,如果可能的话,您可以将其更改为功能组件,或者在下面的问题***.com/questions/54695835/… 的答案中做一个解决方法 这是我的DrawerContent(由App调用的MyHome调用)import AuthContext from '../AuthContext'; const MyDrawer = (props) =&gt; const signOut = useContext(AuthContext); return ( &lt;SafeAreaView style= flex: 1 &gt; &lt;View style= height: 150, alignItems: 'center', justifyContent: 'center' &gt; &lt;TouchableOpacity style= marginTop: 20 onPress=() =&gt; signOut()&gt; &lt;Text&gt;Logout&lt;/Text&gt; &lt;/TouchableOpacity&gt; &lt;/ScrollView&gt; &lt;/SafeAreaView&gt; ); ; export default MyDrawer; 奇怪,它是一个功能组件,所以它应该按预期工作 我在主应用程序中有一个useEffect,可以吗?但是没有useEffect我无法检查用户是否在启动时已经登录,所有功能都在一个传递给AuthContext的useMemo中......也许我有点困惑

以上是关于React Native Context,如何在多个嵌套文件和组件之间共享上下文的主要内容,如果未能解决你的问题,请参考以下文章

收到此错误:错误:捆绑失败:错误:无法解析模块`react-native-safe-area-context`

React this.context 在 React Native 0.61 中返回空对象

在 React Native 中使用 Context 和 React Navigation 处理嵌套屏幕上的更新

React (Native) Context API 导致 Stack Navigator (React Navigation 5) 在状态更新后重新渲染

React Native Context 不会跨文件和屏幕更新

React Native with Context API 警告:“允许需要循环,但可能导致未初始化的值......”