使用新版本react-native 0.71快速开发APP,初步实现了登录、首页、设置和一些demo模块,并自定义了一些通用组件。其中包含路由组件,文本框组件,单元格组件,按钮组件,表格组件和弹窗选择组件简单案列,这些组件都是最简单的实现,是给新手朋友参考学习的。后期可以根据自己需求拓展。
本项目使用了比较新版本的rn框架(0.71),并且使用了ts框架开发。状态管理库没有使用react reducer,使用了Zustand: 一个轻量、现代的状态管理库(类似vue中的Pinia)。
创建react-native项目并安装我们需要的依赖库
1.npx react-native init birckdogApp --version 0.71.0
2.npm install zustand
3.npm install @react-navigation/native
4.npm install @react-navigation/native-stack
5.npm install @react-navigation/bottom-tabs
6.npm install react-native-screens react-native-safe-area-context
7.npm install @react-native-async-storage/async-storage
8.npm install react-native-vector-icons --save
9.npm i --save-dev @types/react-native-vector-icons
apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle");
使用type定义UserInfo的类型和UserStoreInfo类型,使用zustand的create创建一个useUserInfo 状态管理对象。
useUserInfo 状态管理中user对象用于存储用户数据。
useUserInfo 状态管理中setUserInfos方法给user对象赋值,(user: UserInfo | null) => Promise
useUserInfo 状态管理中userInitialize方法是把存储在AsyncStorage数据加载到user对象中,因为操作AsyncStorage是异步操作,所以要在方法前面加async。我们这个方法我们会在App主程序启动的时候去加载用户信息,因为zustand在app关闭之后所有的状态管理对象都会丢失。
import { create } from 'zustand';
import AsyncStorage from '@react-native-async-storage/async-storage';
type UserInfo = {
id: string;
account: string;
userName: string;
token: string;
};
type UserStoreInfo = {
user: UserInfo | null;
setUserInfos: (user: UserInfo | null) => Promise;
userInitialize: () => Promise;
};
const useUserInfo = create((set) => ({
user: null,
setUserInfos: (user) => {
return new Promise(async resolve => {
try {
// 将用户信息保存到本地存储
await AsyncStorage.setItem('user', JSON.stringify(user));
set({ user });
} catch (error) {
console.error('Failed to set user in AsyncStorage:', error);
}
resolve();
});
},
userInitialize: async () => {
return new Promise(async resolve => {
try {
// 从本地存储中获取用户信息
const user = await AsyncStorage.getItem('user') as string;
set({ user: JSON.parse(user) });
} catch (error) {
console.error('Failed to get user from AsyncStorage:', error);
}
resolve();
});
},
}));
export default useUserInfo;
登录页面我们使用了自己定义的BdButton组件,使用自己封装的组件可以更好的统一UI风格,简化代码。后面我们会讲解如何自定组件。
import React, { useState } from 'react';
import { View, TextInput, StyleSheet } from 'react-native';
import useUserInfo from '../../../src/stores/userInfo';
import BdButton from '../../../src/components/bd-Button';
const LoginScreen = () => {
const [account, setAccount] = useState('');
const [password, setPassword] = useState('');
const { setUserInfos } = useUserInfo();
const handleLoginUser = () => {
const newUser = {
id: Date.now().toString(),
account,
userName:'测试',
token: Date.now().toString(),
};
setUserInfos(newUser);
setAccount('');
setPassword('');
};
return (
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F7F8FA',
},
loginContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginBottom:200,
},
input: {
width: '90%',
height: 40,
borderWidth: 1,
borderColor: '#646566',
marginBottom: 10,
paddingHorizontal: 10,
},
button: {
width: '90%',
},
});
export default LoginScreen;
App.tsx使我们程序的主入口,这边使用useEffect钩子在app启动的时候初始化用户数据。
StatusBar 是控制应用状态栏的组件,这边设置手机最上面一条状态栏背景是浅灰色,状态栏中的字体样式深色模式。
Navigator 是我们的页面路由。
import React, { useState, useEffect } from 'react';
import { StatusBar } from 'react-native';
import useUserInfo from '../src/stores/userInfo';
import LoginScreen from '../src/views/login/index';
import Navigator from '../src/components/navigator';
import { BdStyleConfig } from '../src/theme/bd-styles';
const App = () => {
const { user, userInitialize } = useUserInfo();
const [isLoad, setIsLoad] = useState(true);
useEffect(() => {
userInitialize().then(()=>{
setIsLoad(false);
});
},[userInitialize]);
return (
<>
{ isLoad ? '' : (user?.token ? : ) }
>
);
};
export default App;
使用@react-navigation/bottom-tabs构建主程序底部tab页签(首页和设置)
使用@react-navigation/native-stack构建每个tab页面内部路由(首页中有物料上架、仓库收料等子路由页面)
import React from 'react';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import MyHomeScreen from '../../src/views/myHome/index';
import SettingScreen from '../../src/views/setting/index';
import MaterialListingScreen from '../../src/views/business//materialListing/index';
import WarehouseReceiptScreen from '../../src/views/business//warehouseReceipt/index';
import MaterialSearchScreen from '../../src/views/business//warehouseReceipt/materialSearch';
import PrintSettingScreen from '../../src/views/business//printSetting/index';
import { BdStyles, BdStyleConfig } from '../../src/theme/bd-styles';
const Tab = createBottomTabNavigator();
function HomeStack() {
return (
({
tabBarIcon: ({ focused, color, size }) => {
let iconName = '';
if (route.name === 'Home') {
iconName = focused ? 'home' : 'home-outline';
} else if (route.name === 'Setting') {
iconName = focused ? 'settings' : 'settings-outline';
};
return ;
},
tabBarActiveTintColor: BdStyleConfig.color_primary,
tabBarInactiveTintColor: 'gray',
})}
>
);
}
const Stack = createNativeStackNavigator();
function MyHomeStack() {
return (
);
}
export default function Navigator() {
return (
);
}
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { NavigationProp, ParamListBase } from '@react-navigation/native';
import { BdStyles, BdStyleConfig } from '../../../src/theme/bd-styles';
const MyHomeScreen = ({ navigation }: { navigation: NavigationProp }) => {
const handleMenuPress = (menu:string) => {
navigation.navigate(menu);
};
return (
handleMenuPress('WarehouseReceipt')}
>
仓库收料
handleMenuPress('MaterialListing')}
>
物料上架
工单拣料
/**其他菜单**/
);
};
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
},
menuItem: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingBottom:15,
paddingTop:15,
backgroundColor:BdStyleConfig.color_white,
},
menuBorder: {
borderColor: '#ebedf0',
borderWidth: 0.5,
},
menuTitle: {
color: '#646566',
marginTop: 5,
},
});
export default MyHomeScreen;
这个里我们使用了自定义单元格组件BdCell
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Alert } from 'react-native';
import useUserInfo from '../../../src/stores/userInfo';
import Ionicons from 'react-native-vector-icons/Ionicons';
import BdCell from '../../components/bd-cell';
const SettingScreen = () => {
const { user, setUserInfos } = useUserInfo();
const handleLoginOut = () => {
Alert.alert(
"消息",
"是否确定退出登录!",
[
{
text: "取消",
onPress: () => console.log("Cancel Pressed"),
style: "cancel"
},
{ text: "确定", onPress: () => setUserInfos(null) }
]
);
};
return (
{ user?.userName }
欢迎您!
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor:'#F7F8FA',
},
userInfoContainer: {
flexDirection: 'row',
backgroundColor:'#fff',
padding:10,
},
listContainer: {
paddingTop:10,
},
cellStyle:{
borderBottomColor:'#F7F8FA',
borderBottomWidth:1,
}
});
export default SettingScreen;
BdButton自定义按钮组件我们首先用定义了ButtonProps类型为组件包含多少属性,style属性让我们可以改写组件样式,style?中的?是可选连操作符,定义在这边表示这个参数可以为空不传。
import React from 'react';
import { TouchableOpacity, Text, StyleSheet, ViewStyle } from 'react-native';
import { BdStyles, BdStyleConfig } from '../../src/theme/bd-styles';
interface ButtonProps {
style?: ViewStyle;
onPress?: () => void;
text: string;
}
const BdButton: React.FC = ({ style, onPress, text }) => {
return (
{text}
);
};
const styles = StyleSheet.create({
button: {
backgroundColor:BdStyleConfig.color_primary,
padding: 10,
borderRadius: 5,
alignItems: 'center',
justifyContent: 'center',
},
text: {
color: 'white',
fontWeight: 'bold',
},
});
export default BdButton;
表格组件的简单使用,我们只要给组件传data和columns2个属性就可以生成我们的表格。
const tableData = [
{ id: '1001', count: 5, total: 15, remainCount: 3 },
{ id: '1002', count: 7, total: 17, remainCount: 2 },
{ id: '1003', count: 3, total: 12, remainCount: 1 },
// 其他数据...
]
const tableColumns:BdTablePropsColumn[] = [
{ field: 'id', title: '料号', align: 'center', },
{ field: 'count', title: '件数', align: 'center', },
{ field: 'total', title: '总数量', align: 'center', },
{ field: 'remainCount', title: '剩余数量', align: 'center', },
]
const MaterialSearchScreen = () => {
return (
);
};
表格组件代码
import React from 'react';
import { View, Text, VirtualizedList, StyleSheet, TouchableOpacity } from 'react-native';
export interface BdTableProps {
data: any[];
columns: BdTablePropsColumn[];
onClickRow?: ((item:any) => void) | undefined;
hideHeader?: boolean;
}
export interface BdTablePropsColumn {
field: string,
title: string,
align: string,
}
const BdTable: React.FC = ({ columns, data, onClickRow, hideHeader }) => {
const renderItem = (item:any) => (
{ onClickRow?onClickRow(item.item):'' }}>
{columns.map((column) => (
{item.item[column.field]}
))}
);
const tableHeader = () => (
{columns.map((column) => (
{column.title}
))}
);
const keyExtractor = (item:any) => item.id?item.id.toString():new Date().getTime();
const getItemCount = () => data.length;
const getItem = (data: [], index: number) => data[index];
return (
);
};
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
justifyContent: 'space-between',
backgroundColor:'#fff',
},
cell: {
flex: 1,
textAlign: 'center',
borderColor: '#e4eaec',
borderWidth:1,
padding:4,
},
headerCell: {
flex: 1,
textAlign: 'center',
borderColor: '#e4eaec',
borderWidth:1,
fontWeight:'bold',
padding:4,
},
});
export default BdTable;