使用新版本react-native 0.71快速开发APP项目详细介绍(ts+zustand+react-navigation+react-native-vector-icons)

1.项目介绍

使用新版本react-native 0.71快速开发APP,初步实现了登录、首页、设置和一些demo模块,并自定义了一些通用组件。其中包含路由组件,文本框组件,单元格组件,按钮组件,表格组件和弹窗选择组件简单案列,这些组件都是最简单的实现,是给新手朋友参考学习的。后期可以根据自己需求拓展。

本项目使用了比较新版本的rn框架(0.71),并且使用了ts框架开发。状态管理库没有使用react reducer,使用了Zustand: 一个轻量、现代的状态管理库(类似vue中的Pinia)。

 

2. 创建项目

 创建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

2.1 我需要对每个项目依赖的maven改成阿里云的仓库

 2.2 react-native-vector-icons库需要在主项目的build.gradle文件中添加引用

apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle");

3.登录页面

 3.1使用zustand构建一个简单UserInfo状态管理对象

使用type定义UserInfo的类型和UserStoreInfo类型,使用zustand的create创建一个useUserInfo 状态管理对象。

useUserInfo 状态管理中user对象用于存储用户数据。

useUserInfo 状态管理中setUserInfos方法给user对象赋值,(user: UserInfo | null) => Promise<void>表示该方法参数为user,参数类型是UserInfo并且参数可以为空,方法返回类型是void的类型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<void>;
  userInitialize: () => Promise<void>;
};

const useUserInfo = create<UserStoreInfo>((set) => ({
  user: null,
  setUserInfos: (user) => {
    return new  Promise<void>(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<void>(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;

3.2创建登录页面

登录页面我们使用了自己定义的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 (
    <View style={styles.container}>
      <View style={styles.loginContainer}>
        <TextInput
          style={styles.input}
          placeholder="账号"
          value={account}
          onChangeText={setAccount}
        />
        <TextInput
          style={styles.input}
          placeholder="密码"
          value={password}
          onChangeText={setPassword}
        />
        <BdButton
          style={styles.button}
          text="登录"
          onPress={handleLoginUser}
        />
      </View>
    </View>
  );
};
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;

3.3程序入口判断登录逻辑

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 (
    <>
     <StatusBar backgroundColor={BdStyleConfig.color_lightGrey} barStyle='dark-content' />
    { isLoad ? '' : (user?.token ? <Navigator /> : <LoginScreen />) }
    </>
  );
};
export default App;

4.构建路由

使用@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 (
    <NavigationContainer>
      <Tab.Navigator
        screenOptions = {({ route }) => ({
          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 <Ionicons name={iconName} size={size} color={color} />;
          },

          tabBarActiveTintColor: BdStyleConfig.color_primary,
          tabBarInactiveTintColor: 'gray',
        })}
      >
        <Tab.Screen name="Home"  options={{ headerShown:false, title:'首页' }} component={MyHomeStack} />
        <Tab.Screen name="Setting" options={{ headerShown:false, title:'设置' }} component={SettingScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

const Stack = createNativeStackNavigator();

function MyHomeStack() {
  return (
   <Stack.Navigator screenOptions={{
      headerStyle: {
        backgroundColor: BdStyleConfig.color_lightGrey,
      },
      headerTintColor: '#000',
      headerTitleStyle: {
       color:'#000',
       fontSize:18,
      },
      headerTitleAlign: 'center', // 将标题居中
      animation: 'slide_from_right', // 通过animation属性设置动画效果
  }}>
    <Stack.Screen name="MyHome" options={{ title:'首页' }}  component={MyHomeScreen} />
    <Stack.Screen name="MaterialListing" options={{ title:'物料上架' }}  component={MaterialListingScreen} />
    <Stack.Screen name="WarehouseReceipt" options={{ title:'仓库收料' }} component={WarehouseReceiptScreen} />
    <Stack.Screen name="MaterialSearch" options={{ title:'收料查询' }} component={MaterialSearchScreen} />
    <Stack.Screen name="PrintSetting" options={{ title:'打印设置' }} component={PrintSettingScreen} />
  </Stack.Navigator>
  );
}

export default function Navigator() {
    return (
        <HomeStack />
    );
}

5.创建首页

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<ParamListBase> }) => {
  const handleMenuPress = (menu:string) => {
    navigation.navigate(menu);
  };
  return (
    <View style={BdStyles.container}>
      <View style={styles.row}>
        <TouchableOpacity
          style={[styles.menuItem, styles.menuBorder]}
          onPress={() => handleMenuPress('WarehouseReceipt')}
        >
          <Ionicons name="archive-outline" size={30} color="#646566" />
          <Text style={styles.menuTitle}>仓库收料</Text>
        </TouchableOpacity>
        <TouchableOpacity
          style={[styles.menuItem, styles.menuBorder]}
          onPress={() => handleMenuPress('MaterialListing')}
        >
          <Ionicons name="arrow-up-circle-outline" size={30} color="#646566" />
          <Text style={styles.menuTitle}>物料上架</Text>
        </TouchableOpacity>
        <TouchableOpacity
          style={[styles.menuItem, styles.menuBorder]}
        >
          <Ionicons name="library-outline" size={30} color="#646566" />
          <Text style={styles.menuTitle}>工单拣料</Text>
        </TouchableOpacity>
      </View>
      /**其他菜单**/
  </View>
  );
};

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;

6.创建设置

这个里我们使用了自定义单元格组件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 (
    <View style={styles.container}>
      <TouchableOpacity style={styles.userInfoContainer}>
          <Ionicons name="person-circle" size={80} color="#c1c1c1" />
          <View style={{ alignItems: 'center', justifyContent: 'center',}}>
            <Text style={{ fontSize:25, color:'#000', fontWeight: 'bold', marginBottom:5 }}>{ user?.userName }</Text>
            <Text style={{ fontSize:13, color:'#c1c1c1',paddingLeft:7 }}>欢迎您!</Text>
          </View>
      </TouchableOpacity>
      <View style={styles.listContainer}>
        <BdCell title="系统设置" style={styles.cellStyle} />
        <BdCell title="退出" onPress={handleLoginOut} />
      </View>
    </View>
  );
};

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;

7.自定义组件开发

7.1按钮组件BdButton

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<ButtonProps> = ({ style, onPress, text }) => {
  return (
    <TouchableOpacity style={[styles.button, style]} onPress={onPress}>
      <Text style={styles.text}>{text}</Text>
    </TouchableOpacity>
  );
};

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;

7.2表格组件BdTable

表格组件的简单使用,我们只要给组件传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 (
    <View style={BdStyles.container}>
      <BdTable
        data={tableData}
        columns={tableColumns}
      />
    </View>
  );
};

表格组件代码

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<BdTableProps> = ({ columns, data, onClickRow, hideHeader }) => {
  const renderItem = (item:any) => (
    <TouchableOpacity key={new Date().getTime()} style={styles.row} onPress={() =>{ onClickRow?onClickRow(item.item):'' }}>
      {columns.map((column) => (
        <Text style={styles.cell}>{item.item[column.field]}</Text>
      ))}
    </TouchableOpacity>
  );

  const tableHeader = () => (
    <View style={styles.row}>
      {columns.map((column) => (
         <Text key={column.field}  style={styles.headerCell}>{column.title}</Text>
      ))}
    </View>
  );

  const keyExtractor = (item:any) => item.id?item.id.toString():new Date().getTime();
  const getItemCount = () => data.length;
  const getItem = (data: [], index: number) => data[index];
  return (
    <VirtualizedList
      data={data}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      ListHeaderComponent={ !hideHeader?tableHeader:undefined }
      getItemCount={getItemCount}
      getItem={getItem}
    />
  );
};
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;

8.项目结构

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖狗-小强

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值