引言
本来打算将React Navigation-Guides这一章内容集中写在这篇文章内的,但由于篇幅太长,阅读的时候很费劲,所以这里将Guides这一章的内容分篇来记录。接下来,我们来看看Authentication flows!
Authentication flows(即页面的权限控制)
大多数应用程序都要求用户以某种方式进行身份验证,才能访问与用户相关的数据或其他私人内容。通常,流程如下所示:
- 用户打开应用程序。
- 应用程序从持久存储(例如,AsyncStorage)加载一些身份验证状态。
- 加载状态后,将向用户显示身份验证屏幕或主应用程序,具体取决于是否加载了有效的身份验证状态。
- 当用户注销时,我们清除身份验证状态并将其发送回身份验证页面。
注意:我们说“身份验证页面”是因为通常有不止一个。你可能有一个主页面,有一个用户名和密码字段,另一个是“忘记密码”,另一个设置为注册。
1 我们需要什么
我们希望验证流的行为是:当用户登录时,我们希望丢弃验证流的状态并卸载与身份验证相关的所有页面;当我们按下硬件后退按钮时,我们希望无法返回到身份验证流。
2 如何做
我们可以根据某些条件定义不同的页面。例如,如果用户已登录,我们可以定义主页、配置文件、设置等。如果用户未登录,则可以定义登录和注册页面。
例如:
isSignedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</>
) : (
<>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</>
)
当我们定义这样的页面时,当isSignedIn为true时,React Navigation将只看到Home、Profile和Settings页面,而当它为false时,React Navigation将看到SignIn和SignUp页面。这使得在用户未登录时无法导航到Home、Profile和Settings页面,也无法在用户登录时导航到登录和注册页面。
这种模式已经被其他路由库(如React Router)使用了很长一段时间,通常被称为“受保护路由”。在这里,我们需要用户登录的页面是“受保护的”,如果用户没有登录,则无法通过其他方式导航。
当isSignedIn变量的值发生变化时,这种魔力就会发生。比如说,最初的isSignedIn是false的。这意味着,将显示登录或注册页面。用户登录后,isSignedIn的值将变为true。React Navigation将看到登录和注册页面不再被定义,因此它将删除它们。然后它会自动显示主页面,因为这是isSignedIn为true时定义的第一个页面。
需要注意的是,使用这种设置时,不需要通过调用手动导航到主页面navigation.navigate('Home')。当isSignedIn变为true时,React Navigation将自动导航到主页面。
这利用了React导航中的一个新特性:能够根据属性或状态动态定义和更改导航器的页面定义。该示例显示了堆栈导航器,但您可以对任何导航器使用相同的方法。
通过基于变量有条件地定义不同的页面,我们可以以一种简单的方式实现验证流,这种方式不需要额外的逻辑来确保显示正确的页面。
3 定义我们的页面
在navigator中,我们可以有条件地定义适当的页面。对于我们的例子,假设我们有3个页面:
- SplashScreen-这将在我们恢复token时显示一个启动或加载页面。
- SignInScreen-这是当用户尚未登录时显示的页面(我们找不到token)。
- HomeScreen-如果用户已经登录,这是我们显示的页面。
因此我们的导航可以这样:
if (state.isLoading) {
// We haven't finished checking for the token yet
return <SplashScreen />;
}
return (
<Stack.Navigator>
{state.userToken == null ? (
// No token found, user isn't signed in
<Stack.Screen
name="SignIn"
component={SignInScreen}
options={{
title: 'Sign in',
// When logging out, a pop animation feels intuitive
// You can remove this if you want the default 'push' animation
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
}}
/>
) : (
// User is signed in
<Stack.Screen name="Home" component={HomeScreen} />
)}
</Stack.Navigator>
);
在上面的代码片段中,isload意味着我们仍在检查是否有token。这通常可以通过检查AsyncStorage中是否有token并验证token来完成。在我们得到token之后,如果它是有效的,我们需要设置userToken。我们还有另一个名为isSignout的状态,在注销时有一个不同的动画。
需要注意的是,我们根据这些状态变量有条件地定义页面:
- 只有在userToken为null(用户未登录)时才定义登录页面
- 只有在userToken为非null(用户已登录)时才定义主页面
这里,我们有条件地为每个案例定义一个页面。但也可以定义多个页面。例如,当用户未登录时,您可能还需要定义密码重置、注册等页面。同样,对于登录后可访问的页面,您可能有多个页面。我们可以利用React.Fragment定义多个屏幕:
state.userToken == null ? (
<>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
<Stack.Screen name="ResetPassword" component={ResetPassword} />
</>
) : (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</>
);
4 实现还原token的逻辑
注意:以下只是一个示例,说明如何在应用程序中实现身份验证逻辑。你不需要照原样去做。
从前面的片段可以看出,我们需要3个状态变量:
- isLoading-当我们试图检查是否已经在AsyncStorage中保存了token时,我们将其设置为true
- isSignout-我们在用户注销时将其设置为true,否则设置为false
- userToken-用户的token。如果非空,则假定用户已登录,否则没有登录。
因此我们需要:
- 添加一些恢复token、登录和注销的逻辑
- 向其他组件公开登录和注销的方法
在本指南中我们将使用React.useReducer以及React.useContext。但是如果您使用的是一个状态管理库,比如Redux或Mobx,那么您可以将它们用于此功能。事实上,在更大的应用程序中,全局状态管理库更适合存储身份验证token。您可以将相同的方法应用于您的状态管理库。
首先,我们需要为auth创建一个上下文,在这里我们可以公开必要的方法:
import * as React from 'react';
const AuthContext = React.createContext();
我们的组件可以这样:
import * as React from 'react';
import AsyncStorage from '@react-native-community/async-storage';
export default function App({ navigation }) {
const [state, dispatch] = React.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,
}
);
React.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) {
// Restoring token failed
}
// After restoring token, we may need to validate it in production apps
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away.
dispatch({ type: 'RESTORE_TOKEN', token: userToken });
};
bootstrapAsync();
}, []);
const authContext = React.useMemo(
() => ({
signIn: async data => {
// In a production app, we need to send some data (usually username, password) to server and get a token
// We will also need to handle errors if sign in failed
// After getting token, we need to persist the token using `AsyncStorage`
// In the example, we'll use a dummy token
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
signOut: () => dispatch({ type: 'SIGN_OUT' }),
signUp: async data => {
// In a production app, we need to send user data to server and get a token
// We will also need to handle errors if sign up failed
// After getting token, we need to persist the token using `AsyncStorage`
// In the example, we'll use a dummy token
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
}),
[]
);
return (
<AuthContext.Provider value={authContext}>
<Stack.Navigator>
{state.userToken == null ? (
<Stack.Screen name="SignIn" component={SignInScreen} />
) : (
<Stack.Screen name="Home" component={HomeScreen} />
)}
</Stack.Navigator>
</AuthContext.Provider>
);
}
5 填充其他组件
我们将不讨论如何实现身份验证页面的文本输入和按钮,这超出了导航范围。我们只需要填充一些占位符内容。
function SignInScreen() {
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');
const { signIn } = React.useContext(AuthContext);
return (
<View>
<TextInput
placeholder="Username"
value={username}
onChangeText={setUsername}
/>
<TextInput
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<Button title="Sign in" onPress={() => signIn({ username, password })} />
</View>
);
}
上一章节:RN路由-React Navigation--Drawer navigation