RN 從上手到「放棄」

RN 從上手到「放棄」

前言: react-native,相對於最近🔥的飛起的flutter,不算是一個新技術,2015年Facebook 開源,到如今已經4 5 個年頭,一直在維護當中,可是至今未發佈 v1 版本,目前已經更新到0.59。 該技術目標: 跨平臺實現原生應用。 GitHub start 數目: 77602(2019-5-29)。
文章內,圖片不少,佔據了必定的篇幅。班門弄斧之做,如有大神見到,敬請指教,有不對不合理之處,敬請指出!我是邇伶貳!html

正文

一、項目預覽

如今已完成的功能展現:
vue

入手demo項目,本打算模仿微信的功能作一遍。如今已經完成微信的一級界面。截圖以下:
首頁:
通訊錄:node

發現:react

我:
android

朋友圈(上拉加載和下拉刷新):
(未完成,就是調用了接口)ios

聊天界面:git

攝像頭拍照(安卓虛擬機):github


github地址: react-native-wxweb

nodejs後臺:nodejsApinpm

項目主要使用插件(庫):

  1. react-native-camera (調用攝像頭)
  2. react-native-vector-icons (圖標庫)
  3. react-navigation (路由導航)

參考資料:

二、項目運行

~前提: 環境搭建及相關軟件、安卓或者ios 的模擬器安裝, 參考官網便可,https://reactnative.cn/docs/getting-started.html

git clone https://github.com/adouwt/react-native-wx.git

cd react-native-wx

npm i

npm run and (安卓)

npm run ios (蘋果)

(上面的運行命令,我在package.json 作了封裝,一些處理編譯錯誤的命令,我也已經封裝進去)

執行命令後,會自動彈出nodejs 執行終端界面,這個是程序運行的一個監控

模擬器顯示:

三、分步實現

3.1 初始化並運行項目

```
react-native init AwesomeProject
react-native run-ios
```

3.2 項目結構說明

3.3 新建文件夾 app,接下來全部的源碼文件代碼將在這裏

目前新建 component組件、page頁面、及utils 工具三個,後面會根據須要建新的文件夾。

四個一級界面+ 聊天和朋友圈的界面

3.4 安裝插件作頁面導航跳轉

3.4.1 npm install react-navigation -S

3.4.2 修改項目文件下的App.js

這是根文件,咱們的頁面導航寫進這個組件,我項目中已經完成代碼片斷,這裏直接使用,代碼以下:

import React from 'react';
import HomeScreen  from './app/page/Home'
import DiscoverScreen from './app/page/Discover'
import UserListScreen from './app/page/UserList'
import MyScreen from './app/page/My'
import CameraComponent from './app/component/camera'
import ChatScreen from './app/page/Chat'
import FriendCircle from './app/page/friendCircle'
import Icon from "react-native-vector-icons/Ionicons";
import { View, Text } from 'react-native';
import { createAppContainer, createBottomTabNavigator, createStackNavigator, createDrawerNavigator } from 'react-navigation'; // Version can be specified in package.json


const HomeNav = createStackNavigator({
  Home: {
    screen: HomeScreen,
    navigationOptions:{
      headerTitle:'微信',
      headerBackTitle:null,
    }
  },
})
const UserListNav = createStackNavigator({
  UserList: {
    screen: UserListScreen,
  },
})

// 二級頁面寫進一級頁面中
const DiscoverNav = createStackNavigator(
  {
    Discover: {
      screen: DiscoverScreen,
    },
  }
)

const MyNav = createStackNavigator(
  {
    My: MyScreen,
  }
);

let BottomNav = createBottomTabNavigator(
  // createBottomTabNavigator 兩個參數,一個頁面路由,一個是路由配置
  {
    微信: HomeNav,
    通信錄: UserListNav,
    發現: DiscoverNav,
    我: MyNav,
  },
  {
    defaultNavigationOptions: ({ navigation }) => ({
      tabBarIcon: ({ focused, horizontal, tintColor }) => {
        const { routeName } = navigation.state;
        let iconName;
        let badgeCount = 3
        switch(routeName) {
          case '微信':
            iconName = 'ios-text';
            break;
          case '通信錄':
            iconName = 'md-person-add';
            break;
          case '發現':
            iconName = 'md-compass';
            break;
          case '我':
            iconName = 'ios-person';
            break;
        }
        iconColor = `${focused ? '#1AAD19' : '#4D4D4D'}`;

        return (
          <View>
            <Icon name={iconName} size={18} color={iconColor}></Icon>
            { routeName === '發現' && badgeCount > 0 && (
              <View style={{
                // If you're using react-native < 0.57 overflow outside of the parent
                // will not work on Android, see https://git.io/fhLJ8
                position: 'absolute',
                right: -6,
                top: -3,
                backgroundColor: 'red',
                borderRadius: 6,
                width: 12,
                height: 12,
                justifyContent: 'center',
                alignItems: 'center',
                color: '#fff'
              }}>
                <Text style={{ color: '#fff', fontSize: 10, fontWeight: 'bold' }}>{badgeCount}</Text>
              </View>
            )}
          </View>
        )
      },
    }),
    tabBarOptions: {
      activeTintColor: '#1AAD19',
      inactiveTintColor: '#4D4D4D',
    },
  }
);

let RootNav = createStackNavigator({
  BottomNav: {
    screen: BottomNav,
    navigationOptions: ({ navigation, screenProps }) => {
      return {
        header: null,
      };
    }
  },
  Camera: {
    screen: CameraComponent
  },
  Chat: {
    screen: ChatScreen
  },
  FriendCircle: {
    screen: FriendCircle
  }
})

export default createAppContainer(RootNav);

這裏須要參考導航資料:navigation

文檔講的很明白,看看示例就知道怎麼用了,我下面講兩個注意內容,這也是在這幾天的學習中遇到的troubles.

a、建立底部導航:

createBottomTabNavigator 方法,接受兩個參數,一個頁面路由,一個是路由配置,
直接看這個方法名字,就知道這個是建立底部導航的方法。
--第一個參數,頁面路由,這裏你寫多少tab, 底部就會呈現幾個tab 均勻分佈,(不要有槓精來襲,「要是有100個tab,怎麼顯示?」,哪有這樣的設計,你要是有100個tab,你試試這樣排版?)

參數的key,就是底部顯示的名稱,value 就是這個頁面 screen。頁面screen能夠單獨定義引入,以下:

能夠像第一個DiscoverNav,以screen定義的方式引入,也能夠簡略使用,以下面的MyNav

-- 第二個參數,路由配置,在這裏配置,底部導航的樣式、圖標、foucs 狀態及badge等
tabBarIcon 顧名思義,配置他的圖標,我這裏根據navigation.state 裏的routeName 來區分頁面路由,從而爲他們配置不一樣的 icon

b、二級頁面注入Stack Navigator

咱們寫的頁面要注入咱們的導航,這樣才能訪問到,咱們這裏採用的是react-navigation的 createStackNavigaor 的createStackNavigator方法,如圖:

3.4.3 具體頁面邏輯

這裏講兩個頁面,一個是靜態頁面,一個是調用接口的長列表的界面。

靜態頁面 discoverScreen

佈局方式: flex, 屬性和web 書寫不一致,語法參考這個不徹底手冊: https://shenbao.github.io/ishehui/html/RN%20%E5%9F%BA%E7%A1%80/React%20Native%20%E6%A0%B7%E5%BC%8F%E8%A1%A8%E6%8C%87%E5%8D%97.html

點擊按鈕封裝: RN 裏面的點擊方法只能綁定在它的button 組件上,提供的其餘組件咱們麼辦法直接綁定事件,它提供了一個封裝子組件能夠綁定事件的自定義按鈕-Touchable 系列 (TouchableOpacity ,TouchableNativeFeedback)以下書寫能夠點擊的item:

注意: 上面劃線的位置,這個樣式(flex: 1, flexDirection: 'row',)要寫上。有必定的兼容問題,若是沒有這個樣式,在安卓上沒法點擊,ios上沒有影響。說明在實際開發中,咱們還要處理必定的平臺差別問題,真正實現無差別的跨平臺仍是有些困難。

頁面header:

在static 裏面沒辦法直接調用組件的方法,須要藉助 navigation 來作一下中轉,調用setParams將方法放進navigation裏面,這樣在static裏面就可使用navigation.getParams 獲取這個方法了,以下:

過渡動畫

這個方法實現的是一個 動畫,咱們在 寫web 的時候,會用 transform transaction 這樣的動畫屬性,RN裏面也支持這樣的動畫,具體語法有所差別。這裏咱們用一個絕對定位裏面的 right值 作過渡效果。
開始定義:

在點擊時候,修改這個 this.state.animateRightValue 的值,實現動畫效果,

Animated有幾個動畫(),這裏採用了timing,他接受兩個參數,一個是監聽的動畫值,另外一個是這個值的配置,配置動畫方式,動畫時間等。

這個頁面也沒有複雜的頁面邏輯,基本一看就知道怎麼回事,一些語法 api 不會的話,能夠上官網lou 一眼:

調接口的頁面 friendCircle

這個頁面調用了一個分頁接口,上拉加載更多,長列表的組件用的是RN 原生的 FlatList 組件,這個具體使用能夠參考api 文檔看看,

可是就我的使用以後的感受而言,這個真正要用到生產,還得要稍微改造一下,好比loading菊花圖片要改一改。
在生命週期函數componentDidMount 裏面,調用咱們的接口。說道這裏,咱們引出了接口封裝問題,用的是自帶的fetch,這個fetch 底層具體咱們就不考慮怎麼實現的,如今咱們須要對fetch 封裝一下,方便後面在多處使用,fetch 封裝以下:

let base_url = 'https://api.scampus.cn';  //服務器基本地址
// let base_url = 'http://18.10.1.115:4000';  //服務器基本地址
let token = '';   
/**
 * @param {string} url 接口地址
 * @param {string} method 請求方法:GET、POST,只能大寫
 * @param {JSON} [params=''] body的請求參數,默認爲空
 * @return 返回Promise
 */
const  fetchRequest = (url, method, params = '') => {
    let header = {
        "Content-Type": "application/json;charset=UTF-8",
        "accesstoken":token  //用戶登錄後返回的token,某些涉及用戶數據的接口須要在header中加上token
    };
    if(params == ''){   //若是網絡請求中帶有參數
        return new Promise(function (resolve, reject) {
            fetch(base_url + url, {
                method: method,
                headers: header
            }).then((response) => response.json())
                .then((responseData) => {
                    resolve(responseData);
                })
                .catch( (err) => {
                    reject(err);
                });
        });
    } else{   //若是網絡請求中沒有參數
        return new Promise(function (resolve, reject) {
            fetch(base_url + url, {
                method: method,
                headers: header,
                body:JSON.stringify(params)   //body參數,一般須要轉換成字符串後服務器才能解析
            }).then((response) => response.json())
                .then((responseData) => {
                    resolve(responseData);
                })
                .catch( (err) => {
                    reject(err);
                });
        });
    }
}

export default fetchRequest

使用Promise 處理異步問題,將咱們最後的須要的數據通通resolve 出去。封裝中規中距,基本是按照文檔說明 fetch 的用法,稍加修改

四、使用第三方的圖標

npm install -S react-native-vector-icons
圖標地址: https://oblador.github.io/react-native-vector-icons/ 注意這站點不是圖標所有可用,滾動條快速找到中間位置,就能看到咱們須要的圖標。
使用: name 值能夠在上面的地址中尋找,哪一個合適就用哪一個,
就我的看來,這個圖標庫基本夠開發使用,若是不夠能夠繼續引用字體圖標庫。

五、調用手機硬件設備-攝像頭

具體演示實例,拍照功能,用的第三方庫,react-native-camera
安裝: npm install -S react-native-camera
使用:import { RNCamera } from 'react-native-camera';

<View style={styles.container}>
    <RNCamera
        ref={ref => {
            this.camera = ref;
        }}
        style={styles.preview}
        type={ this.state.cameraType}
        flashMode={RNCamera.Constants.FlashMode.on}
        autoFocus={RNCamera.Constants.AutoFocus.on}
        androidCameraPermissionOptions={{
            title: 'Permission to use camera',
            message: 'We need your permission to use your camera',
            buttonPositive: 'Ok',
            buttonNegative: 'Cancel',
        }}
        androidRecordAudioPermissionOptions={{
            title: 'Permission to use audio recording',
            message: 'We need your permission to use your audio',
            buttonPositive: 'Ok',
            buttonNegative: 'Cancel',
        }}
        onGoogleVisionBarcodesDetected={({ barcodes }) => {
            console.log(barcodes);
        }}
    >
        {({ camera, status, recordAudioPermissionStatus }) => {
            if (status !== 'READY') return <PendingView />;
            return (
            <View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-around',marginBottom: 20 }}>
                <TouchableOpacity onPress={() => this.takePicture(camera)} style={styles.capture}>
                    <Text style={{ fontSize: 14 }}> 拍照 </Text>
                </TouchableOpacity>
                <TouchableOpacity onPress={this.swtichCamera} style={styles.capture}>
                    <Icon name="ios-reverse-camera" size={18} color="#333"></Icon>
                </TouchableOpacity>

                <TouchableOpacity onPress={this.lookAlbum} style={styles.imgPreview}>
                        <Image
                            style={styles.imgPreview}
                            source={{uri: this.state.currentUri || 'https://yyb.gtimg.com/aiplat/page/product/visionimgidy/img/demo6-16a47e5d31.jpg?', isStatic: true}}
                        />
                    </TouchableOpacity>
                    
                </View>
                );
            }}
        </RNCamera>
    </View>

在組件裏面能夠定義照相機界面的ui,能夠自定義拍照按鈕,切換攝像頭的按鈕,拍照圖片預覽等,調用api 不難,問題難點在配置調用的文件,你得有權限調用原生的設備。
一、修改android/gradle/wrapper/gradle-wrapper.properties

distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip

二、修改android/app/build.gradle

missingDimensionStrategy 'react-native-camera', 'general'

在實際安裝使用的時候會有相關的提示報錯,按照報錯信息去尋找解決辦法,這裏仍是得推薦github,上面有不少相似的問題,能夠耐心找找。

六、結言

從一開始瞭解RN 到最後上手demo,到如今陸續修改項目,差很少十天時間,本人的技術棧是vue,react 並無生產項目,看看文檔,基本能夠上手。總結而言,使用一些基本的功能,並不難,文檔很全,使用的羣體很大,因此遇到的問題也能夠在相關社區找到合適的解決方法或者替換方案。尚未具體開發生產項目,可是我以爲我將要面臨的問題,應該在體驗優化上,好比過渡動畫,上拉下拉刷新加載,切換視圖;集成第三方庫,調用硬件設備;性能優化問題等。

七、TODO

後面有時間,繼續把這個項目作下去,

  • 登陸註冊
  • 聊天,後面集成聊天機器人
  • 通信錄的人員分組,如今由於是後臺接口尚未完成,只是本地造了一個數據
  • 掃碼功能
  • 發動態
  • 集成地圖
  • 拍照後,圖像識別

若是有興趣的同窗歡迎加入一塊兒完成。

github地址: react-native-wx

nodejs後臺:nodejsApi

班門弄斧之做,如有RN 老手見到,請勿見笑,有不對不合理之處,敬請指教!我是邇伶貳!

-一、相關錯誤處理

  • react-native-camera 插件的使用問題:

解決: [解決](https://github.com/react-native-community/react-native-camera/issues/2150)
  • 編譯問題

解決: cd android   &&  ./gradlew clean
  • Unable to resolve module 'scheduler/tracing' in ReactNative

    解決: yarn add @babel/runtime@7.0.0 再從新跑 react-native run-android


開發中還遇到了其餘問題,可是忘了作記錄 ~~ RN 暫時放一段落,接下來要使用 flutter,打算兩週後 出一個flutter版本。

相關文章
相關標籤/搜索