React-Native概述

环境搭建

本次开发环境

node: v8.9.4
npm: 5.6.0
react: 16.0.0
react-native: 0.51.0
代码编辑器:webstorm
模拟器:ios => siMulator or 真机 / android => Android Studio自带模拟器 or 真机

  1. 安装Chocolatey,并使用Chocolatey安装python2和node.js

cmd下输入如下命令安装Chocolatey(或需翻墙)

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

安装python2

choco install python2

安装node.js

choco install nodejs.install

设置npm镜像

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

安装react-native脚手架

npm install -g yarn react-native-cli

配置yarn镜像

yarn config set registry https://registry.npm.taobao.org --global
yarn config set disturl https://npm.taobao.org/dist --global
  1. 安装Android SDK 并 配置ANDROID_HOME
  2. 创建并初始化项目,运行
react-native init projectDemo
cd projectDemo
react-native run-ios  // ios启动
react-native run-android  // android启动

最终生成项目目录这里写图片描述

android: 安卓开发源码
ios: ios开发源码
node_modules: 项目依赖
.babelrc: babel的配置文件,将各种js版本的语言转换成运行环境所能识别的
App.js: react-native入口模板文件,供index.js使用,非固定
index.js: 项目入口文件
package.json 依赖配置文件

根据项目需求小做修改
这里写图片描述

可以看到我们为前端专门创建了一个项目文件src,并将 react-native入口模板文件迁移到项目文件夹里。根据项目需求分包

assets: 资源目录
imgs: 图片资源
styles: 样式资源
|—common.js: 公用样式资源
|—color.js: 定义项目颜色
|—layout.js: 定于项目布局
|—size.js: 定义项目尺寸
|—index.js: 整合以上资源
|—entry.js 对应页面路由样式资源
utils: 工具资源
|—api.js: 集成了整个项目的请求
|—http.js: 封装了fetch请求
|—screen.js: 有关屏幕的各种属性资源
|—system.js: 有关系统的各种资源
|—utils.js: 其他工具封装
components: 全局组件封装
pages: 页面
router: 路由
store: 全局状态管理
App.js: 入口模板文件

PS:编译错误一般会提示在node.js中,运行错误手机或模拟器弹出红色错误页面。修改js代码无需重装,点击红色错误页面的【RELOAD】即可或摇动手机弹出开发菜单【RELOAD】

React-Native中文网 环境搭建文档

组件 控件

RN中每个页面都是由【Component 】组成,也可用【Component 】来做组合控件
基本组件代码

//引入React Component
import React, {Component} from 'react'
//引入控件
import {
   View,
   Text,
} from 'react-native'
//变量的定义只能放Component不可放Component里面,否则调用报错。函数可以放Component里面
let val = 1;

//export default 才可以把这个组件暴露出去,否则其他文件调用会报错(提示找不到Demo组件或者找到的Demo组件不是Component)
export default class Demo extends Component {
//构造函数,props作为父子控件直接传递数据,后面讲
   constructor(props) {
      super(props)
   }
//渲染 最终的UI效果在此
   render() {
      return (
         <View>
            <Text style={{marginTop: 20, fontSize: 16}}>react-native通用基础模板</Text>
            <View>
               {/*<Text>{listStore.num}</Text>*/}
            </View>
         </View>
      )
   }
}

常用控件
Text Button Image TextInput ScrollView ListView ,具体控件和属性、方法参考 React Native 中文网文档

布局 与 导航

布局 在控件的style中直接设置,共有5种属性,可搭配使用(正常是Flex Direction确认主轴后搭配其他)

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

Flex Direction:确定主轴即确定布局方向,可以有从左往右【row】、从上往下【column】、从右往左【row-reverse】、从下往上【column-reverse】
这里写图片描述

Justify Content:沿着主轴的排列方式,可以有:flex-start、center、flex-end、space-around以及space-between
这里写图片描述

Align Items:决定其子元素沿着次轴(与主轴垂直的轴,比如若主轴方向为row,则次轴方向为column)的排列方式,可以有flex-start、center、flex-end以及stretch
这里写图片描述

Flex wrap:根据子元素内容大小自适应布局。可以有nowrap、warp、wrap-reverse
这里写图片描述

Align Content:根据子元素内容大小自适应布局。可以有stretch、flexstart、center、flex-end、space-between、space-around
这里写图片描述

导航 :路由导航器【StackNavigator】 Tab选项卡【TabNavigator】侧滑抽屉【DrawerNavigator】

【StackNavigator】 组件采用堆栈式的页面导航来实现各个界面跳转。它的构造函数:

StackNavigator(RouteConfigs, StackNavigatorConfig)

RouteConfigs 参数表示各个页面路由配置
StackNavigatorConfig 参数表示导航器的配置

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

const StackNavigatorConfig = {
    initialRouteName: 'Home',
    initialRouteParams: {initPara: '初始页面参数'},
    navigationOptions: {
        title: '标题',
        headerTitleStyle: {fontSize: 18, color: '#666666'},
        headerStyle: {height: 48, backgroundColor: '#fff'},
    },
    mode: 'card',
    headerMode: 'screen',
    cardStyle: {backgroundColor: "#ffffff"},
    transitionConfig: (() => ({
        screenInterpolator: CardStackStyleInterpolator.forHorizontal,
    })),
    onTransitionStart: (() => {
        console.log('页面跳转动画开始');
    }),
    onTransitionEnd: (() => {
        console.log('页面跳转动画结束');
    }),
};

navigation:导航器对象,在导航器中的每一个页面,都有 navigation 属性,该属性有以下几个属性/方法:

navigate - 跳转到其他页面
state - 当前页面导航器的状态
setParams - 更改路由的参数
goBack - 返回
dispatch - 发送一个action

跳转示例

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

【TabNavigator】
构造函数

TabNavigator(RouteConfigs, TabNavigatorConfig)

api和 StackNavigator 类似
参数 RouteConfigs 是路由配置,
参数 TabNavigatorConfig是Tab选项卡配置。
【DrawerNavigator】
基本配置和上面两者api相似

属性(Props) 与 状态(State)

props 组件属性,只可以用来保存组件间数据传递
eg:父组件传递参数给子组件

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

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

eg:子组件传递参数给父组件

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

    show(sonV) {

    } 
}

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

state 组件状态,组件是更新渲染依赖于其state属性,所以不是跟渲染相关的属性不要设置到这里,加大RN的渲染负担(state有所变动都会导致RN进行是否渲染的判断)。

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

  constructor(props){
    super(props);
    //设置state属性
    this.state = { showData: false };
    //设置变量
    this.dataText = "";
  }
  render() {
    return (
        <View>
            <Text tyle={{backgroundColor:'white',textAlign:'center',textAlignVertical:'center'}} onPress={this._onPressButton.bind(this)}>
                {this.state.showData ? this.dataText : "点击改变状态"}
            </Text>
        </View>
    );

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

手势识别

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

定时器

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

requestAnimationFrame(fn)和setTimeout(fn, 0)不同,前者会在每帧刷新之后执行一次,而后者则会尽可能快的执行(在iPhone5S上有可能每秒1000次以上)。

setImmediate则会在当前JavaScript执行块结束的时候执行,就在将要发送批量响应数据到原生之前。注意如果你在setImmediate的回调函数中又执行了setImmediate,它会紧接着立刻执行,而不会在调用之前等待原生代码。

Promise的实现就使用了setImmediate来执行异步调用。

网络访问

async getMoviesFromApi() {
    try {
      // 注意这里的await语句,其所在的函数必须有async关键字声明
      let response = await fetch('https://facebook.github.io/react-native/movies.json');
      let responseJson = await response.json();
      return responseJson.movies;
    } catch(error) {
      console.error(error);
    }
  }

调试

这里写图片描述

  1. Reload: 重载rn项目
  2. Debug JS Remotely : 打开浏览器debug模式,会在浏览器打开一个新的标签页面,
    地址是http://localhost:8081/debugger-ui
  3. Live Reload : 实时刷新 当你的js代码发生变化后,React Native会自动生成bundle然后传输到模拟器或手机上
  4. Hot Reloading: 热加载 当你每次保存代码时Hot Reloading功能便会生成此次修改代码的增量包,然后传输到手机或模拟器上以实现热加载。相比 Enable Live Reload需要每次都返回到启动页面,Enable Live Reload则会在保持你的程序状态的情况下,就可以将最新的代码部署到设备上
  5. Toggle Inspector 调试网络、布局、点击等
  6. Show Perf Monitor 打开性能监控

    【debug调试】

  7. 在工程目录下输入npm start启动服务

  8. 手机与电脑同一个wifi环境下,真机摇晃弹出上述菜单,选择【Dev Settings】–【Debug server host & port for device】填写代理的ip和端口(eg:172.22.185.210:8081)。
  9. Chrome浏览器打开 http://localhost:8081/debugger-ui/ 然后手机调试菜单【Reload】下,会自动连接到Chrome上(没有UI)
  10. 连上后画面如下
    这里写图片描述

原生与React-Native交互

【例子为已有老项目,接入少量RN模块,仍以原生为主流】
按上面教程搭建好环境,
初始化一个新项目

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

用AS打开android目录,里面默认生成的是一个MainActivity和MainApplication,且MainActivity继承自ReactActivity,即app一启动打开MainActivity,MainActivity又去通过【原生调用RN界面】方式去启动我们的RN项目。(老项目迁移建议按照环境搭建步骤创建一个新项目,然后再对android目录下代码进行修改,把老项目copy过来比较简单)

然后把自己的老项目迁移过来,启动页的Activity也换成自己的,MainActivity可以删除,MainApplication里的代码是需要的可以跟自己原有的Application合并下。

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

React-Native的内容依赖于【index.android.bundle】文件,必须保证【index.android.bundle】文件是最新的才能看到最新修改的效果,在项目目录下启动服务(即npm start)并配置到代理,每次run项目就会自动更新;或者打离线【index.android.bundle】包也可不依赖RN服务(一般打包的时候用,否则RN内容打开会白屏)。
【自动更新】
用Node.js命令行进入项目工程输入
npm start
启动服务(如果服务连接不上可直接运行react-native run-android重新跑下)

摇晃手机调出RN的调试界面,选择【Dev Settings】-> 【Debug server host & port for device】输入电脑的ip,端口号是8081,格式如【172.22.185.190:8081】。

【手动更新 即 离线包】
如果无法自动更新,可以产用手动更新的方法
在android下创建assets【android/app/src/main/assets】目录。然后打开Node.js命令行工具,进入根目录,运行如下代码

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

这会在assets下生成两个文件
这里写图片描述

PS:运行的app可能会报如下【index.android.bundle文件找不到错误】也是这个原因,启动服务,配置代理即可或打离线包均可解决。

【原生调RN界面】

修改MainActivity变成我们的项目,这样app启动起来就不调用RN而是我们的MainActivity了

public class MainActivity extends AppCompatActivity {

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

这个原生界面有两个Button,点击后分别跳转OneReactActivity和TwoReactActivity两个模块,OneReactActivity和TwoReactActivity是跳转RN界面的入口Activity,继承自ReactActivity。
OneReactActivity

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

Two也类似,getMainComponentName返”Two”即可
MainApplication保持原来的不动。
这样原生部分的代码编写完毕,现在写两个RN的界面并注册
One和Two的代码类似,One的代码如下

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

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

然后在index.js中注册下

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

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

ok编译运行。

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

这部分的流程就是原生将自己的方法注册到RN中,RN对方法进行调用,跳转界面也是RN调用原生方法,再在原生方法中进行跳转。其实原生跳RN也一样,只不过这部分封装在ReactActivity中所以感知不深。
编写ReactContextBaseJavaModule子类,写一个MyJavaModule吧

public class MyJavaModule extends ReactContextBaseJavaModule {

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

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

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

注意 【ReactMethod】这个注解
编写ReactPackage子类,编写一个MyReactPackage吧

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

        modules.add(new MyJavaModule(reactContext));

        return modules;
    }

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

最后再MainApplication中注册下

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

写一个ResultActivity来提供跳转,其实你做其他的事情也可以。原生部分结束。
RN部分修改下One.js

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

let JavaModule = NativeModules.MyJavaModule;

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

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

很简单,主要是NativeModules部分。

【原生与RN通信】

MyJavaModule添加如下代码

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

context是ReactApplicationContext对象,取自MyJavaModule的构造函数
原生部分这样就可以了,我们在需要的地方调用sendEvent即可,params参数可以为空
RN部分,我们在接收的地方加入以下代码即可

componentDidMount(){
   DeviceEventEmitter.addListener('result', function() {
      alert("send success");
   });
}

猜你喜欢

转载自blog.csdn.net/xiaoru5127/article/details/80507463