初試React-Native小記

環境搭建

本次開發環境

node: v8.9.4
npm: 5.6.0
react: 16.0.0
react-native: 0.51.0
代碼編輯器:webstorm
模擬器:ios => siMulator or 真機 / android => Android Studio自帶模擬器 or 真機

  1. 安裝Chocolatey,並使用Chocolatey安裝python2和node.js

cmd下輸入如下命令安裝Chocolatey(或需翻牆)

@powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin

安裝python2

choco install python2

安裝node.js

choco install nodejs.install

設置npm鏡像

npm config set registry https://registry.npm.taobao.org --global
npm config set disturl https://npm.taobao.org/dist --global
  1. Yarn、React Native的命令行工具

安裝react-native腳手架

npm install -g yarn react-native-cli

配置yarn鏡像

yarn config set registry https://registry.npm.taobao.org --global
yarn config set disturl https://npm.taobao.org/dist --global
  1. 安裝Android SDK 並 配置ANDROID_HOME
  2. 創建並初始化項目,運行
react-native init projectDemo
cd projectDemo
react-native run-ios  // ios啓動
react-native run-android  // android啓動

最終生成項目目錄這裏寫圖片描述

android: 安卓開發源碼
ios: ios開發源碼
node_modules: 項目依賴
.babelrc: babel的配置文件,將各種js版本的語言轉換成運行環境所能識別的
App.js: react-native入口模板文件,供index.js使用,非固定
index.js: 項目入口文件
package.json 依賴配置文件

根據項目需求小做修改
這裏寫圖片描述

可以看到我們爲前端專門創建了一個項目文件src,並將 react-native入口模板文件遷移到項目文件夾裏。根據項目需求分包

assets: 資源目錄
imgs: 圖片資源
styles: 樣式資源
|—common.js: 公用樣式資源
|—color.js: 定義項目顏色
|—layout.js: 定於項目佈局
|—size.js: 定義項目尺寸
|—index.js: 整合以上資源
|—entry.js 對應頁面路由樣式資源
utils: 工具資源
|—api.js: 集成了整個項目的請求
|—http.js: 封裝了fetch請求
|—screen.js: 有關屏幕的各種屬性資源
|—system.js: 有關係統的各種資源
|—utils.js: 其他工具封裝
components: 全局組件封裝
pages: 頁面
router: 路由
store: 全局狀態管理
App.js: 入口模板文件

PS:編譯錯誤一般會提示在node.js中,運行錯誤手機或模擬器彈出紅色錯誤頁面。修改js代碼無需重裝,點擊紅色錯誤頁面的【RELOAD】即可或搖動手機彈出開發菜單【RELOAD】

React-Native中文網 環境搭建文檔

組件 控件

RN中每個頁面都是由【Component 】組成,也可用【Component 】來做組合控件
基本組件代碼

//引入React Component
import React, {Component} from 'react'
//引入控件
import {
   View,
   Text,
} from 'react-native'
//變量的定義只能放Component不可放Component裏面,否則調用報錯。函數可以放Component裏面
let val = 1;

//export default 纔可以把這個組件暴露出去,否則其他文件調用會報錯(提示找不到Demo組件或者找到的Demo組件不是Component)
export default class Demo extends Component {
//構造函數,props作爲父子控件直接傳遞數據,後面講
   constructor(props) {
      super(props)
   }
//渲染 最終的UI效果在此
   render() {
      return (
         <View>
            <Text style={{marginTop: 20, fontSize: 16}}>react-native通用基礎模板</Text>
            <View>
               {/*<Text>{listStore.num}</Text>*/}
            </View>
         </View>
      )
   }
}

常用控件
Text Button Image TextInput ScrollView ListView ,具體控件和屬性、方法參考 React Native 中文網文檔

佈局 與 導航

佈局 在控件的style中直接設置,共有5種屬性,可搭配使用(正常是Flex Direction確認主軸後搭配其他)

<View style={{flex: 1, flexDirection: 'row'}}>

Flex Direction:確定主軸即確定佈局方向,可以有從左往右【row】、從上往下【column】、從右往左【row-reverse】、從下往上【column-reverse】
這裏寫圖片描述

Justify Content:沿着主軸的排列方式,可以有:flex-start、center、flex-end、space-around以及space-between
這裏寫圖片描述

Align Items:決定其子元素沿着次軸(與主軸垂直的軸,比如若主軸方向爲row,則次軸方向爲column)的排列方式,可以有flex-start、center、flex-end以及stretch
這裏寫圖片描述

Flex wrap:根據子元素內容大小自適應佈局。可以有nowrap、warp、wrap-reverse
這裏寫圖片描述

Align Content:根據子元素內容大小自適應佈局。可以有stretch、flexstart、center、flex-end、space-between、space-around
這裏寫圖片描述

導航 :路由導航器【StackNavigator】 Tab選項卡【TabNavigator】側滑抽屜【DrawerNavigator】

【StackNavigator】 組件採用堆棧式的頁面導航來實現各個界面跳轉。它的構造函數:

StackNavigator(RouteConfigs, StackNavigatorConfig)

RouteConfigs 參數表示各個頁面路由配置
StackNavigatorConfig 參數表示導航器的配置

const RouteConfigs = {
    Home: {
        screen: HomePage,
        navigationOptions: ({navigation}) => ({
            title: '首頁',
        }),
    },
    Find: {
        screen: FindPage,
        navigationOptions: ({navigation}) => ({
            title: '發現',
        }),
    },
    Mine: {
        screen: MinePage,
        navigationOptions: ({navigation}) => ({
            title: '我的',
        }),
    },
};

const StackNavigatorConfig = {
    initialRouteName: 'Home',
    initialRouteParams: {initPara: '初始頁面參數'},
    navigationOptions: {
        title: '標題',
        headerTitleStyle: {fontSize: 18, color: '#666666'},
        headerStyle: {height: 48, backgroundColor: '#fff'},
    },
    mode: 'card',
    headerMode: 'screen',
    cardStyle: {backgroundColor: "#ffffff"},
    transitionConfig: (() => ({
        screenInterpolator: CardStackStyleInterpolator.forHorizontal,
    })),
    onTransitionStart: (() => {
        console.log('頁面跳轉動畫開始');
    }),
    onTransitionEnd: (() => {
        console.log('頁面跳轉動畫結束');
    }),
};

navigation:導航器對象,在導航器中的每一個頁面,都有 navigation 屬性,該屬性有以下幾個屬性/方法:

navigate - 跳轉到其他頁面
state - 當前頁面導航器的狀態
setParams - 更改路由的參數
goBack - 返回
dispatch - 發送一個action

跳轉示例

this.props.navigation.navigate('MinePage', { key: 'val' })

【TabNavigator】
構造函數

TabNavigator(RouteConfigs, TabNavigatorConfig)

api和 StackNavigator 類似
參數 RouteConfigs 是路由配置,
參數 TabNavigatorConfig是Tab選項卡配置。
【DrawerNavigator】
基本配置和上面兩者api相似

屬性(Props) 與 狀態(State)

props 組件屬性,只可以用來保存組件間數據傳遞
eg:父組件傳遞參數給子組件

// Father.js
export default class Father extends Components {
    render() {
        return (
            <View>
                <Son val="18" />
            </View>  
        )
    }
}

// Son.js
export default class Son extends Component {
    render() {
        return (
            <View>
                <Text>{this.props.val}</Text>
            </View>  
        )
    }
}

eg:子組件傳遞參數給父組件

// Father.js
export default class Father extends Components {
    render() {
        return (
            <View>
                <Son age="18" todo={this.show} />
            </View>  
        )
    }

    show(sonV) {

    } 
}

// Son.js
export default class Son extends Components {
    render() {
        return (
            <View>
                <Text
                    onPress={() => {this.props.todo('子組件傳遞值')}}
                >{this.props.age}</Text>
            </View>  
        )
    }
}

state 組件狀態,組件是更新渲染依賴於其state屬性,所以不是跟渲染相關的屬性不要設置到這裏,加大RN的渲染負擔(state有所變動都會導致RN進行是否渲染的判斷)。

import React, { Component } from 'react';
import { AppRegistry, Text, View,} from 'react-native';
export default class ResultScreen extends Component {

  constructor(props){
    super(props);
    //設置state屬性
    this.state = { showData: false };
    //設置變量
    this.dataText = "";
  }
  render() {
    return (
        <View>
            <Text tyle={{backgroundColor:'white',textAlign:'center',textAlignVertical:'center'}} onPress={this._onPressButton.bind(this)}>
                {this.state.showData ? this.dataText : "點擊改變狀態"}
            </Text>
        </View>
    );

 _onPressButton() {
     this.dataText = "Success";
      this.setState({
           showData: true,
      });
  } 
 }

手勢識別

https://reactnative.cn/docs/0.51/gesture-responder-system.html#content

定時器

setTimeout, clearTimeout
setInterval, clearInterval
setImmediate, clearImmediate
requestAnimationFrame, cancelAnimationFrame

requestAnimationFrame(fn)和setTimeout(fn, 0)不同,前者會在每幀刷新之後執行一次,而後者則會儘可能快的執行(在iPhone5S上有可能每秒1000次以上)。

setImmediate則會在當前JavaScript執行塊結束的時候執行,就在將要發送批量響應數據到原生之前。注意如果你在setImmediate的回調函數中又執行了setImmediate,它會緊接着立刻執行,而不會在調用之前等待原生代碼。

Promise的實現就使用了setImmediate來執行異步調用。

網絡訪問

async getMoviesFromApi() {
    try {
      // 注意這裏的await語句,其所在的函數必須有async關鍵字聲明
      let response = await fetch('https://facebook.github.io/react-native/movies.json');
      let responseJson = await response.json();
      return responseJson.movies;
    } catch(error) {
      console.error(error);
    }
  }

調試

這裏寫圖片描述

  1. Reload: 重載rn項目
  2. Debug JS Remotely : 打開瀏覽器debug模式,會在瀏覽器打開一個新的標籤頁面,
    地址是http://localhost:8081/debugger-ui
  3. Live Reload : 實時刷新 當你的js代碼發生變化後,React Native會自動生成bundle然後傳輸到模擬器或手機上
  4. Hot Reloading: 熱加載 當你每次保存代碼時Hot Reloading功能便會生成此次修改代碼的增量包,然後傳輸到手機或模擬器上以實現熱加載。相比 Enable Live Reload需要每次都返回到啓動頁面,Enable Live Reload則會在保持你的程序狀態的情況下,就可以將最新的代碼部署到設備上
  5. Toggle Inspector 調試網絡、佈局、點擊等
  6. Show Perf Monitor 打開性能監控

    【debug調試】

  7. 在工程目錄下輸入npm start啓動服務

  8. 手機與電腦同一個wifi環境下,真機搖晃彈出上述菜單,選擇【Dev Settings】–【Debug server host & port for device】填寫代理的ip和端口(eg:172.22.185.210:8081)。
  9. Chrome瀏覽器打開 http://localhost:8081/debugger-ui/ 然後手機調試菜單【Reload】下,會自動連接到Chrome上(沒有UI)
  10. 連上後畫面如下
    這裏寫圖片描述

原生與React-Native交互

【例子爲已有老項目,接入少量RN模塊,仍以原生爲主流】
按上面教程搭建好環境,
初始化一個新項目

react-native init ReactTest
cd ReactTest
react-native run-android

用AS打開android目錄,裏面默認生成的是一個MainActivity和MainApplication,且MainActivity繼承自ReactActivity,即app一啓動打開MainActivity,MainActivity又去通過【原生調用RN界面】方式去啓動我們的RN項目。(老項目遷移建議按照環境搭建步驟創建一個新項目,然後再對android目錄下代碼進行修改,把老項目copy過來比較簡單)

然後把自己的老項目遷移過來,啓動頁的Activity也換成自己的,MainActivity可以刪除,MainApplication裏的代碼是需要的可以跟自己原有的Application合併下。

【自動更新 和 手動更新 index.android.bundle 】

React-Native的內容依賴於【index.android.bundle】文件,必須保證【index.android.bundle】文件是最新的才能看到最新修改的效果,在項目目錄下啓動服務(即npm start)並配置到代理,每次run項目就會自動更新;或者打離線【index.android.bundle】包也可不依賴RN服務(一般打包的時候用,否則RN內容打開會白屏)。
【自動更新】
用Node.js命令行進入項目工程輸入
npm start
啓動服務(如果服務連接不上可直接運行react-native run-android重新跑下)

搖晃手機調出RN的調試界面,選擇【Dev Settings】-> 【Debug server host & port for device】輸入電腦的ip,端口號是8081,格式如【172.22.185.190:8081】。

【手動更新 即 離線包】
如果無法自動更新,可以產用手動更新的方法
在android下創建assets【android/app/src/main/assets】目錄。然後打開Node.js命令行工具,進入根目錄,運行如下代碼

react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/

這會在assets下生成兩個文件
這裏寫圖片描述

PS:運行的app可能會報如下【index.android.bundle文件找不到錯誤】也是這個原因,啓動服務,配置代理即可或打離線包均可解決。

【原生調RN界面】

修改MainActivity變成我們的項目,這樣app啓動起來就不調用RN而是我們的MainActivity了

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.one).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this,OneReactActivity.class));
            }
        });
        findViewById(R.id.two).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, TwoReactActivity.class));
            }
        });
    }
}

這個原生界面有兩個Button,點擊後分別跳轉OneReactActivity和TwoReactActivity兩個模塊,OneReactActivity和TwoReactActivity是跳轉RN界面的入口Activity,繼承自ReactActivity。
OneReactActivity

public class OneReactActivity extends ReactActivity {
    @Nullable
    @Override
    protected String getMainComponentName() {
        return "One";
    }
}

Two也類似,getMainComponentName返”Two”即可
MainApplication保持原來的不動。
這樣原生部分的代碼編寫完畢,現在寫兩個RN的界面並註冊
One和Two的代碼類似,One的代碼如下

import React from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    View
} from 'react-native';

export default class One extends React.Component {
    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.hello}>One</Text>
            </View>
        )
    }
}
var styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
    },
    hello: {
        fontSize: 20,
        textAlign: 'center',
        margin: 10,
    },
});

然後在index.js中註冊下

import { AppRegistry } from 'react-native';
import One from './One';
import Two from './Two'

AppRegistry.registerComponent('Two', () => Two);
AppRegistry.registerComponent('One', () => One);

ok編譯運行。

【RN調用原生/原生界面】

這部分的流程就是原生將自己的方法註冊到RN中,RN對方法進行調用,跳轉界面也是RN調用原生方法,再在原生方法中進行跳轉。其實原生跳RN也一樣,只不過這部分封裝在ReactActivity中所以感知不深。
編寫ReactContextBaseJavaModule子類,寫一個MyJavaModule吧

public class MyJavaModule extends ReactContextBaseJavaModule {

    public MyJavaModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return MyJavaModule.class.getSimpleName();
    }

    @ReactMethod
    public void JumpResultActivity(){
        Activity activity = getCurrentActivity();
        activity.startActivity(new Intent(activity, ResultActivity.class));
    }
}

注意 【ReactMethod】這個註解
編寫ReactPackage子類,編寫一個MyReactPackage吧

public class MyReactPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();

        modules.add(new MyJavaModule(reactContext));

        return modules;
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

最後再MainApplication中註冊下

 @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),new MyReactPackage()
      );
    }

寫一個ResultActivity來提供跳轉,其實你做其他的事情也可以。原生部分結束。
RN部分修改下One.js

import React from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    NativeModules
} from 'react-native';

let JavaModule = NativeModules.MyJavaModule;

export default class One extends React.Component {
    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.hello} onPress={this.buttonClick}>One</Text>
            </View>
        )
    }

    buttonClick(){
        JavaModule.JumpResultActivity();
    }
}
var styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
    },
    hello: {
        fontSize: 20,
        textAlign: 'center',
        margin: 10,
    },
});

很簡單,主要是NativeModules部分。

【原生與RN通信】

MyJavaModule添加如下代碼

public static void sendEvent(String eventName, WritableMap params){
        if(context != null){
            context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit(eventName, paramss);
        }
    }

context是ReactApplicationContext對象,取自MyJavaModule的構造函數
原生部分這樣就可以了,我們在需要的地方調用sendEvent即可,params參數可以爲空
RN部分,我們在接收的地方加入以下代碼即可

componentDidMount(){
   DeviceEventEmitter.addListener('result', function() {
      alert("send success");
   });
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章