• 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

    转载请注明出处!

  • 相关阅读:
    JasperReport
    Linux
    Linux
    Linux
    Linux
    Groovy
    Linux
    VS
    Aliyun
    Linux
  • 原文地址:https://www.cnblogs.com/qinaidexin/p/6655755.html
Copyright © 2020-2023  润新知