概述
在上一次的学习中,认识React Native的一些基本内容,如:JS文件的构成要素,基础UI组件,基础通用API等待,并制定了大致的学习方向,包括UI组件的封装,生命周期的认识等等。这次学习就以完善上次的movielist程序为主,对基础组件进行封装。
movielist程序中,采用了listview、image、text等UI组件,通过联网获取数据展示在界面上。通常在Android的原生程序中,我们往往要考虑到数据为空怎么办,图片加载不到怎么办……基于此,我们首先对listview的loading状态进行封装。
LoadingListView的封装
在上次的movielist中有提到可以通过setState
强制调起render
生命周期重新绘制界面,因此我们的loading模式就可以采用相同原理来封装我们movielist程序:
constructor() {
super();
this.state = {
loaded: false,
dataSource: new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2 })
};
}
render() {
if (this.state.loaded) {
return this.renderList();
}else {
return this.renderLoading();
}
}
我们通过this.state.loaded
作为标志来判断加载对应的ui组件,当state的loaded属性发生变化时,就会加载到指定的组件,补全代码:
componentDidMount() {
fetch(request_url)
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.movies),
loaded: true
});
})
.done()
}
renderLoading() {
return (
<View style={styles.container} >
<Text style={styles.loading}>Loading...</Text>
</View>
);
}
ErrorImage的封装
完成了ListView的loading状态之后,继续Image的相应处理。Imageyou三种状态:place,success,error。在官网中,对Image提供了onLoad,onLoadStart,onLoadEnd的加载状态三个属性。对于ios开发者来说,多了defaultSource,onError,onProgress这几个重要方法。然而android平台的不支持,使得我们无法直接调用这几个属性来配置相应的状态。那么我们就针对onError的情况做处理并进行UI的封装;
首先理清思路,要对onError的状态处理,第一步,要获取加载失败的状态;第二步,要在error的状态下加载error的图片。因此:
step 1: error状态的获取
Android的Image中,没有提供onError,所以我们必须通过现有的属性获取到onError的状态。经过官方文档的属性说明,我们发现了一个可以帮助我们判断是否加载成功的方法:onLoad
:
Invoked when load completes successfully.
这个官网对onLoad
的说明,即当加载成功时候会调用这个方法。但是我们获取的是失败的状态,仅通过onLoad
方法有没有调用的这个想法是无法实现的。官网同时提供了另外一个属性:onLoadEnd
:
Invoked when load either succeeds or fails.
也就是说在加载结束的时候会被调用。也就是说如果图片加载成功,会依次经过onLoad
,onLoadEnd
,颇有些类似于Activity的生命周期一样,于是我们联想到标志位的使用。
即初始化一个标记isError,如果这个标记没有在onLoad
被状态的时候修改,那我们则在onLoadEnd
的时候就会得到一个结果:图片未加载成功。此时我们的Android版的onError
状态也就出来了。
step 2: error图片的加载
很遗憾的是,React Native并没有提供类似于Picasso.load().error()
的方式,所以我们发现我们没有办法拿到当前加载的Image去重新设置他的source并重新加载。因此只能另辟蹊径,采用强制刷新ImageView的方式,强制加载errorSource。这种思路跟上述的movielist有些类似,根据不同的状态,返回不同的view。而不同的状态是通过setState
的方式强制刷新进行新的判断。于是我们的大致思路变成了
1. 设置全局标记,检测Image的error状态;
2. 当获取到error状态的时候,通过setState方法强制刷新
3. 初始化或刷新时根据不同的状态加载不同的组件或者配置不同source
step 3: 自定义ErrorImage的编写
在上次的学习中有提到Reading这个demo,在/app/component
这个目录级别下我们看到了有一个组件叫做ImageButton
,因此我们可以参考该组件进行封装:
首先我们需要初始化这个组件,包括创建组件并引入相应的API,设置组件可被引用:
improt React ,{
Componet,
StyleSheet,
View,
Image
} from 'react-native';
var request_url = {uri : "http://d3biamo577v4eu.cloudfront.net/static/images/redesign/poster_default_thumb"};
class ErrorImage extends React.Component{
render() {
return (
<Image source={{uri:request_url}}/>
);
}
}
export default ErrorImage;
接下来根据step 1中所描述的,建立检测机制:
var isLoaded = false;
class ErrorImage extends React.Component{
render() {
return (
<Image
source={{uri:request_url}}
onLoad={this.LoadingSuccess}
onLoadEnd={this.loadingFinish}/>
);
}
loadingSuccess() {
isLoaded = true;
}
loadingFinish() {
if(!isLoaded){
// 加载失败需要刷新view
}
}
}
紧接着,我们需要重新刷新view来展示error的状态,根据step 2,建立刷新机制:
class ErrorImage extends React.Component{
construct(props){
super(props);
this.state = {finishError : false};
}
loadingFinish() {
if(!isLoaded){
this.setState({finishError : true});
}
}
}
刷新机制建立好后必然要跟的是我们的error状态处理:
var errorSoruce = require('error.png');
class ErrorImage extends React.Component{
render() {
return (
<Image
source={this.state.finishError ? errirSource : request_url}
onLoad={this.LoadingSuccess}
onLoadEnd={this.loadingFinish}/>
);
}
}
在代码
source={this.state.finishError ? errorSource : request_url}
中,我们可以看到有一个三元表达式,相信熟悉Java的同学都知道是什么意思。在建立刷新机制的时候,我们通过设置state.finishError状态来强制刷新,因此我们也可以通过state.finishError的状态来改变Image的source,从来达到刷新View的效果。
最后,我们在代码中涉及到的request_url,errorSource都是属于我们自己设定好的内容,交给外部引用的时候自然需要不同的source,因此我们需要像自定义属性那样设定我们ErrorImage的一些配置,通过属性PropTypes。虽然在官网中也只有几句简简单单的解释,但是有了ImageButton的demo在,也并不妨碍我们的理解。初始化我们的自定义属性:
import {PropTypes} from 'react'
const PropTypes= {
}
根据所需,我们需要建立至少包括source
,errorSource
,onLoad
,onLoadEnd
,style
等可能被外部或内部引用改变的东西,并指明他们的类型,同时配置相关的属性:
import {PropTypes} from 'react'
const PropTypes= {
disabled: PropTypes.bool,
source: PropTypes.oneOfType([
PropTypes.shape({
uri: PropTypes.string,
}),
PropTypes.number,
]),
errorSource: PropTypes.number,
onLoad: PropTypes.func,
onLoadEnd: PropTypes.func,
style: View.propTypes.style
}
LoadingImage.propTypes = propTypes;
LoadingImage.defaultProps = {
disabled: false
};
最后通过我们设定好的配置参数来配置我们的Image:
<Image
style={this.props.style}
source={this.state.finishError ? this.props.errorSource : this.props.source}
onLoad={this.LoadingSuccess}
onLoadEnd={this.loadingFinish}/>
/>
到此,我们自定义的ErrorImage就全部完工了,我们可以通过movielist程序来进行测试:
<ErrorImage style={styles.imageShow}
errorSource = {require('./img/icon_error.png')}
source = {{uri : movie.posters.thumbnail}} />
step 4: 总结
通过以上三个步骤,了解了自定义UI组件的封装过程,以及相关的属性、设置,为高级组件封装做了基本的了解。然而在此次的UI组件封装过程中,也是出现了不少的遗漏,比如如何将LoadListView当作单独的组件剥离出来,如何实现IOS的Image的defaultSource : A static image to display while loading the image source
的效果(即占位图的效果)等等。其实Android的原生组件的封装思路和React Native的组件封装思路就我个人而言,有着大致相同的思想,所以我们思考的时候,可以通过例如:生命周期、状态设置、方法回调等等的方式来达到目的。在此推荐一个React Native Component封装的网站,里面提供了比较多的自定义组件:
Native Components
在这个网站上有一个叫做React-Native-Image-Progress
的组件,获取能达成类似LoadingImage的效果:
React-Native-Image-Progress