ReactNative 知识整理
一、ReactNative 布局
1、指定宽高#
最简单的给组件设定尺寸的方式就是在样式中指定固定的width和height。React Native中的尺寸都是无单位的,表示的是与设备像素密度无关的逻辑像素点。
配合marginLeft,paddingLeft等使用
2、弹性(Flex)宽高#
在组件样式中使用flex可以使其在可利用的空间中动态地扩张或收缩。一般而言我们会使用flex:1来指定某个组件扩张以撑满所有剩余的空间。如果有多个并列的子组件使用了flex:1,则这些子组件会平分父容器中剩余的空间。如果这些并列的子组件的flex值不一样,则谁的值更大,谁占据剩余空间的比例就更大(即占据剩余空间的比等于并列组件间flex值的比)。
组件能够撑满剩余空间的前提是其父容器的尺寸不为零。如果父容器既没有固定的width和height,也没有设定flex,则父容器的尺寸为零。其子组件如果使用了flex,也是无法显示的。
3、Flex Direction
在组件的style中指定flexDirection可以决定布局的主轴。子元素是应该沿着水平轴(row)方向排列,还是沿着竖直轴(column)方向排列呢?默认值是竖直轴(column)方向。
4、Justify Content
在组件的style中指定justifyContent可以决定其子元素沿着主轴的排列方式。子元素是应该靠近主轴的起始端还是末尾段分布呢?亦或应该均匀分布?对应的这些可选项有:flex-start、center、flex-end、space-around以及space-between。
*如果主轴是竖直flex-start代表顶端,如果主轴是水平flex-start代表左边,其他以此类推。
5、Align Items
在组件的style中指定alignItems可以决定其子元素沿着次轴(与主轴垂直的轴,比如若主轴方向为row,则次轴方向为column)的排列方式。子元素是应该靠近次轴的起始端还是末尾段分布呢?亦或应该均匀分布?对应的这些可选项有:flex-start、center、flex-end以及stretch。
注意:要使stretch选项生效的话,子元素在次轴方向上不能有固定的尺寸。以下面的代码为例:只有将子元素样式中的width: 50去掉之后,alignItems: ‘stretch'才能生效。
*次属性用于次轴生效,如果次轴是相对主轴是顶端,lex-start代表左上角;如果次轴相对主轴是左边,则lex-start代表左上角。
二、类文件引用
两种方法创建公共类文件
1、先创建class Test再 module.exports = Test;(ES5方法)
2、直接创建export default class Test;(ES6方法)
其他文件引进该类 import Test from ‘./Test’(ES6)
var MyComponent = require(‘./MyComponent’);(ES5)
*./(当前目录) ../(上级目录)
三、引用原生模块或者原生UI组件
1、引用原生模块
OC:创建CalendarManager类
1)简单的参数传递 ->.h文件
#import
#import
#import
@interface CalendarManager : RCTEventEmitter
@end
.m文件
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(juEvent:(NSString *)name location:(NSString *)location)
{
RCTLogInfo(@"原生的 %@ at %@", name, location);
CalendarManager *test=[[CalendarManager alloc]init];
[test juTest];
}
-(void)juTest{
NSLog(@"原生的1234");
}
JS:导入原生类
import { NativeModules } from 'react-native';
var CalendarManager = NativeModules.CalendarManager;
CalendarManager.juEvent('Birthday Party', '4 Privet Drive, Surrey’);
2)回调函数
RCTReponseSenderBlock->(.m文件)
RCT_EXPORT_METHOD(findCall:(RCTResponseSenderBlock)callback)
{
NSArray *events = @[@"1235",@"76543"];
callback(@[[NSNull null], events]);
}
JS:文件
CalendarManager.findCall((error, events) => {
if (error) {
console.error(error);
} else {
console.log(events);
// this.setState({events: events});
}
})
Promises->(.m文件)
RCT_EXPORT_METHOD(executeCommand:(NSDictionary *)cmdDic
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSArray *events = @[@"1235",@"76543"];
if (resolve) {
resolve( events);
}
}
RCT_REMAP_METHOD(alertUserPromise, resolver:(RCTPromiseResolveBlock)resolver rejecter:(RCTPromiseRejectBlock)reject){
reject(@"0",@"cancel",err);
resolver(@[@1]);
}
js文件
RCT_EXPORT_METHOD(函数带参数)
CalendarManager.executeCommand({"jute":"123456"})
.then(
(result) => {
console.log(result);
})
.catch(
(error) => {
});
RCT_REMAP_METHOD(函数不带参数)
CalendarManager.alertUserPromise()
.then((datas)=> {
console.warn('data', datas);
})
.catch((err)=> {
console.warn('err', err);
});或async _alertPromise() {
try {
var datas = await CalendarManager.alertUserPromise();
console.warn('data', datas);
} catch (e) {
console.warn('err', e);
}
}
*理解Resolve和Reject
Resolve和Reject分量是Promise的两种状态,表示已解决和已拒绝,Resolve是正常的执行结果,而Reject会触发catch操作。
在Object-C与之相对应的是:RCTPromiseResolveBlock和RCTPromiseRejectBlock,两个都是定义好的Object-C bridge。
这里的then处理的是Resovle状态的结果,而catch处理的是Reject状态的结果。
也可以使用async/await实现
async/await是两个关键词,用来把Promises的思想融入到语言本身。使用他们就不再需要写catch这样的伪同步的代码,直接使用try/catch/return这样的关键词就可以了.
Promises对于回调的使用很便利,尽量避免了JavaScript的回调地狱。
*使用时需要注意多线程
3)给Javascript发送事件
即使没有被JavaScript调用,原生模块也可以给JavaScript发送事件通知。最好的方法是继承RCTEventEmitter,实现suppportEvents方法并调用self sendEventWithName:。
.m文件
- (NSArray *)supportedEvents
{
return @[@"EventReminder"];
}
- (void)calendarEventReminderReceived:(NSNotification *)notification
{
NSString *eventName = notification.userInfo[@"name"];
[self sendEventWithName:@"EventReminder" body:@{@"name": eventName}];
}
js文件
const subscription = calendarManagerEmitter.addListener(
'EventReminder',
(reminder) => console.log(reminder.name)
);
...
// 别忘了取消订阅,通常在componentWillUnmount生命周期方法中实现。
subscription.remove();
优化无监听处理的事件
/ 在添加第一个监听函数时触发
-(void)startObserving {
hasListeners = YES;
// Set up any upstream listeners or background tasks as necessary
}
// Will be called when this module's last listener is removed, or on dealloc.
-(void)stopObserving {
hasListeners = NO;
// Remove upstream listeners, stop unnecessary background tasks
}
2、引用原生UI组件
OC:创建CalendarManager类
.h文件
#import
#import
@interface RNTMapManager : RCTViewManager
@end
.m文件
#import "RNTMapManager.h"
@implementation RNTMapManager
RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(pitchEnabled, BOOL)
- (UIView *)view
{
return [[MKMapView alloc] init];
}
@end
JS:首先创建MapView.js(以下两种方法皆可,第一种无事件)
//第一种
var { requireNativeComponent } = require('react-native');
module.exports = requireNativeComponent('RNTMap', null);
//第二种
import React, { Component, PropTypes } from 'react';
import { requireNativeComponent } from 'react-native';
var RNTMap = requireNativeComponent('RNTMap', MapView);
export default class MapView extends Component {
static propTypes = {
/**
*当这个属性被设置为true,并且地图上绑定了一个有效的可视区域的情况下,
*可以通过捏放操作来改变摄像头的偏转角度。
*当这个属性被设置成false时,摄像头的角度会被忽略,地图会一直显示为俯视状态。
*/
pitchEnabled: PropTypes.bool,
};
render() {
return ;
}
}
导入原生类
import MapView from ‘./MapView'
使用:
四、图片
1、静态图片资源(不设置宽度会根据图片尺寸自适应)
相应js文件下
├── button.js
└── img
├── check@2x.png
└── check@3x.png
2、使用混合App的图片资源(需要知道宽高否则不显示)
(通过Xcode的asset类目或者Android的drawable文件夹打包)
3、网络图片(需要知道宽高否则不显示)
4、网络图片缓存(http://blog.csdn.net/sinat_17775997/article/details/65442050,http://blog.csdn.net/sinat_17775997/article/details/65442054)
安装依赖方法:
1、 npm install react-native-img-cache --save
2、npm install --save react-native-fetch-blob
react-native-img-cache该库使用是需要依赖react-native-fetch-blob
3、链接blob react-native link
四、导航栏
1、iOS导航栏(NavigatorIOS)
render() {
initialRoute={{
component: FirstPageComponent,
title: 'title哈哈',
}}
style={{flex: 1}}
/>
)
}
2、通用导航栏
1)Old方法( Navigator)
render() {
let defaultName = 'firstPageName';
let defaultComponent = FirstPageComponent;
return (
styles = {styles.container}
initialRoute = {{name: defaultName,component : defaultComponent}}
configureScene = {
(route)=>{
return Navigator.SceneConfigs.FloatFromRight
}
}
renderScene = {(route,navigator)=>{
let Component = route.component;
return
}}
/>
)
}
2)新方法 (React Navigation)
首先安装 npm install --save react-navigation
export default class RnWidget extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
return Hello, Navigation!;
}
}
const SimpleApp = StackNavigator({
Home: { screen: RnWidget },
});
AppRegistry.registerComponent('testRN', () => SimpleApp);
ReactNative发布(打包方式)
1、离线包
1)通过代码直接xcode自动生成
在App Store上发布应用首先需要编译一个“发布版本”(release)的应用。具体的做法是在Xcode中选择Product -> Scheme -> Edit Scheme (cmd + <),然后选择Run选项卡,将Build Configuration设置为release。 Release版本的应用会自动禁用开发者菜单,同时也会将js文件和静态图片打包压缩后内置到包中,这样应用可以在本地读取而无需访问开发服务器(同时这样一来你也无法再调试,需要调试请将Buiid Configuration再改为debug)。
*次方法入口函数只能在文件(index.ios.js)
2)通过命名打相应的发布文件
rm -rf bundle && mkdir bundle && react-native bundle --entry-file index.ios.js --bundle-output ./bundle/index.ios.jsbundle--platform ios --assets-dest ./bundle --dev false && open bundle
*如有多个入口函数需更改相应的js(index.ios.js)文件和打包后文件名(index.ios.jsbundle)
2、线上包(需提供IP地址)
更改客户端IP:[RCTBundleURLProvider sharedSettings].jsLocation=@“192.168.8.18";
注意:以上方法若有多个独立模块需注册多个入口函数
AppRegistry.registerComponent('testRN', () => testRN);
‘testRN'入口函数名 testRN类名;
客户端需要改相应代码
ios方法:
1)[[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:@“index.ios”]
*jsBundleURLForBundleRoot:@“index.ios” 动态文件(IP地址)使用不同文件
fallbackResource:@“index.ios”离线包模块;
2)RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@“testRN”
initialProperties:nil
launchOptions:nil];
moduleName:@“testRN”(模块名)
ReactNative默认属性与方法
1、默认属性defaultProps和propTypes
class Video extends React.Component {
static defaultProps = {
autoPlay: false,
maxLoops: 10,
}; // 注意这里有分号
static propTypes = {
autoPlay: React.PropTypes.bool.isRequired,
maxLoops: React.PropTypes.number.isRequired,
posterFrameSrc: React.PropTypes.string.isRequired,
videoSrc: React.PropTypes.string.isRequired,
}; // 注意这里有分号
render() {
return (
);
}// 注意这里既没有分号也没有逗号
}
2、初始化STATE
//ES5
var Video = React.createClass({
getInitialState:function() {
return {
loopsRemaining:this.props.maxLoops,
};
},
})
//ES6
class Video extends React.Component {
state = {
loopsRemaining:this.props.maxLoops,
}
}
3、通过构造函数初始化constructor
class Video extends React.Component {
constructor(props){
super(props);
this.state = {
loopsRemaining:this.props.maxLoops,
};
}
}
state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变。这就是为什么有些容器组件需要定义 state 来更新和修改数据。 而子组件只能通过 props 来传递数据。
把方法作为回调提供
很多习惯于ES6的用户反而不理解在ES5下可以这么做:
//ES5
var PostInfo = React.createClass({
handleOptionsButtonClick:function(e) {
// Here, 'this' refers to the component instance.
this.setState({showOptionsModal: true});
},
render:function(){
return (
{this.props.label}
)
},
});
在ES5下,React.createClass会把所有的方法都bind一遍,这样可以提交到任意的地方作为回调函数,而this不会变化。但官方现在逐步认为这反而是不标准、不易理解的。
在ES6下,你需要通过bind来绑定this引用,或者使用箭头函数(它会绑定当前scope的this引用)来调用
//ES6
class PostInfo extends React.Component
{
handleOptionsButtonClick(e){
this.setState({showOptionsModal: true});
}
render(){
return (
onPress={this.handleOptionsButtonClick.bind(this)}
onPress={e=>this.handleOptionsButtonClick(e)}
>
{this.props.label}
)
},
}
箭头函数
箭头函数实际上是在这里定义了一个临时的函数,箭头函数的箭头=>之前是一个空括号、单个的参数名、或用括号括起的多个参数名,而箭头之后可以是一个表达式(作为函数的返回值),或者是用花括号括起的函数体(需要自行通过return来返回值,否则返回的是undefined)。
// 箭头函数的例子
()=>1
v=>v+1
(a,b)=>a+b
()=>{
alert("foo");
}
e=>{
if (e == 0){
return 0;
}
return 1000/e;
}
需要注意的是,不论是bind还是箭头函数,每次被执行都返回的是一个新的函数引用,因此如果你还需要函数的引用去做一些别的事情(譬如卸载监听器),那么你必须自己保存这个引用
// 错误的做法
class PauseMenu extends React.Component{
componentWillMount(){
AppStateIOS.addEventListener('change', this.onAppPaused.bind(this));
}
componentDidUnmount(){
AppStateIOS.removeEventListener('change', this.onAppPaused.bind(this));
}
onAppPaused(event){
}
}
// 正确的做法
class PauseMenu extends React.Component{
constructor(props){
super(props);
this._onAppPaused = this.onAppPaused.bind(this);
}
componentWillMount(){
AppStateIOS.addEventListener('change', this._onAppPaused);
}
componentDidUnmount(){
AppStateIOS.removeEventListener('change', this._onAppPaused);
}
onAppPaused(event){
}
}
从这个帖子中我们还学习到一种新的做法:
// 正确的做法
class PauseMenu extends React.Component{
componentWillMount(){
AppStateIOS.addEventListener('change', this.onAppPaused);
}
componentDidUnmount(){
AppStateIOS.removeEventListener('change', this.onAppPaused);
}
onAppPaused = (event) => {
//把方法直接作为一个arrow function的属性来定义,初始化的时候就绑定好了this指针
}
}
网络请求
getDataFromFetchs() {
fetch('http://gank.io/api/search/query/listview/category/福利/count/10/page/1',{
})//请求地址
.then((response) => response.json())//取数据
.then((responseJson) => {//处理数据
//通过setState()方法重新渲染界面
console.log("返回",responseJson.results);
})
.catch((error) => {
console.warn('失败',error);
}).done();
}
//三方网络请求
httpRequest(){
var request = new XMLHttpRequest();
request.onreadystatechange = (e) => {
if (request.readyState !== 4) {
return;
}
if (request.status === 200) {
console.log('成功', request.responseText);
} else {
console.warn('error');
}
};
request.open('GET', 'http://gank.io/api/search/query/listview/category/福利/count/10/page/1');
request.send();
}
额外学习知识
1、CSS相关属性,控制View相关属性。
如backgroundColor,color。(-全部用首字母大写表示);
2、React :JSX,State,Props
3、JavaScript:至少从ES5开始学习;
4、OC、Android相关知识(可在reactnative中嵌套原生、或者原生中嵌套相关reactnative模块)。