上一篇说到项目初始化配置,这一篇记录下Route和Store的用法
一、技术选型
根据官方文档的推荐,路由的库就直接选用React Navigation了;作为一个前端应用,那么基本的权限管控,登录和未登录页面区分开是最基本的,目前没有需要根据权限显示页面的需求,就暂不考虑,我习惯于将token等用户信息持久化存在cookie/localStorage中,在APP渲染时,将其取出放在store中,所以使用了Redux,作为状态容器,结合实现路由配置。
二、安装依赖
React Navigation的包里包含了许多模块,但有些东西我们暂时还用不到,只安装一些基础的东西即可;
yarn add @react-navigation/native
yarn add @react-navigation/native-stack
yarn add react-native-screens react-native-safe-area-context
然后是Redux:
yarn add @reduxjs/toolkit
三、Store配置
先来创建store的目录:
cd js
mkdir store
cd store
mkdir reducer
cd reducer
在reducer目录下创建我们的userReducer.ts:
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface UserState {
username: string;
token: string;
}
const initialState: UserState = {
username: '',
token: ''
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
login: (state, action: PayloadAction<UserState>) => {
console.log(action);
state.token = action.payload.token;
state.username = action.payload.username;
},
logout: (state) => {
state.token = '';
state.username = '';
}
}
});
export const { login, logout } = userSlice.actions;
export default userSlice.reducer;
这个reducer非常简单,包含username
和token
,login
、logout
两个action
,login
将payload中的信息赋给state,logout
则是简单的将state的重置,接下回到store目录,来创建store对象;
// store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './reducer/userReducer';
export const store = configureStore({
reducer: {
user: userReducer
}
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
再回到源代码目录(我的是js),创建hooks目录,来编写store简单的的hooks;
// hooks/useAppStore.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from '~/store';
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
最后,将store对象通过Provider挂载在根节点(App.tsx)下
import { Provider } from 'react-redux';
import { store } from '~/store';
const App = () => {
return (
<SafeAreaProvider>
<Provider store={store}>
<Text>Hello world!</Text>
</Provider>
</SafeAreaProvider>
);
};
export default App;
这样我们在其他的组件中就可以使用useAppSelector
来读取state、useAppDispatch
来提交变更了,如下:
// test.tsx
import { useAppDispatch, useAppSelector } from '~/hooks/useAppStore';
function Test () {
// 读取token
const isLogin = useAppSelector((state) => state.user.token);
// 设置用户信息
dispatch(login({ username: 'admin', token: 'xxxxx' }));
}
四、Navigation配置
基础路由
先来创建一个简单的navigation:
// route/index.tsx
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { View, Text } from 'react-native';
function HomeScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
</View>
);
}
const BaseStack = createNativeStackNavigator();
export function Navigation() {
return (
<NavigationContainer>
<BaseStack.Navigator>
<BaseStack.Screen name="Home" component={HomeScreen} options={{ title="首页" }} />
</BaseStack.Navigator>
</NavigationContainer>
);
}
Auth路由
上面的Screen就是对应的一个个路由,name为唯一的标识,component是对应的页面组件,options为路由的配置对象,下面我们来加上登录的校验:
// route/index.tsx
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { View, Text } from 'react-native';
import { useAppSelector, useAppDispatch } from '~/hooks/useAppStore';
function HomeScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
</View>
);
}
function LoginScreen() {
const dispatch = useAppDispatch();
return <Button
title="login"
onPress={() => dispatch(login({ username: 'admin', token: 'xxxxx' }))}
}
const BaseStack = createNativeStackNavigator();
export function Navigation() {
const isLogin = useAppSelector((state) => state.user.token);
return (
<NavigationContainer>
<BaseStack.Navigator>
{isLogin ?
<BaseStack.Screen name="Home" component={HomeScreen} options={{ title="首页" }} /> :
<BaseStack.Screen name="Login" component={LoginScreen} />
}
</BaseStack.Navigator>
</NavigationContainer>
);
}
上面是官方推荐的做法,在此基础上我们可以再用Group做一层包装:
function AuthGroup() {
return (
<BaseStack.Group>
<BaseStack.Screen name="Home" component={HomeScreen} options={{ title="首页" }} />
</BaseStack.Group>
)
}
function NoAuthGroup() {
return (
<BaseStack.Group>
<BaseStack.Screen name="Login" component={LoginScreen} />
</BaseStack.Group>
)
}
export function BaseStackNavigation() {
const isLogin = useAppSelector((state) => state.user.token);
return (
<NavigationContainer>
<BaseStack.Navigator>
{isLogin ? AuthGroup() : NoAuthGroup()}
</BaseStack.Navigator>
</NavigationContainer>
);
}
通用配置
基础的创建路由就完成了,我们还可以再Navigator上进行一些通用的配置,比如header标题的位置、切换的动画等等;
export function BaseStackNavigation() {
const isLogin = useAppSelector((state) => state.user.token);
return (
<NavigationContainer>
<BaseStack.Navigator
screenOptions={{
headerShadowVisible: false,
headerTitleAlign: 'center',
headerTitleStyle: head,
animation: 'slide_from_right'
}}
>
{isLogin ? AuthGroup() : NoAuthGroup()}
</BaseStack.Navigator>
</NavigationContainer>
);
}
screenOptions还支持function的参数,具体可见官方文档;
组件中的使用
下面讲讲如何在组件中使用navigation和route,进行页面跳转、传参、读参;
首先是获取navigation和route方式,一般有两种种常用的方式:
// 第一种,通过Screen Props
export default function ({navigation, route}) {
// ...
}
// 第二种,通过hooks
export default function () {
const navigation = useNavigation();
const route = useRoute();
}
推荐使用第二种,useNavigation
和useRoute
在Screen的子组件中也是可以调用的,使用起来比较方便;
然后是参数
export default function () {
// 跳转传参
const navigation = useNavigation();
navigation.navigate('Home', {
a: 1
});
// 读取参数
const route = useRoute();
const params = route.params;
}
最后补充一下关于结合TypeScript的使用;
TS的话首先需要定义Screen 的参数类型:
const BaseStack = createNativeStackNavigator();
export type BaseNavigationProps = {
Home: {
a: number;
};
Login: undefined;
};
然后在获取navigation和route的时候使用rn navigation提供的辅助类型定义对应的类型;
// Props的类型
type HomeScreenProps = NativeStackScreenProps<BaseNavigationProps, 'Home'>;
export default function ({navigation, route}: HomeScreenProps) {
}
// navigation的类型
type HomeScreenNavigation = NativeStackNavigationProp<BaseNavigationProps, RouteNames.HOME>;
export default function () {
const navigation = useNavigation<HomeScreenNavigation>();
}
// route的类型
type HomeRouteProps = RouteProp<BaseNavigationProps, 'Home'>;
export default function () {
const route = useRoute<ProjectDetailRouteProps>();
}