跨端应用--ReactNative开发

前言

    React Native使你只使用JavaScript也能编写原生移动应用AndroidiOS。 想必前端跨平台都据说过如今如今比较火热的FlutterReactNativeWeexFlutter它多是使用了dart语言也是跨端,而后是一种新的语法可能对前端来讲得从新学习一遍。而ReactNativeWeex它两自己就是经过前端ReactVue语法,只要会这两个这两个框架仍是很好上手的。ReactNative 它在设计原理上和React一致,经过声明式的组件机制来搭建丰富多彩的用户界面。稍微区别的就是style样式的写法稍微有点区别感受剩下的都是跟React一致。css

环境搭建

    由于是跨端项目须要安卓环境以及iOS的开发环境,目前我本身就只是搭建了安卓的环境。前端

    必须安装的依赖有:Node、JDK Android Studio。 这里有关Node以及JDK和安卓的的环境搭建就不一一介绍了,网上都是有的,官网也是有的。node

(这里注意的是node的版本以及jdk的版本还有就是安卓中SDK的版本)react

  • node >=12
  • jdk 1.8 (必须)
  • Android SDK Platform 29

官网ReactNative安卓环境搭建(官方文档): reactnative.cn/docs/enviro…ios

初始化项目

经过node安装带的npx初始化项目git

npx react-native init AwesomeProject
复制代码

这里的AwesomeProject就是项目的名称, 也能够经过react-native-cli初始化项目,可是这个必须是全局安装了react-native-cli,感受是仍是npx来的方便因此我也是选择这个。这个咱们默认初始化react-native的最高版版本,咱们也能够经过--version X.XX.X来指定项目react-native的版本。github

还有就是咱们能够经过--template来指定下载项目初始化的指定模板,好比官网有示列写的使用typescript的版本web

npx react-native init AwesomeTSProject --template react-native-template-typescript
复制代码

由于到接触到react-native开发APP项目本身也是开发好几个,可是每次新建项目都是经过复制以前的项目的依赖来作项目的初始化感受很麻烦因此本身也手动根据本身须要基础依赖来搭建了一个初始化的模板。typescript

npm包地址:www.npmjs.com/package/rea…npm

github地址:github.com/jetBn/react…

该模板主要是配置了一些eslint和一些项目基本依赖配置信息,好比使用Antreact-native的UI框架以及网络请求,上传文件等等。 具体依赖以及文件目录信息以下:

主要入口文件

项目主要入口是index.js可是咱们页面有不少页面,因此项目中使用React Navigation承接页面中tabbar以及子页面等等。

React Navigation(官方文档): reactnavigation.org/

建立TabBar主要是是用React Navigation@react-navigation/bottom-tabs这个包官方文档中都是有详细的建立说明,因此这里也不列举说明了,我相信这个文档仍是难不倒咱们的,虽然文档是英文的。

在此记录下自定义tabBar

import React, { useEffect, useState } from 'react'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs' // 引入建立底部Tab主包

const Tab = createBottomTabNavigator() // 获取组件

const MyTabBar=({ state, descriptors, navigation }) => {
  const focusedOptions = descriptors[state.routes[state.index].key].options

  if (focusedOptions.tabBarVisible === false) {
    return null
  }
  
  return (
   // 主要tabbar版块布局
    <View
      style={{
        // overflow: 'hidden',
        height: calc(50, 'height'),
        flexDirection: 'row',
        justifyContent: 'space-between',
        alignItems: 'center',
        borderTopColor: '#F4F4F4',
        backgroundColor: '#FFFFFF',
        borderTopWidth: 1,
        paddingBottom: isIPhoneX() ? 16 : 0, // 判断是否为isIPhoneX
        position: 'relative'
      }}>
      
      // 这里咱们获取我在Tab.Navigator中定义的Tab.Screen循环出来
      {state.routes.map((route, index) => {
      
        const { options } = descriptors[route.key]
        // console.log(options.tabBarIcon())
        
        //这里判断咱们是否有些tabBarLabel没有写就是用title再没有就是用route的name
        const label =
          options.tabBarLabel !== undefined
            ? options.tabBarLabel
            : options.title !== undefined
            ? options.title
            : route.name

        const isFocused = state.index === index //判断是不是是选中的tab

        //点击事件
        const onPress = () => {
          const event = navigation.emit({
            type: 'tabPress',
            target: route.key
          })

          if (!isFocused && !event.defaultPrevented) {
            navigation.navigate(route.name)
          }
        }
        
        //长按事件
        const onLongPress = () => {
          navigation.emit({
            type: 'tabLongPress',
            target: route.key
          })
        }
    
       // 返回每一项经过TouchableOpacity
        return (
          <TouchableOpacity
            accessibilityRole="button"
            accessibilityStates={isFocused ? ['selected'] : []}
            accessibilityLabel={options.tabBarAccessibilityLabel}
            testID={options.tabBarTestID}
            onPress={onPress}
            onLongPress={onLongPress}
            key={index}
            style={{
              // flex: ,
              width: calc(150),
              justifyContent: 'center',
              alignItems: 'center'
            }}>
            {options.tabBarIcon({ focused: isFocused })}  
            <Text
              style={{
                color: isFocused ? '#222222' : '#777777',
                fontSize: sT(11),
                textAlign: 'center',
                fontWeight: '500',
                marginTop: calc(6)
              }}>
              {label}
            </Text>
          </TouchableOpacity>
        )
      })}
      
      // 因为上面是咱们定义布局flex这里我定义咱们自定义的图标定位在中间
      <View
        style={{
          width: calc(83),
          height: calc(83),
          position: 'absolute',
          left: '50%',
          marginLeft: -calc(41),
          bottom: calc(5)
        }}>
        <TouchableOpacity
          activeOpacity={1}
          onPress={() => {
            getToken().then((res) => {
              if (res === '') {
                navigation.navigate('Login')
              } else {
                showImgPikcer()
              }
            })
          }}>
          <Image
            style={{
              width: '100%',
              height: '100%'
            }}
            source={require('../../assets/images/home_tab_scanning.png')}
          />
        </TouchableOpacity>
      </View>
    </View>
  )
}


const  Tabs = () => {
 return (
    <Tab.Navigator
      tabBar={(props) => <MyTabBar {...props} />} //将tabBar中主要的pops传入我自定义的方法
      tabBarOptions={{
        labelStyle: { bottom: 5 },
        keyboardHidesTabBar: true,
        allowFontScaling: false
      }}>
      <Tab.Screen
        name="Home"
        component={Home}
        options={{
          tabBarLabel: '首页',
          title: '首页',
          tabBarVisible: isShowTabbar,
          tabBarIcon: ({ focused }) => {
            if (focused) {
              return (
                <Image
                  source={require('../../assets/images/home_tab_file_s.png')}
                />
              )
            } else {
              return (
                <Image
                  source={require('../../assets/images/home_tab_file_n.png')}
                />
              )
            }
          }
        }}
      />
      <Tab.Screen
        name="Mine"
        component={Mine}
        options={{
          tabBarLabel: '个人',
          tabBarIcon: ({ focused }) => {
            if (focused) {
              return (
                <Image
                  source={require('../../assets/images/home_tab_user_s.png')}
                />
              )
            } else {
              return (
                <Image
                  source={require('../../assets/images/home_tab_user_n.png')}
                />
              )
            }
          }
        }}
      />
    </Tab.Navigator>
  )

}
export default Tabs


复制代码

在此咱们定义的tab.js文件只需在个人模板中router文件夹下的index.js中路由栈中引入使用就能够了,效果图以下图。

image.png

基本的页面建立方式是在index.js中引入@react-navigation/stack包这里我使用分出来文件的形式在main文件夹下写咱们全部的页面文件配置。

image.png

image.png

image.png

这里配置了webview的页面在screenprops配置中我能够配置页面的信息好比标题栏颜色,标题栏左侧,右侧显示等等,也能够自定义标题栏。

网络请求封装

在项目中我主要使用axios做为网络请求的封装,具体主要实现网络请求以及响应的拦截器。主要是实现了网络请求中参数使用qs封装变成form表单请求数据,以及响应结果中全局判断是否正常返回数据。

const service = axios.create({
  baseURL: BASE_URL, // api 的 base_url
  timeout: 60000, // 请求超时时间
  header: {
    'Content-Type':'application/x-www-form-urlencoded',
  }
})

// request拦截器
service.interceptors.request.use(
  async config => {
    if(config.method === 'post') {
      config.data = Qs.stringify(config.data)
    }
    
    config.headers["token"] = await getToken()

 // 获取网络的状态
    NetInfo.fetch().then(state => {
      if(!state.isConnected) {
        Toast.show('网络链接失败', {
          duration: Toast.durations.LONG,
          position: 0,
          shadow: true,
          animation: true,
          hideOnPress: true,
          delay: 1000
        })
      }
    });
    console.log(config.headers)
    return config
  },
  error => {
    console.log('error', error)
    Promise.reject(error)
  }
)

// response 拦截器
service.interceptors.response.use(
  response => {
    console.log(response.data.status)
    if(typeof response.data.status !=='undefined' && response.data.status !== 200000500){
      return response.data.data
    }
    if(typeof response.data ==='object' && typeof response.data.status === 'undefined') {
      return response.data
    }
    // console.log(response.status) 
    Toast.show(response.data.message)
    return Promise.reject(response.data)

  },
  error => {
    if (error.response.status == 401) {
   
    }
    return Promise.reject(error)
  }
)

export default service

复制代码

文件上传封装

由于我这边网络请求用的是axios当时文件上传一下就懵了,不知道该怎么弄,毕竟手机上跟网页上仍是有差异的。由于axios也是在网页上使用的。由于在网页上选择的文件都是使用blob上传的。而我在手机上选择的文件都是file开头的文件路径或者就直接返回文件路径。并且后端的api也是经过网页中上传文件的方式接收对应文件来进行上传。

当时就想这怎么转成跟前端网页的同样形式,最后仍是也是找到了一个fetch封装的包rn-fetch-blob,它支持文件上传下载。

github地址:github.com/joltup/rn-f…

我本身经过Promise的方式封装了图片文件上传等方法

image.png

黑暗模式适配

因为使用React Navigation因此我也是直接使用官方提供的react-native-appearance包中提供的hook方法(useColorScheme),而且使用此包中提供AppearanceProvider包裹总体的路由组件。

  1. 定义一个主题的config配置信息在config文件下,而后对应darklight模式下颜色配置,而后我须要经过useColorScheme中获取的值直接对应这个Object的字段key就能够了,配置文件以下

页面中使用

image.png 2. 手动设置与自动设置配置,若是当咱们设置了自动跟随系统还好,就直接经过这个hook咱们就能获取对应的颜色信息了,可是若是用户设置了手动那咱们又得从新判断。个人作法就是设置要给key存到缓存中,而后在index.js中也就是主的入口中经过hookuseEffect获取缓存中的key判断是否为自动仍是手动,若是是手动就获取对应的value经过Appearance.set方法设置咱们的主题颜色。

image.png

这里的Appearance是经过react-native-appearance导入的。

页面适配方法

因为是跨端的而后手机也是各式各样这个页面适配的js是必不可少的,否则在不一样的手机上就会产生页面bug显示不一。主要是经过基准的设计稿做为固定参数而后再经过api获取设备的屏幕宽高进行相除获取对应值而后再乘设置的值。

image.png

还有就是横屏的适配,其实就是判断是否为横屏而后再经过将基准的宽度设置为屏幕的高度,这样横过来也就能就进行页面的适配了。

总结

react-native开发中主要的仍是环境的搭建可能比较费时。在写法中跟react中基本都是同样的,除了样式的编写,native中主要用style的形式用Object的形式,写多了感受跟写css就剥离了,以前写多了react-native的样式,而后回去写css就会总会写成native中同样写Object的形式。

还有就是如今的react-native使用第三的包都不用手动的link了,如今自动会关联进去,因此使用仍是很方便的。虽然有时候使用的仍是有各类bug因此尽可能不要去使用好久之前的第三方包。😂😂

相关文章
相关标签/搜索