【React Native 实战:构建电商 App】20-总结

知识点:

  • 适配 iPhone X
  • 添加额外的线程
  • 缓存
  • 关于 Alert、Modal
  • 样式
  • 自定义组件

适配 iPhone X

RN 版本 0.52 之后提供了适配 iPhone X 的组件 SafeView,不过单独这个是不行的,还需要在 iOS 中修改配置,把视图从一个短小的区域拉伸到全屏的大小。

enter image description here

这里通过修改设置打开默认的安全区域。

enter image description here

由于之前使用的还是旧版的 RN,这里为了兼容旧的版本做了一个 JS 的判断,通过判断高度是否符合 812 来判断是否 iPhone X。同时有一个小小的缓存,不需要每次都重复判断,这里的 1.0.6 是开始使用新能力的版本号。

enter image description here

添加额外的线程

有些功能其实没必要跑在 UI 线程上,这些东西如果放在组件里反而不是那么融洽,甚至有时候会对正常的逻辑产生影响。

可以使用下面的方式注册一下额外的层,这个不显示 UI,但是可以用来更新一些缓存,返回远程数据等。

AppRegistry.registerRunnable('RunableTask', TaskRun)

AppRegistry.runApplication('RunableTask', {});

在线上产品中就是利用了这一个层做了一些额外的事情,这里我使用事件触发的形式来做正常组件和这一个层的通知。

enter image description here

需要小心一点,如果在这一层做了定时器、循环事件等操作,记得要取消掉,不然事件就会变成两次。

缓存

App 还是需要缓存的,这里可以缓存一些上次打开的数据,比如广告、用户信息等。

在打开 App 的时候可以先用这些数据走正常的流程,然后再用一个异步的任务去鉴定、更新这些数据。这样下来可以让 App 打开非常流畅,同时用户也不会发现数据变化大等异常,这些同时也是需要后端接口数据支持的。

关于 Alert、Modal

正常情况下 Alert、Modal 是独立于当前组件 View 之上的,如果这个时候遇到意外情况,比如组件销毁、暂停等情况下 Alert 还未销毁,这个时候就会发生比较严重的错误。

Alert 大多数会报错,App 闪退。

Modal 部分会报错、部分会暂停、失去事件等操作,导致 App 一直停留在 Modal 视图上,用户也不能操作任何东西,时间长了系统就会判断 App 未响应等等。

如果你不想处理这些东西,可以考虑使用一个自定义的 Alert 或者内嵌在组件中的 View 来代替 Modal。

样式

最好能将部分样式抽取出来,变成一个公共的样式库,这样的话 App 启动之后就会缓存这部分样式,同时也非常容易将已经有的部分样式替换掉。

这些可以参考开源的 RN 组件库等。

自定义组件

在 App 中其实有些属性是要经常用到的,比如 Text 组件的禁止使用系统字体大小、Image 的缩放方式等,这些组件在平常使用过程中会经常重复添加各种属性,可以将这些组件独立出来,并且使用更加语义化的属性名来代替 RN 提供的使用方式。

在这个的基础上还可以将按钮、卡片等组件进一步抽象出来,对于不使用第三方开源组件的项目来说还是很有必要的。

解决遇到的问题

使用 RN 开发会遇到很多问题,这些问题都可以在 RN 的 Issues 中找到答案,如果还没有就到 Google 搜一下。

有些问题需要修改 RN 的代码,不要怕麻烦,如果是 JS 部分的修改,直接将下载的包修改成可上传的包就行了。这里记得上传到私有 npm 库中,如果是 Native 的问题最好是在自己的 Native 代码中覆盖掉旧的代码,毕竟前端去编译 RN 的源代码还是有很大的风险的。

不要去百度,百度真的没有答案。

按钮

RN 提供的按钮其实很丑,正常开发的时候大家都是使用自定义的按钮。

enter image description here

使用这种模式做出来的按钮非常漂亮,也更能符合设计的结果,不过使用的时候尽量不要给按钮本身加样式,有样式尽量添加到内部的 View 等组件上。

向下兼容

RN 使用的过程中我们升级了一个大的版本,从 0.40 系列升级到了 0.50 系列,由于大部分手机用户还存在不愿意升级 App 的情况,向下兼容成了唯一的选择。

我使用的策略是修改源代码……

比如升级 0.53 的时候发现有些 Native 组件名变了,原来使用的是 RCTMultilineTextInputView,现在变成了 RCTTextView,不管 RN 出于什么理由改变了这个组件名字,造成的结果就是现在的旧代码不能跑在新的 App 壳下。

我们可以打印出 NativeModules 的 UIManager 对象看看。

enter image description here

而兼容也是从这里开始做的,我们判断一下当前的 RN 是否在 UIManager 中提供了对应的原生组件即可判断当前的环境是否正确的环境。

比如要升级到 53,修改 53 的源代码 react-native/Libraries/Components/TextInput/TextInput.js。

//安卓的没变化,要排除
if (Platform.OS === 'android') {
  var AndroidTextInput = requireNativeComponent('AndroidTextInput', null);
} else if (Platform.OS === 'ios') {
//如果支持RCTTextView,既新APP
    if(UIManager.RCTTextView){
        var RCTMultilineTextInputView = requireNativeComponent('RCTTextView', null);
        var RCTSinglelineTextInputView = requireNativeComponent('RCTTextField', null);
    }else{
    //其他情况
        var RCTMultilineTextInputView = requireNativeComponent('RCTMultilineTextInputView', null);
        var RCTSinglelineTextInputView = requireNativeComponent('RCTSinglelineTextInputView', null);
    }
}

使用类似这样的手段就能区别两种 App 壳的环境,用户在使用的时候也不至于要必须升级 App 才能使用新版 App 的功能了。

使用 RN 开发 App 确实非常快,但是各位在开发中还是不要忘记钻研 RN 的源代码,很多解决方案其实很简单,有些时候可能只是由于低版本的 APP 不支持而导致的。

如果你想对 RN 有一个进一步的了解,还需要对原生的代码也有一定的了解,当然正常情况下还是有位这方面的同事一起开发比较好。

【React Native 实战:构建电商 App】18-热更新

知识点:

  • 原理
  • React-Native-Pushy
  • 自定义 Android 下载
  • 自定义 iOS 下载

原理

RN 的热更新非常简单,在 App 打开的时候检测 JS 文件是否需要升级,如果需要升级就下载最新的文件,然后使用新的 JS 启动 App 就可以达到更新最新的 UI 的目的。

enter image description here

第三方热更新

ReactNative 中文网推出了一个热更新的包,这个更新方式比较简单,所有新更新全部提交到中文网,个人只需要负责开发就好了。在用户启动 App 的时候更新逻辑也不复杂,体验也还可以,这里介绍一下怎么安装这个包。

集成 pushy 的代码全部放在新分支 add-pushy 中,要看代码的可以先切个分支了。

首先要注册一个账号,详见这里,在中文网注册一个账号并不复杂,注册完了之后顺便新建一个应用吧,Android 和 iOS 是分开的,需要单独创建。

enter image description here

在项目根目录安装需要的包,这里第一个是全局安装命令,只有在第一次需要执行。

$ npm install -g react-native-update-cli
$ npm install --save react-native-update
$ react-native link react-native-update

如果有使用旧版 RN 的需要看一下下面的对照关系,这里我直接安装就可以了,目前最新的版本是 5.0.0。

React Native 版本 React-Native-update 版本
0.26及以下 1.0.x
0.27 – 0.28 2.x
0.29 – 0.33 3.x
0.34 – 0.45 4.x
0.46及以上 5.x

安装命令可以参考下面的这个命令:npm install –save react-native-update@5.x。

Android

在 Android 中添加新的方法,该方法可以将 bundle 的文件从新加的包中处理一次再返回。

enter image description here

iOS

在工程 Target 的 Build Phases→Link Binary with Libraries 中加入 libz.tbd、libbz2.1.0.tbd:

enter image description here

修改 bundle 的获取方式:

// ... 其他代码

#import "RCTHotUpdate.h"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if DEBUG
  // 原来的jsCodeLocation保留在这里
  jsCodeLocation = ..........
#else
  // 非DEBUG情况下启用热更新
  jsCodeLocation=[RCTHotUpdate bundleURL];
#endif
  // ... 其他代码
}

enter image description here

注意,在项目根目录下登录 pushy。

enter image description here

根目录下会新建一个 .update 文件,将该文件加入 Git 排除目录,不要将这个敏感文件共享到公网去。

如果之前没有在注册之后创建 App,这里还可以使用命令创建,命令创建 Android 或者 iOS 的 App,这里的应用名字就输入我们的项目名称就好了。

$ pushy createApp --platform ios
App Name: <输入应用名字>
$ pushy createApp --platform android
App Name: <输入应用名字>

enter image description here

如果你已经在网页端或者其他地方创建过应用,也可以直接选择应用,可以看到提示网上新建的 App 和刚才命令创建的 App。

enter image description here

选择之后就可以看到当前目录下新建了 update.json 文件,该文件配置了应用的对应关系。

{
    "ios": {
        "appId": 1,
        "appKey": "<一串随机字符串>"
    },
    "android": {
        "appId": 2,
        "appKey": "<一串随机字符串>"
    }
}

使用 JS 去获取新版本的可以参考官网的例子,这个比较简单就不再介绍了。

https://github.com/reactnativecn/react-native-pushy/blob/master/docs/guide2.md

使用 IDE 打包 Android 或者 iOS 之后也可以直接发布到 pushy 上。

pushy uploadIpa <your-package.ipa>
pushy uploadApk android/app/build/outputs/apk/app-release.apk

在根目录下使用命令上传新 bundlepushy bundle --platform <ios|android>

如果遇到下面的错误,请安装一下 metro-bundler:

Cannot find module 'metro-bundler/src/babelRegisterOnly'

上传之后会提示是否发布这个新 bundle,后面还会提示是否绑定包。这里由于是第一次提交,是没有可绑定的对象的,取消就好了。

enter image description here

enter image description here

也可以直接使用命令,让用户更新最新的包:pushy update –platform

自定义 Android 下载

自定义请求地址这里写了一个默认值“http://www.guofangchao.com/down”,这是一个不存在的地址,每次都会返回 404,我们的逻辑是:

  • 从 App 拿到 bundle 的 MD5 值,放在请求的 header 里;
  • 服务器根据 MD5 判断是否需要下载文件;
  • 需要则直接下载文件,否则返回 httpcode 级别的 404。

安卓自定义的代码放在 android-push 分支下,有需要的请切换分支。

首先需要新建一个 load 页,该页面主要用来承载 bundle 的下载更新功能,进入首页之后会销毁该页面。

enter image description here

页面样式这里先不去了解,我们主要了解一下原生代码的逻辑。

enter image description here

onCreate 是进入页面的方法,这里执行初始化的操作,最后调用我们自定义的 DownBundle 方法去请求 bundle。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_load);

        //设置组件对象
        progressBar = (ProgressBar) findViewById(R.id.progressBar);
        tv_update = (TextView) findViewById(R.id.tv_update);
        iv_arrow = (ImageView) findViewById(R.id.iv_arrow);
        //进入下载节奏
        DownBundle();
    }

我们来实现一下自定义的下载方法,这里简单的做一下下载。

//下载文件,如果出错就启动
private void DownBundle(){
    String token=getBundleMD5();
    Request req=new Request.Builder().addHeader("token",token).url("http://www.guofangchao.com/down").build();
    new OkHttpClient().newCall(req).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            //下载失败直接启动app
            startApp();
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            tv_update.setVisibility(View.VISIBLE);
            progressBar.setVisibility(View.VISIBLE);
            iv_arrow.setVisibility(View.VISIBLE);
            progressBar.setProgress(0);

            //下载成功
            InputStream is = null;
            byte[] buf = new byte[2048];
            int len = 0;
            FileOutputStream fos = null;
            // 储存下载文件的目录
            File file = new File(getFilesDir().getAbsolutePath()+"/index.android.bundle");
           try{
               is = response.body().byteStream();
               long total = response.body().contentLength();
               fos = new FileOutputStream(file);
               long sum = 0;
               while ((len = is.read(buf)) != -1) {
                   fos.write(buf, 0, len);
                   sum += len;
                   int progress = (int) (sum * 1.0f / total * 100);
                   // 下载中更新进度条
                   progressBar.setProgress(progress);
               }
               fos.flush();
           }catch (Exception e){
               startApp();
           }finally {
               try {
                   if (is != null)
                       is.close();
               } catch (IOException e) {
               }
               try {
                   if (fos != null)
                       fos.close();
               } catch (IOException e) {
               }
               startApp();
           }
        }
    });
}

自定义 iOS 热更新

这里切换了新的分支 ios-push,在项目下添加一个 Utils 文件夹:

enter image description here

在文件夹里添加 MD5 计算方法,右键选择添加一个新文件。

enter image description here

在弹出的地方选择 CocaaTouchClass 选项。

enter image description here

单击“下一步”按钮,输入我们的文件名,也是后面用到的类名,继续直到完成。

enter image description here

在 Filehash.h 中我们添加三个接口,在后面的 m 后缀名的文件中我们再实现这三个方法。

#import <Foundation/Foundation.h>

@interface FileHash : NSObject

+ (NSString *)md5HashOfFileAtPath:(NSString *)filePath;
+ (NSString *)sha1HashOfFileAtPath:(NSString *)filePath;
+ (NSString *)sha512HashOfFileAtPath:(NSString *)filePath;

@end

在 AppDelegate.m 中引用刚才的方法 #import “FileHash.h”。

这里改造启动方法,在调试模式下不参与下载。

enter image description here

这里使用 iOS 7 之后添加的方法去下载文件,如果打算支持低版本的 iOS 的话可能需要换个别的方法了。

enter image description here

这里使用的方法没有添加进度条,有需要的可以了解一下使用代理监听下载进度。

如果对方法前面的 +- 符号不理解的,这里提醒一下:可以简单默认 + 符号代表的是静态方法,调用要加方法所在的类;- 符号代表内部方法,内部调用使用 self,外部调用要加实例化之后的对象名称。

总结

这里使用的是全量下载方式,对于 App 逐渐增大的项目来说是有一些弊端的,这里提供一些优化方案。

  • 使用 zip 压缩:服务器返回的是压缩文件,App 需要在下载完成之后解压缩才能继续,这里使用 zip 的压缩算法减少了下载包的大小。
  • 将 JS 文件和图片分开:之前的方法会比较方便,但是当图片比较多的时候图片所占体积太大了,这个时候就需要把图片和 JS 分开下载,这里使用 zip 解压可以非常方便的实现到图片和 JS 分开打包的实现方式。
  • 使用 diff 算法:可以使用现成的 diff 算法比较 JS 的更新部分,将更新的 JS 打包成一个补丁,这样基本上就是最极致的更新了,也是 App 增量更新的主要实现方式。

其他还有一个 code-push 的更新方式,不过这个服务器是在海外,在下载速度上很不理想,国内基本不考虑了。

【React Native 实战:构建电商 App】18-集成极光

知识点:

  • 集成极光
  • 集成友盟

集成极光

极光设置

创建一个新的应用,名字可以随便。

enter image description here

配置“推送设置”信息,这里 Android 输入框中填写我们自己的包名,默认是 com.;项目名称,这里的项目名称就是使用 RN 命令创建的时候输入的名称;iOS 的设置只需要上传证书,这里就不展示了。

enter image description here

安装包

极光有一个官方维护的 npm 包,单击这里查看 Github 地址

在项目根目录执行 npm install jpush-react-native jcore-react-native –save 命令安装第三方包。

执行命令 react-native link 尝试自动 link 原生部分,这个过程需要输入一次 appkey,该值在刚才创建的应用设置中可看到。

enter image description here

这里稍微注意一下,微信的 link 之前是改动过的,如果使用这种方式的话会导致之前的配置被修改,需要再手动改回去。

也可以指定包名称去 link,比如 react-native link jpush-react-native。

iOS 手动配置

使用 Xcode 打开 iOS 项目,修改配置TARGETS-> BUILD Phases -> LinkBinary with Libraries,将 require 修改成 optional。

enter image description here

如果要使用 iOS 的推送,需要修改下面这个地方,将 off 修改为 on,此时需要登录 iOS 的开发者账户。

enter image description here

Android 手动配置

修改安装的配置文件 /android/app/build.gradle,一般情况下 Appkey 已经填好了,只需要引入推送的两个包就好了。

android {
    defaultConfig {
        applicationId "com.anxintao"
        ...
        manifestPlaceholders = [
                JPUSH_APPKEY: "yourAppKey", //在此替换你的APPKey
                APP_CHANNEL: "developer-default"    //应用渠道号
        ]
    }
}
...
dependencies {
    compile fileTree(dir: "libs", include: ["*.jar"])
    compile project(':jpush-react-native')  // 添加 jpush 依赖
    compile project(':jcore-react-native')  // 添加 jcore 依赖
    compile "com.facebook.react:react-native:+"  // From node_modules
}

enter image description here

修改 /android/settings.gradle 配置文件,引入包的地址:

include ':app', ':jpush-react-native', ':jcore-react-native'
project(':jpush-react-native').projectDir = new File(rootProject.projectDir, '../node_modules/jpush-react-native/android')
project(':jcore-react-native').projectDir = new File(rootProject.projectDir, '../node_modules/jcore-react-native/android')

enter image description here

检查一下参数,默认情况下这个配置是好的,文件路径 /android/app/src/main/AndroidManifest.xml。

enter image description here

最后还要修改 Java 文件,将推送的包加入到 RN 组件中,文件路径 /android/app/src/main/java/com/anxintao/MainApplication.java,导入 Java 包并设置参数。

enter image description here

下面是一个调用推送的 demo。

import JPushModule from 'jpush-react-native';

...

// example
componentDidMount() {
    JPushModule.notifyJSDidLoad();
    JPushModule.addReceiveCustomMsgListener((message) => {
      this.setState({pushMsg: message});
    });
    JPushModule.addReceiveNotificationListener((message) => {
      console.log("receive notification: " + message);
    })
  }

  componentWillUnmount() {
    JPushModule.removeReceiveCustomMsgListener();
    JPushModule.removeReceiveNotificationListener();
  }

集成友盟

友盟的推送、统计、分享等功能真的非常实用,这里还是推荐集成一个的。

首先要在官网下载友盟的 SDK,这里 Android 和 iOS 是区别下载的,要根据自己的需求来下载具体的 SDK,下载地址请单击这里

enter image description here

如果选择了分享 SDK,这里注意一下,在选择了之后是可以单独设置分享到那些平台的。

enter image description here

Android 集成

首先要将下载的 jar 包导入到项目的 app/libs 下,如果文件夹不存在可以创建一个新的。

enter image description here

enter image description here

如果内部还包含依赖,比如下面这个就是还有依赖,里面的文件还包括 .so 后缀的文件,此时需要将整个文件夹也拷贝进去。

enter image description here

在 MainApplication.java 中添加友盟的初始化配置,这里第三个参数就是推送的 key,如果没有集成推送可以不填。

enter image description here

如果提示 UMConfigure 找不到引用,这应该是刚才导入的 jar 包没有引用到项目所导致的,可以在 jar 上点击右键,在弹出的菜单中选择 add as lib… 将 jar 包链接到我们的项目。

enter image description here

现在来修改 AndroidManifest.xml 文件,将相关权限和友盟的 Appkey 等信息填进去。

enter image description here

填写 key 的时候注意一下,key 是填写在 application 节点下的。

enter image description here

如果需要统计页面的消息,还需要在首页加入统计的代码,在 MainActivity.java 中设置开启统计,默认创建的代码是没有 onCreate 方法的,需要我们自己来添加这个方法。

enter image description here

iOS 集成

请在你的工程目录结构中,添加友盟统计框架,在选项 TARGETS→Build Phases→Link Binary With Libraries→Add Other,选择文件 UMMobClick.framework 等文件并选择确认。

检查下面的引用库,查看是否存在下面的几个库,没有就添加上就好了。

enter image description here

SystemConfiguration.framework
CoreGraphics.framework
CoreTelephony.framework
ImageIO.framework
libsqlite3.tbd
libc++.tbd
libz.tbd

如果有更多的分享还需要将分享的平台加入白名单。

enter image description here

在 AppDelegate.m 中添加友盟的初始化代码,如果提示引用错误,请返回检查 link 的库是否正确。

#import <UMCommon/UMConfigure.h>


  [UMConfigure setLogEnabled:YES];
  [UMConfigure initWithAppkey:@"599d6d81c62dca07c5001db6" channel:@"App Store"];
  //统计
  [MobClick setScenarioType:E_UM_NORMAL];

到这里友盟的集成就告一段落了,更多更详细的设置还需要大家在后面自行研究,如果你对 iOS 的语法不熟悉的话还是建议先学一学,后面需要设置的东西更加精细化也更加复杂,若对 iOS 没有一定的了解的话很难解决其中遇到的问题。

如果想了解集成支付宝的可以参考支付宝第三方组件,详见这里

【React Native 实战:构建电商 App】17-使用 TypeScript 编写代码

知识点:

  • 安装 TypeScript
  • 修改配置
  • VSCode 自带 Type
  • Parcel 自动处理
  • Webpack 自动处理

很多人已经在用 TypeScript 做开发了,这种方式确实有很大的优势,在开发和测试阶段给前端提供了非常大的助力,如果你还没有使用过,不妨试一试。

安装 TypeScript

在项目跟目录下运行 npm install -g typescript 即可安装好 TypeScript,输入 tsc -v 看一下是否已安装好。

enter image description here

在 TS 中可以很方便的使用一些 ES 6 中存在的特性,比如继承、箭头函数、class 类等,在这个的基础上,TS 还可以输入参数的类型、接口、枚举等高级特性,往常这些特性都是存在于强类型语言中的,这里使用这些特性增强了 JS 的开发效率,减少了很多不必要的错误。

在根目录下输入命令 tsc -init 初始化默认的 TS 配置文件。

在根目录下添加一个 demo.ts 文件,简单的使用一些 TS 特性的语法。

class Base {
    //变量a,类型string
    a: string = "test_a";
}

class Demo extends Base {
    b: string = "test_b";
    //方法c,参数类型string,返回string
    c(d: string): string {
        return this.a;
    }
    static e() {
        return "aaa";
    }
}

let demo = new Demo();
console.log(demo.c("t"))
console.log(Demo.e())

执行 tsc demo.ts 命令,在文件的同一目录下回生成 demo.js 文件,这个就是 TS 转化之后的结果。可以打开看看,TS 已经把其特性都转化为了 JS 的形式,类型检测则在编译的时候去掉了。

执行 node demo.js,运行生成的代码,可以看到正确输出了我们想要输出的值。

test_a
aaa

修改配置文件

要想在项目中使用 tsc 还需要修改配置文件,不然 tsc 命令会把所有 TS 文件都生成在源文件的同一目录下。

删掉旧的 tsconfig.json 文件,运行生成命令 tsc –init –pretty –sourceMap –target es2015 –outDir ./dist –module commonjs –jsx react 直接生成新的配置文件。

也可以自己修改文件的参数,具体的解释在配置中也有说明。

"target": "es6",      //新语法
 "module": "commonjs",  //加载方式
 "jsx": "react",   //支持jsx
 "sourceMap": true, //生成map文件
 "outDir": "./dist",  //生成文件的位置
 "strict": true,     //严格模式
 "esModuleInterop": true     //

不要忘了将要编译的源代码路径写到配置里,这样就不会编译其他地方的代码了。

"include": ["./src/"]

本地安装 jest 的 ts 支持。

npm install --save-dev ts-jest typescript

修改 packagejson 文件,给 jest 的配置加上 ts 的内容。

"jest": {
    "preset": "react-native",
    "moduleFileExtensions": [
        "ts",
        "tsx",
        "js"
    ],
    "transform": {
        "^.+\\.(js)$": "<rootDir>/node_modules/babel-jest",
        "\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
    },
    "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
    "testPathIgnorePatterns": [
        "\\.snap$",
        "<rootDir>/node_modules/",
        "<rootDir>/lib/"
    ],
    "cacheDirectory": ".jest/cache"
}

如果使用 VScode 的话还可以安装下面的几个包,让编辑器可以智能提示方法、参数等。

npm install --save-dev @types/jest @types/react @types/react-native @types/react-test-renderer @types/react-navigation

我们改造一下 src 下的 index.js,试一下 TS 的效果怎么样,在 src 下新建一个 index.tsx 文件,因为使用到了 jsx 的语法,所以这里必须使用 tsx 的后缀名。

'use strict';

import React from 'react';
import {
    StyleSheet,
    Image
} from 'react-native';
//添加路由组件
import Navigation from 'react-navigation';

//创建路由
const Pages = Navigation.StackNavigator({

}, {
        // initialRouteName:'OrderDetail',
        //这里做了一个页面跳转的动画
        transitionConfig: () => ({
            screenInterpolator: sceneProps => {
                const { layout, position, scene } = sceneProps;
                const { index } = scene;
                const translateX = position.interpolate({
                    inputRange: [index - 1, index, index + 1],
                    outputRange: [layout.initWidth, 0, 0]
                });
                const opacity = position.interpolate({
                    inputRange: [index - 1, index - 0.99, index, index + 0.99, index + 1],
                    outputRange: [0, 1, 1, 0.3, 0]
                });
                return { opacity, transform: [{ translateX }] };
            }
        }),
        navigationOptions: {
            header: null
        }
    });

//创建一个自己的容器,方便以后对路由做一些处理
export default class extends React.Component {
    constructor(props: any) {
        super(props);
    }

    render() {
        return <Pages onNavigationStateChange={this.listenChange.bind(this)}>
        </Pages>;
    }
    //监听路由的跳转
    listenChange(state1: any, state2: any, action: any) {}
}

在根目录下执行命令 tsc,就可以在 dist 中发现生成的 index.js 文件,该文件就是我们最后要的结果了。

不要忘记修改根目录下的 index.js 文件,将引用的地址指向 dist 文件夹,不然启动模拟器之后是不会加载编译之后的代码的。

使用 VScode

如果你使用的是 VScode 编辑器的话,编辑器自带了一个运行命令,可以很简单的执行 TS 命令。

单击菜单中的“运行/运行”命令,在弹出的命令中可以发现有两个专门为 TS 准备的命令。执行构建,即可编译一次项目的 ts 文件,执行监视即可监听所有文件改动。在 ts 文件被修改的时候会自动执行一次编译。

enter image description here

使用 Parcel

Parcel 是一个新的打包构建工具,安装和使用非常简单,中文文档

执行命令 npm install -g parcel-bundler 全局安装打包工具。

由于我们之前已经安装好了 TypeScript 的所有东西,此时其实已经不需要在干额外的事情了,Parcel 本身是支持非常多的第三方处理包的。例如,Web 常用的 Less、图片压缩、HTML 压缩等,这里我们只用了 TS 的功能。

  • 执行 parcel index.js 命令直接生成结果文件。
  • 执行 parcel watch index.js 命令监听文件的变化。

使用 Webpack

Webpack 已经有非常多的人在使用了,如果对这个东西非常熟悉,也可以使用 Webpack 处理 TS 到 JS 的转化。

安装依赖包:

npm install --save-dev babel babel-loader babel-polyfill babel-preset-env ts-loader typescript uglifyjs-webpack-plugin webpack webpack-dev-server webpack-merge
"target": "es2017",// 可以使用 Promise、asyncawait 等新语法

配置处理规则:

rules: [{
  test: /\.ts(x?)$/,
  exclude: /node_modules/,
  use: [
    {
      loader: 'babel-loader',
      options: babelOptions
    },
    {
      loader: 'ts-loader'
    }
  ]
}]},

到这里就可以正常使用 TS 了。

一般情况下,直接处理可以使用 ES 6 的语法开发 TS,如果还想要使用 ES 7 的语法就需要使用 babel 预先处理一次 TS 了。

【React Native 实战:构建电商 App】16-使用 ESlint 规范化代码

知识点

  • 安装 ESlint
  • 配置规则
  • 兼容多数 IDE

安装 ESlint

npm install -g eslint

eslint --init根据提示安装选择需要的配置,这里我选择的是 json 格式的文件。

最后就在根目录下生成.eslintrc.json

配置规则

ESlint 是基于规则在检测代码的。

  • env:你的脚本将要运行在什么环境中;
  • globals:额外的全局变量;
  • parserOptions:JavaScript 选项。
"parserOptions": {
   // ECMAScript 版本
  "ecmaVersion":6,
  "sourceType":"script",//module
  // 想使用的额外的语言特性:
  "ecmaFeatures":  {
  // 允许在全局作用域下使用 return 语句
  "globalReturn":true,
  // impliedStric
  "impliedStrict":true,
  // 启用 JSX
  "jsx":true
   }
 }

rules:开启规则和发生错误时报告的等级,规则的错误等级有三种:

  • 0 或’off’:关闭规则。
  • 1 或’warn’:打开规则,并且作为一个警告(并不会导致检查不通过)。
  • 2 或’error’:打开规则,并且作为一个错误(退出码为1,检查不通过)。
'rules': {
///////////////
// 可能的错误 //
////////////////

// 禁止条件表达式中出现赋值操作符
"no-cond-assign": 2,
// 禁用 console
"no-console": 0,
// 禁止在条件中使用常量表达式
// if (false) {
// doSomethingUnfinished();
// } //cuowu
"no-constant-condition": 2,
// 禁止在正则表达式中使用控制字符 :new RegExp("\x1f")
"no-control-regex": 2,
// 数组和对象键值对最后一个逗号, never参数:不能带末尾的逗号, always参数:必须带末尾的逗号,
// always-multiline:多行模式必须带逗号,单行模式不能带逗号
"comma-dangle": [1, "always-multiline"],
// 禁用 debugger
"no-debugger": 2,
// 禁止 function 定义中出现重名参数
"no-dupe-args": 2,
// 禁止对象字面量中出现重复的 key
"no-dupe-keys": 2,
// 禁止重复的 case 标签
"no-duplicate-case": 2,
// 禁止空语句块
"no-empty": 2,
// 禁止在正则表达式中使用空字符集 (/^abc[]/)
"no-empty-character-class": 2,
// 禁止对 catch 子句的参数重新赋值
"no-ex-assign": 2,
// 禁止不必要的布尔转换
"no-extra-boolean-cast": 2,
// 禁止不必要的括号 //(a * b) + c;//报错
"no-extra-parens": 0,
// 禁止不必要的分号
"no-extra-semi": 2,
// 禁止对 function 声明重新赋值
"no-func-assign": 2,
// 禁止在嵌套的块中出现 function 或 var 声明
"no-inner-declarations": [2, "functions"],
// 禁止 RegExp 构造函数中无效的正则表达式字符串
"no-invalid-regexp": 2,
// 禁止在字符串和注释之外不规则的空白
"no-irregular-whitespace": 2,
// 禁止在 in 表达式中出现否定的左操作数
"no-negated-in-lhs": 2,
// 禁止把全局对象 (Math 和 JSON) 作为函数调用 错误:var math = Math();
"no-obj-calls": 2,
// 禁止直接使用 Object.prototypes 的内置属性
"no-prototype-builtins": 0,
// 禁止正则表达式字面量中出现多个空格
"no-regex-spaces": 2,
// 禁用稀疏数组
"no-sparse-arrays": 2,
// 禁止出现令人困惑的多行表达式
"no-unexpected-multiline": 2,
// 禁止在return、throw、continue 和 break语句之后出现不可达代码
/*
 function foo() {
 return true;
 console.log("done");
 }//错误
 */
"no-unreachable": 2,
// 要求使用 isNaN() 检查 NaN
"use-isnan": 2,
// 强制使用有效的 JSDoc 注释
"valid-jsdoc": 1,
// 强制 typeof 表达式与有效的字符串进行比较
// typeof foo === "undefimed" 错误
"valid-typeof": 2,
//////////////
// 最佳实践 //
//////////////

// 定义对象的set存取器属性时,强制定义get
"accessor-pairs": 2,
// 强制数组方法的回调函数中有 return 语句
"array-callback-return": 0,
// 强制把变量的使用限制在其定义的作用域范围内
"block-scoped-var": 0,
// 限制圈复杂度,也就是类似if else能连续接多少个
"complexity": [2, 9],
// 要求 return 语句要么总是指定返回的值,要么不指定
"consistent-return": 0,
// 强制所有控制语句使用一致的括号风格
"curly": [2, "all"],
// switch 语句强制 default 分支,也可添加 // no default 注释取消此次警告
"default-case": 2,
// 强制object.key 中 . 的位置,参数:
// property,'.'号应与属性在同一行
// object, '.' 号应与对象名在同一行
"dot-location": [2, "property"],
// 强制使用.号取属性
// 参数: allowKeywords:true 使用保留字做属性名时,只能使用.方式取属性
// false 使用保留字做属性名时, 只能使用[]方式取属性 e.g [2, {"allowKeywords": false}]
// allowPattern: 当属性名匹配提供的正则表达式时,允许使用[]方式取值,否则只能用.号取值 e.g [2, {"allowPattern": "^[a-z]+(_[a-z]+)+$"}]
"dot-notation": [2, {"allowKeywords": false}],
// 使用 === 替代 == allow-null允许null和undefined==
"eqeqeq": [2, "allow-null"],
// 要求 for-in 循环中有一个 if 语句
"guard-for-in": 2,
// 禁用 alert、confirm 和 prompt
"no-alert": 0,
// 禁用 arguments.caller 或 arguments.callee
"no-caller": 2,
// 不允许在 case 子句中使用词法声明
"no-case-declarations": 2,
// 禁止除法操作符显式的出现在正则表达式开始的位置
"no-div-regex": 2,
// 禁止 if 语句中有 return 之后有 else
"no-else-return": 0,
// 禁止出现空函数.如果一个函数包含了一条注释,它将不会被认为有问题。
"no-empty-function": 2,
// 禁止使用空解构模式no-empty-pattern
"no-empty-pattern": 2,
// 禁止在没有类型检查操作符的情况下与 null 进行比较
"no-eq-null": 1,
// 禁用 eval()
"no-eval": 2,
// 禁止扩展原生类型
"no-extend-native": 2,
// 禁止不必要的 .bind() 调用
"no-extra-bind": 2,
// 禁用不必要的标签
"no-extra-label:": 0,
// 禁止 case 语句落空
"no-fallthrough": 2,
// 禁止数字字面量中使用前导和末尾小数点
"no-floating-decimal": 2,
// 禁止使用短符号进行类型转换(!!fOO)
"no-implicit-coercion": 0,
// 禁止在全局范围内使用 var 和命名的 function 声明
"no-implicit-globals": 1,
// 禁止使用类似 eval() 的方法
"no-implied-eval": 2,
// 禁止 this 关键字出现在类和类对象之外
"no-invalid-this": 0,
// 禁用 __iterator__ 属性
"no-iterator": 2,
// 禁用标签语句
"no-labels": 2,
// 禁用不必要的嵌套块
"no-lone-blocks": 2,
// 禁止在循环中出现 function 声明和表达式
"no-loop-func": 1,
// 禁用魔术数字(3.14什么的用常量代替)
"no-magic-numbers":[1, {"ignore": [0, -1, 1] }],
// 禁止使用多个空格
"no-multi-spaces": 2,
// 禁止使用多行字符串,在 JavaScript 中,可以在新行之前使用斜线创建多行字符串
"no-multi-str": 2,
// 禁止对原生对象赋值
"no-native-reassign": 2,
// 禁止在非赋值或条件语句中使用 new 操作符
"no-new": 2,
// 禁止对 Function 对象使用 new 操作符
"no-new-func": 0,
// 禁止对 String,Number 和 Boolean 使用 new 操作符
"no-new-wrappers": 2,
// 禁用八进制字面量
"no-octal": 2,
// 禁止在字符串中使用八进制转义序列
"no-octal-escape": 2,
// 不允许对 function 的参数进行重新赋值
"no-param-reassign": 0,
// 禁用 __proto__ 属性
"no-proto": 2,
// 禁止使用 var 多次声明同一变量
"no-redeclare": 2,
// 禁用指定的通过 require 加载的模块
"no-return-assign": 0,
// 禁止使用 javascript: url
"no-script-url": 0,
// 禁止自我赋值
"no-self-assign": 2,
// 禁止自身比较
"no-self-compare": 2,
// 禁用逗号操作符
"no-sequences": 2,
// 禁止抛出非异常字面量
"no-throw-literal": 2,
// 禁用一成不变的循环条件
"no-unmodified-loop-condition": 2,
// 禁止出现未使用过的表达式
"no-unused-expressions": 0,
// 禁用未使用过的标签
"no-unused-labels": 2,
// 禁止不必要的 .call() 和 .apply()
"no-useless-call": 2,
// 禁止不必要的字符串字面量或模板字面量的连接
"no-useless-concat": 2,
// 禁用不必要的转义字符
"no-useless-escape": 0,
// 禁用 void 操作符
"no-void": 0,
// 禁止在注释中使用特定的警告术语
"no-warning-comments": 0,
// 禁用 with 语句
"no-with": 2,
// 强制在parseInt()使用基数参数
"radix": 2,
// 要求所有的 var 声明出现在它们所在的作用域顶部
"vars-on-top": 0,
// 要求 IIFE 使用括号括起来
"wrap-iife": [2, "any"],
// 要求或禁止 “Yoda” 条件
"yoda": [2, "never"],
// 要求或禁止使用严格模式指令
"strict": 0,
//////////////
// 变量声明 //
//////////////

// 要求或禁止 var 声明中的初始化(初值)
"init-declarations": 0,
// 不允许 catch 子句的参数与外层作用域中的变量同名
"no-catch-shadow": 0,
// 禁止删除变量
"no-delete-var": 2,
// 不允许标签与变量同名
"no-label-var": 2,
// 禁用特定的全局变量
"no-restricted-globals": 0,
// 禁止 var 声明 与外层作用域的变量同名
"no-shadow": 0,
// 禁止覆盖受限制的标识符
"no-shadow-restricted-names": 2,
// 禁用未声明的变量,除非它们在 /*global */ 注释中被提到
"no-undef": 2,
// 禁止将变量初始化为 undefined
"no-undef-init": 2,
// 禁止将 undefined 作为标识符
"no-undefined": 0,
// 禁止出现未使用过的变量
"no-unused-vars": [2, {"vars": "all", "args": "none"}],
// 不允许在变量定义之前使用它们
"no-use-before-define": 0,
//////////////////////////
// Node.js and CommonJS //
//////////////////////////

// require return statements after callbacks
"callback-return": 0,
// 要求 require() 出现在顶层模块作用域中
"global-require": 1,
// 要求回调函数中有容错处理
"handle-callback-err": [2, "^(err|error)$"],
// 禁止混合常规 var 声明和 require 调用
"no-mixed-requires": 0,
// 禁止调用 require 时使用 new 操作符
"no-new-require": 2,
// 禁止对 __dirname 和 __filename进行字符串连接
"no-path-concat": 0,
// 禁用 process.env
"no-process-env": 0,
// 禁用 process.exit()
"no-process-exit": 0,
// 禁用同步方法
"no-sync": 0,
//////////////
// 风格指南 //
//////////////

// 指定数组的元素之间要以空格隔开(, 后面), never参数:[ 之前和 ] 之后不能带空格,always参数:[ 之前和 ] 之后必须带空格
"array-bracket-spacing": [2, "never"],
// 禁止或强制在单行代码块中使用空格(禁用)
"block-spacing":[1, "never"],
//强制使用一致的缩进 第二个参数为 "tab" 时,会使用tab,
// if while function 后面的{必须与if在同一行,java风格。
"brace-style": [2, "1tbs", {"allowSingleLine": true}],
// 双峰驼命名格式
"camelcase": 2,
// 控制逗号前后的空格
"comma-spacing": [2, {"before": false, "after": true}],
// 控制逗号在行尾出现还是在行首出现 (默认行尾)
// http://eslint.org/docs/rules/comma-style
"comma-style": [2, "last"],
//"SwitchCase" (默认:0) 强制 switch 语句中的 case 子句的缩进水平
// 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always
"computed-property-spacing": [2, "never"],
// 用于指统一在回调函数中指向this的变量名,箭头函数中的this已经可以指向外层调用者,应该没卵用了
// e.g [0,"that"] 指定只能 var that = this. that不能指向其他任何值,this也不能赋值给that以外的其他值
"consistent-this": [1, "that"],
// 强制使用命名的 function 表达式
"func-names": 0,
// 文件末尾强制换行
"eol-last": 2,
"indent": [2, 4, {"SwitchCase": 1}],
// 强制在对象字面量的属性中键和值之间使用一致的间距
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
// 强制使用一致的换行风格
"linebreak-style": [1, "unix"],
// 要求在注释周围有空行 ( 要求在块级注释之前有一空行)
"lines-around-comment": [1, {"beforeBlockComment": true}],
// 强制一致地使用函数声明或函数表达式,方法定义风格,参数:
// declaration: 强制使用方法声明的方式,function f(){} e.g [2, "declaration"]
// expression:强制使用方法表达式的方式,var f = function() {} e.g [2, "expression"]
// allowArrowFunctions: declaration风格中允许箭头函数。 e.g [2, "declaration", { "allowArrowFunctions": true }]
"func-style": 0,
// 强制回调函数最大嵌套深度 5层
"max-nested-callbacks": [1, 5],
// 禁止使用指定的标识符
"id-blacklist": 0,
// 强制标识符的最新和最大长度
"id-length": 0,
// 要求标识符匹配一个指定的正则表达式
"id-match": 0,
// 强制在 JSX 属性中一致地使用双引号或单引号
"jsx-quotes": 0,
// 强制在关键字前后使用一致的空格 (前后腰需要)
"keyword-spacing": 2,
// 强制一行的最大长度
"max-len":[1, 200],
// 强制最大行数
"max-lines": 0,
// 强制 function 定义中最多允许的参数数量
"max-params":[1, 7],
// 强制 function 块最多允许的的语句数量
"max-statements":[1, 200],
// 强制每一行中所允许的最大语句数量
"max-statements-per-line": 0,
// 要求构造函数首字母大写 (要求调用 new 操作符时有首字母大小的函数,允许调用首字母大写的函数时没有 new 操作符。)
"new-cap": [2, {"newIsCap": true, "capIsNew": false}],
// 要求调用无参构造函数时有圆括号
"new-parens": 2,
// 要求或禁止 var 声明语句后有一行空行
"newline-after-var": 0,
// 禁止使用 Array 构造函数
"no-array-constructor": 2,
// 禁用按位运算符
"no-bitwise": 0,
// 要求 return 语句之前有一空行
"newline-before-return": 0,
// 要求方法链中每个调用都有一个换行符
"newline-per-chained-call": 1,
// 禁用 continue 语句
"no-continue": 0,
// 禁止在代码行后使用内联注释
"no-inline-comments": 0,
// 禁止 if 作为唯一的语句出现在 else 语句中
"no-lonely-if": 0,
// 禁止混合使用不同的操作符
"no-mixed-operators": 0,
// 不允许空格和 tab 混合缩进
"no-mixed-spaces-and-tabs": 2,
// 不允许多个空行
"no-multiple-empty-lines": [2, {"max": 2}],
// 不允许否定的表达式
"no-negated-condition": 0,
// 不允许使用嵌套的三元表达式
"no-nested-ternary": 0,
// 禁止使用 Object 的构造函数
"no-new-object": 2,
// 禁止使用一元操作符 ++ 和 --
"no-plusplus": 0,
// 禁止使用特定的语法
"no-restricted-syntax": 0,
// 禁止 function 标识符和括号之间出现空格
"no-spaced-func": 2,
// 不允许使用三元操作符
"no-ternary": 0,
// 禁用行尾空格
"no-trailing-spaces": 2,
// 禁止标识符中有悬空下划线_bar
"no-underscore-dangle": 0,
// 禁止可以在有更简单的可替代的表达式时使用三元操作符
"no-unneeded-ternary": 2,
// 禁止属性前有空白
"no-whitespace-before-property": 0,
// 强制花括号内换行符的一致性
"object-curly-newline": 0,
// 强制在花括号中使用一致的空格
"object-curly-spacing": 0,
// 强制将对象的属性放在不同的行上
"object-property-newline": 0,
// 强制函数中的变量要么一起声明要么分开声明
"one-var": [2, {"initialized": "never"}],
// 要求或禁止在 var 声明周围换行
"one-var-declaration-per-line": 0,
// 要求或禁止在可能的情况下要求使用简化的赋值操作符
"operator-assignment": 0,
// 强制操作符使用一致的换行符
"operator-linebreak": [2, "after", {"overrides": {"?":"before", ":": "before"}}],
// 要求或禁止块内填充
"padded-blocks": 0,
// 要求对象字面量属性名称用引号括起来
"quote-props": 0,
// 强制使用一致的反勾号、双引号或单引号
"quotes": [2, "single", "avoid-escape"],
// 要求使用 JSDoc 注释
"require-jsdoc": 1,
// 要求或禁止使用分号而不是 ASI(这个才是控制行尾部分号的,)
"semi": [2, "always"],
// 强制分号之前和之后使用一致的空格
"semi-spacing": 0,
// 要求同一个声明块中的变量按顺序排列
"sort-vars": 0,
// 强制在块之前使用一致的空格
"space-before-blocks": [2, "always"],
// 强制在 function的左括号之前使用一致的空格
"space-before-function-paren": [2, "always"],
// 强制在圆括号内使用一致的空格
"space-in-parens": [2, "never"],
// 要求操作符周围有空格
"space-infix-ops": 2,
// 强制在一元操作符前后使用一致的空格
"space-unary-ops": [2, {"words": true, "nonwords": false}],
// 强制在注释中 // 或 /* 使用一致的空格
"spaced-comment": [2, "always", {"markers": ["global", "globals", "eslint", "eslint-disable", "*package","!"] }],
// 要求或禁止 Unicode BOM
"unicode-bom": 0,
// 要求正则表达式被括号括起来
"wrap-regex": 0,
//////////////
// ES6.相关 //
//////////////

// 要求箭头函数体使用大括号
"arrow-body-style": 2,
// 要求箭头函数的参数使用圆括号
"arrow-parens": 2,
"arrow-spacing":[2, {"before": true, "after": true}],
// 强制在子类构造函数中用super()调用父类构造函数,TypeScrip的编译器也会提示
"constructor-super": 0,
// 强制 generator 函数中 * 号周围使用一致的空格
"generator-star-spacing": [2, {"before": true, "after": true}],
// 禁止修改类声明的变量
"no-class-assign": 2,
// 不允许箭头功能,在那里他们可以混淆的比较
"no-confusing-arrow": 0,
// 禁止修改 const 声明的变量
"no-const-assign": 2,
// 禁止类成员中出现重复的名称
"no-dupe-class-members": 2,
// 不允许复制模块的进口
"no-duplicate-imports": 0,
// 禁止 Symbol 的构造函数
"no-new-symbol": 2,
// 允许指定模块加载时的进口
"no-restricted-imports": 0,
// 禁止在构造函数中,在调用 super() 之前使用 this 或 super
"no-this-before-super": 2,
// 禁止不必要的计算性能键对象的文字
"no-useless-computed-key": 0,
// 要求使用 let 或 const 而不是 var
"no-var": 0,
// 要求或禁止对象字面量中方法和属性使用简写语法
"object-shorthand": 0,
// 要求使用箭头函数作为回调
"prefer-arrow-callback": 0,
// 要求使用 const 声明那些声明后不再被修改的变量
"prefer-const": 0,
// 要求在合适的地方使用 Reflect 方法
"prefer-reflect": 0,
// 要求使用扩展运算符而非 .apply()
"prefer-spread": 0,
// 要求使用模板字面量而非字符串连接
"prefer-template": 0,
// Suggest using the rest parameters instead of arguments
"prefer-rest-params": 0,
// 要求generator 函数内有 yield
"require-yield": 0,
// enforce spacing between rest and spread operators and their expressions
"rest-spread-spacing": 0,
// 强制模块内的 import 排序
"sort-imports": 0,
// 要求或禁止模板字符串中的嵌入表达式周围空格的使用
"template-curly-spacing": 1,
// 强制在 yield* 表达式中 * 周围使用空格
"yield-star-spacing": 2
}

目前我在使用的规则是下面这个,兼容了之前的旧代码并且放弃了一部分样式规则。

{
    "env": {
        "node": true,
        "es6": true
    },
    "parser": "babel-eslint",
    "extends": ["eslint:recommended", "plugin:react/recommended"],
    "parserOptions": {
        "ecmaFeatures": {
            "experimentalObjectRestSpread": true,
            "jsx": true
        },
        "sourceType": "module"
    },
    "plugins": ["react"],
    "globals": {
        "fetch": false,
        "__DEV__": false,
        "WebSocket":false,
        "FormData":false
    },
    "rules": {
        "indent": ["error", 4],
        "linebreak-style": ["error", "unix"],
        "no-extra-semi": ["error"],
        "no-unused-vars": 0,
        "react/display-name": 0,
        "react/prop-types":0,
        "react/no-string-refs":0,
        "keyword-spacing": "error",
        "no-spaced-func": "error",
        "space-in-parens": "error",
        "no-console": ["error", {
            "allow": ["log"]
        }],
        "curly": [2, "multi-line"],
        "array-bracket-spacing": 2,
        "block-spacing": 2,
        "brace-style": 2,
        "comma-spacing": 2,
        "space-infix-ops": 2,
        "arrow-spacing": 2,
        "constructor-super": 2,
        "no-var": 2,
        "no-extra-parens":"error"
    }
}

IDE 支持

WebStorm:Preferences → Languages & Frameworks → JavaScript → Code Quality Tools → Eslint → Enable (勾选) → Apply → OK

Atom:安装 linter-eslint

Sublime:安装 SublimeLinter

安装 SublimeLinter-contrib-eslint

IDE 配置统一规则

在根目录下创建一个样式规则文件 . editorconfig,这个文件就是为了统一不同的 IDE 下的规则的。这里简单设置了一下文件的编码格式 UTF8,以及最重要的统一使用 4 个空格!!

# EditorConfig is awesome: http://EditorConfig.org

# top-most EditorConfig file
root = true

# Set default charset
[*.{js}]
charset = utf-8

[*.js]
indent_style = space
indent_size = 4

如果不想让 ESlint 去检查 Jest 规范的文件,可以在根目录下创建 . eslintignore 文件,这个文件和 Git 的排除是一样的,模仿使用即可。

__tests__
.git
.vscode
android
bundle
ios
lib
node_modules

这里再教大家一个简单的全局样式处理方法,该方法就是使用 ESlint 的规则直接修改文件内容,执行命令node_modules/eslint/bin/eslint.js . --fix就可以了,这里使用本地路径中的 ESlint。