React RN移动端开发
文章目录
了解React-Native
Facebook发起的开源的一套新的APP开发方案,Facebook在当初深入研究Hybrid(其实就是手机内置的浏览器,12306)开发后,觉得这种模式有先天的缺陷,所以果断放弃,转而自行研究,后来推出了自己的“React Native”方案,不同于H5,也不同于原生Native,更像是用JS写出原生应用
ReactJs和React Native的原理是相同的,都是由js实现的虚拟dom来驱动界面view层渲染。只不过ReactJs是驱动html dom渲染; React Native是驱动android/ios原生组件渲染。
- 优点
-
开发成本小于Native模式 Andriod-Java-Kotlin IOS-OC-Swift
-
性能体验高于Hybrid
-
一次学习,跨平台开发Android和iOS, 小程序
-
社区繁荣
- 几种开发技术对比 :应用质量和开发效率的平衡折衷的结果
- ReactNative原理
它里面有一个虚拟dom的东西,我们写的每一个组件都是dom中的一个节点,最终都会翻译成各个平台的组件 比如web 我们写的div都会翻译过去,但是自定义的hello标签会翻译成符合规则的html标签
- 三种开发类型的对比
了解React-Native工作流程
- 项目开发:使用Node初始化项目(需要安装Node),使用JavaScript/JSX语言开发
- 语言翻译:Python, C++将js翻译成java代码(需要安装Python2)
- 代码编译:Android-SDK将java编译成字节码(二进制),打包成可安装的apk(需要JDK8 & Android-SDK)
- 安装运行:通过Adb工具,把apk运行到Android模拟器
ReactNative中文网(查看流程和必须)
https://reactnative.cn/docs/getting-started/
创建第一个React-Native项目
注意 Node 的版本必须高于 8.3,Python 的版本必须为 2.x(不支持 3.x),而 JDK 的版本必须是 1.8(目前不支持 1.9 及更高版本)
-
安装python环境
-
配置SDK
- 创建
ANDROID_HOME
环境变量ANDROID_HOME=G:\sdk
- 添加
path
%ANDROID_HOME%\platform-tools
- 创建
-
打开usb调试连接手机
adb devices
查看是否有连接- 可以通过
adb connect 127.0.0.1:62001
尝试连接- 如果连接不上可以把
sdk
下platform-tools
里面的adb.exe
拷贝到夜神模拟器下替换adb.exe
和nox_adb.exe
- 如果连接不上可以把
-
安装脚手架react-native-cli 同时安装新的版包管理工具
npm install -g yarn react-native-cli
-
创建项目:doubanMovie(在不包含中文的目录执行)
react-native init doubanMovie --version react-native@0.55.4
建议指定–version 不然使用最新的 以来的android sdk版本比较新
-
运行项目
-
更改gradle路径
doubanMovie\android\gradle\wrapper\gradle-wrapper.properties
distributionUrl
值修改为file:///E:/myandroid/gradle-2.14.1-all.zip
直接复制过来的路径要把反斜线\改成正斜线/
-
在项目根目录执行
react-native run-android
运行期间会开启一个Node服务,不要关闭
react-native start 只会开启服务
-
第一次运行报错,需要在设备上设置app的Node服务地址
解决方式: 打开app > 菜单按钮 > Dev Settings -> Debug server host …
填写服务ip和端口号, 注意冒号用英文半角,重启服务,重启应用
-
出现问题:https://blog.csdn.net/jasonzds/article/details/78747524
了解React-Native项目及结构
-
目录结构
- .babelrc 配置编译reactnative代码
- .flowconfig js语法规则 js会有语法提示 类型检查工具
- index.js 项目入口文件
- App.js 项目根组件,用户看到的首页组件
- package.json 项目配置文件
- app.json 配置项目名称
-
React-Native与React对比
-
组件写法
RN提供View,Text组件,没有html的dom元素
View -> div 布局容器
Text -> p 显示文字
-
样式写法
使用
const styles = StyleSheet.create({...})
-
-
React-Native平台相关代码处理
const instructions = Platform.select({ ios: 'Press Cmd+R to reload,\n Cmd+D or shake for dev menu', android: 'Double tap R on your keyboard to reload,\n', });
开发资料
配置reactnative文件模板
-
打开setting
-
File and Code template
-
添加模板 名称以及后缀名
-
把下面这段代码添加到文件中
import React from 'react' import { StyleSheet, Text, View } from 'react-native'; class ${NAME} extends React.Component { render() { return ( <View> <Text>#[[$Title$]]#</Text> </View> ); } } export default ${NAME}
项目开发
路由(react-native-router-flux)
-
react-native-router-flux
-
源码地址:https://github.com/aksonov/react-native-router-flux
应用场景:在RN项目中进行路由跳转时使用
安装方式:
yarn add react-native-router-flux
-
使用:
Router(路由): 一般写在项目的根组件
Stack (栈):给Scene场景提供容器
Scene(场景):设置路由跳转规则
Actions (动作):触发路由跳转
-
导入依赖
//导入路由 重点 import {Router, Stack, Scene} from 'react-native-router-flux'
-
定义路由规则
export default class App extends Component<Props> { render() { return ( <Router> <Stack> <Scene key='home' component={HomeScene} title='主界面' initial={true}></Scene> <Scene key='movieList' component={MovieListScene} title='主界面'></Scene> <Scene key='about' component={AboutScene} title='主界面'></Scene> </Stack> </Router> ); } }
-
触发路由:三种方式 (注意导入Actions组件)
import {Actions} from 'react-native-router-flux' <Text onPress={Actions.movieList}>电 影</Text> <Text onPress={() => { Actions.movieList()}}>电 影</Text> <Text onPress={() => { Actions['about'].call() }}>关 于</Text>
注意事项:需要导入Router Stack Scene 界面中使用View以及Text不能使用div
###去除警告
-
最新版的react-native-router-flux会在react-native 0.55.4版本出现isMounted(…)警告,可在App.js添加以下代码忽略警告。随后两个框架更新后,此警告也可消除。
import { YellowBox } from 'react-native' YellowBox.ignoreWarnings(['Warning: isMounted(...) is deprecated'])
###首页三个标题
路由跳转
样式的定义
import React from 'react'
import {Actions} from 'react-native-router-flux'
import {
StyleSheet,
Text,
View
} from 'react-native';
class HomeScene extends React.Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.item} onPress={Actions.movie}>正在上映</Text>
<Text style={styles.item} onPress={Actions.movie}>即将上映</Text>
<Text style={styles.item} onPress={Actions.about}>关于</Text>
</View>
);
}
}
let styles = StyleSheet.create({
container:{
display: 'flex',
flexDirection: 'row',
backgroundColor:'#311B92',
height:30,
alignItems: 'center'
},
item:{
flex: 1,
textAlign: 'center',
color:'#fff'
}
})
export default HomeScene
开发组件(swiper)
-
开源轮播图react-native-swiper
-
源码地址:https://github.com/leecade/react-native-swiper
应用场景:在首页展示轮播图
安装方式:
yarn add react-native-swiper
或
npm i react-native-swiper --save
-
常用属性:
index={1} 默认位置, 从0开始 showsButtons={true} 是否显示按钮 autoplayTimeout={2.5} 自动播放停留时间 autoplay={true} 是否自动播放 showsPagination={true} 显示分页指示器
-
导入组件
import Swiper from 'react-native-swiper';
-
使用Swiper
import React from 'react' import {Actions} from 'react-native-router-flux' import { StyleSheet, Text, View } from 'react-native'; //导入Swiper import Swiper from 'react-native-swiper'; class HomeScene extends React.Component { render() { return ( <View style={{height:180}}> <Swiper style={styles.wrapper} showsButtons={false} autoplay={true} autoplayTimeout={1}> <View style={styles.slide1}> <Text style={styles.text}>Hello Swiper</Text> </View> <View style={styles.slide2}> <Text style={styles.text}>Beautiful</Text> </View> <View style={styles.slide3}> <Text style={styles.text}>And simple</Text> </View> </Swiper> <View style={styles.container}> <Text style={styles.item} onPress={Actions.movie}>正在上映</Text> <Text style={styles.item} onPress={Actions.movie}>即将上映</Text> <Text style={styles.item} onPress={Actions.about}>关于</Text> </View> </View> ); } } let styles = StyleSheet.create({ container:{ display: 'flex', flexDirection: 'row', backgroundColor:'#311B92', height:30, alignItems: 'center' }, item:{ flex: 1, textAlign: 'center', color:'#fff' }, wrapper: { }, slide1: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#9DD6EB', }, slide2: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#97CAE5', }, slide3: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#92BBD9', }, text: { color: '#fff', fontSize: 30, fontWeight: 'bold', } }) export default HomeScene
注意:swiper不能控制高度,需要在父标签view中指定高度
替换swiper中条目为图片
使用Image
控件
-
引入Image控件
import { StyleSheet, Text, View, Image } from 'react-native';
-
使用Image
<Swiper style={styles.wrapper} showsButtons={false} autoplay={true} autoplayTimeout={1}> <View style={styles.slide1}> <Image style={{width:'100%',height:'100%'}} source={{uri:'http://www.itheima.com/images/slidead/HOMEPAGE/20180613114526968x385.jpg'}}/> </View> <View style={styles.slide2}> <Image style={{width:'100%',height:'100%'}} source={{uri:'http://www.itheima.com/images/slidead/HOMEPAGE/2018051409245620185529165539341.jpg'}}/> </View> <View style={styles.slide3}> <Image style={{width:'100%',height:'100%'}} source={require('../static/images/blockchain.png')}/> </View> </Swiper>
注意:Image中设置图片地址分为两种方式
第一种:
source={{uri:'http://www.itheima.com/images/slidead/HOMEPAGE/2018051409245620185529165539341.jpg'}
第二种:
source={require('../static/images/blockchain.png')}
###长列表FlatList展示电影列表
https://facebook.github.io/react-native/docs/using-a-listview
import React from 'react'
import {
StyleSheet,
Text,
View,
FlatList
} from 'react-native';
class MovieListScene extends React.Component {
constructor(){
super()
this.state = {
data:[{key: 'Devin'},
{key: 'Jackson'},
{key: 'James'},
{key: 'Joel'},
{key: 'John'},
{key: 'Jillian'},
{key: 'Jimmy'},
{key: 'Julie'}]
}
}
render() {
return (
<View>
<FlatList data={this.state.data}
renderItem={({item,index})=>{
return<Text>{item.key}</Text>
}}/>
<Text>movie列表界面</Text>
</View>
);
}
}
export default MovieListScene
注意:
数据data需要有key属性
网络请求(fetch)
豆瓣api查找一下
-
在
componentDidMount
执行请求并在回调中执行setState// 组件已经挂载 componentDidMount() { const url = 'http://api.douban.com/v2/movie/in_theaters'; fetch(url).then(res => res.json()) .then(data => { // 处理网络json数据 this.setState({ isLoading: false, data: data.subjects }) // console.warn(data.subjects) }).catch((err) => { console.error(err); }); }
-
请求完之后记得替换key
keyExtractor={(item, index) => item.title}
###加载进度条展示ActivityIndicator
导入
import {
StyleSheet,
Text,
View,
FlatList,
ActivityIndicator
} from 'react-native';
使用
if(this.state.isLoading){
return(<View><ActivityIndicator size='large' color='#f00'></ActivityIndicator></View>)
}
条目数据展示
import React from 'react'
import {
StyleSheet,
Text,
View,
FlatList,
ActivityIndicator,
Image
} from 'react-native';
class MovieListScene extends React.Component {
constructor(){
super()
this.state = {
isLoading:true,
data:[{key: 'Devin'},
{key: 'Jackson'},
{key: 'James'},
{key: 'Joel'},
{key: 'John'},
{key: 'Jillian'},
{key: 'Jimmy'},
{key: 'Julie'}]
}
}
render() {
if(this.state.isLoading){
return(<View><ActivityIndicator size='large' color='#f00'></ActivityIndicator></View>)
}
return (
<View>
<FlatList data={this.state.data}
renderItem={({item,index})=>{
return(
<View style={styles.container}>
<Image source={{uri:item.images.large}} style={styles.img}></Image>
<View style={styles.content_wrapper}>
<Text style={{fontWeight:'bold',fontSize:20}}>{item.title}</Text>
<Text style={styles.item}>电影类型:{item.genres.join(',')}</Text>
<Text style={styles.item}>上映年份:{item.year}</Text>
<Text style={styles.item}>豆瓣评分:{item.rating.average}</Text>
</View>
</View>
)
}}
keyExtractor={(item, index) => item.title}/>
</View>
);
}
componentDidMount() {
const url = 'http://api.douban.com/v2/movie/in_theaters'
fetch(url).then(res=>res.json())
.then(data=>{
// console.error(data)
this.setState({
data:data.subjects,
isLoading:false
})
})
.catch((error)=>{
console.error('失败:'+error)
this.setState({
isLoading:false
})
})
}
}
let styles = StyleSheet.create({
container:{
padding: 10,
display: 'flex',
flexDirection: 'row',
borderBottomColor:'#ccc',
borderBottomWidth: 1
},
img:{
width:100,
height:140
},
content_wrapper:{
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-around',
marginLeft: 10
},
item: {
fontSize: 18,
},
})
export default MovieListScene
长列表条目显示和点击事件
触摸变色的控件TouchableOpacity
activeOpacity
属性控制透明度
-
对于条目view不能设置onPress 可以通过 TouchableOpacity
-
条目点击跳转
return (
<View>
<FlatList data={this.state.data}
renderItem={({item,index})=>{
return(
<TouchableOpacity style={styles.container}
onPress={()=>Actions.detail({id:item.id,title:item.title})}
activeOpacity={0.6}>
<Image source={{uri:item.images.large}} style={styles.img}></Image>
<View style={styles.content_wrapper}>
<Text style={{fontWeight:'bold',fontSize:20}}>{item.title}</Text>
<Text style={styles.item}>电影类型:{item.genres.join(',')}</Text>
<Text style={styles.item}>上映年份:{item.year}</Text>
<Text style={styles.item}>豆瓣评分:{item.rating.average}</Text>
</View>
</TouchableOpacity>
)
}}
keyExtractor={(item, index) => item.title}/>
</View>
);
加载电影详情数据
import React from 'react'
import {
StyleSheet,
Text,
View
} from 'react-native';
class MovieDetailScene extends React.Component {
render() {
return (
<View>
<Text>电影详情</Text>
</View>
);
}
componentDidMount() {
let url = `http://api.douban.com/v2/movie/subject/${this.props.id}`
fetch(url)
.then((res)=>res.json())
.then((result)=>{
console.error(result)
})
.catch((error)=>{
console.warn(error)
})
}
}
export default MovieDetailScene
详情界面加载进度条
###显示详情界面数据
-
标题和大图片展示,注意
resizeMode
return ( <View> <Text style={styles.title}>{this.state.data.title}</Text> <Image style={styles.img} source={{uri:this.state.data.images.large}}></Image> </View> );
let styles = StyleSheet.create({ title:{ textAlign:'center', fontSize:30, fontWeight:'bold' }, img:{ height: 250, marginTop:20, resizeMode:'contain' } })
-
演员表滚动 使用Scrollview 数组.map 方法
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}> { this.state.data.casts.map((item, index, arr) => { return ( <View style={{padding:10}} key={item.id}> <Image source={{uri: item.avatars.small}} style={{width:50, height:80}}/> <Text>{item.name}</Text> </View> ) }) } </ScrollView>
-
其它布局实现
<Text style={{fontSize:20}}>剧情介绍:</Text> <Text style={{marginBottom:10}}>{this.state.data.summary}</Text>
打包及发布
参见中文官网文档:https://reactnative.cn/docs/0.51/signed-apk-android.html#content
-
第一步:生成签名文件
keytool -genkey -v -keystore itheima.keystore -alias itheima -keyalg RSA -keysize 2048 -validity 10000
-
把生成的
itheima.keystore
放到android/app
下 -
编辑
andorid
下的gradle.properties
文件加上MYAPP_RELEASE_STORE_FILE=itheima.keystore MYAPP_RELEASE_KEY_ALIAS=itheima MYAPP_RELEASE_STORE_PASSWORD=123456 MYAPP_RELEASE_KEY_PASSWORD=123456
-
编辑
android
下app
下的build.gradle
android { ... defaultConfig { ... } signingConfigs { release { storeFile file(MYAPP_RELEASE_STORE_FILE) storePassword MYAPP_RELEASE_STORE_PASSWORD keyAlias MYAPP_RELEASE_KEY_ALIAS keyPassword MYAPP_RELEASE_KEY_PASSWORD } } buildTypes { release { ... signingConfig signingConfigs.release } } }
-
cd到android文件夹下执行下面命令打包
gradlew assembleRelease
-
执行安装
gradlew installRelease
-
注意:如果安装到手机出现显示welcome界面 执行首先清空app下main下的assets文件夹 执行下面命令
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/
-
修改图标
- [项目名称]\android\app\src\main\res\mipmap-xhdpi