ReactNative 快速开始
学习使用 ReactNative 最令人头疼的就是环境问题,因为大多数 Web 开发者,并不熟悉 Android 与 IOS 的开发环境,配置起来比较繁琐。当然 Android 程序员也不熟悉 IOS 环境,反之亦然,这导致很多人因为繁杂的开发环境而放弃学习或者暂时搁浅。
为了改善这个问题,一个快速的开发环境解决方案应运而生。这个方案需要两个工具:
- create-react-native-app
- Expo
create-react-native-app
这是一个用来创建 ReactNative 项目的脚手架工具。特点是无需配置繁杂的 Android 或 IOS 开发环境,便可进行原生应用的开发,极大简化了环境搭建与配置的过程,很适合学习使用。(这种模式也称之为沙盒模式)
- [中文文档] reactnative.cn/docs/gettin…
- [英文官方文档] facebook.github.io/react-nativ…
安装
# 安装
npm install -g create-react-native-app
# 查看版本信息
C:\Users\Administrator>create-react-native-app --version
create-react-native-app version: 2.0.2
使用
# 创建项目
create-react-native-app projectName
# 启动项目
cd projectName
npm run start
Expo
这是一款安装在手机上的 app,专门负责运行上述方式构建的 App,这有点像是微信里面运行的小程序。IOS 可以到 AppStore 里面搜索下载,Android 可以到 GooglePlay 里面搜索下载,也可以到 Github 里下载。
- [Github] github.com/expo/expo
真机运行
准备工作:使用create-react-native-app创建一个项目,然后手机安装Expo应用,并确保手机与开发机处于同一局域网下。
项目运行:npm run start启动项目,打开Expo应用,使用里面的扫一扫功能,扫描项目启动后生成的巨大二维码,等待 JS 打包传输完毕,就会在手机上运行。
IP错误问题
因为开发机可能会有多个 ip 地址,比如有线网 IP、虚拟 IP、无线局域网 IP 等等。当项目启动时通常会无脑的选择 IP 列表中的第一个,假如是有线网 IP,那么就与手机所处的无线局域网不同,造成网络连接失败。
下面是这种情况的图例,项目启动时使用的是 IP 列表中的第一个网卡的 IP 地址,并没有使用无线局域网IP。
如果发生这种情况,先Ctrl+C停掉项目,设置一个环境变量,然后再重启项目即可。
- [解决方案] github.com/react-commu…
- [解决方案] github.com/react-commu…
# 设置环境变量
set REACT_NATIVE_PACKAGER_HOSTNAME=my-custom-ip-address
# 重启项目
npm run start
开发初体验
- 在项目根目录下有个
App.js, 是应用的根组件, 我们手机上看到的内容就是这个组件实现的。 - 尝试修改
App.js中的文本内容,然后手机摇一摇,点击Reload选项即可更新修改内容。
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
// 1. 导出根组件, 组件的定义语法与之前学习的 react 的完全一样
export default class App extends React.Component {
render() {
return (
// 2. 返回的视图需要使用 View 组件包裹, 作用相当于 div 标签
<View style={styles.container}>
{/* 3. 文本内容使用Text组件包裹, 作用相当于span标签 */}
<Text>Open up App.js to start working on your app!</Text>
</View>
);
}
}
// 4. 定义样式, 这里的尺寸大小无需加单位, 元素可以通过数组引用多个样式
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f00',
alignItems: 'center',
justifyContent: 'center',
},
});
错误提示 - 红屏和黄屏
React-Native 代码运行时会把错误分为红屏和黄屏两种,红屏会以全屏红色显示在应用中,导致程序无法正常运行,红屏通常是编码错误导致的,必须修复。
黄屏代表警告,会显示在应用下方,不影响程序运行,不修复也可以,警告可以通过代码禁止。
红屏和黄屏只在 debug 版本中才会显示,在 release 版本中不会。一般情况下,通过 React-Native 红屏中的错误提示信息,就可以定位问题,如果是语法等常见运行错误,RN也会给出错误代码所在位置与行数,所以出现红屏一定要阅读描述信息。
// 1. 屏蔽黄色警告,参数为一个数组:数组中的字符串就是要屏蔽的警告的开头的内容。
YellowBox.ignoreWarnings(['Warning: ']);
// 2. 手动触发红屏和警告
console.error("严重错误");
console.warn("警告");
调试菜单
React-Native 开发调试没有本地代码方便,但也是可以调的,在调试菜单中可以进行开发调试相关的配置。调试菜单只在 debug 版本中可以被调出,如果是模拟器环境,Windows平台按window+m可调出,也可能是F1或F2,Mac平台按commond+m可调出。如果是真机,摇一摇就可以调出调试菜单。
调试菜单说明
- Reload: 重新加载整个页面
- Debug JS Remotely: 启动远程调试, 会自动打开chrome浏览器, 然后可在开发者工具中调试js
- Enable Live Reload: 开启自动加载,代码变动会自动更新整个页面
- Enable Hot Reloading: 热更新, 代码变动自动的进行局部更新
- Dev Sttings: 开发调试配置
内置组件
- 在
React Native中你需要使用官方提供的组件进行应用构建 - 因为是开发
原生应用, 我们的代码最终会转为原生组件的方式渲染, 所以不会看到任何以html标签命名的组件 - [中文文档] reactnative.cn/docs/activi…
View
- 视图容器,作用相当于
html的div标签,它是创建UI所需的最基础组件,支持Flexbox布局、样式、触摸事件,它可以放到其它视图中,也可以包含任意多个任意子视图。 - reactnative.cn/docs/view/
import React, { Component } from 'react';
import { StyleSheet, View, Text, Button } from 'react-native';
export default class ViewStudy extends Component {
render() {
return (
{/* 1. View标签相当于div,可以嵌套 */}
<View style={style.container}>
<View>
<Text>React Native</Text>
</View>
<View>
<Text>react-native 学习</Text>
</View>
</View>
);
}
}
// 2. 注意border、background等样式不能简写,必须一个一个的设置属性
let style = StyleSheet.create({
container: {
width: 350,
padding: 15,
borderStyle: "dashed",
borderWidth: 2,
borderColor: "red",
borderRadius: 2,
},
text: {
fontSize: 28,
color: "#0f0",
}
});
Text
- 文本容器,作用相当于
html的span标签。Text标签支持嵌套、触摸事件。在RN中,文本必须放置到Text中才可以被渲染,否则报错。 - 注意: 除了Text外, 别的组件内都不能包含文本
- reactnative.cn/docs/text/
文本布局
Text采用的是文本布局,多个子文本在渲染时会折叠合并在一起,如果把View理解成块级元素,那么Text就可以理解为行内元素。
import React, { Component } from "react";
import { StyleSheet, View, Text } from "react-native";
export default class TextStudy extends Component {
render() {
return (
// 可以嵌套
<Text>
<Text>文本一</Text>
<Text>文本二</Text>
</Text>
);
}
}
文本样式
在RN中,父文本的样式可以传递给后代文本,也就是样式继承。但是除了文本之外其它组件都无法继承样式。
render() {
return (
<Text style={style.rootText}>
<Text>文本一</Text>
<Text>文本二</Text>
{/* 文本样式继承 */}
<Text>
<Text>文本三</Text>
</Text>
</Text>
);
}
let style = StyleSheet.create({
rootText: {
fontSize: 25,
color: "blue",
lineHeight: 25
}
});
Image
- 作用相当于
html的img标签用于承载图片 - 组件通过
source属性设置图片地址 - reactnative.cn/docs/image.…
- reactnative.cn/docs/0.50/i…
载入本地图片
- 本地图片通过
require方法导入 - 之前的版本中require方法必须传入
静态字符串,不能使用表达式和字符串拼接, 也就是写死,同时图片名称也不允许以数字开头,现在的新版本已经修复了这两个bug
import React, { Component } from 'react';
import { StyleSheet, View, Image } from 'react-native';
export default class ImageStudy extends Component {
render() {
let pic = {
uri: 'https://ss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=645293140,2005760885&fm=173'
};
let imgBasePath = "../public/img/";
return (
<View >
{/* 通过source属性设置图片地址,通过require方法载入本地图片 */}
<Image style={styles.img} source={require('../public/img/12.jpg')}/>
{/* 在之前的版本中,图片路径必须是静态的,不能写表达式,现在可以了 */}
<Image style={styles.img} source={require(imgBasePath + '12.jpg')} />
{/* 在之前的版本中,图片名称也不能以数字开头,现在也可以了 */}
<Image style={styles.img} source={require('./56.jpg')} />
</View>
)
}
}
let styles = StyleSheet.create({
img: {
width: 55,
height: 55
}
});
载入网络图片
- 如果是通过uri载入的网络图片,必须要设置宽高,否则无法显示
- 如果某些网站的图片载入失败尝试换一个域名图片试试
export default class ImageTest extends Component {
render() {
let pic = {
uri: 'https://ss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=645293140,2005760885&fm=173'
};
let imgBasePath = "../public/img/";
return (
<View >
{/* 没有宽高,图片不会显示 */}
<Image source={pic} />
{/* 设置宽高,图片正常显示 */}
<Image source={pic} style={{width:200, height: 100}} />
</View>
)
}
}
批量载入网络图片
- 之前如果想
写活地址, 必须定义一个对象赋值 <Image source={对象} />- 现在更加灵活,只需要把uri写活就可以
<Image source={{uri: 变量}} />
import React, { Component } from 'react';
import { StyleSheet, View, Image } from 'react-native';
export default class ImageTest extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View>
{
this.props.imgs && this.props.imgs.map((uri, i) => {
return <Image key={`key${i}`} source={{uri: uri}} style={{width:200, height: 100}} />;
})
}
</View>
)
}
}
Button
- 作用相当于
html的button标签用于触发点击 - 按钮需要通过
title属性设置文本内容, 值必须为字符串,其他数值或者不设都会报错 - 按钮通过
onPress属性监听点击事件 - reactnative.cn/docs/0.50/b…
import React, { Component } from "react";
import { StyleSheet, View, Text, Button, Alert } from "react-native";
export default class ButtonStudy extends Component {
constructor(props) {
super(props);
this.state = {
num: 1
};
}
onPressHandler() {
this.setState({num: ++this.state.num});
}
render() {
return (
<View>
<Text>{this.state.num}</Text>
<Button onPress={() => {this.onPressHandler()}} title="点我+1"></Button>
</View>
)
}
}
TextInput
- 作用相当于
html的input标签用于输入文本 - 需要通过
value属性指定文本内容, 通过onChangeText属性监听文本的变化事件 - reactnative.cn/docs/0.50/t…
import React, { Component } from 'react';
import { StyleSheet, View, Text, TextInput } from 'react-native';
export default class TextInputStudy extends Component {
constructor(props) {
super(props);
this.state = {
text: '默认值'
}
}
render() {
return (
<View>
{/* 模拟双向数据绑定 */}
<Text>{ this.state.text }</Text>
<TextInput value={ this.state.text } onChangeText={(text) => this.setState({text})} />
<Text>{ this.state.text }</Text>
<TextInput placeholder="请输入密码" onChangeText={(text) => this.setState({text})} />
</View>
);
}
}
Alert
import React, { Component } from "react";
import { StyleSheet, View, Text, Button, Alert } from "react-native";
export default class AlertTest extends Component {
onPressHanlder() {
Alert.alert(
// 标题
'温馨提示',
// 内容
'您的餐凉了,要不要热一下',
// 配置一个按钮是确定,两个按钮是取消与确定,三个按钮是稍后再试、取消与确定
[
{text: '再等一会', onPress: () => console.log('先聊会天')},
{text: '算了', onPress: () => console.log('凉着吃吧')},
{text: '好的', onPress: () => console.log('热的好吃')},
],
// 点击屏幕关闭
{ cancelable: false }
);
}
render() {
return (
<Button onPress={()=>{this.onPressHanlder()}} title="点我弹框"></Button>
)
}
}
Dimensions
有时候我们需要设置元素大小为屏幕的大小,在Web开发中可以使用%百分比单位,但是RN并不支持这种单位,这时候我们可以使用RN内置的Dimensions对象API方法动态获取屏幕宽高然后进行设置。
const Dimensions = require('Dimensions');
const screenSize = Dimensions.get("window");
const styles = StyleSheet.create({
container: {
width: screenSize.width,
height: screenSize.height
}
});
ScrollView
- 默认情况下,
超出屏幕的内容是看不到的, 不像浏览器环境下会自动添加滚动条 - 如果需要滚动, 可以使用这个
组件把要相应的内容包裹起来, 被包裹的内容就会处于滚动条中 - 滚动的过程中,可以通过onScroll绑定回调,每帧最多调用一次回调
- reactnative.cn/docs/0.50/s…
基本使用
ScrollView使用非常简单,只需要把标签内容通过ScrollView组件包裹起来即可。
return (
<ScrollView>
<View style={styles.container} onScroll={() => {this.onScrollHandler()}}>
<Text>Open up App.js to start working on your app!</Text>
<Text>Changes you make will automatically reload.</Text>
<Text>Shake your phone to open the developer menu1.</Text>
<ViewTest></ViewTest>
<ViewTest></ViewTest>
<ViewTest></ViewTest>
<ViewTest></ViewTest>
<ViewTest></ViewTest>
<ViewTest></ViewTest>
<ViewTest></ViewTest>
<TextTest></TextTest>
<ImageTest imgs={imgs}></ImageTest>
<ButtonTest></ButtonTest>
<TextInputTest></TextInputTest>
<AlertTest></AlertTest>
</View>
</ScrollView>
);
简易Swiper
我们可以利用ScrollView的几个特殊属性来实现一个简易的Swiper。
import React, { Component } from 'react';
import {
StyleSheet,
View,
Text,
ScrollView
} from 'react-native';
const Dimensions = require('Dimensions');
const screenSize = Dimensions.get("window");
export default class ScrollViewSwiper extends Component {
// 获取渲染列表
getList() {
const backgroundList = ["orange", "purple", "pink", "aqua"];
return backgroundList.map((color, i) => {
return (
<View
key={ `key${i}` }
style={ [styles.swipeItem, {backgroundColor: color}] }>
<Text>{ color }</Text>
</View>
)
});
}
render() {
return (
// horizontal属性可设置列表水平排列,
// pagingEnabled属性能够让列表一页一页切换,
// showsHorizontalScrollIndicator属性控制滚动条显示隐藏
<ScrollView
style={styles.swipe}
horizontal={ true }
pagingEnabled={ true }
showsHorizontalScrollIndicator={ false }>
{ this.getList() }
</ScrollView>
);
}
}
const styles = StyleSheet.create({
swipe: {
marginTop: 24,
},
swipeItem: {
width: screenSize.width,
height: 200
}
});
FlatList
import React, { Component } from "react";
import { StyleSheet, View, Text, FlatList } from 'react-native';
export default class FlatListTest extends Component {
render() {
return (
<View>
<FlatList
data={[
{key: 'Devin'},
{key: 'Jackson'},
{key: 'James'},
{key: 'Joel'},
{key: 'John'},
{key: 'Jillian'},
{key: 'Jimmy'},
{key: 'Julie'},
]}
renderItem={(e) => <Text style={styles.item}>{e.index + ":" + e.item.key}</Text>}
/>
</View>
);
}
}
const styles = StyleSheet.create({
item: {
padding: 10,
fontSize: 18,
height: 44,
},
});
ActivityIndicator
- 展示一个小圆形的
loading - 通过属性
animating控制显示隐藏,color设置颜色 - reactnative.cn/docs/0.50/a…
import React, { Component } from "react";
import { StyleSheet, ActivityIndicator } from "react-native";
export default class ActivityIndicatorTest extends Component {
constructor(props) {
super(props);
this.state = {
isShow: true
}
}
render() {
return (
<ActivityIndicator animating={this.state.isShow} color="green" size="large"></ActivityIndicator>
);
}
}
触控系列组件
在需要捕捉用户点击操作时,可以使用Touchable开头的一系列组件。这些组件通过onPress属性设置点击事件的处理函数。当在本组件上按下手指并且抬起手指时也没有移开到组件外时,此函数会被调用。Touchable组件最大的特点是附带反馈效果。
import React, { Component } from 'react';
import {
StyleSheet,
View,
Image,
Text,
TouchableHighlight,
TouchableOpacity,
TouchableNativeFeedback
} from 'react-native';
import StyleBoxTest from './StyleBoxTest';
export default class TouchableGroupTest extends Component {
opacityHandler() {
console.log("透明按钮");
}
HighlighrHandler() {
console.log("高亮按钮");
}
FeedbackHandler() {
console.log("原生反馈按钮");
}
render() {
return (
<View>
{/* 透明效果,支持多个子节点 */}
<TouchableOpacity
activeOpacity={0.5}
onPress={this.opacityHandler.bind(this)}>
<View style={styles.base}>
<Text style={styles.baseFont}>透明按钮</Text>
</View>
</TouchableOpacity>
{/* 透明与底色两种效果,只支持一个子节点,可以用一个View再包装多个子节点 */}
{/* 可以包裹图片,点击时加深背景 */}
<TouchableHighlight
activeOpacity={0.5}
underlayColor="#c1c1c1"
onPress={this.HighlighrHandler.bind(this)}>
<View style={styles.base}>
<Image source={require("./56.jpg")} style={{width:300,height:100}} resizeMode="stretch"></Image>
</View>
</TouchableHighlight>
{/* 使用原生状态渲染反馈效果,比如涟漪,只能放置一个view子组件 */}
{/* 效果有三个可选方法:SelectableBackground、SelectableBackgroundBorderless、Ripple(color)*/}
<TouchableNativeFeedback
background={TouchableNativeFeedback.SelectableBackground()}
onPress={this.FeedbackHandler.bind(this)}>
<View style={styles.base}>
<Text style={styles.baseFont}>原生按钮</Text>
</View>
</TouchableNativeFeedback>
</View>
);
}
}
const styles = StyleSheet.create({
base: {
margin: 10,
width: 300,
height: 100,
borderRadius: 5,
backgroundColor: 'green',
justifyContent: 'center',
},
baseFont: {
color: "orange",
textAlign: "center",
lineHeight: 50
}
});