├── .watchmanconfig
├── .gitattributes
├── .eslintignore
├── src
├── stores
│ ├── index.js
│ └── app-store.js
├── assets
│ ├── styles
│ │ └── colors-theme.js
│ └── animations
│ │ └── loading.json
├── common
│ ├── service-error.js
│ ├── code.js
│ ├── constants.js
│ ├── event.js
│ ├── loading.js
│ ├── global-error-handler.js
│ ├── history.js
│ ├── tab-nav.js
│ └── notification-center.js
├── services
│ ├── list-service.js
│ └── base-service.js
├── pages
│ ├── page1
│ │ └── page1.js
│ ├── page2
│ │ └── page2.js
│ ├── page3
│ │ └── page3.js
│ ├── detail
│ │ └── detail.js
│ ├── home
│ │ └── home.js
│ └── list
│ │ └── list.js
├── router.js
├── hocs
│ └── loading-hoc.js
└── components
│ └── navi-bar.js
├── app.json
├── images-folder
└── app.gif
├── android
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ └── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ ├── assets
│ │ │ │ └── fonts
│ │ │ │ │ ├── Entypo.ttf
│ │ │ │ │ ├── Zocial.ttf
│ │ │ │ │ ├── Feather.ttf
│ │ │ │ │ ├── Ionicons.ttf
│ │ │ │ │ ├── Octicons.ttf
│ │ │ │ │ ├── antfill.ttf
│ │ │ │ │ ├── AntDesign.ttf
│ │ │ │ │ ├── EvilIcons.ttf
│ │ │ │ │ ├── FontAwesome.ttf
│ │ │ │ │ ├── Foundation.ttf
│ │ │ │ │ ├── antoutline.ttf
│ │ │ │ │ ├── MaterialIcons.ttf
│ │ │ │ │ ├── SimpleLineIcons.ttf
│ │ │ │ │ ├── FontAwesome5_Solid.ttf
│ │ │ │ │ ├── FontAwesome5_Brands.ttf
│ │ │ │ │ ├── FontAwesome5_Regular.ttf
│ │ │ │ │ └── MaterialCommunityIcons.ttf
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── reactnativeappdemo
│ │ │ │ │ ├── MainActivity.java
│ │ │ │ │ └── MainApplication.java
│ │ │ └── AndroidManifest.xml
│ │ └── debug
│ │ │ └── AndroidManifest.xml
│ ├── build_defs.bzl
│ ├── proguard-rules.pro
│ ├── BUCK
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── keystores
│ ├── debug.keystore.properties
│ └── BUCK
├── settings.gradle
├── gradle.properties
├── build.gradle
├── gradlew.bat
└── gradlew
├── ios
├── ReactNativeAppDemo
│ ├── Images.xcassets
│ │ ├── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── AppDelegate.h
│ ├── main.m
│ ├── AppDelegate.m
│ ├── Info.plist
│ └── Base.lproj
│ │ └── LaunchScreen.xib
├── ReactNativeAppDemoTests
│ ├── Info.plist
│ └── ReactNativeAppDemoTests.m
├── ReactNativeAppDemo-tvOSTests
│ └── Info.plist
├── ReactNativeAppDemo-tvOS
│ └── Info.plist
└── ReactNativeAppDemo.xcodeproj
│ └── xcshareddata
│ └── xcschemes
│ ├── ReactNativeAppDemo.xcscheme
│ └── ReactNativeAppDemo-tvOS.xcscheme
├── .buckconfig
├── index.js
├── __tests__
└── App-test.js
├── metro.config.js
├── babel.config.js
├── .gitignore
├── package.json
├── .flowconfig
├── App.js
├── .eslintrc
└── README.md
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | android
2 | ios
3 | node_modules
--------------------------------------------------------------------------------
/src/stores/index.js:
--------------------------------------------------------------------------------
1 | import {appStore} from './app-store'
2 |
3 | export {appStore}
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ReactNativeAppDemo",
3 | "displayName": "ReactNativeAppDemo"
4 | }
--------------------------------------------------------------------------------
/images-folder/app.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/images-folder/app.gif
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ReactNativeAppDemo
3 |
4 |
--------------------------------------------------------------------------------
/ios/ReactNativeAppDemo/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.buckconfig:
--------------------------------------------------------------------------------
1 |
2 | [android]
3 | target = Google Inc.:Google APIs:23
4 |
5 | [maven_repositories]
6 | central = https://repo1.maven.org/maven2
7 |
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/Entypo.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/Entypo.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/Zocial.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/Zocial.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/Feather.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/Feather.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/Ionicons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/Ionicons.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/Octicons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/Octicons.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/antfill.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/antfill.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/AntDesign.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/AntDesign.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/EvilIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/EvilIcons.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/FontAwesome.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/FontAwesome.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/Foundation.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/Foundation.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/antoutline.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/antoutline.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/MaterialIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/MaterialIcons.ttf
--------------------------------------------------------------------------------
/android/keystores/debug.keystore.properties:
--------------------------------------------------------------------------------
1 | key.store=debug.keystore
2 | key.alias=androiddebugkey
3 | key.store.password=android
4 | key.alias.password=android
5 |
--------------------------------------------------------------------------------
/src/assets/styles/colors-theme.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 控制全app的颜色
3 | * @type {{statusBarColor: string}}
4 | */
5 | export const colors = {
6 | statusBarColor: '#23A2FF'
7 | }
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/SimpleLineIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/SimpleLineIcons.ttf
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niunai2016/ReactNativeAppDemo/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/keystores/BUCK:
--------------------------------------------------------------------------------
1 | keystore(
2 | name = "debug",
3 | properties = "debug.keystore.properties",
4 | store = "debug.keystore",
5 | visibility = [
6 | "PUBLIC",
7 | ],
8 | )
9 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
6 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import {AppRegistry} from 'react-native';
6 | import App from './App';
7 | import {name as appName} from './app.json';
8 |
9 | console.disableYellowBox = true;
10 |
11 | AppRegistry.registerComponent(appName, () => App);
12 |
--------------------------------------------------------------------------------
/src/common/service-error.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 服务报错处理
3 | */
4 |
5 | export default class ServiceError extends Error{
6 | constructor(code, message){
7 | super(message);
8 | this.code = code;
9 | this.hash = Math.random() * 100000000000000000;
10 | this.signature = 'ServiceError';
11 | }
12 | }
--------------------------------------------------------------------------------
/__tests__/App-test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import 'react-native';
6 | import React from 'react';
7 | import App from '../App';
8 |
9 | // Note: test renderer must be required after react-native.
10 | import renderer from 'react-test-renderer';
11 |
12 | it('renders correctly', () => {
13 | renderer.create();
14 | });
15 |
--------------------------------------------------------------------------------
/src/common/code.js:
--------------------------------------------------------------------------------
1 | /**
2 | * code.js提供全局的请求服务字段处理
3 | */
4 | export default {
5 | SUCCESS: 'SUCCESS', //请求成功
6 | REQUEST_FAILED: 'REQUEST_FAILED', //请求失败
7 | REQUEST_TIMEOUT: 'REQUEST_TIMEOUT', //请求超时
8 | UN_KNOWN_ERROR: 'UN_KNOWN_ERROR', //未知错误
9 | TOKEN_INVALID: 'TOKEN_INVALID', //token失效
10 | SESSION_TIMEOUT: 'SESSION_TIMEOUT', //会话超时
11 | }
--------------------------------------------------------------------------------
/src/services/list-service.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 列表页服务
3 | */
4 | import BaseService from './base-service';
5 |
6 | export default class ListService extends BaseService {
7 | /**
8 | * 获取列表
9 | * @return {Promise}
10 | */
11 | async getList() {
12 | const res = await this.postJson('qryList.do', {})
13 |
14 | return res;
15 | }
16 | }
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Metro configuration for React Native
3 | * https://github.com/facebook/react-native
4 | *
5 | * @format
6 | */
7 |
8 | module.exports = {
9 | transformer: {
10 | getTransformOptions: async () => ({
11 | transform: {
12 | experimentalImportSupport: false,
13 | inlineRequires: false,
14 | },
15 | }),
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/stores/app-store.js:
--------------------------------------------------------------------------------
1 | import {observable, action} from 'mobx'
2 |
3 | class AppStore {
4 | @observable
5 | list = []
6 |
7 | @observable
8 | timer = 0
9 |
10 | @action
11 | setList(data){
12 | this.list = data
13 | }
14 |
15 | @action
16 | resetTimer() {
17 | this.timer = 0
18 | }
19 |
20 | @action
21 | tick() {
22 | this.timer += 1
23 | }
24 | }
25 |
26 | const appStore = new AppStore()
27 | export {appStore}
--------------------------------------------------------------------------------
/src/common/constants.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 提供基础配置信息
3 | * constants.js 提供如服务器地址、分页数量、设备类型、设备号、版本号等配置
4 | */
5 | import { Platform } from 'react-native'
6 | import DeviceInfo from 'react-native-device-info'
7 |
8 | export default {
9 | serverUrl: 'http://127.0.0.1:3600/portal',
10 | pageSize: 10,
11 | deviceType: Platform.OS.toUpperCase(),
12 | deviceNo: DeviceInfo.getUniqueID().replace('-').substr(0, 12),
13 | versionName: DeviceInfo.getVersion(), //也可写死如'1.0.0'
14 | }
--------------------------------------------------------------------------------
/ios/ReactNativeAppDemo/AppDelegate.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 | @property (nonatomic, strong) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/ios/ReactNativeAppDemo/main.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 |
10 | #import "AppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | @autoreleasepool {
14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/reactnativeappdemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.reactnativeappdemo;
2 |
3 | import com.facebook.react.ReactActivity;
4 |
5 | public class MainActivity extends ReactActivity {
6 |
7 | /**
8 | * Returns the name of the main component registered from JavaScript.
9 | * This is used to schedule rendering of the component.
10 | */
11 | @Override
12 | protected String getMainComponentName() {
13 | return "ReactNativeAppDemo";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/pages/page1/page1.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { View, Text } from 'react-native'
3 | import NaviBar from '../../components/navi-bar';
4 |
5 | export default class Page1 extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 | Page1
12 |
13 |
14 | )
15 | }
16 | }
--------------------------------------------------------------------------------
/src/pages/page2/page2.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { View, Text } from 'react-native'
3 | import NaviBar from '../../components/navi-bar';
4 |
5 | export default class Page2 extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 | Page2
12 |
13 |
14 | )
15 | }
16 | }
--------------------------------------------------------------------------------
/src/pages/page3/page3.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { View, Text } from 'react-native'
3 | import NaviBar from '../../components/navi-bar';
4 |
5 | export default class Page3 extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 | Page3
12 |
13 |
14 | )
15 | }
16 | }
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:metro-react-native-babel-preset', '@babel/preset-flow'],
3 | plugins: [
4 | '@babel/transform-flow-strip-types',
5 | [
6 | '@babel/plugin-proposal-decorators', { 'legacy' : true }
7 | ],
8 | [
9 | '@babel/plugin-proposal-class-properties', {'loose': true}
10 | ],
11 | [
12 | '@babel/plugin-transform-runtime', {}
13 | ],
14 | ['import', { 'libraryName': '@ant-design/react-native' }]
15 | ]
16 | };
17 |
--------------------------------------------------------------------------------
/src/common/event.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 一个JavaScript 事件消息总线
3 | */
4 | import NotificationCenter from './notification-center';
5 |
6 | export default class Event {
7 | static listen(eventName, callback, observer) {
8 | NotificationCenter.addNotification(eventName, callback, observer);
9 | }
10 | static emit(eventName, params) {
11 | NotificationCenter.postNotificationName(eventName, params);
12 | }
13 | static remove(eventName, observer) {
14 | NotificationCenter.removeNotification(eventName, observer);
15 | }
16 | }
--------------------------------------------------------------------------------
/src/pages/detail/detail.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { View, Text } from 'react-native'
3 | import NaviBar from '../../components/navi-bar';
4 | import history from '../../common/history';
5 |
6 | export default class Detail extends Component {
7 | render() {
8 | return (
9 |
10 |
14 |
15 | Detail
16 |
17 |
18 | )
19 | }
20 | }
--------------------------------------------------------------------------------
/android/app/build_defs.bzl:
--------------------------------------------------------------------------------
1 | """Helper definitions to glob .aar and .jar targets"""
2 |
3 | def create_aar_targets(aarfiles):
4 | for aarfile in aarfiles:
5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
6 | lib_deps.append(":" + name)
7 | android_prebuilt_aar(
8 | name = name,
9 | aar = aarfile,
10 | )
11 |
12 | def create_jar_targets(jarfiles):
13 | for jarfile in jarfiles:
14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
15 | lib_deps.append(":" + name)
16 | prebuilt_jar(
17 | name = name,
18 | binary_jar = jarfile,
19 | )
20 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | import {createStackNavigator, createAppContainer} from 'react-navigation'
2 | import List from './pages/list/list';
3 | import Detail from './pages/detail/detail';
4 | import {TabNav} from './common/tab-nav';
5 |
6 | function generateRoute(path, screen) {
7 | return {
8 | path,
9 | screen
10 | }
11 | }
12 |
13 |
14 | const stackRouterMap = {
15 | list: generateRoute('/list', List),
16 | detail: generateRoute('/detail', Detail),
17 | main: TabNav
18 | }
19 |
20 | const stackNavigate = createStackNavigator(stackRouterMap, {
21 | initialRouteName: 'main',
22 | headerMode: 'none'
23 | })
24 |
25 | const Router = createAppContainer(stackNavigate)
26 |
27 | export default Router
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'ReactNativeAppDemo'
2 | include ':lottie-react-native'
3 | project(':lottie-react-native').projectDir = new File(rootProject.projectDir, '../node_modules/lottie-react-native/src/android')
4 | include ':react-native-device-info'
5 | project(':react-native-device-info').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-device-info/android')
6 | include ':react-native-vector-icons'
7 | project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
8 | include ':react-native-gesture-handler'
9 | project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')
10 |
11 | include ':app'
12 |
--------------------------------------------------------------------------------
/ios/ReactNativeAppDemo/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/src/hocs/loading-hoc.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Dimensions, View} from 'react-native';
3 | const { width, height } = Dimensions.get('window')
4 | import Event from '../common/event'
5 |
6 | export default function LoadingHoc(WrappedComponent) {
7 | return class ComposedComponent extends Component {
8 | showLoading(){
9 | Event.emit('SHOW_LOADING')
10 | }
11 |
12 | hideLoading(){
13 | Event.emit('HIDE_LOADING')
14 | }
15 |
16 | render() {
17 | const props = {...this.props, ...{
18 | showLoading: this.showLoading.bind(this),
19 | hideLoading: this.hideLoading.bind(this)
20 | }};
21 | return (
22 |
23 |
24 |
25 | );
26 | }
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/ios/ReactNativeAppDemoTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/ios/ReactNativeAppDemo-tvOSTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext {
5 | buildToolsVersion = "28.0.3"
6 | minSdkVersion = 16
7 | compileSdkVersion = 28
8 | targetSdkVersion = 28
9 | supportLibVersion = "28.0.0"
10 | }
11 | repositories {
12 | google()
13 | jcenter()
14 | }
15 | dependencies {
16 | classpath 'com.android.tools.build:gradle:3.3.1'
17 |
18 | // NOTE: Do not place your application dependencies here; they belong
19 | // in the individual module build.gradle files
20 | }
21 | }
22 |
23 | allprojects {
24 | repositories {
25 | mavenLocal()
26 | google()
27 | jcenter()
28 | maven {
29 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
30 | url "$rootDir/../node_modules/react-native/android"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | project.xcworkspace
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 |
33 | # node.js
34 | #
35 | node_modules/
36 | npm-debug.log
37 | yarn-error.log
38 | yarn.lock
39 | npm.lock
40 | package-lock.json
41 |
42 | # BUCK
43 | buck-out/
44 | \.buckd/
45 | *.keystore
46 |
47 | # fastlane
48 | #
49 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
50 | # screenshots whenever they are needed.
51 | # For more information about the recommended setup visit:
52 | # https://docs.fastlane.tools/best-practices/source-control/
53 |
54 | */fastlane/report.xml
55 | */fastlane/Preview.html
56 | */fastlane/screenshots
57 |
58 | # Bundle artifact
59 | *.jsbundle
60 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/common/loading.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {View, Dimensions, StyleSheet} from 'react-native';
3 | import LottieView from 'lottie-react-native';
4 | import LoadingAnimation from '../assets/animations/loading';
5 |
6 | const { width, height } = Dimensions.get('window')
7 |
8 | export default class LoadingView extends Component {
9 | render(){
10 | if(this.props.visible){
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | )
18 | }else{
19 | return
20 | }
21 | }
22 | }
23 |
24 | const styles = StyleSheet.flatten({
25 | wrapper: {
26 | position: 'absolute',
27 | top: 0,
28 | left: 0,
29 | width: width,
30 | height: height,
31 | backgroundColor: 'rgba(0, 0, 0, 0.2)'
32 | },
33 | loading:{
34 | position: 'absolute',
35 | top: height / 2 - 100,
36 | left: width / 2 - 70,
37 | width: 140,
38 | height: 140
39 | }
40 | });
--------------------------------------------------------------------------------
/src/common/global-error-handler.js:
--------------------------------------------------------------------------------
1 | import code from './code';
2 | import Event from './event'
3 |
4 | export function handleErrors(error){
5 | if(error && error.signature && error.signature === 'ServiceError') {
6 | defaultServiceErrorHandler(error);
7 | }else{
8 | defaultErrorHandler(error);
9 | }
10 | }
11 |
12 | function defaultServiceErrorHandler(error){
13 | if(error && error.code === code.SESSION_TIMEOUT){
14 | Event.emit('GLOBAL_ERROR', {
15 | type: 'SESSION_TIMEOUT'
16 | })
17 | }else if(error && error.message) {
18 | Event.emit('GLOBAL_ERROR', {
19 | type: 'SERVICE_ERROR',
20 | message: error.message
21 | })
22 | }else {
23 | Event.emit('GLOBAL_ERROR', {
24 | type: 'SERVICE_ERROR',
25 | message: '服务出错,请稍后再试.'
26 | })
27 | }
28 | }
29 |
30 | function defaultErrorHandler(error){
31 | if(error && error.message) {
32 | Event.emit('GLOBAL_ERROR', {
33 | type: 'SERVICE_ERROR',
34 | message: error.message
35 | })
36 | }else {
37 | Event.emit('GLOBAL_ERROR', {
38 | type: 'SERVICE_ERROR',
39 | message: '服务出错,请稍后再试.'
40 | })
41 | }
42 | }
--------------------------------------------------------------------------------
/android/app/BUCK:
--------------------------------------------------------------------------------
1 | # To learn about Buck see [Docs](https://buckbuild.com/).
2 | # To run your application with Buck:
3 | # - install Buck
4 | # - `npm start` - to start the packager
5 | # - `cd android`
6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
8 | # - `buck install -r android/app` - compile, install and run application
9 | #
10 |
11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
12 |
13 | lib_deps = []
14 |
15 | create_aar_targets(glob(["libs/*.aar"]))
16 |
17 | create_jar_targets(glob(["libs/*.jar"]))
18 |
19 | android_library(
20 | name = "all-libs",
21 | exported_deps = lib_deps,
22 | )
23 |
24 | android_library(
25 | name = "app-code",
26 | srcs = glob([
27 | "src/main/java/**/*.java",
28 | ]),
29 | deps = [
30 | ":all-libs",
31 | ":build_config",
32 | ":res",
33 | ],
34 | )
35 |
36 | android_build_config(
37 | name = "build_config",
38 | package = "com.reactnativeappdemo",
39 | )
40 |
41 | android_resource(
42 | name = "res",
43 | package = "com.reactnativeappdemo",
44 | res = "src/main/res",
45 | )
46 |
47 | android_binary(
48 | name = "app",
49 | keystore = "//android/keystores:debug",
50 | manifest = "src/main/AndroidManifest.xml",
51 | package_type = "debug",
52 | deps = [
53 | ":app-code",
54 | ],
55 | )
56 |
--------------------------------------------------------------------------------
/ios/ReactNativeAppDemo/AppDelegate.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import "AppDelegate.h"
9 |
10 | #import
11 | #import
12 | #import
13 |
14 | @implementation AppDelegate
15 |
16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
17 | {
18 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
19 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
20 | moduleName:@"ReactNativeAppDemo"
21 | initialProperties:nil];
22 |
23 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
24 |
25 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
26 | UIViewController *rootViewController = [UIViewController new];
27 | rootViewController.view = rootView;
28 | self.window.rootViewController = rootViewController;
29 | [self.window makeKeyAndVisible];
30 | return YES;
31 | }
32 |
33 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
34 | {
35 | #if DEBUG
36 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
37 | #else
38 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
39 | #endif
40 | }
41 |
42 | @end
43 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/reactnativeappdemo/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.reactnativeappdemo;
2 |
3 | import android.app.Application;
4 |
5 | import com.facebook.react.ReactApplication;
6 | import com.airbnb.android.react.lottie.LottiePackage;
7 | import com.learnium.RNDeviceInfo.RNDeviceInfo;
8 | import com.oblador.vectoricons.VectorIconsPackage;
9 | import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
10 | import com.facebook.react.ReactNativeHost;
11 | import com.facebook.react.ReactPackage;
12 | import com.facebook.react.shell.MainReactPackage;
13 | import com.facebook.soloader.SoLoader;
14 |
15 | import java.util.Arrays;
16 | import java.util.List;
17 |
18 | public class MainApplication extends Application implements ReactApplication {
19 |
20 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
21 | @Override
22 | public boolean getUseDeveloperSupport() {
23 | return BuildConfig.DEBUG;
24 | }
25 |
26 | @Override
27 | protected List getPackages() {
28 | return Arrays.asList(
29 | new MainReactPackage(),
30 | new LottiePackage(),
31 | new RNDeviceInfo(),
32 | new VectorIconsPackage(),
33 | new RNGestureHandlerPackage()
34 | );
35 | }
36 |
37 | @Override
38 | protected String getJSMainModuleName() {
39 | return "index";
40 | }
41 | };
42 |
43 | @Override
44 | public ReactNativeHost getReactNativeHost() {
45 | return mReactNativeHost;
46 | }
47 |
48 | @Override
49 | public void onCreate() {
50 | super.onCreate();
51 | SoLoader.init(this, /* native exopackage */ false);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/ios/ReactNativeAppDemo-tvOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UIViewControllerBasedStatusBarAppearance
38 |
39 | NSLocationWhenInUseUsageDescription
40 |
41 | NSAppTransportSecurity
42 |
43 |
44 | NSExceptionDomains
45 |
46 | localhost
47 |
48 | NSExceptionAllowsInsecureHTTPLoads
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/common/history.js:
--------------------------------------------------------------------------------
1 | const NAVIGATION_THROTTLE = 1000; // 1s内不准重复跳转
2 | const lastNavigationTimeStamps = {};
3 |
4 | /**
5 | * 校验页面跳转参数 防止同一个path在很短的时间内被反复调用
6 | * @param path
7 | */
8 | function validate(path) {
9 | const timestamp = new Date().valueOf();
10 | if(lastNavigationTimeStamps[path] && (timestamp - lastNavigationTimeStamps[path]) < NAVIGATION_THROTTLE) {
11 | lastNavigationTimeStamps[path] = timestamp
12 | return false
13 | } else {
14 | lastNavigationTimeStamps[path] = timestamp
15 | }
16 |
17 | return true
18 | }
19 |
20 | /**
21 | * 处理路由跳转的状态
22 | * @param prevState
23 | * @param newState
24 | * @param action
25 | */
26 | export function handleNavigationChange(prevState, newState, action) {
27 | console.log('@@@@@ prevState', prevState)
28 | console.log('@@@@@ newState', newState)
29 | console.log('@@@@@ action', action)
30 | }
31 |
32 | const history = {
33 | push: (instance, path, state) => {
34 | if(validate(path)) {
35 | const navigationController = instance.props.navigation;
36 | const nativePath =
37 | path.charAt(0) === '/' ? path.substring(1, path.length) : path;
38 |
39 | navigationController.push(nativePath, state)
40 | }
41 | },
42 | replace: (instance, path, state) => {
43 | if(validate(path)) {
44 | const navigationController = instance.props.navigation;
45 | const nativePath =
46 | path.charAt(0) === '/' ? path.substring(1, path.length) : path;
47 |
48 | navigationController.replace(nativePath, state)
49 | }
50 | },
51 | goBack: (instance) => {
52 | if(instance) {
53 | const navigationController = instance.props.navigation;
54 | navigationController.goBack()
55 | }
56 | },
57 | pop: (instance, n) => {
58 | if(instance) {
59 | const navigationController = instance.props.navigation;
60 | navigationController.pop(-1 * n || -1)
61 | }
62 | }
63 | }
64 |
65 | export default history;
--------------------------------------------------------------------------------
/src/components/navi-bar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { View, Text, StyleSheet, Dimensions, TouchableOpacity, Platform } from 'react-native'
3 | import PropTypes from 'prop-types'
4 | import {colors} from '../assets/styles/colors-theme';
5 | import Icon from 'react-native-vector-icons/Ionicons'
6 |
7 | const { width } = Dimensions.get('window')
8 |
9 | export default class NaviBar extends Component {
10 | static propTypes = {
11 | style: PropTypes.object,
12 | leftItem: PropTypes.node, //原则上控制在宽度40的icon
13 | rightItem: PropTypes.node, //原则上控制在宽度40的icon
14 | title: PropTypes.string,
15 | titleColor: PropTypes.string,
16 | onBack: PropTypes.func,
17 | iconColor: PropTypes.string
18 | }
19 |
20 | render() {
21 | const props = this.props;
22 |
23 | return (
24 |
25 |
26 | {
27 | props.leftItem ? props.leftItem : (
28 | props.onBack ? (
29 |
30 |
35 |
36 | ) :
37 | )
38 | }
39 |
40 | {props.title}
41 |
42 | {
43 | props.rightItem ? props.rightItem :
44 | }
45 |
46 |
47 | )
48 | }
49 | }
50 |
51 | const styles = StyleSheet.flatten({
52 | naviBar: {
53 | width,
54 | height: Platform.OS === 'ios' ? 44 : 56, //ios原生导航高度是44,android是56
55 | backgroundColor: colors.statusBarColor,
56 | flexDirection: 'row',
57 | alignItems: 'center',
58 | justifyContent: 'space-between'
59 | }
60 | })
--------------------------------------------------------------------------------
/src/pages/home/home.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
3 | import {colors} from '../../assets/styles/colors-theme';
4 | import history from '../../common/history';
5 | import NaviBar from '../../components/navi-bar';
6 | import { inject, observer } from 'mobx-react';
7 |
8 | @inject('rootStore')
9 | @observer
10 | export default class Home extends Component {
11 | constructor(props) {
12 | super(props)
13 | this.store = props.rootStore.appStore
14 | }
15 |
16 | render() {
17 | return (
18 |
19 |
20 |
21 | Home
22 | {
23 | //添加timer的次数
24 | this.store.tick();
25 | const list = this.store.list;
26 |
27 | list.push({
28 | number: this.store.timer,
29 | label: '第'+this.store.timer + '次点击'
30 | })
31 |
32 | this.store.setList(list)
33 | history.push(this, '/list', {name: 'niunai'})
34 | }}>
35 | 跳转到List
36 |
37 |
38 | 统计跳转到List的次数: {this.store.timer}
39 |
40 | {
41 | this.store.setList([]);
42 | this.store.resetTimer();
43 | }}>
44 | 重置List和timer
45 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | const styles = StyleSheet.flatten({
53 | button: {
54 | marginTop: 20,
55 | width: 100,
56 | height: 40,
57 | alignItems: 'center',
58 | justifyContent: 'center',
59 | backgroundColor: colors.statusBarColor
60 | },
61 | buttonText: {
62 | color: '#fff'
63 | }
64 | })
--------------------------------------------------------------------------------
/ios/ReactNativeAppDemoTests/ReactNativeAppDemoTests.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 | #import
10 |
11 | #import
12 | #import
13 |
14 | #define TIMEOUT_SECONDS 600
15 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!"
16 |
17 | @interface ReactNativeAppDemoTests : XCTestCase
18 |
19 | @end
20 |
21 | @implementation ReactNativeAppDemoTests
22 |
23 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
24 | {
25 | if (test(view)) {
26 | return YES;
27 | }
28 | for (UIView *subview in [view subviews]) {
29 | if ([self findSubviewInView:subview matching:test]) {
30 | return YES;
31 | }
32 | }
33 | return NO;
34 | }
35 |
36 | - (void)testRendersWelcomeScreen
37 | {
38 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
39 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
40 | BOOL foundElement = NO;
41 |
42 | __block NSString *redboxError = nil;
43 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
44 | if (level >= RCTLogLevelError) {
45 | redboxError = message;
46 | }
47 | });
48 |
49 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
50 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
51 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
52 |
53 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
54 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
55 | return YES;
56 | }
57 | return NO;
58 | }];
59 | }
60 |
61 | RCTSetLogFunction(RCTDefaultLogFunction);
62 |
63 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
64 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
65 | }
66 |
67 |
68 | @end
69 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ReactNativeAppDemo",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "start": "node node_modules/react-native/local-cli/cli.js start",
7 | "test": "jest"
8 | },
9 | "dependencies": {
10 | "@ant-design/react-native": "^3.1.5",
11 | "fetch-intercept": "^2.3.1",
12 | "lottie-react-native": "^2.6.1",
13 | "mobx": "^5.9.4",
14 | "mobx-react": "^5.4.3",
15 | "promise-polyfill": "^8.1.0",
16 | "prop-types": "^15.7.2",
17 | "react": "16.8.3",
18 | "react-native": "0.59.4",
19 | "react-native-device-info": "^1.5.0",
20 | "react-native-gesture-handler": "^1.1.0",
21 | "react-native-vector-icons": "^6.4.2",
22 | "react-navigation": "^3.7.1"
23 | },
24 | "devDependencies": {
25 | "@babel/cli": "^7.4.3",
26 | "@babel/core": "^7.4.3",
27 | "@babel/plugin-proposal-class-properties": "^7.4.0",
28 | "@babel/plugin-proposal-decorators": "^7.4.0",
29 | "@babel/plugin-proposal-object-rest-spread": "^7.4.3",
30 | "@babel/plugin-transform-classes": "^7.4.3",
31 | "@babel/plugin-transform-flow-strip-types": "^7.4.0",
32 | "@babel/plugin-transform-runtime": "^7.4.3",
33 | "@babel/polyfill": "^7.4.3",
34 | "@babel/preset-env": "^7.4.3",
35 | "@babel/preset-flow": "^7.0.0",
36 | "@babel/preset-react": "^7.0.0",
37 | "@babel/runtime": "^7.4.3",
38 | "babel-eslint": "^10.0.1",
39 | "babel-jest": "^24.7.1",
40 | "babel-loader": "^8.0.5",
41 | "babel-plugin-import": "^1.11.0",
42 | "babel-plugin-module-resolver": "^3.2.0",
43 | "babel-plugin-transform-runtime": "^6.23.0",
44 | "babel-polyfill": "^6.26.0",
45 | "babel-preset-es2015": "^6.24.1",
46 | "babel-preset-react": "^6.24.1",
47 | "babel-preset-react-native": "^4.0.1",
48 | "babel-preset-react-native-stage-0": "^1.0.1",
49 | "babel-preset-react-native-syntax": "^1.0.0",
50 | "eslint": "^5.16.0",
51 | "eslint-config-prettier": "^4.1.0",
52 | "eslint-plugin-flowtype": "^3.6.1",
53 | "eslint-plugin-import": "^2.17.1",
54 | "eslint-plugin-jsx-a11y": "^6.2.1",
55 | "eslint-plugin-prettier": "^3.0.1",
56 | "eslint-plugin-promise": "^4.1.1",
57 | "eslint-plugin-react": "^7.12.4",
58 | "jest": "^24.7.1",
59 | "metro-react-native-babel-preset": "^0.53.1",
60 | "react-test-renderer": "16.8.3"
61 | },
62 | "jest": {
63 | "preset": "react-native"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | ; We fork some components by platform
3 | .*/*[.]android.js
4 |
5 | ; Ignore "BUCK" generated dirs
6 | /\.buckd/
7 |
8 | ; Ignore unexpected extra "@providesModule"
9 | .*/node_modules/.*/node_modules/fbjs/.*
10 |
11 | ; Ignore duplicate module providers
12 | ; For RN Apps installed via npm, "Libraries" folder is inside
13 | ; "node_modules/react-native" but in the source repo it is in the root
14 | .*/Libraries/react-native/React.js
15 |
16 | ; Ignore polyfills
17 | .*/Libraries/polyfills/.*
18 |
19 | ; Ignore metro
20 | .*/node_modules/metro/.*
21 |
22 | [include]
23 |
24 | [libs]
25 | node_modules/react-native/Libraries/react-native/react-native-interface.js
26 | node_modules/react-native/flow/
27 |
28 | [options]
29 | emoji=true
30 |
31 | esproposal.optional_chaining=enable
32 | esproposal.nullish_coalescing=enable
33 |
34 | module.system=haste
35 | module.system.haste.use_name_reducers=true
36 | # get basename
37 | module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
38 | # strip .js or .js.flow suffix
39 | module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
40 | # strip .ios suffix
41 | module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
42 | module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
43 | module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
44 | module.system.haste.paths.blacklist=.*/__tests__/.*
45 | module.system.haste.paths.blacklist=.*/__mocks__/.*
46 | module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.*
47 | module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.*
48 |
49 | munge_underscores=true
50 |
51 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
52 |
53 | module.file_ext=.js
54 | module.file_ext=.jsx
55 | module.file_ext=.json
56 | module.file_ext=.native.js
57 |
58 | suppress_type=$FlowIssue
59 | suppress_type=$FlowFixMe
60 | suppress_type=$FlowFixMeProps
61 | suppress_type=$FlowFixMeState
62 |
63 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
64 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
65 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
66 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
67 |
68 | [version]
69 | ^0.92.0
70 |
--------------------------------------------------------------------------------
/ios/ReactNativeAppDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ReactNativeAppDemo
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | NSLocationWhenInUseUsageDescription
28 |
29 | UILaunchStoryboardName
30 | LaunchScreen
31 | UIRequiredDeviceCapabilities
32 |
33 | armv7
34 |
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UIViewControllerBasedStatusBarAppearance
42 |
43 | NSAppTransportSecurity
44 |
45 | NSAllowsArbitraryLoads
46 |
47 | NSExceptionDomains
48 |
49 | localhost
50 |
51 | NSExceptionAllowsInsecureHTTPLoads
52 |
53 |
54 |
55 |
56 | UIAppFonts
57 |
58 | AntDesign.ttf
59 | Entypo.ttf
60 | EvilIcons.ttf
61 | Feather.ttf
62 | FontAwesome.ttf
63 | FontAwesome5_Brands.ttf
64 | FontAwesome5_Regular.ttf
65 | FontAwesome5_Solid.ttf
66 | Foundation.ttf
67 | Ionicons.ttf
68 | MaterialCommunityIcons.ttf
69 | MaterialIcons.ttf
70 | Octicons.ttf
71 | SimpleLineIcons.ttf
72 | Zocial.ttf
73 | antfill.ttf
74 | antoutline.ttf
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/src/common/tab-nav.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Icon from 'react-native-vector-icons/Ionicons'
3 | import {createBottomTabNavigator} from 'react-navigation';
4 |
5 | import Home from '../pages/home/home';
6 | import Page3 from '../pages/page3/page3';
7 | import Page1 from '../pages/page1/page1';
8 | import Page2 from '../pages/page2/page2';
9 | import {colors} from '../assets/styles/colors-theme';
10 |
11 | const TabRouterMap = {
12 | home: {
13 | screen: Home,
14 | navigationOptions: {
15 | tabBarLabel: 'Home',
16 | tabBarIcon:({focused}) => (
17 |
23 | )
24 | }
25 | },
26 | page1: {
27 | screen: Page1,
28 | navigationOptions: {
29 | tabBarLabel: 'Page1',
30 | tabBarIcon:({focused}) => (
31 |
37 | )
38 | }
39 | },
40 | page2: {
41 | screen: Page2,
42 | navigationOptions: {
43 | tabBarLabel: 'Page2',
44 | tabBarIcon:({focused}) => (
45 |
51 | )
52 | }
53 | },
54 | page3: {
55 | screen: Page3,
56 | navigationOptions: {
57 | tabBarLabel: 'Page3',
58 | tabBarIcon:({focused}) => (
59 |
65 | )
66 | }
67 | }
68 | }
69 |
70 | export const TabNav = createBottomTabNavigator(TabRouterMap,{
71 | initialRouteName: 'home',
72 | tabBarOptions: {
73 | //当前选中的tab bar的文本颜色和图标颜色
74 | activeTintColor: colors.statusBarColor,
75 | //当前未选中的tab bar的文本颜色和图标颜色
76 | inactiveTintColor: '#000',
77 | //是否显示tab bar的图标,默认是false
78 | showIcon: true,
79 | //showLabel - 是否显示tab bar的文本,默认是true
80 | showLabel: true,
81 | //是否将文本转换为大小,默认是true
82 | upperCaseLabel: false,
83 | //material design中的波纹颜色(仅支持Android >= 5.0)
84 | pressColor: 'red',
85 | //按下tab bar时的不透明度(仅支持iOS和Android < 5.0).
86 | pressOpacity: 0.8,
87 | //tab bar的样式
88 | // style: {
89 | // backgroundColor: '#fff',
90 | // paddingBottom: 1,
91 | // borderTopWidth: 0.2,
92 | // paddingTop:1,
93 | // borderTopColor: '#ccc',
94 | // },
95 | //tab bar的文本样式
96 | labelStyle: {
97 | fontSize: 11,
98 | margin: 1
99 | },
100 | //tab 页指示符的样式 (tab页下面的一条线).
101 | indicatorStyle: {height: 0},
102 | },
103 | //tab bar的位置, 可选值: 'top' or 'bottom'
104 | tabBarPosition: 'bottom',
105 | //是否允许滑动切换tab页
106 | swipeEnabled: true,
107 | //是否在切换tab页时使用动画
108 | animationEnabled: false,
109 | //是否懒加载
110 | lazy: true,
111 | //返回按钮是否会导致tab切换到初始tab页? 如果是,则设置为initialRoute,否则为none。 缺省为initialRoute。
112 | backBehavior: 'none'
113 | })
114 |
--------------------------------------------------------------------------------
/src/pages/list/list.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {View, Text, TouchableOpacity, StyleSheet, Dimensions} from 'react-native'
3 | import NaviBar from '../../components/navi-bar';
4 | import history from '../../common/history';
5 | import {colors} from '../../assets/styles/colors-theme';
6 | import ListService from '../../services/list-service';
7 | import LoadingHoc from '../../hocs/loading-hoc';
8 | import {inject, observer} from 'mobx-react';
9 |
10 | const { width } = Dimensions.get('window')
11 |
12 | @LoadingHoc
13 | @inject('rootStore')
14 | @observer
15 | export default class List extends Component {
16 | constructor(props) {
17 | super(props)
18 | this.state = {
19 | list: [],
20 | name: props.navigation.state.params.name
21 | }
22 | this.listService = new ListService(props)
23 | this.store = props.rootStore.appStore
24 | }
25 |
26 | async componentDidMount() {
27 | const res = await this.listService.getList();
28 |
29 | if(res) {
30 | this.setState({
31 | list: res
32 | })
33 | }
34 | }
35 |
36 | render() {
37 | return (
38 |
39 |
43 |
44 |
45 | Home页传过来的name:{this.state.name}
46 |
47 |
48 | 统计到{this.store.timer}次跳转到List
49 |
50 |
51 |
52 | 次数
53 |
54 |
55 | 描述
56 |
57 |
58 | {
59 | this.store.list.map((item, index) => {
60 | return (
61 |
62 |
63 | {item.number}
64 |
65 |
66 | {item.label}
67 |
68 |
69 | )
70 | })
71 | }
72 | history.push(this, '/detail', {name: 'suannai'})}>
73 | 跳转到Detail
74 |
75 |
76 |
77 | )
78 | }
79 | }
80 |
81 | const styles = StyleSheet.flatten({
82 | button: {
83 | marginTop: 20,
84 | width: 100,
85 | height: 40,
86 | alignItems: 'center',
87 | justifyContent: 'center',
88 | backgroundColor: colors.statusBarColor
89 | },
90 | buttonText: {
91 | color: '#fff'
92 | },
93 | number: {
94 | width: 0.3 * width,
95 | height: 40,
96 | alignItems: 'center',
97 | justifyContent: 'center'
98 | },
99 | label: {
100 | width: 0.7 * width,
101 | height: 40,
102 | alignItems: 'center',
103 | justifyContent: 'center'
104 | }
105 | })
--------------------------------------------------------------------------------
/ios/ReactNativeAppDemo/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {StatusBar} from 'react-native';
3 | import {SafeAreaView} from 'react-navigation'
4 | import Router from './src/router';
5 | import {colors} from './src/assets/styles/colors-theme';
6 | import {handleNavigationChange} from './src/common/history';
7 | import {Provider as ProviderAntd, Modal} from '@ant-design/react-native'
8 | import LoadingView from './src/common/loading';
9 | import { Provider, observer } from 'mobx-react'
10 | import * as stores from './src/stores/index';
11 | import {handleErrors} from './src/common/global-error-handler';
12 | import Event from './src/common/event';
13 |
14 | @observer
15 | export default class App extends Component {
16 | constructor(props) {
17 | super(props)
18 | this.timer = null
19 | this.state = {
20 | loadingCount: 0
21 | }
22 |
23 | require('promise/setimmediate/rejection-tracking').enable({
24 | allRejections: true,
25 | onUnhandled: (id, error) => {
26 | handleErrors(error);
27 | }
28 | })
29 |
30 | this._handleGlobalError = this.handleGlobalError.bind(this)
31 | this._handleShowLoading = this.handleShowLoading.bind(this)
32 | this._handleHideLoading = this.handleHideLoading.bind(this)
33 | }
34 |
35 | componentDidMount() {
36 | // 监听全局报错
37 | Event.listen('GLOBAL_ERROR', this._handleGlobalError, this)
38 |
39 | // 显示加载动画
40 | Event.listen('SHOW_LOADING', this._handleShowLoading, this)
41 |
42 | // 隐藏加载动画
43 | Event.listen('HIDE_LOADING', this._handleHideLoading, this)
44 | }
45 |
46 | //组件卸载之前移除监听
47 | componentWillUnmount() {
48 | Event.remove('GLOBAL_ERROR', this)
49 | Event.remove('SHOW_LOADING', this)
50 | Event.remove('HIDE_LOADING', this)
51 | }
52 |
53 | render() {
54 | return (
55 |
56 |
57 |
64 |
70 |
73 | 0} />
74 |
75 |
76 |
77 | );
78 | }
79 |
80 | /**
81 | * showLoading
82 | */
83 | handleShowLoading() {
84 | if(this.timer) {
85 | clearTimeout(this.timer);
86 | }
87 |
88 | this.timer = setTimeout(() => {
89 | this.setState({
90 | loadingCount: this.state.loadingCount + 1
91 | })
92 | }, 50)
93 | }
94 |
95 | /**
96 | * hideLoading
97 | * @param bForece
98 | */
99 | handleHideLoading(bForece){
100 | if(this.timer){
101 | clearTimeout(this.timer);
102 | }
103 |
104 | this.timer = setTimeout(() => {
105 | if(this.state.loadingCount > 0){
106 | this.setState({
107 | loadingCount: (bForece ? 0 : this.state.loadingCount - 1)
108 | });
109 | }
110 | }, 50)
111 | }
112 |
113 | /**
114 | * 全局报错处理
115 | * @param event
116 | */
117 | handleGlobalError(event) {
118 | // 报错时,取消加载动画
119 | if(this.state.loadingCount > 0){
120 | this.handleHideLoading(true)
121 | }
122 |
123 | if(event && event.type){
124 | switch(event.type){
125 | case 'SESSION_TIMEOUT':
126 | Modal.alert('会话超时', '您的会话已超时,请重新登录')
127 | break;
128 | case 'SERVICE_ERROR':
129 | if(event.message) {
130 | Modal.alert('出错了', event.message)
131 | }
132 | break;
133 | default:
134 | if(event.message) {
135 | Modal.alert('温馨提示', '系统未知异常')
136 | }
137 | break;
138 | }
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/services/base-service.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 基础服务类封装
3 | * BaseService
4 | */
5 | import fetchIntercept from 'fetch-intercept'
6 | import constants from '../common/constants';
7 | import ServiceError from '../common/service-error';
8 | import code from '../common/code';
9 |
10 | const fetchApi = fetch; // eslint-disable-line
11 |
12 | fetchIntercept.register({
13 | request: function (url, config) {
14 | return [url, config];
15 | },
16 | requestError: function (error) {
17 | return Promise.reject(error);
18 | },
19 | response: function (res) {
20 | return res;
21 | },
22 | responseError: function (error) {
23 | return Promise.reject(error);
24 | }
25 | });
26 |
27 | export default class BaseService {
28 | constructor(props){
29 | if(props && props.showLoading){
30 | this.showLoading = props.showLoading;
31 | }
32 | if(props && props.hideLoading){
33 | this.hideLoading = props.hideLoading;
34 | }
35 | }
36 |
37 | async request(method, url, params, errorMsgIndex, showLoading = true, acceptType = 'application/json') {
38 | // 如果url不全,则自动补全
39 | if(url.indexOf('http://') < 0 && url.indexOf('https://') < 0){
40 | url = constants.serverUrl + '/' + url;
41 | }
42 |
43 | if(showLoading && this.showLoading){
44 | this.showLoading();
45 | }
46 |
47 | let res = null
48 | let timer = null
49 |
50 | try {
51 | const options = {
52 | method: method,
53 | credentials: 'include',
54 | headers: {
55 | 'content-type': 'application/json',
56 | 'accept': acceptType,
57 | 'Cache-Control': 'no-cache'
58 | }
59 | }
60 |
61 | if(method === 'POST' || method === 'PUT') {
62 | params.DeviceType = constants.deviceType
63 | params.DeviceNo = constants.deviceNo
64 |
65 | options.body = JSON.stringify(params || {})
66 | }
67 |
68 | res = await fetchApi(url, options)
69 | } catch (e) {
70 | if(this.hideLoading){
71 | if(timer) {
72 | clearTimeout(timer)
73 | }
74 | timer = setTimeout(() => {
75 | this.hideLoading()
76 | }, 2000)
77 | }
78 |
79 | throw new ServiceError(code.REQUEST_FAILED, '网络请求失败')
80 | }
81 |
82 | if(res.status && res.status >= 200 && res.status < 300) {
83 | const contentType = res.headers.get('Content-Type')
84 |
85 | if(this.hideLoading){
86 | this.hideLoading()
87 | }
88 |
89 | if(contentType.indexOf('text/plain') >= 0 || contentType.indexOf('text/html') >= 0){
90 | return res.text()
91 | }else{
92 | const responseJson = await res.json();
93 | if (responseJson && !responseJson.jsonError) {
94 | return responseJson
95 | } else {
96 | throw new ServiceError(responseJson.jsonError[0]._exceptionMessageCode || code.REQUEST_FAILED, responseJson.jsonError[0]._exceptionMessage);
97 | }
98 | }
99 | } else {
100 | if(this.hideLoading){
101 | if(timer) {
102 | clearTimeout(timer)
103 | }
104 |
105 | timer = setTimeout(() => {
106 | this.hideLoading()
107 | }, 2000)
108 | }
109 |
110 | if (res.status === 401) {
111 | throw new ServiceError(code.REQUEST_TIMEOUT, res.data.message);
112 | } else if (res.ok) {
113 | try {
114 | const responseJson = await res.json();
115 | const { message } = responseJson;
116 | throw new ServiceError(code.REQUEST_FAILED, message);
117 | } catch (e) {
118 | throw new ServiceError(code.REQUEST_FAILED, '服务未知错误');
119 | }
120 | }
121 | }
122 | }
123 |
124 | /**
125 | * GET 后台数据
126 | * @param url
127 | * @param errorMsg 报错消息
128 | * @returns {Promise<*>}
129 | */
130 | async fetchJson(url, errorMsg, showLoading = true){
131 | return await this.request('GET', url, null, errorMsg, showLoading)
132 | }
133 |
134 | /**
135 | * POST请求
136 | * @param url
137 | * @param params
138 | * @param errorMsg 报错消息
139 | * @returns {Promise.}
140 | */
141 | async postJson(url, params, errorMsg, showLoading = true){
142 | return await this.request('POST', url, params, errorMsg, showLoading)
143 | }
144 |
145 | }
--------------------------------------------------------------------------------
/src/common/notification-center.js:
--------------------------------------------------------------------------------
1 | const __notices = [];
2 | /**
3 | * addNotification
4 | * 注册通知对象方法
5 | *
6 | * 参数:
7 | * name: 注册名,一般let在公共类中
8 | * selector: 对应的通知方法,接受到通知后进行的动作
9 | * observer: 注册对象,指Page对象
10 | */
11 | function addNotification(name, selector, observer) {
12 | if (name && selector) {
13 | if (!observer) {
14 | console.log(
15 | "addNotification Warning: no observer will can't remove notice"
16 | );
17 | }
18 | const newNotice = {
19 | name: name,
20 | selector: selector,
21 | observer: observer
22 | };
23 |
24 | addNotices(newNotice);
25 | } else {
26 | console.log('addNotification error: no selector or name');
27 | }
28 | }
29 |
30 | /**
31 | * 仅添加一次监听
32 | *
33 | * 参数:
34 | * name: 注册名,一般let在公共类中
35 | * selector: 对应的通知方法,接受到通知后进行的动作
36 | * observer: 注册对象,指Page对象
37 | */
38 | function addOnceNotification(name, selector, observer) {
39 | if (__notices.length > 0) {
40 | for (let i = 0; i < __notices.length; i++) {
41 | const notice = __notices[i];
42 | if (notice.name === name) {
43 | if (notice.observer === observer) {
44 | return;
45 | }
46 | }
47 | }
48 | }
49 | this.addNotification(name, selector, observer);
50 | }
51 |
52 | function addNotices(newNotice) {
53 | // if (__notices.length > 0) {
54 | // for (var i = 0; i < __notices.length; i++) {
55 | // var hisNotice = __notices[i];
56 | // //当名称一样时进行对比,如果不是同一个 则放入数组,否则跳出
57 | // if (newNotice.name === hisNotice.name) {
58 | // if (!cmp(hisNotice, newNotice)) {
59 | // __notices.push(newNotice);
60 | // }
61 | // return;
62 | // }else{
63 | // __notices.push(newNotice);
64 | // }
65 |
66 | // }
67 | // } else {
68 |
69 | // }
70 |
71 | __notices.push(newNotice);
72 | }
73 |
74 | /**
75 | * removeNotification
76 | * 移除通知方法
77 | *
78 | * 参数:
79 | * name: 已经注册了的通知
80 | * observer: 移除的通知所在的Page对象
81 | */
82 |
83 | function removeNotification(name, observer) {
84 | console.log('removeNotification:' + name);
85 | for (let i = 0; i < __notices.length; i++) {
86 | const notice = __notices[i];
87 | if (notice.name === name) {
88 | if (notice.observer === observer) {
89 | __notices.splice(i, 1);
90 | return;
91 | }
92 | }
93 | }
94 | }
95 |
96 | /**
97 | * postNotificationName
98 | * 发送通知方法
99 | *
100 | * 参数:
101 | * name: 已经注册了的通知
102 | * info: 携带的参数
103 | */
104 |
105 | function postNotificationName(name, info) {
106 | console.log('postNotificationName:' + name);
107 | if (__notices.length === 0) {
108 | console.log("postNotificationName error: u hadn't add any notice.");
109 | return;
110 | }
111 |
112 | for (let i = 0; i < __notices.length; i++) {
113 | const notice = __notices[i];
114 | if (notice.name === name) {
115 | notice.selector(info);
116 | }
117 | }
118 | }
119 |
120 | // 用于对比两个对象是否相等
121 | function cmp(x, y) { // eslint-disable-line
122 | // If both x and y are null or undefined and exactly the same
123 | if (x === y) {
124 | return true;
125 | }
126 |
127 | // If they are not strictly equal, they both need to be Objects
128 | if (!(x instanceof Object) || !(y instanceof Object)) {
129 | return false;
130 | }
131 |
132 | // They must have the exact same prototype chain, the closest we can do is
133 | // test the constructor.
134 | if (x.constructor !== y.constructor) {
135 | return false;
136 | }
137 |
138 | for (const p in x) {
139 | // Inherited properties were tested using x.constructor === y.constructor
140 | if (x.hasOwnProperty(p)) {
141 | // Allows comparing x[ p ] and y[ p ] when set to undefined
142 | if (!y.hasOwnProperty(p)) {
143 | return false;
144 | }
145 |
146 | // If they have the same strict value or identity then they are equal
147 | if (x[p] === y[p]) {
148 | continue;
149 | }
150 |
151 | // Numbers, Strings, Functions, Booleans must be strictly equal
152 | if (typeof x[p] !== 'object') {
153 | return false;
154 | }
155 |
156 | // Objects and Arrays must be tested recursively
157 | if (!Object.equals(x[p], y[p])) {
158 | return false;
159 | }
160 | }
161 | }
162 |
163 | for (const p in y) {
164 | // allows x[ p ] to be set to undefined
165 | if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) {
166 | return false;
167 | }
168 | }
169 | return true;
170 | }
171 |
172 | module.exports = {
173 | addNotification: addNotification,
174 | removeNotification: removeNotification,
175 | postNotificationName: postNotificationName,
176 | addOnceNotification: addOnceNotification
177 | };
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | // babel parser to support ES6/7 features
3 | "parser": "babel-eslint",
4 | "parserOptions": {
5 | "ecmaVersion": 7,
6 | "ecmaFeatures": {
7 | "legacyDecorators": true,
8 | "experimentalObjectRestSpread": true,
9 | "jsx": true
10 | },
11 | "sourceType": "module"
12 | },
13 | "extends": [
14 | "prettier",
15 | "prettier/react"
16 | ],
17 | "plugins": [
18 | "promise",
19 | "react"
20 | ],
21 | "env": {
22 | "es6": true,
23 | "node": true
24 | },
25 | "globals": {
26 | "document": false,
27 | "navigator": false,
28 | "location": false,
29 | "window": false,
30 | // Flow global types
31 | "HTMLInputElement": false,
32 | "ReactClass": false,
33 | "ReactComponent": false,
34 | "ReactElement": false,
35 | "ReactPropsChainableTypeChecker": false,
36 | "ReactPropsCheckType": false,
37 | "ReactPropTypes": false,
38 | "SyntheticEvent": false,
39 | "cancelAnimationFrame": false,
40 | "requestAnimationFrame": false,
41 | "Image": false
42 | },
43 | "rules": {
44 | "camelcase": 0,
45 | "constructor-super": 2,
46 | "default-case": [2, { "commentPattern": "^no default$" }],
47 | "eqeqeq": [2, "allow-null"],
48 | "handle-callback-err": [2, "^(err|error)$" ],
49 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
50 | "no-alert": 1,
51 | "no-array-constructor": 2,
52 | "no-caller": 2,
53 | "no-case-declarations": 2,
54 | "no-class-assign": 2,
55 | "no-cond-assign": 2,
56 | "no-const-assign": 2,
57 | "no-control-regex": 2,
58 | "no-debugger": 2,
59 | "no-delete-var": 2,
60 | "no-dupe-args": 2,
61 | "no-dupe-class-members": 2,
62 | "no-dupe-keys": 2,
63 | "no-duplicate-case": 2,
64 | "no-empty-character-class": 2,
65 | "no-empty-pattern": 2,
66 | "no-eval": 2,
67 | "no-ex-assign": 2,
68 | "no-extend-native": 2,
69 | "no-extra-bind": 2,
70 | "no-extra-boolean-cast": 2,
71 | "no-fallthrough": 2,
72 | "no-floating-decimal": 2,
73 | "no-func-assign": 2,
74 | "no-implied-eval": 2,
75 | "no-inner-declarations": [2, "functions"],
76 | "no-invalid-regexp": 2,
77 | "no-irregular-whitespace": 2,
78 | "no-iterator": 2,
79 | "no-label-var": 2,
80 | "no-labels": [2, { "allowLoop": false, "allowSwitch": false }],
81 | "no-lone-blocks": 2,
82 | "no-loop-func": 2,
83 | "no-multi-str": 2,
84 | "no-native-reassign": 2,
85 | "no-negated-in-lhs": 2,
86 | "no-new": 2,
87 | "no-new-func": 2,
88 | "no-new-object": 2,
89 | "no-new-require": 2,
90 | "no-new-symbol": 2,
91 | "no-new-wrappers": 2,
92 | "no-obj-calls": 2,
93 | "no-octal": 2,
94 | "no-octal-escape": 2,
95 | "no-path-concat": 2,
96 | "no-proto": 2,
97 | "no-redeclare": 2,
98 | "no-regex-spaces": 2,
99 | "no-return-assign": [2, "except-parens"],
100 | "no-script-url": 2,
101 | "no-self-assign": 2,
102 | "no-self-compare": 2,
103 | "no-sequences": 2,
104 | "no-shadow-restricted-names": 2,
105 | "no-sparse-arrays": 2,
106 | "no-this-before-super": 2,
107 | "no-throw-literal": 2,
108 | "no-undef": 2,
109 | "no-undef-init": 2,
110 | "no-unexpected-multiline": 2,
111 | "no-unmodified-loop-condition": 2,
112 | "no-unneeded-ternary": [2, { "defaultAssignment": false }],
113 | "no-unreachable": 2,
114 | "no-unsafe-finally": 2,
115 | "no-unused-vars": [2, { "vars": "all", "args": "none" }],
116 | "no-useless-call": 2,
117 | "no-useless-computed-key": 2,
118 | "no-useless-concat": 2,
119 | "no-useless-constructor": 2,
120 | "no-useless-escape": 2,
121 | "no-var": 2,
122 | "no-with": 2,
123 | "prefer-const": 2,
124 | "prefer-rest-params": 2,
125 | "quotes": [2, "single", "avoid-escape"],
126 | "radix": 2,
127 | "use-isnan": 2,
128 | "valid-typeof": 2,
129 | "yoda": [2, "never"],
130 |
131 | // promise
132 | "promise/param-names": 2,
133 |
134 | // react
135 | "react/display-name": 0,
136 | "react/jsx-no-bind": 0,
137 | "react/jsx-no-duplicate-props": 2,
138 | "react/jsx-no-undef": 2,
139 | "react/jsx-pascal-case": 2,
140 | "react/jsx-sort-props": 0,
141 | "react/jsx-uses-react": 2,
142 | "react/jsx-uses-vars": 2,
143 | "react/no-did-mount-set-state": 0,
144 | "react/no-did-update-set-state": 2,
145 | "react/no-direct-mutation-state": 2,
146 | "react/no-multi-comp": 0,
147 | "react/no-string-refs": 2,
148 | "react/no-unknown-property": 2,
149 | "react/prefer-es6-class": 2,
150 | "react/prop-types": 0,
151 | "react/react-in-jsx-scope": 0,
152 | "react/self-closing-comp": 2,
153 | "react/sort-comp": 0,
154 | "react/sort-prop-types": 0,
155 | "react/wrap-multilines": 0
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/ios/ReactNativeAppDemo.xcodeproj/xcshareddata/xcschemes/ReactNativeAppDemo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
61 |
67 |
68 |
69 |
70 |
71 |
77 |
78 |
79 |
80 |
81 |
82 |
92 |
94 |
100 |
101 |
102 |
103 |
104 |
105 |
111 |
113 |
119 |
120 |
121 |
122 |
124 |
125 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/ios/ReactNativeAppDemo.xcodeproj/xcshareddata/xcschemes/ReactNativeAppDemo-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
61 |
67 |
68 |
69 |
70 |
71 |
77 |
78 |
79 |
80 |
81 |
82 |
92 |
94 |
100 |
101 |
102 |
103 |
104 |
105 |
111 |
113 |
119 |
120 |
121 |
122 |
124 |
125 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 |
3 | import com.android.build.OutputFile
4 |
5 | /**
6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
7 | * and bundleReleaseJsAndAssets).
8 | * These basically call `react-native bundle` with the correct arguments during the Android build
9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
10 | * bundle directly from the development server. Below you can see all the possible configurations
11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the
12 | * `apply from: "../../node_modules/react-native/react.gradle"` line.
13 | *
14 | * project.ext.react = [
15 | * // the name of the generated asset file containing your JS bundle
16 | * bundleAssetName: "index.android.bundle",
17 | *
18 | * // the entry file for bundle generation
19 | * entryFile: "index.android.js",
20 | *
21 | * // whether to bundle JS and assets in debug mode
22 | * bundleInDebug: false,
23 | *
24 | * // whether to bundle JS and assets in release mode
25 | * bundleInRelease: true,
26 | *
27 | * // whether to bundle JS and assets in another build variant (if configured).
28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
29 | * // The configuration property can be in the following formats
30 | * // 'bundleIn${productFlavor}${buildType}'
31 | * // 'bundleIn${buildType}'
32 | * // bundleInFreeDebug: true,
33 | * // bundleInPaidRelease: true,
34 | * // bundleInBeta: true,
35 | *
36 | * // whether to disable dev mode in custom build variants (by default only disabled in release)
37 | * // for example: to disable dev mode in the staging build type (if configured)
38 | * devDisabledInStaging: true,
39 | * // The configuration property can be in the following formats
40 | * // 'devDisabledIn${productFlavor}${buildType}'
41 | * // 'devDisabledIn${buildType}'
42 | *
43 | * // the root of your project, i.e. where "package.json" lives
44 | * root: "../../",
45 | *
46 | * // where to put the JS bundle asset in debug mode
47 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
48 | *
49 | * // where to put the JS bundle asset in release mode
50 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release",
51 | *
52 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
53 | * // require('./image.png')), in debug mode
54 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
55 | *
56 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
57 | * // require('./image.png')), in release mode
58 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
59 | *
60 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means
61 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
62 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle
63 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
64 | * // for example, you might want to remove it from here.
65 | * inputExcludes: ["android/**", "ios/**"],
66 | *
67 | * // override which node gets called and with what additional arguments
68 | * nodeExecutableAndArgs: ["node"],
69 | *
70 | * // supply additional arguments to the packager
71 | * extraPackagerArgs: []
72 | * ]
73 | */
74 |
75 | project.ext.react = [
76 | entryFile: "index.js"
77 | ]
78 |
79 | apply from: "../../node_modules/react-native/react.gradle"
80 |
81 | /**
82 | * Set this to true to create two separate APKs instead of one:
83 | * - An APK that only works on ARM devices
84 | * - An APK that only works on x86 devices
85 | * The advantage is the size of the APK is reduced by about 4MB.
86 | * Upload all the APKs to the Play Store and people will download
87 | * the correct one based on the CPU architecture of their device.
88 | */
89 | def enableSeparateBuildPerCPUArchitecture = false
90 |
91 | /**
92 | * Run Proguard to shrink the Java bytecode in release builds.
93 | */
94 | def enableProguardInReleaseBuilds = false
95 |
96 | android {
97 | compileSdkVersion rootProject.ext.compileSdkVersion
98 |
99 | compileOptions {
100 | sourceCompatibility JavaVersion.VERSION_1_8
101 | targetCompatibility JavaVersion.VERSION_1_8
102 | }
103 |
104 | defaultConfig {
105 | applicationId "com.reactnativeappdemo"
106 | minSdkVersion rootProject.ext.minSdkVersion
107 | targetSdkVersion rootProject.ext.targetSdkVersion
108 | versionCode 1
109 | versionName "1.0"
110 | }
111 | splits {
112 | abi {
113 | reset()
114 | enable enableSeparateBuildPerCPUArchitecture
115 | universalApk false // If true, also generate a universal APK
116 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
117 | }
118 | }
119 | buildTypes {
120 | release {
121 | minifyEnabled enableProguardInReleaseBuilds
122 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
123 | }
124 | }
125 | // applicationVariants are e.g. debug, release
126 | applicationVariants.all { variant ->
127 | variant.outputs.each { output ->
128 | // For each separate APK per architecture, set a unique version code as described here:
129 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
130 | def versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a": 3, "x86_64": 4]
131 | def abi = output.getFilter(OutputFile.ABI)
132 | if (abi != null) { // null for the universal-debug, universal-release variants
133 | output.versionCodeOverride =
134 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
135 | }
136 | }
137 | }
138 | }
139 |
140 | dependencies {
141 | implementation project(':lottie-react-native')
142 | implementation project(':react-native-device-info')
143 | implementation project(':react-native-vector-icons')
144 | implementation project(':react-native-gesture-handler')
145 | implementation fileTree(dir: "libs", include: ["*.jar"])
146 | implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
147 | implementation "com.facebook.react:react-native:+" // From node_modules
148 | }
149 |
150 | // Run this once to be able to run the application with BUCK
151 | // puts all compile dependencies into folder libs for BUCK to use
152 | task copyDownloadableDepsToLibs(type: Copy) {
153 | from configurations.compile
154 | into 'libs'
155 | }
156 |
--------------------------------------------------------------------------------
/src/assets/animations/loading.json:
--------------------------------------------------------------------------------
1 | {"v":"5.4.2","fr":25,"ip":0,"op":93,"w":120,"h":120,"nm":"橙色球","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"橙色球2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":0,"s":[40,70,0],"e":[60,82,0],"to":[-10.6666669845581,20.25,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":11,"s":[60,82,0],"e":[60,82,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":13,"s":[60,82,0],"e":[60,32,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":27,"s":[60,32,0],"e":[60,32,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":29,"s":[60,32,0],"e":[40,44,0],"to":[-12.5833330154419,-17.25,0],"ti":[-11.1666669845581,-10.25,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":43,"s":[40,44,0],"e":[40,44,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":45,"s":[40,44,0],"e":[80,70,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":59,"s":[80,70,0],"e":[80,70,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":61,"s":[80,70,0],"e":[80,44,0],"to":[18.875,-0.45833334326744,0],"ti":[17.625,0.08333333581686,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":75,"s":[80,44,0],"e":[80,44,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":77,"s":[80,44,0],"e":[40,70,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":91,"s":[40,70,0],"e":[40,70,0],"to":[0,0,0],"ti":[0,0,0]},{"t":93}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[80,80,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.980392156863,0.537254901961,0.098039215686,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":249,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"橙色球3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":0,"s":[80,70,0],"e":[40,44,0],"to":[-6.66666650772095,-4.33333349227905,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":11,"s":[40,44,0],"e":[40,44,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":13,"s":[40,44,0],"e":[40,70,0],"to":[-20.125,-0.29166665673256,0],"ti":[-18.5,0.54166668653488,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":27,"s":[40,70,0],"e":[40,70,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":29,"s":[40,70,0],"e":[80,44,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":43,"s":[80,44,0],"e":[80,44,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":45,"s":[80,44,0],"e":[60,32,0],"to":[11.9166669845581,-13.25,0],"ti":[12.0833330154419,-16.5,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":59,"s":[60,32,0],"e":[60,32,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":61,"s":[60,32,0],"e":[60,82,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":75,"s":[60,82,0],"e":[60,82,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":77,"s":[60,82,0],"e":[80,70,0],"to":[7.58333349227905,21.75,0],"ti":[16.9166660308838,11.75,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":91,"s":[80,70,0],"e":[80,70,0],"to":[0,0,0],"ti":[0,0,0]},{"t":93}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[80,80,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.980392156863,0.537254901961,0.098039215686,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":249,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"橙色球1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":0,"s":[60,32,0],"e":[80,44,0],"to":[5.33333349227905,-20.25,0],"ti":[15.4166669845581,-16,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":11,"s":[80,44,0],"e":[80,44,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":13,"s":[80,44,0],"e":[80,70,0],"to":[26.875,0.58333331346512,0],"ti":[20,-0.08333333581686,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":27,"s":[80,70,0],"e":[80,70,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":29,"s":[80,70,0],"e":[60,82,0],"to":[15.6666669845581,15,0],"ti":[6.08333349227905,16.5,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":43,"s":[60,82,0],"e":[60,82,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":45,"s":[60,82,0],"e":[40,70,0],"to":[-14.5833330154419,18.25,0],"ti":[-11.9166669845581,17.75,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":59,"s":[40,70,0],"e":[40,70,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":61,"s":[40,70,0],"e":[40,44,0],"to":[-25.5,-0.33333334326744,0],"ti":[-23.75,-0.41666665673256,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":75,"s":[40,44,0],"e":[40,44,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":77,"s":[40,44,0],"e":[60,32,0],"to":[-17.1666660308838,-12,0],"ti":[-6.58333349227905,-19.75,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"n":"0p667_0p667_0p333_0p333","t":91,"s":[60,32,0],"e":[60,32,0],"to":[0,0,0],"ti":[0,0,0]},{"t":93}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[80,80,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.980392156863,0.537254901961,0.098039215686,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":249,"st":0,"bm":0}],"markers":[]}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ReactNativeAppDemo
2 | **ReactNativeAppDemo** 是一个以react-native+mobx为基础搭建的app案例,旨在让初学者了解基本的RNApp的搭建与应用。
3 |
4 | 教程包含基础框架搭建、路由封装、导航栏封装、service封装、全局报错处理封装、高阶组件封装、全局事件消息总线封装...
5 |
6 | ## 支持平台
7 | - [x] IOS
8 | - [x] Android
9 |
10 | ## 效果图
11 | 
12 |
13 | ## 基础环境搭建
14 | 按照[react-native中文官网](https://reactnative.cn/docs/getting-started/)搭建基础环境
15 |
16 | ```
17 | $ react-native init ReactNativeAppDemo
18 | ```
19 |
20 | **相关配置**:(如Eslint、git、Babel等)
21 |
22 | 1.Eslint配置: 在根目录新增.eslintrc 和 .eslintignore,具体配置看本文源码,也可查阅官方资料
23 |
24 | ```
25 | $ yarn add eslint babel-eslint eslint-config-prettier eslint-plugin-flowtype eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-promise eslint-plugin-react -D
26 | ```
27 | 2.git配置:根据官方自行配置,也可参考本文源码
28 |
29 |
30 |
31 | ## 路由功能
32 | 一般RN的路由通过官方推荐的[react-navigation](https://reactnavigation.org/docs/zh-Hans/getting-started.html)来实现,目前官方出到3.x
33 |
34 | 在你的 React Native 项目中安装`react-navigation`这个包
35 | ```
36 | $ yarn add react-navigation
37 | ```
38 | 然后,安装 react-native-gesture-handler。
39 |
40 | ```
41 | $ yarn add react-native-gesture-handler
42 | ```
43 | Link 所有的原生依赖
44 |
45 | ```
46 | $ react-native link react-native-gesture-handler
47 | ```
48 | 若想获取更多配置和用法,请移步至[react-navigation中文官网](https://reactnavigation.org/zh-Hans/)
49 |
50 | **路由组件封装**
51 |
52 | 1.根目录新建`src/pages/`目录,在`pages`下新建`home/home.js`
53 |
54 | a)`home.js`示例:(所用的history及colors在后文也有示例)
55 |
56 | ```js
57 | import React, { Component } from 'react'
58 | import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
59 | import {colors} from '../../assets/styles/colors-theme';
60 | import history from '../../common/history';
61 |
62 | export default class Home extends Component {
63 | render() {
64 | return (
65 |
66 | Home
67 | history.push(this, '/list', {name: 'niunai'})}>
68 | 跳转到List
69 |
70 |
71 | )
72 | }
73 | }
74 |
75 | const styles = StyleSheet.flatten({
76 | button: {
77 | marginTop: 20,
78 | width: 100,
79 | height: 40,
80 | alignItems: 'center',
81 | justifyContent: 'center',
82 | backgroundColor: colors.statusBarColor
83 | },
84 | buttonText: {
85 | color: '#fff'
86 | }
87 | })
88 | ```
89 |
90 | b)在`pages`下新建`page1/page1.js`、`page2/page2.js`、`page3/page3.js`、`list/list.js`、`detail/detail.js`,示例如下:(仅写一个示例,其他自行模仿)
91 |
92 | `page1`示例:
93 |
94 | ```js
95 | import React, { Component } from 'react'
96 | import { View, Text } from 'react-native'
97 |
98 | export default class Page1 extends Component {
99 | render() {
100 | return (
101 |
102 | Page1
103 |
104 | )
105 | }
106 | }
107 | ```
108 |
109 | c)在`pages`下新建`router.js`,示例如下(`tab-nav`下文会讲到):
110 |
111 | ```js
112 | import {createStackNavigator, createAppContainer} from 'react-navigation'
113 | import List from './pages/list/list';
114 | import Detail from './pages/detail/detail';
115 | import {TabNav} from './common/tab-nav';
116 |
117 | function generateRoute(path, screen) {
118 | return {
119 | path,
120 | screen
121 | }
122 | }
123 |
124 |
125 | const stackRouterMap = {
126 | list: generateRoute('/list', List),
127 | detail: generateRoute('/detail', Detail),
128 | main: TabNav
129 | }
130 |
131 | const stackNavigate = createStackNavigator(stackRouterMap, {
132 | initialRouteName: 'main',
133 | headerMode: 'none'
134 | })
135 |
136 | const Router = createAppContainer(stackNavigate)
137 |
138 | export default Router
139 | ```
140 |
141 | d)`tab-nav`的封装: `src`目录下新建`common/tab-nav.js`,示例如下(Icon 下文有介绍):
142 |
143 | ```js
144 | import React from 'react'
145 | import Icon from 'react-native-vector-icons/Ionicons'
146 | import {createBottomTabNavigator} from 'react-navigation';
147 |
148 | import Home from '../pages/home/home';
149 | import Page3 from '../pages/page3/page3';
150 | import Page1 from '../pages/page1/page1';
151 | import Page2 from '../pages/page2/page2';
152 | import {colors} from '../assets/styles/colors-theme';
153 |
154 | const TabRouterMap = {
155 | home: {
156 | screen: Home,
157 | navigationOptions: {
158 | tabBarLabel: 'Home',
159 | tabBarIcon:({focused}) => (
160 |
165 | )
166 | }
167 | },
168 | page1: {
169 | screen: Page1,
170 | navigationOptions: {
171 | tabBarLabel: 'Page1',
172 | tabBarIcon:({focused}) => (
173 |
178 | )
179 | }
180 | },
181 | page2: {
182 | screen: Page2,
183 | navigationOptions: {
184 | tabBarLabel: 'Page2',
185 | tabBarIcon:({focused}) => (
186 |
191 | )
192 | }
193 | },
194 | page3: {
195 | screen: Page3,
196 | navigationOptions: {
197 | tabBarLabel: 'Page3',
198 | tabBarIcon:({focused}) => (
199 |
204 | )
205 | }
206 | }
207 | }
208 |
209 | export const TabNav = createBottomTabNavigator(TabRouterMap,{
210 | initialRouteName: 'home',
211 | tabBarOptions: {
212 | //当前选中的tab bar的文本颜色和图标颜色
213 | activeTintColor: colors.statusBarColor,
214 | //当前未选中的tab bar的文本颜色和图标颜色
215 | inactiveTintColor: '#000',
216 | //是否显示tab bar的图标,默认是false
217 | showIcon: true,
218 | //showLabel - 是否显示tab bar的文本,默认是true
219 | showLabel: true,
220 | //是否将文本转换为大小,默认是true
221 | upperCaseLabel: false,
222 | //material design中的波纹颜色(仅支持Android >= 5.0)
223 | pressColor: 'red',
224 | //按下tab bar时的不透明度(仅支持iOS和Android < 5.0).
225 | pressOpacity: 0.8,
226 | //tab bar的样式
227 | // style: {
228 | // backgroundColor: '#fff',
229 | // paddingBottom: 1,
230 | // borderTopWidth: 0.2,
231 | // paddingTop:1,
232 | // borderTopColor: '#ccc',
233 | // },
234 | //tab bar的文本样式
235 | labelStyle: {
236 | fontSize: 11,
237 | margin: 1
238 | },
239 | //tab 页指示符的样式 (tab页下面的一条线).
240 | indicatorStyle: {height: 0},
241 | },
242 | //tab bar的位置, 可选值: 'top' or 'bottom'
243 | tabBarPosition: 'bottom',
244 | //是否允许滑动切换tab页
245 | swipeEnabled: true,
246 | //是否在切换tab页时使用动画
247 | animationEnabled: false,
248 | //是否懒加载
249 | lazy: true,
250 | //返回按钮是否会导致tab切换到初始tab页? 如果是,则设置为initialRoute,否则为none。 缺省为initialRoute。
251 | backBehavior: 'none'
252 | })
253 | ```
254 |
255 | e)在`App.js`中使用路由:
256 |
257 | ```js
258 | import React, {Component} from 'react';
259 | import {StatusBar} from 'react-native';
260 | import {SafeAreaView} from 'react-navigation'
261 | import Router from './src/router';
262 | import {colors} from './src/assets/styles/colors-theme';
263 |
264 | export default class App extends Component {
265 | render() {
266 | return (
267 |
274 |
280 |
281 |
282 | );
283 | }
284 | }
285 | ```
286 |
287 | **2.history的基本封装**
288 | history是控制路由跳转的模块,一般封装出push、replace、goback、pop等,在`src`目录下新建`common/history.js`,示例如下:
289 |
290 | ```js
291 | const NAVIGATION_THROTTLE = 1000; // 1s内不准重复跳转
292 | const lastNavigationTimeStamps = {};
293 |
294 | /**
295 | * 校验页面跳转参数 防止同一个path在很短的时间内被反复调用
296 | * @param path
297 | */
298 | function validate(path) {
299 | const timestamp = new Date().valueOf();
300 | if(lastNavigationTimeStamps[path] && (timestamp - lastNavigationTimeStamps[path]) < NAVIGATION_THROTTLE) {
301 | lastNavigationTimeStamps[path] = timestamp
302 | return false
303 | } else {
304 | lastNavigationTimeStamps[path] = timestamp
305 | }
306 |
307 | return true
308 | }
309 |
310 | /**
311 | * 处理路由跳转的状态
312 | * @param prevState
313 | * @param newState
314 | * @param action
315 | */
316 | export function handleNavigationChange(prevState, newState, action) {
317 | console.log('@@@@@ prevState', prevState)
318 | console.log('@@@@@ newState', newState)
319 | console.log('@@@@@ action', action)
320 | }
321 |
322 | const history = {
323 | push: (instance, path, state) => {
324 | if(validate(path)) {
325 | const navigationController = instance.props.navigation;
326 | const nativePath =
327 | path.charAt(0) === '/' ? path.substring(1, path.length) : path;
328 |
329 | navigationController.push(nativePath, state)
330 | }
331 | },
332 | replace: (instance, path, state) => {
333 | if(validate(path)) {
334 | const navigationController = instance.props.navigation;
335 | const nativePath =
336 | path.charAt(0) === '/' ? path.substring(1, path.length) : path;
337 |
338 | navigationController.replace(nativePath, state)
339 | }
340 | },
341 | goBack: (instance) => {
342 | if(instance) {
343 | const navigationController = instance.props.navigation;
344 | navigationController.goBack()
345 | }
346 | },
347 | pop: (instance, n) => {
348 | if(instance) {
349 | const navigationController = instance.props.navigation;
350 | navigationController.pop(-1 * n || -1)
351 | }
352 | }
353 | }
354 |
355 | export default history;
356 | ```
357 |
358 | 修改`App.js`中的`Router`
359 |
360 | ```js
361 |
362 |
363 | 改为
364 |
365 | import {handleNavigationChange} from './src/common/history'
366 |
369 | ```
370 |
371 | ## 字体图标库
372 | app中需要用到大量的小图标,本文选择[react-native-vector-icons](https://oblador.github.io/react-native-vector-icons/)
373 |
374 | ```js
375 | $ yarn add react-native-vector-icons
376 | $ react-native link react-native-vector-icons
377 |
378 | 使用方法:
379 | import Icon from 'react-native-vector-icons/Ionicons'
380 |
381 |
385 |
386 | ```
387 |
388 | ## 其他配置
389 | **1.样式配置**
390 | `src`目录下新建`assets/styles/colors-theme.js`示例:全局控制整个APP所需的颜色
391 |
392 | ```js
393 | export const colors = {
394 | statusBarColor: '#23A2FF'
395 | }
396 | ```
397 |
398 | **2.服务基础配置**
399 | `src/common`目录下新建`constants.js`, 用于配置全局所需的服务地址、设备号、设备类型、版本号、分页数量等等
400 |
401 | ```js
402 | (如果不需要设备号则无需下载)
403 | $ yarn add react-native-device-info
404 | $ react-native link react-native-device-info
405 |
406 |
407 | /**
408 | * 提供基础配置信息
409 | * constants.js 提供如服务器地址、分页数量、设备类型、设备号、版本号等配置
410 | */
411 | import { Platform } from 'react-native'
412 | import DeviceInfo from 'react-native-device-info'
413 |
414 | export default {
415 | serverUrl: 'http://127.0.0.1:3600/portal',
416 | pageSize: 10,
417 | deviceType: Platform.OS.toUpperCase(),
418 | deviceNo: DeviceInfo.getUniqueID().replace('-').substr(0, 12),
419 | versionName: DeviceInfo.getVersion(), //也可写死如'1.0.0'
420 | }
421 |
422 | ```
423 |
424 | **3.服务报错配置**
425 | `src/common`目录下新建`service-error.js`, 用于配置全局服务报错
426 |
427 | ```js
428 | /**
429 | * 服务报错处理
430 | */
431 |
432 | export default class ServiceError extends Error{
433 | constructor(code, message){
434 | super(message);
435 | this.code = code;
436 | this.hash = Math.random() * 100000000000000000;
437 | this.signature = 'ServiceError';
438 | }
439 | }
440 | ```
441 |
442 | **4.code配置**
443 | `src/common`目录下新建`code.js`, 用于配置全局请求code
444 |
445 | ```js
446 | /**
447 | * code.js提供全局的请求服务字段处理
448 | */
449 | export default {
450 | SUCCESS: 'SUCCESS', //请求成功
451 | REQUEST_FAILED: 'REQUEST_FAILED', //请求失败
452 | REQUEST_TIMEOUT: 'REQUEST_TIMEOUT', //请求超时
453 | UN_KNOWN_ERROR: 'UN_KNOWN_ERROR', //未知错误
454 | TOKEN_INVALID: 'TOKEN_INVALID', //token失效
455 | SESSION_TIMEOUT: 'SESSION_TIMEOUT', //会话超时
456 | }
457 | ```
458 |
459 |
460 | ## 封装顶部导航栏NaviBar
461 | 顶部导航栏用于显示当前页面的标题,操作路由的跳转,放置部分功能模块,如分享、弹框、设置等
462 |
463 | 新增`prop-types`,用于封装类型校验
464 | ```
465 | $ yarn add prop-types
466 | ```
467 |
468 | 在`src`下新建`components/navi-bar.js`,示例如下:
469 | ```js
470 | import React, { Component } from 'react'
471 | import { View, Text, StyleSheet, Dimensions, TouchableOpacity, Platform } from 'react-native'
472 | import PropTypes from 'prop-types'
473 | import {colors} from '../assets/styles/colors-theme';
474 | import Icon from 'react-native-vector-icons/Ionicons'
475 |
476 | const { width } = Dimensions.get('window')
477 |
478 | export default class NaviBar extends Component {
479 | static propTypes = {
480 | style: PropTypes.object,
481 | leftItem: PropTypes.node, //原则上控制在宽度40的icon
482 | rightItem: PropTypes.node, //原则上控制在宽度40的icon
483 | title: PropTypes.string,
484 | titleColor: PropTypes.string,
485 | onBack: PropTypes.func,
486 | iconColor: PropTypes.string
487 | }
488 |
489 | render() {
490 | const props = this.props;
491 |
492 | return (
493 |
494 |
495 | {
496 | props.leftItem ? props.leftItem : (
497 | props.onBack ? (
498 |
499 |
504 |
505 | ) :
506 | )
507 | }
508 |
509 | {props.title}
510 |
511 | {
512 | props.rightItem ? props.rightItem :
513 | }
514 |
515 |
516 | )
517 | }
518 | }
519 |
520 | const styles = StyleSheet.flatten({
521 | naviBar: {
522 | width,
523 | height: Platform.OS === 'ios' ? 44 : 56, //ios原生导航高度是44,android是56
524 | backgroundColor: colors.statusBarColor,
525 | flexDirection: 'row',
526 | alignItems: 'center',
527 | justifyContent: 'space-between'
528 | }
529 | })
530 |
531 | ```
532 |
533 | 修改`list.js`,示例如下:
534 |
535 | ```js
536 | import React, { Component } from 'react'
537 | import {View, Text, TouchableOpacity, StyleSheet} from 'react-native'
538 | import NaviBar from '../../components/navi-bar';
539 | import history from '../../common/history';
540 | import {colors} from '../../assets/styles/colors-theme';
541 |
542 | export default class List extends Component {
543 | render() {
544 | return (
545 |
546 |
550 |
551 | List
552 | history.push(this, '/detail', {name: 'suannai'})}>
553 | 跳转到Detail
554 |
555 |
556 |
557 | )
558 | }
559 | }
560 |
561 | const styles = StyleSheet.flatten({
562 | button: {
563 | marginTop: 20,
564 | width: 100,
565 | height: 40,
566 | alignItems: 'center',
567 | justifyContent: 'center',
568 | backgroundColor: colors.statusBarColor
569 | },
570 | buttonText: {
571 | color: '#fff'
572 | }
573 | })
574 | ```
575 |
576 | ## 基础服务请求返回封装
577 | 一般来说,大项目都需要统一封装一个基础服务组件,通过这个组件去全局处理request和返回response,处理全局的服务报错。
578 |
579 | 1.`fetch`拦截器的实现:
580 | ```js
581 | $ yarn add fetch-intercept
582 |
583 | 示例如下:
584 | import fetchIntercept from 'fetch-intercept'
585 |
586 | fetchIntercept.register({
587 | request: function (url, config) {
588 | return [url, config];
589 | },
590 | requestError: function (error) {
591 | return Promise.reject(error);
592 | },
593 | response: function (res) {
594 | return res;
595 | },
596 | responseError: function (error) {
597 | return Promise.reject(error);
598 | }
599 | });
600 |
601 | ```
602 |
603 | 2.在`src`下新建`services/base-service.js`,封装`BaseService`(constants、ServiceError、code的封装在上面)
604 |
605 | ```js
606 | /**
607 | * 基础服务类封装
608 | * BaseService
609 | */
610 | import fetchIntercept from 'fetch-intercept'
611 | import constants from '../common/constants';
612 | import ServiceError from '../common/service-error';
613 | import code from '../common/code';
614 |
615 | const fetchApi = fetch; // eslint-disable-line
616 |
617 | fetchIntercept.register({
618 | request: function (url, config) {
619 | return [url, config];
620 | },
621 | requestError: function (error) {
622 | return Promise.reject(error);
623 | },
624 | response: function (res) {
625 | return res;
626 | },
627 | responseError: function (error) {
628 | return Promise.reject(error);
629 | }
630 | });
631 |
632 | export default class BaseService {
633 | constructor(props){
634 | if(props && props.showLoading){
635 | this.showLoading = props.showLoading;
636 | }
637 | if(props && props.hideLoading){
638 | this.hideLoading = props.hideLoading;
639 | }
640 | }
641 |
642 | async request(method, url, params, errorMsgIndex, showLoading = true, acceptType = 'application/json') {
643 | // 如果url不全,则自动补全
644 | if(url.indexOf('http://') < 0 && url.indexOf('https://') < 0){
645 | url = constants.serverUrl + '/' + url;
646 | }
647 |
648 | if(showLoading && this.showLoading){
649 | this.showLoading();
650 | }
651 |
652 | let res = null
653 | let timer = null
654 |
655 | try {
656 | const options = {
657 | method: method,
658 | credentials: 'include',
659 | headers: {
660 | 'content-type': 'application/json',
661 | 'accept': acceptType,
662 | 'Cache-Control': 'no-cache'
663 | }
664 | }
665 |
666 | if(method === 'POST' || method === 'PUT') {
667 | params.DeviceType = constants.deviceType
668 | params.DeviceNo = constants.deviceNo
669 |
670 | options.body = JSON.stringify(params || {})
671 | }
672 |
673 | res = await fetchApi(url, options)
674 | } catch (e) {
675 | if(this.hideLoading){
676 | if(timer) {
677 | clearTimeout(timer)
678 | }
679 | timer = setTimeout(() => {
680 | this.hideLoading()
681 | }, 2000)
682 | }
683 |
684 | throw new ServiceError(code.REQUEST_FAILED, '网络请求失败')
685 | }
686 |
687 | if(res.status && res.status >= 200 && res.status < 300) {
688 | const contentType = res.headers.get('Content-Type')
689 |
690 | if(this.hideLoading){
691 | this.hideLoading()
692 | }
693 |
694 | if(contentType.indexOf('text/plain') >= 0 || contentType.indexOf('text/html') >= 0){
695 | return res.text()
696 | }else{
697 | const responseJson = await res.json();
698 | if (responseJson && !responseJson.jsonError) {
699 | return responseJson
700 | } else {
701 | throw new ServiceError(responseJson.jsonError[0]._exceptionMessageCode || code.REQUEST_FAILED, responseJson.jsonError[0]._exceptionMessage);
702 | }
703 | }
704 | } else {
705 | if(this.hideLoading){
706 | if(timer) {
707 | clearTimeout(timer)
708 | }
709 |
710 | timer = setTimeout(() => {
711 | this.hideLoading()
712 | }, 2000)
713 | }
714 |
715 | if (res.status === 401) {
716 | throw new ServiceError(code.REQUEST_TIMEOUT, res.data.message);
717 | } else if (res.ok) {
718 | try {
719 | const responseJson = await res.json();
720 | const { message } = responseJson;
721 | throw new ServiceError(code.REQUEST_FAILED, message);
722 | } catch (e) {
723 | throw new ServiceError(code.REQUEST_FAILED, '服务未知错误');
724 | }
725 | }
726 | }
727 | }
728 |
729 | /**
730 | * GET 后台数据
731 | * @param url
732 | * @param errorMsg 报错消息
733 | * @returns {Promise<*>}
734 | */
735 | async fetchJson(url, errorMsg, showLoading = true){
736 | return await this.request('GET', url, null, errorMsg, showLoading)
737 | }
738 |
739 | /**
740 | * POST请求
741 | * @param url
742 | * @param params
743 | * @param errorMsg 报错消息
744 | * @returns {Promise.}
745 | */
746 | async postJson(url, params, errorMsg, showLoading = true){
747 | return await this.request('POST', url, params, errorMsg, showLoading)
748 | }
749 |
750 | }
751 | ```
752 |
753 | **使用`BaseService`**
754 | 在`src/services`下新建`list-service.js`
755 |
756 | ```js
757 | /**
758 | * 列表页服务
759 | */
760 | import BaseService from './base-service';
761 |
762 | export default class ListService extends BaseService {
763 | /**
764 | * 获取列表
765 | * @return {Promise}
766 | */
767 | async getList() {
768 | const res = await this.postJson('qryList.do', {})
769 |
770 | return res;
771 | }
772 | }
773 | ```
774 |
775 | 修改`list.js`
776 |
777 | ```js
778 | ...
779 | import ListService from '../../services/list-service';
780 |
781 | export default class List extends Component {
782 | constructor(props) {
783 | super(props)
784 | ...
785 | this.listService = new ListService(props)
786 | }
787 |
788 | async componentDidMount() {
789 | const res = await this.listService.getList();
790 | ...
791 | }
792 |
793 | ...
794 | }
795 |
796 | ```
797 |
798 | ## 基础服务的使用
799 |
800 | **1.封装LoadingView**
801 | 封装`LoadingView`是给全局提供一个加载动画,服务器的加载需要时间,一般以加载动画来过渡。目前我选择国际上最火的[lottie](http://airbnb.io/lottie/#/react-native),动画所需`json`文件自行去[lottiefiles](https://lottiefiles.com/recent)下载
802 |
803 | ```js
804 | $ yarn add lottie-react-native
805 | $ react-native link lottie-react-native
806 | $ react-native link lottie-ios
807 |
808 | 针对IOS的XCode配置
809 | General > Embedded Binaries > add Lottie.framework
810 | ```
811 |
812 | 在`src/common`下新建`loading.js`, 同时在`src/assets`下新建`animations/loading.json`
813 |
814 | ```js
815 | import React, { Component } from 'react'
816 | import {View, Dimensions, StyleSheet} from 'react-native';
817 | import LottieView from 'lottie-react-native';
818 | import LoadingAnimation from '../assets/animations/loading';
819 |
820 | const { width, height } = Dimensions.get('window')
821 |
822 | export default class LoadingView extends Component {
823 | render(){
824 | if(this.props.visible){
825 | return (
826 |
827 |
828 |
829 |
830 |
831 | )
832 | }else{
833 | return
834 | }
835 | }
836 | }
837 |
838 | const styles = StyleSheet.flatten({
839 | wrapper: {
840 | position: 'absolute',
841 | top: 0,
842 | left: 0,
843 | width: width,
844 | height: height,
845 | backgroundColor: 'rgba(0, 0, 0, 0.2)'
846 | },
847 | loading:{
848 | position: 'absolute',
849 | top: height / 2 - 100,
850 | left: width / 2 - 70,
851 | width: 140,
852 | height: 140
853 | }
854 | });
855 | ```
856 |
857 | 在`App.js`中使用`LoadingView`,引入LoadingView放在Router下方就行
858 |
859 | ```js
860 | import LoadingView from './src/common/loading'
861 |
862 | ...
863 |
866 | 0} />
867 | ...
868 | ```
869 |
870 | **2.loading-hoc高阶组件封装**
871 |
872 | 在`src`下新建`hocs/loading-hoc.js`, loading-hoc是一个高阶组件,用于在页面外部以`@`修饰符引用`LoadingHoc`(查看Event事件消息总线封装)
873 |
874 | ```js
875 | import React, {Component} from 'react';
876 | import {Dimensions, View} from 'react-native';
877 | const { width, height } = Dimensions.get('window')
878 | import Event from '../common/event'
879 |
880 | export default function LoadingHoc(WrappedComponent) {
881 | return class ComposedComponent extends Component {
882 | showLoading(){
883 | Event.emit('SHOW_LOADING')
884 | }
885 |
886 | hideLoading(){
887 | Event.emit('HIDE_LOADING')
888 | }
889 |
890 | render() {
891 | const props = {...this.props, ...{
892 | showLoading: this.showLoading.bind(this),
893 | hideLoading: this.hideLoading.bind(this)
894 | }};
895 | return (
896 |
897 |
898 |
899 | );
900 | }
901 | };
902 | }
903 |
904 | ```
905 |
906 | 在`List`中引入`LoadingHoc`
907 |
908 |
909 | ```js
910 | ...
911 | @LoadingHoc
912 | export default class List extends Component {
913 | constructor(props) {
914 | super(props)
915 | ...
916 | this.listService = new ListService(props)
917 | }
918 |
919 | async componentDidMount() {
920 | const res = await this.listService.getList();
921 | ...
922 | }
923 | }
924 |
925 | ```
926 |
927 | **3.全局事件消息总线封装**
928 | 在`src/common`下新建`notification-center.js`和`event.js`
929 |
930 |
931 | notification-center.js示例:
932 |
933 | ```js
934 | const __notices = [];
935 | /**
936 | * addNotification
937 | * 注册通知对象方法
938 | *
939 | * 参数:
940 | * name: 注册名,一般let在公共类中
941 | * selector: 对应的通知方法,接受到通知后进行的动作
942 | * observer: 注册对象,指Page对象
943 | */
944 | function addNotification(name, selector, observer) {
945 | if (name && selector) {
946 | if (!observer) {
947 | console.log(
948 | "addNotification Warning: no observer will can't remove notice"
949 | );
950 | }
951 | const newNotice = {
952 | name: name,
953 | selector: selector,
954 | observer: observer
955 | };
956 |
957 | addNotices(newNotice);
958 | } else {
959 | console.log('addNotification error: no selector or name');
960 | }
961 | }
962 |
963 | /**
964 | * 仅添加一次监听
965 | *
966 | * 参数:
967 | * name: 注册名,一般let在公共类中
968 | * selector: 对应的通知方法,接受到通知后进行的动作
969 | * observer: 注册对象,指Page对象
970 | */
971 | function addOnceNotification(name, selector, observer) {
972 | if (__notices.length > 0) {
973 | for (let i = 0; i < __notices.length; i++) {
974 | const notice = __notices[i];
975 | if (notice.name === name) {
976 | if (notice.observer === observer) {
977 | return;
978 | }
979 | }
980 | }
981 | }
982 | this.addNotification(name, selector, observer);
983 | }
984 |
985 | function addNotices(newNotice) {
986 | // if (__notices.length > 0) {
987 | // for (var i = 0; i < __notices.length; i++) {
988 | // var hisNotice = __notices[i];
989 | // //当名称一样时进行对比,如果不是同一个 则放入数组,否则跳出
990 | // if (newNotice.name === hisNotice.name) {
991 | // if (!cmp(hisNotice, newNotice)) {
992 | // __notices.push(newNotice);
993 | // }
994 | // return;
995 | // }else{
996 | // __notices.push(newNotice);
997 | // }
998 |
999 | // }
1000 | // } else {
1001 |
1002 | // }
1003 |
1004 | __notices.push(newNotice);
1005 | }
1006 |
1007 | /**
1008 | * removeNotification
1009 | * 移除通知方法
1010 | *
1011 | * 参数:
1012 | * name: 已经注册了的通知
1013 | * observer: 移除的通知所在的Page对象
1014 | */
1015 |
1016 | function removeNotification(name, observer) {
1017 | console.log('removeNotification:' + name);
1018 | for (let i = 0; i < __notices.length; i++) {
1019 | const notice = __notices[i];
1020 | if (notice.name === name) {
1021 | if (notice.observer === observer) {
1022 | __notices.splice(i, 1);
1023 | return;
1024 | }
1025 | }
1026 | }
1027 | }
1028 |
1029 | /**
1030 | * postNotificationName
1031 | * 发送通知方法
1032 | *
1033 | * 参数:
1034 | * name: 已经注册了的通知
1035 | * info: 携带的参数
1036 | */
1037 |
1038 | function postNotificationName(name, info) {
1039 | console.log('postNotificationName:' + name);
1040 | if (__notices.length === 0) {
1041 | console.log("postNotificationName error: u hadn't add any notice.");
1042 | return;
1043 | }
1044 |
1045 | for (let i = 0; i < __notices.length; i++) {
1046 | const notice = __notices[i];
1047 | if (notice.name === name) {
1048 | notice.selector(info);
1049 | }
1050 | }
1051 | }
1052 |
1053 | // 用于对比两个对象是否相等
1054 | function cmp(x, y) { // eslint-disable-line
1055 | // If both x and y are null or undefined and exactly the same
1056 | if (x === y) {
1057 | return true;
1058 | }
1059 |
1060 | // If they are not strictly equal, they both need to be Objects
1061 | if (!(x instanceof Object) || !(y instanceof Object)) {
1062 | return false;
1063 | }
1064 |
1065 | // They must have the exact same prototype chain, the closest we can do is
1066 | // test the constructor.
1067 | if (x.constructor !== y.constructor) {
1068 | return false;
1069 | }
1070 |
1071 | for (const p in x) {
1072 | // Inherited properties were tested using x.constructor === y.constructor
1073 | if (x.hasOwnProperty(p)) {
1074 | // Allows comparing x[ p ] and y[ p ] when set to undefined
1075 | if (!y.hasOwnProperty(p)) {
1076 | return false;
1077 | }
1078 |
1079 | // If they have the same strict value or identity then they are equal
1080 | if (x[p] === y[p]) {
1081 | continue;
1082 | }
1083 |
1084 | // Numbers, Strings, Functions, Booleans must be strictly equal
1085 | if (typeof x[p] !== 'object') {
1086 | return false;
1087 | }
1088 |
1089 | // Objects and Arrays must be tested recursively
1090 | if (!Object.equals(x[p], y[p])) {
1091 | return false;
1092 | }
1093 | }
1094 | }
1095 |
1096 | for (const p in y) {
1097 | // allows x[ p ] to be set to undefined
1098 | if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) {
1099 | return false;
1100 | }
1101 | }
1102 | return true;
1103 | }
1104 |
1105 | module.exports = {
1106 | addNotification: addNotification,
1107 | removeNotification: removeNotification,
1108 | postNotificationName: postNotificationName,
1109 | addOnceNotification: addOnceNotification
1110 | };
1111 |
1112 | ```
1113 |
1114 |
1115 | event.js示例:
1116 |
1117 | ```js
1118 | /**
1119 | * 一个JavaScript 事件消息总线
1120 | */
1121 | import NotificationCenter from './notification-center';
1122 |
1123 | export default class Event {
1124 | static listen(eventName, callback, observer) {
1125 | NotificationCenter.addNotification(eventName, callback, observer);
1126 | }
1127 | static emit(eventName, params) {
1128 | NotificationCenter.postNotificationName(eventName, params);
1129 | }
1130 | static remove(eventName, observer) {
1131 | NotificationCenter.removeNotification(eventName, observer);
1132 | }
1133 | }
1134 |
1135 | ```
1136 |
1137 |
1138 | **4.全局报错处理**
1139 | 在`src/common`下新建`global-error-handler.js`
1140 |
1141 |
1142 | global-error-handler.js示例:
1143 |
1144 |
1145 | ```js
1146 | import code from './code';
1147 | import Event from './event'
1148 |
1149 | export function handleErrors(error){
1150 | if(error && error.signature && error.signature === 'ServiceError') {
1151 | defaultServiceErrorHandler(error);
1152 | }else{
1153 | defaultErrorHandler(error);
1154 | }
1155 | }
1156 |
1157 | function defaultServiceErrorHandler(error){
1158 | if(error && error.code === code.SESSION_TIMEOUT){
1159 | Event.emit('GLOBAL_ERROR', {
1160 | type: 'SESSION_TIMEOUT'
1161 | })
1162 | }else if(error && error.message) {
1163 | Event.emit('GLOBAL_ERROR', {
1164 | type: 'SERVICE_ERROR',
1165 | message: error.message
1166 | })
1167 | }else {
1168 | Event.emit('GLOBAL_ERROR', {
1169 | type: 'SERVICE_ERROR',
1170 | message: '服务出错,请稍后再试.'
1171 | })
1172 | }
1173 | }
1174 |
1175 | function defaultErrorHandler(error){
1176 | if(error && error.message) {
1177 | Event.emit('GLOBAL_ERROR', {
1178 | type: 'SERVICE_ERROR',
1179 | message: error.message
1180 | })
1181 | }else {
1182 | Event.emit('GLOBAL_ERROR', {
1183 | type: 'SERVICE_ERROR',
1184 | message: '服务出错,请稍后再试.'
1185 | })
1186 | }
1187 | }
1188 |
1189 | ```
1190 |
1191 |
1192 | **5.全局监听**
1193 |
1194 | ```js
1195 | $ yarn add promise-polyfill
1196 | $ yarn add @ant-design/react-native
1197 | $ react-native link @ant-design/icons-react-native
1198 | ```
1199 |
1200 | `App.js`添加`promise`的报错处理和使用`antd-mobileRN版本`来进行弹框报错。
1201 |
1202 | 注:**如果需要使用`Modal`以及`Toast`还需要在 `App` 的入口处加上`Provider`, 因`mobx`也需要使用`Provider`, 本文另定义为`ProviderAntd`, `mobx`的使用教程在下面可找到**
1203 |
1204 | ```js
1205 | import {Provider as ProviderAntd, Modal} from '@ant-design/react-native'
1206 | import LoadingView from './src/common/loading';
1207 | import {handleErrors} from './src/common/global-error-handler';
1208 | import Event from './src/common/event';
1209 |
1210 | @observer
1211 | export default class App extends Component {
1212 | constructor(props) {
1213 | super(props)
1214 | this.timer = null
1215 | this.state = {
1216 | loadingCount: 0
1217 | }
1218 |
1219 | require('promise/setimmediate/rejection-tracking').enable({
1220 | allRejections: true,
1221 | onUnhandled: (id, error) => {
1222 | handleErrors(error);
1223 | }
1224 | })
1225 |
1226 | this._handleGlobalError = this.handleGlobalError.bind(this)
1227 | this._handleShowLoading = this.handleShowLoading.bind(this)
1228 | this._handleHideLoading = this.handleHideLoading.bind(this)
1229 | }
1230 |
1231 | componentDidMount() {
1232 | // 监听全局报错
1233 | Event.listen('GLOBAL_ERROR', this._handleGlobalError, this)
1234 |
1235 | // 显示加载动画
1236 | Event.listen('SHOW_LOADING', this._handleShowLoading, this)
1237 |
1238 | // 隐藏加载动画
1239 | Event.listen('HIDE_LOADING', this._handleHideLoading, this)
1240 | }
1241 |
1242 | //组件卸载之前移除监听
1243 | componentWillUnmount() {
1244 | Event.remove('GLOBAL_ERROR', this)
1245 | Event.remove('SHOW_LOADING', this)
1246 | Event.remove('HIDE_LOADING', this)
1247 | }
1248 |
1249 | render() {
1250 | return (
1251 |
1252 |
1253 |
1254 | ...
1255 | 0} />
1256 |
1257 |
1258 |
1259 | );
1260 | }
1261 |
1262 | /**
1263 | * showLoading
1264 | */
1265 | handleShowLoading() {
1266 | if(this.timer) {
1267 | clearTimeout(this.timer);
1268 | }
1269 |
1270 | this.timer = setTimeout(() => {
1271 | this.setState({
1272 | loadingCount: this.state.loadingCount + 1
1273 | })
1274 | }, 50)
1275 | }
1276 |
1277 | /**
1278 | * hideLoading
1279 | * @param bForece
1280 | */
1281 | handleHideLoading(bForece){
1282 | if(this.timer){
1283 | clearTimeout(this.timer);
1284 | }
1285 |
1286 | this.timer = setTimeout(() => {
1287 | if(this.state.loadingCount > 0){
1288 | this.setState({
1289 | loadingCount: (bForece ? 0 : this.state.loadingCount - 1)
1290 | });
1291 | }
1292 | }, 50)
1293 | }
1294 |
1295 | /**
1296 | * 全局报错处理
1297 | * @param event
1298 | */
1299 | handleGlobalError(event) {
1300 | // 报错时,取消加载动画
1301 | if(this.state.loadingCount > 0){
1302 | this.handleHideLoading(true)
1303 | }
1304 |
1305 | if(event && event.type){
1306 | switch(event.type){
1307 | case 'SESSION_TIMEOUT':
1308 | Modal.alert('会话超时', '您的会话已超时,请重新登录')
1309 | break;
1310 | case 'SERVICE_ERROR':
1311 | if(event.message) {
1312 | Modal.alert('出错了', event.message)
1313 | }
1314 | break;
1315 | default:
1316 | if(event.message) {
1317 | Modal.alert('温馨提示', '系统未知异常')
1318 | }
1319 | break;
1320 | }
1321 | }
1322 | }
1323 | }
1324 | ```
1325 |
1326 |
1327 | ## mobx的使用
1328 |
1329 | ```
1330 | $ yarn add mobx mobx-react
1331 | $ yarn add @babel/cli @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-proposal-object-rest-spread @babel/plugin-transform-classes @babel/plugin-transform-flow-strip-types @babel/plugin-transform-runtime @babel/polyfill @babel/preset-env @babel/preset-flow @babel/preset-react babel-loader babel-plugin-import babel-plugin-module-resolver babel-plugin-transform-runtime babel-polyfill babel-preset-es2015 babel-preset-react babel-preset-react-native babel-preset-react-native-stage-0 babel-preset-react-native-syntax -D
1332 | ```
1333 |
1334 | `.babelrc`或`babel.config.js`添加如下代码:
1335 |
1336 | ```js
1337 | {
1338 | presets: ['module:metro-react-native-babel-preset', '@babel/preset-flow'],
1339 | plugins: [
1340 | '@babel/transform-flow-strip-types',
1341 | [
1342 | '@babel/plugin-proposal-decorators', { 'legacy' : true }
1343 | ],
1344 | [
1345 | '@babel/plugin-proposal-class-properties', {'loose': true}
1346 | ],
1347 | [
1348 | '@babel/plugin-transform-runtime', {}
1349 | ],
1350 | ['import', { 'libraryName': '@ant-design/react-native' }]
1351 | ]
1352 | }
1353 | ```
1354 |
1355 | 在`src`下新建`stores/app-store.js`和`stores/index.js`
1356 |
1357 | `stores/app-store.js`示例:
1358 |
1359 | ```js
1360 | import {observable, action} from 'mobx'
1361 |
1362 | class AppStore {
1363 | @observable
1364 | list = []
1365 |
1366 | @observable
1367 | timer = 0
1368 |
1369 | @action
1370 | setList(data){
1371 | this.list = data
1372 | }
1373 |
1374 | @action
1375 | resetTimer() {
1376 | this.timer = 0
1377 | }
1378 |
1379 | @action
1380 | tick() {
1381 | this.timer += 1
1382 | }
1383 | }
1384 |
1385 | const appStore = new AppStore()
1386 | export {appStore}
1387 |
1388 | ```
1389 |
1390 | `stores/index.js`示例:
1391 |
1392 | ```
1393 | import {appStore} from './app-store'
1394 |
1395 | export {appStore}
1396 |
1397 | ```
1398 |
1399 | 在`App.js`中新增`mobx`,具体使用参考[mobx中文文档](https://cn.mobx.js.org/)
1400 |
1401 | `App.js`示例:
1402 |
1403 | ```js
1404 | ...
1405 | import { Provider, observer } from 'mobx-react'
1406 | import * as stores from './src/stores/index';
1407 |
1408 | @observer
1409 | export default class App extends Component {
1410 | ...
1411 |
1412 | render() {
1413 | return (
1414 |
1415 | ...
1416 |
1417 | );
1418 | }
1419 | }
1420 |
1421 | ```
1422 |
1423 | 在`home.js`中引入`mobx`, 统计点击list的次数和给List页传值
1424 |
1425 | ```js
1426 | ...
1427 | import NaviBar from '../../components/navi-bar';
1428 | import { inject, observer } from 'mobx-react';
1429 |
1430 | @inject('rootStore')
1431 | @observer
1432 | export default class Home extends Component {
1433 | constructor(props) {
1434 | ...
1435 | this.store = props.rootStore.appStore
1436 | }
1437 |
1438 | render() {
1439 | return (
1440 |
1441 |
1442 |
1443 | Home
1444 | {
1445 | //添加timer的次数
1446 | this.store.tick();
1447 | const list = this.store.list;
1448 |
1449 | list.push({
1450 | number: this.store.timer,
1451 | label: '第'+this.store.timer + '次点击'
1452 | })
1453 |
1454 | this.store.setList(list)
1455 | history.push(this, '/list', {name: 'niunai'})
1456 | }}>
1457 | 跳转到List
1458 |
1459 |
1460 | 统计跳转到List的次数: {this.store.timer}
1461 |
1462 | {
1463 | this.store.setList([]);
1464 | this.store.resetTimer();
1465 | }}>
1466 | 重置List和timer
1467 |
1468 |
1469 |
1470 | )
1471 | }
1472 | }
1473 | ```
1474 |
1475 |
1476 | 在`list.js`中引入`mobx`, 统计点击list的次数和渲染`list`
1477 |
1478 | ```js
1479 | import React, { Component } from 'react'
1480 | import {View, Text, TouchableOpacity, StyleSheet, Dimensions} from 'react-native'
1481 | import NaviBar from '../../components/navi-bar';
1482 | import history from '../../common/history';
1483 | import {colors} from '../../assets/styles/colors-theme';
1484 | import ListService from '../../services/list-service';
1485 | import LoadingHoc from '../../hocs/loading-hoc';
1486 | import {inject, observer} from 'mobx-react';
1487 |
1488 | const { width } = Dimensions.get('window')
1489 |
1490 | @LoadingHoc
1491 | @inject('rootStore')
1492 | @observer
1493 | export default class List extends Component {
1494 | constructor(props) {
1495 | super(props)
1496 | this.state = {
1497 | list: [],
1498 | name: props.navigation.state.params.name
1499 | }
1500 | this.listService = new ListService(props)
1501 | this.store = props.rootStore.appStore
1502 | }
1503 |
1504 | async componentDidMount() {
1505 | const res = await this.listService.getList();
1506 |
1507 | if(res) {
1508 | this.setState({
1509 | list: res
1510 | })
1511 | }
1512 | }
1513 |
1514 | render() {
1515 | return (
1516 |
1517 |
1521 |
1522 |
1523 | Home页传过来的name:{this.state.name}
1524 |
1525 |
1526 | 统计到{this.store.timer}次跳转到List
1527 |
1528 |
1529 |
1530 | 次数
1531 |
1532 |
1533 | 描述
1534 |
1535 |
1536 | {
1537 | this.store.list.map((item, index) => {
1538 | return (
1539 |
1540 |
1541 | {item.number}
1542 |
1543 |
1544 | {item.label}
1545 |
1546 |
1547 | )
1548 | })
1549 | }
1550 | history.push(this, '/detail', {name: 'suannai'})}>
1551 | 跳转到Detail
1552 |
1553 |
1554 |
1555 | )
1556 | }
1557 | }
1558 |
1559 | const styles = StyleSheet.flatten({
1560 | button: {
1561 | marginTop: 20,
1562 | width: 100,
1563 | height: 40,
1564 | alignItems: 'center',
1565 | justifyContent: 'center',
1566 | backgroundColor: colors.statusBarColor
1567 | },
1568 | buttonText: {
1569 | color: '#fff'
1570 | },
1571 | number: {
1572 | width: 0.3 * width,
1573 | height: 40,
1574 | alignItems: 'center',
1575 | justifyContent: 'center'
1576 | },
1577 | label: {
1578 | width: 0.7 * width,
1579 | height: 40,
1580 | alignItems: 'center',
1581 | justifyContent: 'center'
1582 | }
1583 | })
1584 |
1585 | ```
1586 |
1587 | **注意:本例中主要是通过在`App.js`中建立一个全局的`store`,命名为`rootStore`来控制所有页面的状态,
1588 | 在`home.js`页去触发`action`增加`timer`和`setList`以及重置`timer`和`list`,
1589 | 在`list.js`去渲染`timer`和`list`**
1590 |
1591 |
1592 | ## Demo总结
1593 | 至此,本文主要描述了如何构架一个react-native的APP,从基础搭建、全局的路由、路由`history`控制、全局`event`事件消息总线封装、全局`error`报错处理和监听、服务配置、基础`service`搭建到使用`mobx`进行全局状态监控就基本上讲完了,其中还包含了如何封装顶部导航栏`NaviBar`、引用字体图标库`react-native-vector-icons`、引入蚂蚁金服的`@ant-design/react-native`, 同时涉及到高阶组件`loading-hoc`的搭建使用和`lottie`动画的使用。
1594 |
1595 |
--------------------------------------------------------------------------------