跨端應用--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因此儘可能不要去使用好久之前的第三方包。😂😂

相關文章
相關標籤/搜索