navigation 传值及页面获取
react-navigation
,这个应该是经常使用的导航栏组件吧,我用的就是它,使用它来在多个页面间进行切换并传输数据.这里主要讲两点:
- 侧边抽屉式导航栏与顶部导航栏实现讲解
- 两个使用
navigation
进行跳转的页面间如何传值
顶部导航栏组件与抽屉式组件混用
首先是第一个,项目入口文件定义一个顶部导航StackNavigator
,在其中你可以定义一些首页,登录页等,我这里拿登录页来做例子,并在其中调用定义了DrawerNavigator
的页面.
import {StackNavigator} from 'react-navigation'
import Login from ''
import DrawerNavigator_Setting from ''
const AppIndex=StackNavigator({
Login:{screen:Login},
DrawerNavigator_Setting:{screen:DrawerNavigator_Setting}
},{
initialRouteName: "Login"
});
export default AppIndex
这里定义了两个页面,登录页和侧边栏导航类的配置文件,在登录页中可以使用navigate('DrawerNavigator_Setting');
跳转到配置页中.其中initialRouteName
表示默认跳转的页面.接下来看配置页内容:
import {DrawerNavigator} from 'react-navigation'
import Home from ''
const DrawerNavigator_Setting==DrawerNavigator({
Home:{screen:Home}
},{
initialRouteName:"Home"
})
其中在Home
页面中,可以使用事件触发this.props.navigation.navigate("DrawerOpen")
该函数来打开侧边栏导航,也可以使用StackNavigator
中定义好的页面,简单来说就是两个组件是共用路由的.
自定义抽屉式导航组件样式
默认给出的抽屉式导航组件贼丑,而且不支持中文,所以需要我们重写自己定义导航栏组件的样式,只需要在其中使用为事件绑定navigate()
导航函数,就可以模仿原生的导航组件形式:
import {DrawerNavigator} from 'react-navigation'
import Home from ''
// 这个就是自定义的导航栏组件,注意下面的使用方式
import DIY_SlideBar from ''
const DrawerNavigator_Setting==DrawerNavigator({
Home:{screen:Home}
},{
initialRouteName:"Home",
contentComponent: props => <DIY_SideBar {...props} />
})
之后你想在DIY_SlideBar
内部怎么渲染侧边导航栏样式都随你,DrawerNavigator
组件只是负责在运行navigation.navigate("DrawerOpen")
时将这个页面从侧边弹出.而且侧边栏中的路由你都可以使用.
navigate传值
很简单,直接看代码:
navigate('RouteName',{key:"Value"})
到了对应的页面后的读取:
// this.props.navigation.state.params 所有传递过来的值都保存在里面,比如我这里传递过来的是key
let key=this.props.navigation.state.params.key;
而且支持很多复杂形式的数据,都可以传递过来,好像JavaScript的一个核心思想就是一切皆对象,所以即使是数据,也可以理解成对象,然后被传递过来.
后端获取数据与前端渲染
这里后端我采用的thinkphp
的3.2.3版本,返回的数据是多条格式相同的数据,例如下面的形式:
// 后端返回数据代码,注意,数组的下标最好从0开始,如果从1开始,前端好像会出问题,具体问题在前端代码处展示
$returnData=array(
0=>array("name"=>'trouble',"age"=>23),
1=>array("name"=>'I',"age"=>23),
2=>array("name"=>'Am',"age"=>23),
3=>array("name"=>'In',"age"=>23)
);
$this->ajaxReturn(array('row'=>$returnData));
前端获取后端数据采用的基本方式如下:
constructor(props,context){
super(props,context);
// 初始state中的row来保存后端传输过来的数据
this.state={
data:[]
};
}
componentWillMount(){
// 使用fetch获取后端数据
fetch("http://www.domain.com")
.then((response)=>{
// 对后端返回数据做处理
return response.json();
})
.then((responseData)=>{
// 这里就是提取后端返回的数据,貌似如果后端返回的数组形式索引不从0开始,这里直接赋值就会发生错误
this.setState({
data:responseData.row
});
})
.catch((error)=>{
// 我个人很喜欢在这里加一个显示错误的提醒,在调试的时候他能帮助你查找问题所在
console.warn("获取后台数据出错");
console.log(error);
});
}
如果要渲染数据,那么就可以使用map
,如下所示(这里不写import
各个组件的代码了):
<View>
{this.state.data.map((Item,index)=>{
return(
<Text>{Item.name}<Text>
<Text>{Item.age}</Text>
)
})}
</View>
这样就是在前端渲染多条数据格式相同的数据.而且,甚至后端返回如下嵌套格式的数据:
$returnData=array(
0=>array(
'name'=>"trouble i am in",
'skills'=>array(
'HTML',
'CSS',
'JavaScript',
'React'=>array('React','React Native'),
'PHP',
'Database'=>array('MySQL','MongoDB','Redis')
)
)
);
$this->ajaxReturn(array('data'=>$returnData));
前端也只要像上一个例子一样,使用一个空数组,就可以存放下后端传递过来的多维数组.
搭建公共style样式
我个人感觉这个真的是很重要的东西,给你们讲一个真实的故事,当我写了好几个复杂页面的时候,设计师过来看了一眼最终结果,轻描淡写的说了句,"背景颜色好像比我预估的淡啊,你加深一下."当时幸好我是把公共样式设置在一个文件内部的,否则一个一个页面的去改,我就宁可直接推脱回去:"之后有时间再改吧,功能第一."好了回到正题,如何定义一个公共的样式文件:
const React = require("react-native");
const {StyleSheet}=React;
export default {
common_background:{
backgroundColor:"#DCDCDC"
},
common_fontSize:{
fontSize:14,
color:"#333333"
}
};
这里最后的export default
就是一个演示,你可以在一个文件内部定义多个样式,分别export
.在需要的页面你只需要引用该文件就可以了:
import styles from 'common_css.js'
class Loadding extends React.Component{
render(){
return (
<View style={styles.common_background}>
</View>
)
}
}
export default Loadding
base-native组件
这是非常好的一个组件库,里面包含了很多集成好的组件,类似的还有阿里开发的ant design mobile,但是我环境总是配不好,一怒之下,直接放弃,转而找到了这个,起码运行环境给我配起来了,在我手机上运行也正常,没出错.跟着他的教程一步步把环境搭起来,之后起码你不会像ant design mobile
一样报找不到该库的错误.
说到手机实际运行,给你们推荐一个软件:Expo,这个软件真心好,有时候想看一个组件的实际运行效果,要么你去该组件的GitHub上下载源码,自己编译到手机上看,要么作者有实际的演示效果动态图,如果没有,你就只能自己编译了.这个软件好的地方就是你可以直接搜索作者或者组件名称,找到后就可以直接下载到手机上看实际运行效果,真心棒棒哒.我这里有安卓版的百度云链接:
链接: https://pan.baidu.com/s/1qYbV0kO 密码: xcjf
在上面能找到这个组件库以及蚂蚁金服的ant design mobile
组件的实际效果.PS:在测试蚂蚁金服组件的时候,使用他们的时间选择组件时,我手机直接显示出现错误,强制关闭了程序,有点庆幸自己没有用他们的组件.
react-native-easy-grid使用
这个是上一个base-native
团队开发的另一个组件库,在布局方面除了一些地方略坑之外,总体上来说是非常好用的,这是他在npm上的例子,只好看过一遍,就知道该怎么使用了:
下面就是我对他原理的一些猜测:
Row
layouts:{
flexDirection:"row",
flexWrap:"nowrap",
justifyContent:"space-around",
alignItems:"flex-start"
}
Col
layouts:{
flexDirection:"column",
flexWrap:"nowrap",
justifyContent:"space-around",
alignItems:"flex-start"
}
至于其中的size
的使用原理,我就没有深究了.知道这个有什么用呢,你可以通过修改该组件的原始react-native
布局属性来达到修改其默认布局的作用,这个组件最坑的地方就是总是默认均分布局,所以导致很多页面在数据不确定的情况下渲染的数据样式也总是乱掉,所以数据不确定的情况下最好不要使用这个布局.顺便一提,这个布局组件是可以完美嵌入到上一个base-native
组件中混合使用的.
fetch传值
上面演示了如何使用fetch
从后端获取值,这里先讲两个坑,这两个坑是我的两位大哥踩了一下午才找出来的,先在这里感谢他们的奉献:
- 微信内置浏览器不支持
fetch
,所以有这方面需要的,只能使用javascript
的原生ajax
代码进行数据传递 - iphone内置的浏览器一般不支持
fetch
传值,我一个同事把他的ios版本升级到了10后是可以的,之下的版本好像都不支持
说了这两个坑,接下来就是具体的数据传递了,我只知道下面两种形式:
形式1
let key1="注意看下面的各个值之间的间隔方式";
let key2="这里比较麻烦的是你需要拼接传递回去的内容";
fetch("http://www.yourdomain.com",{
method:"POST",
header:{
'Content-Type': 'application/x-www-form-urlencoded'
},
body:"key1="+key1+"&key2="+key2
})
.then((response)=>{
return response.json();
})
.then((responseData)=>{
// 这里的responseData就是后端返回的数据了,假设后端传递过来的数据是{result:true,message:"数据传递成功"}
if(responseData.result===true){
alert(responseData.message);
}else{
alert("操作失败");
}
})
.catch((error)=>{
console.warn("将数据发送给后端时发生错误");
console.log(error);
});
后端我使用的是PHP,所以获取最终传递过来的数据
$key1=$_POST['key1'];
$key2=$_POST['key2'];
// 之后返回处理结果就跟最上面的例子一样
$this->ajaxReturn(array('result'=>true,'message'=>"数据传输成功"));
接下来演示第二种传值方式,这种传值方式使用的是FormData
,非常适合需要传输文件类的内容,后端代码的思路跟上面是一致的,所以不再演示了,直接看前端代码:
形式2
var formData=new Formate();
// 接下来就像传统使用FormData一样往其中添加数据
formData.append('key1',"key1的内容");
formData.append('key2','注意下面fetch的header内容');
// 甚至你可以往其中添加文件,这个会在接下来的图片上传中进行说明
fetch("http://www.yourDomain.com",{
method:"POST",
headers:{
'Content-Type': 'multipart/form-data'
},
body:formData
})
.then((response)=>{
//这里就是跟上面一样处理后端返回的数据了,不再赘述
})
图片上传
这个应该是手机app常用的一个功能,拍一张照片,或者从手机相册里选择一张照片,发送到服务器端.这里需要用的组件是react-native-image-picker
,这个是需要进行配置后才能运行的组件,配置需要去看他官网的例子,也可以百度,其中内容很多.这里我就是展示一段我项目中的代码:
import {
Platform
} from 'react-native'
import ImagePicker from 'react-native-image-picker'
/**
* 调用组件 react-native-image-picker 来启动图片选择
* @returns {boolean}
* @constructor
*/
export function ImageChoose() {
// 如果正在上传图片,则禁止再次操作
if (this.state.sendingImage === "图片上传中") {
return false;
}
// 设置组件参数,下面的配置内容我是从另一个CSDN博客上抄来的
const options = {
title: '选择图片',
cancelButtonTitle: '取消',
takePhotoButtonTitle: '拍照',
chooseFromLibraryButtonTitle: '图片库',
cameraType: 'back',
mediaType: 'photo',
videoQuality: 'high',
durationLimit: 10,
maxWidth: 600,
maxHeight: 600,
aspectX: 2,
aspectY: 1,
quality: 0.8,
angle: 0,
allowsEditing: false,
noData: false,
storageOptions: {
skipBackup: true,
path: 'images'
}
};
// 显示选择框
ImagePicker.showImagePicker(options, (response) => {
if (response.didCancel) {
console.log("用户关闭了操作");
return false;
} else if (response.error) {
console.warn("react-native-image-picker出现错误");
console.log(response.error);
return false;
} else {
// 下面是对安卓和ios分别做处理,内容是不一样的
var source;
if (Platform.OS === 'android') {
source = {uri: response.uri, isStatic: true}
} else {
source = {uri: response.uri.replace('file://', ''), isStatic: true}
}
let file;
if (Platform.OS === 'android') {
file = response.uri
} else {
file = response.uri.replace('file://', '')
}
}
// 这里调用另一个函数将数据发送给后端,实际上就是使用fetch和FormData将数据发送给后端
let responseData = SendImage(source.uri);
})
}
你可以直接把上面的代码拿去用,SendImage
的代码接下来我也会贴出来的.这段函数你可以绑定在Button
或者TouchableHighlight
等手势响应上,只需要在前面按照官网配置好后触发该函数,就会正常执行,会显示是从手机相册选择还是拍照还是取消.功能上还是很全面的.
/**
* 使用FormData将数据发送回后台
* @constructor
*/
function SendImage(URI) {
var formData = new FormData();
// 添加图片
const time = new Date();
var Image = {uri: URI, type: 'multipart/form-data', name: time + 'QualificationDocument.jpg'}
// 这里的名字需要与你的后台$_FILES[''];中的键对应
formData.append('uploadfile', Image);
// 注意这里的地址换成你项目的地址
const uri = "http://www.yourDomain.com";
fetch(uri, {
method: "POST",
header: {
'Content-Type': 'multipart/form-data'
},
body: formData
})
.then((response) => {
// console.log("输出后台返回数据");
// console.log(response);
return response.json();
})
.then((responseData) => {
// 之后这里就是处理你后端的返回值了,可以根据你的业务需要进行编写
})
.catch((error) => {
console.warn("上传图片出错");
console.log(error);
})
}
编程习惯
由于是第一次使用react native
写手机APP,甚至是第一次使用React
写项目,最后居然写成功了,我也感觉是奇迹.期间起步时看了一个实战视频,没看完,看了前期的知识介绍,之后就没看下去,自己开始捣鼓,花了3个星期,也算是基本把APP写出来了,庆祝自己成为了全套程序员,所以奖励了自己一台Mac Mini主机,我真的是为了来开发苹果APP的,真的......
好了,回到正题,首先是项目的布局,我个人建议是把一些函数,一些样式,一些配置等信息都封装到一些单独的文件中,比如上面的公共样式我就是封装到一个单独的文件中作为一般网页开发中的CSS来使用的.不止是样式文件,还有配置文件,函数文件等.
配置文件
最简单的例子,开发过程中的域名与实际上线后的域名是两个域名,那么在写每个fetch
时,要吗对fetch
进行封装,要吗就是把域名写成一个配置文件:
export const Domain="http://www.develop_domain.com"
当然这只是其中的一种形式,还有很多配置,看具体的项目了.这里再穿插一句,如果要开发React Native的话,建议使用WebStorm,我用过Atom,卡,下插件慢,Visual Status Code,不习惯,Sublime Text,配置太麻烦.PhpStorm,可以,但是不适合.最后兜兜转转到了WebStorm,发现跟使用PHPStorm开发PHP一样,开发前端贼爽.我用的是Ubuntu和Windows装的双系统,开发在Ubuntu上,运行WebStorm基本不卡,除了偶尔卡一下之外.电脑也不是什么好电脑,ThinkPad E450,已经停产的老电脑了,但是就是可以顺利运行.
函数
上面扯远了,这个自然是毋庸置疑的肯定是有一些自定义的封装函数是最好的吧.
export funcion FunctionName(){
// 其实就是export一个函数而已
}
而且我个人建议是把一些单独页面中的复杂函数处理整理出单独一个函数文件,这样那个页面的render
部分就不会显得很凌乱,整体文件也不会显得很大.