react-native開發經驗


# **RN開發經驗**

## 一、環境配置
關於環境配置,前輩已有完整的總結:http://tvrn.devops.letv.com/docs/Environment.html

**IDE准備:** [AndroidStudio](https://developer.android.com/studio/index.html),[VisualStudioCode](https://code.visualstudio.com/)

如何打造現代化RN開發環境:http://tvrn.devops.letv.com/docs/tools/vscode.html

## 二、TVRN Demo 運行
TVRN是為了方便TV上的開發者使用的組件庫。詳見:http://tvrn.devops.letv.com/ (每個人都可以修改更新文檔,入庫后第二天早上10點更新)

git地址如下:

`git clone ssh://username@athena.devops.letv.com:29418/LETVRD/tvrn-libs`

playground項目是一個標准的RN-app工程,只不過是嵌在tvrn-libs里面,同時直接引用了tvrn-libs。 用途:

    1、開發時作為組件的host,能直接看到組件的運行效果
    2、作為tvrn組件庫的演示examples-app

###### **運行playground**
    1、在根目錄執行 npm install(package.json中列出了項目所有依賴);
    2、修改vi playground/android/gradle/wrapper/gradle-wrapper.properties,
      注釋掉從網絡下載,使用本地gradle-2.10-all.zip;
    3、拷貝一個gradle包到playground/android/gradle/wrapper/目錄下:
      cp ../*****/gradle-2.10-all.zip playground/android/gradle/wrapper/;
    4、根目錄下執行tvrn run-android --root playground(--root指定目錄);
    注:此Demo目前暫只能在EUI6.0上跑通。

## 三、使用組件
```JavaScript
import React, { Component } from 'react';

import {
    AppRegistry,
    Navigator,
} from 'react-native';
import { FocusableView } from 'tvrn-libs';
import AccountCenter from './src/ui/AccountCenter';

```
如果export default class AccountCenter,則AccountCenter不需要大括號,如import AccountCenter from './AccountCenter';

如果export class AccountCenter則不需要大括號,如import {AccountCenter} from './AccountCenter';
可以export出多個組件。




## 四、使用導航器跳轉頁面
Navigator使用純JavaScript實現了一個導航棧,因此可以跨平台工作。

Navigator可以在renderScene方法中根據當前路由渲染不同的組件。默認情況下新的場景會從屏幕右側滑進來,但你也可以通過configureScene方法來管理這一行為。

```JavaScript
<Navigator
    initialRoute={{ component: AccountLogin }} //這個指定了默認的頁面
    configureScene={this.configureScene} //跳轉動畫
    renderScene={this.renderScene} /> //加載頁面
```
跳轉:
```JavaScript
_gotoRegister() {
  this.props.navigator.push({
    title: 'RegisterView1',
    component: RegisterView1,
    params: {//跳轉攜帶參數
      name: 'test'
    },
    // type: 'Bottom'
  });
}
```

## 五、component生命周期

**RN中的component跟Android中的activity,fragment等一樣,存在生命周期。**
![聲明周期圖](/home/fan/圖片/picture/reactnative-component.png)

如圖,可以把組件生命周期大致分為三個階段:

* 第一階段:是組件第一次繪制階段,如圖中的上面虛線框內,在這里完成了組件的加載和初始化;
* 第二階段:是組件在運行和交互階段,如圖中左下角虛線框,這個階段組件可以處理用戶交互,或者接收事件更新界面;
* 第三階段:是組件卸載消亡的階段,如圖中右下角的虛線框中,這里做一些組件的清理工作。

**下面來詳細介紹生命周期中的各回調函數。**
#### **getDefaultProps**
在組件創建之前,會先調用 getDefaultProps(),這是全局調用一次,嚴格地來說,這不是組件的生命周期的一部分。

#### **getInitialState**
在組件被創建並加載候,首先調用 getInitialState(),來初始化組件的狀態。

#### **componentWillMount**
這個函數調用時機是在組件創建,並初始化了狀態之后,在第一次繪制 render() 之前。可以在這里做一些業務初始化操作,也可以設置組件狀態。這個函數在整個生命周期中只被調用一次。

#### **render**
render繪制組件到界面上

#### **componentDidMount**
在組件第一次繪制之后,會調用 componentDidMount(),通知組件已經加載完成。從這個函數開始,就可以和 JS 其他框架交互了,例如設置計時 setTimeout 或者 setInterval,或者發起網絡請求。這個函數也是只被調用一次。

#### **componentWillReceiveProps**
如果組件收到新的屬性(props),就會調用 componentWillReceiveProps()。在這個回調函數里面,你可以根據屬性的變化,通過調用 this.setState() 來更新你的組件狀態,這里調用更新狀態是安全的,並不會觸發額外的 render() 調用。

#### **shouldComponentUpdate**
當組件接收到新的屬性和狀態改變的話,都會觸發調用 shouldComponentUpdate(...)。這個函數的返回值決定是否需要更新組件,如果 true 表示需要更新,繼續走后面的更新流程。否者,則不更新,直接進入等待狀態。
默認情況下,這個函數永遠返回 true 用來保證數據變化的時候 UI 能夠同步更新。你可以自己重載這個函數,通過檢查變化前后屬性和狀態,來決定 UI 是否需要更新,能有效提高應用性能。

#### **componentWillUpdate**
如果組件狀態或者屬性改變,並且上面的 shouldComponentUpdate(...) 返回為 true,就會開始准更新組件,並調用 componentWillUpdate()。
需要特別注意的是,在這個函數里面,你就不能使用 this.setState 來修改狀態。
緊接着這個函數,就會調用 render() 來更新界面了。

#### **componentDidUpdate**
調用了 render() 更新完成界面之后,會調用 componentDidUpdate() 來得到通知,

#### **componentWillUnmount**
當組件要被從界面上移除的時候,就會調用 componentWillUnmount()。在這個函數中,可以做一些組件相關的清理工作,例如取消計時器、網絡請求等。

|生命周期|調用次數|能否使用setState()|
|--|--|--|
|getDefaultProps|1|否|
|getInitialState|1|否|
|componentWillMount|1|是|
|render|>=1|否|
|componentDidMount|1|是|
|componentWillReceiveProps|>=0|是|
|shouldComponentUpdate|>=0|否|
|componentWillUpdate|>=0|否|
|componentDidUpdate|>=0|否|
|componentWillUnmount|1|否|


## 六、back鍵處理
**安卓back鍵的處理主要就是一個事件監聽:**
```JavaScript
BackAndroid.addEventListener('hardwareBackPress', this.onBackPressed);
BackAndroid.removeEventListener('hardwareBackPress', this.onBackPressed);
```
**根據當前界面決定作何動作**
有時候我們有這樣的需求:當用戶處於某些界面下時,back鍵要做特殊的動作,如:提示用戶是否要保存數據,或者解鎖界面禁止back鍵返回等等。此時,最佳實踐是在route或route中對應的Component上保存關於如何處理back鍵的信息:
```JavaScript
onBackAndroid = () => {
   const nav = this.navigator;
   const routers = nav.getCurrentRoutes();
   if (routers.length > 1) {
     const top = routers[routers.length - 1];
     if (top.ignoreBack || top.component.ignoreBack){
       // 路由或組件上決定這個界面忽略back鍵
       return true;
     }
     const handleBack = top.handleBack || top.component.handleBack;
     if (handleBack) {
       // 路由或組件上決定這個界面自行處理back鍵
       return handleBack();
     }
     // 默認行為: 退出當前界面。
     nav.pop();
     return true;
   }
   return false;
 };
```

## 七、RN中JS如何調用Java代碼
1. 首先新建一個類繼承自ReactContextBaseJavaModule這個抽象類
```java
public class LetvAccountManager extends ReactContextBaseJavaModule {
```

2. 重寫getName方法,命名一下擴展。以后可以在js里面按照名字找到這個擴展。
```java
@Override
public String getName() {
    return "LetvAccountManager";
}
```
3. 實現暴露給js的接口
```java
@ReactMethod
public void removeLocalAccount(String userName, Promise promise) {
    Log.d(TAG, "do removeLocalAccount");
    LocalAccountConfig.removeAccoutRecord(mContext, userName);
}
```
4. 書寫注冊接口待用。新建一個MyPackage,在createNativeModules中完成注冊接口。
```Java
public class MyPackage implements ReactPackage {

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<NativeModule>();
        modules.add(new LetvAccountManager(reactContext));
        return modules;
    }
```

5. 將寫好的接口注冊到MainActivity中去,其中有一個已經寫好的方法 --- getPackages,們在其中,加入我們寫好的接口(MyPackage)。
```java
    @Override
    protected List<ReactPackage> getPackages() {
        return Arrays.<ReactPackage>asList(
            new MainReactPackage(),
            new TvrnLibsPackage(),
            new com.letv.tvrn.overseaaccountdemo.MyPackage()
        );
    }
```
6. 到這一步終於可以js調用了

 ```JavaScript
  import { NativeModules } from 'react-native';

  const LetvAccountManager = NativeModules.LetvAccountManager;
  LetvAccountManager.removeLocalAccount;
 ```

## 八、國際化 I18N
 **先來個名詞解釋:**

 I18N,其來源是英文單詞 internationalization的首末字符i和n,18為中間的字符數,是“國際化”的簡稱。

I18N是使用native監聽系統語言變化后,JS端自動刷新的一套國際化方案,用法如下:

1、在I18N下添加配置文件,格式如下:
  ```JavaScript
  const zh_CN = {
    app_name: '樂視賬號',
    account_login: '立即登錄',
    account_find_password: '找回密碼',
  };

  export default zh_CN;
  ```

2、 在入口js中引用多語言組件,在constructor()中調用init()方法,再在componentWillMount()中使用timer延遲50ms后調用getLang()方法,最后聲明ContextProps,向子組件傳遞數據。
```JavaScript
constructor(props){
  super(props);
  ....
  Language.init();
}

componentWillMount() {
    this.timer = setTimeout(() => {
      lang = Language.getLang();
      this.setState({
          ready:true,
      });
      },50
    );
  }

Main.childContextTypes = {
  language: React.PropTypes.object
};
```

3、子組件使用國際化組件
```JavaScript
AccountLogin.contextTypes = {
  language: React.PropTypes.object,
};

<Text style={styles.title_text}>
 {this.context.language.app_name}
</Text>

```


## 九、集成方案
把react-native中公共的so和jar集成到系統中,以減小APP的空間占用,使用gradle編譯,引用系統中的reactnative so和jar。

在項目根目錄下執行:
```
cd android && ./gradlew assembleRelease
```
會在android/app/build/outputs/apk目錄下會有app-release-unsigned.apk,然后把此apk進行對應系統簽名即可。





## 十、常見錯誤

1.invariant violation:expected a component class,got[object object]

  創建自定義組件首字母要大寫,否則會報錯.比如<login/>應該寫成<Login/>


2.Module 0 is not a registered callable module.

將gradle升級成最新版本(cd Android 進入android目錄執行:sudo ./gradlew clean) 或者通過android studio工具升級.

3.Element type is invalid: expected a string (for built-in components) or a class/function but got: object
   一般是你引用了無效的組件,如果組件確實正確,看下引用的組件是否正常導出:(export defalut)

4.react native  undefined is not an object (evaluating this....

  發生該錯誤的一般是忘記bind(this),只要回調函數中需要用到this的,一般都需要bind.
  如:onPress={this.doRegister()} ==》 onPress={this.doRegister.bind(this)}

5.Could not get BatchedBridge, make sure your bundle is packaged corrrectly

adb reverse tcp:8081 tcp:8081

6.await is a reserved word

函數中有異步操作的時候,函數名前要加async,如async function getSystemAccount() {}

7.The android gradle plugin version 2.3.0-beta1 is too old, please update to the latest version.

修改playground/android/gradle/wrapper/gradle-wrapper.properties,注釋掉從網絡下載,使用本地gradle-2.10-all.zip;
```
# distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
  distributionUrl=./gradle-2.10-all.zip
```

8.Unexpected character ‘�’ (1:0) 圖片加載錯誤

根目錄下重啟服務 tvrn start



## 圖書分享

《React Native開發指南》

《React Native入門與實戰》

鏈接: https://pan.baidu.com/s/1pLqFr2z 密碼: pp65

 

轉載請注明出處!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM