react native 入门实践
上周末开始接触react native,版本为0.37,边学边看写了个demo,语法使用es6/7和jsx。准备分享一下这个过程。之前没有native开发和react的使用经验,不对之处烦请指出。笔者认为从Vue过度到React应该是非常容易的,毕竟都是面向组件的,一通百通,只是每个框架的特性可能有所不同。本文的目的在于希望对在web开发有一定基础,又想涉及app开发的同学有帮助,共同进步。
一、环境安装
首先是开发环境安装,我是在win7/8 64位环境下安装的安卓环境。模拟器用的是android studio自带模拟器(android emulator),安卓API 24(7.0),因为我没有mac -.-。文中组件的使用也会以安卓的为主。具体的安装流程官网或中文网都有讲解,我也是按照那个流程走的。
这里说下安装和运行过程中经常出现但教程又没有提到的问题:
1.gradle-2.4-all.zip无法安装引发的错误,可以参考这里。
2.环境安装完成,app成功构建,但是更改代码react packager没有监听到文件变动并重新打包bundle,导致reload后无法更新app的问题。只有重新构建app并重启react packager才能解决。但这样太麻烦了,解决办法可以参考这里。我按照这个方式修改以后,环境表现就正常了。
3.安卓模拟器经常崩溃。
第一个问题与是否使用科学上网工具以及安卓环境配置和SDK有关系。第二个问题在win7环境下遇到过,修改数值后正常了,win8正常。第三个问题无解。
rn在windows下的安卓开发环境的坑还是比较多的。
4.这是一个比较完整的参考
二、demo app的功能和项目结构
首先看看这个demo的流程:
流程描述:
第一个场景是登录界面,输入账号和密码以后登录。登录后进入第二个场景,这是一个tabview容器,包含了三个tab:搜图书列表、电影排行列表和一个关于界面。列表视图支持上啦加载更多和下拉刷新,返回顶部以及跳转列表项的详情。关于界面里放了个静态的 swiper、说明以及一个登出的按钮,会返回到登录页。
说明:1.登录页做的是假的,后期可以加上session验证。2.搜图书和电影Top250排行都直接调用的豆瓣开放接口。
项目结构如下:
目录描述:
common - 公用工具(公用方法以及豆瓣接口Model的封装)
components - 全局公用组件(和业务无关)
images - 公用组件和业务视图组件的图片
views - 业务视图组件和业务公用组件(按照场景分文件夹)
views/MainView - 根组件(渲染了一个Navigator来控制整个App的场景跳转)
index.android.js - 入口文件(注册根组件,runApplication的前奏)
package.json - rn和第三方相关依赖
下面开始对每个场景进行拆分介绍。
三、入口文件和根组件
index.android.js这个文件按照官方文档的写法就可以,需要注意的是registerComponent方法传入的项目名一定要和命令行工具中执行react-native init xxx初始化命令时候输入的项目名称保持一致。
import React, {Component} from 'react';
import {AppRegistry} from 'react-native'; import MainView from './views/MainView'; AppRegistry.registerComponent('rndemo', () => MainView);
MainView.js作为根组件主要渲染了一个导航器来控制App场景跳转,所有业务视图组件都在它的控制下。
import React, {Component} from 'react';
import {View, Navigator} from 'react-native'; import LoginView from './login/LoginView'; export default class MainView extends Component {
render() {
return (
<Navigator
initialRoute={{name: 'LoginView', component: LoginView}}
configureScene={(route) => Navigator.SceneConfigs.PushFromRight}
renderScene={(route, navigator) => <route.component {...route.params} navigator={navigator} />}
/>
);
}
}
这个导航器类似于路由栈,是一种栈式结构,出栈和入栈的配合就能实现最基本的界面跳转,也提供有更高级的方法。
initialRoute要求指定默认显示的组件,这里import了登录视图组件,并指定为导航器的默认组件。confgureScene是导航器手势控制和动画配置,renderScene会渲染当前导航栈中被压入或者指定跳转的组件。
需要注意的是 <route.component {...route.params} navigator={navigator} /> 这里, {...route.params} 这是一个es6延展操作语法,能够进行不定参数接收、数组和对象的拆分等。能够进行批量赋值,可以将params对象的所有key->value结构转换成不同的prop。
比如:route.params值为 {a: 123,b: (a) => a + 1,c: true} ,最后的结果相当于 <route.component a={123} b={(a) => a + 1} c={true} navigator={navigator} />
虽然是语法范畴,但经常会用到,还是介绍一下。route.params可以用来给要跳转到的视图组件传递参数。如果数据过为复杂还是需要专门的数据流(状态)管理工具。这个demo因为数据简单,props传参已足够使用,也就没有用上redux,后面的各类组件也不会用到。有兴趣的话可以到别处了解一下redux。我在前段时间的vue.js组件化开发实践中对flux思路下的vuex和redux有一定介绍。vuex是专门针对vue的一个定制版,泛用性没有redux高,但和vue组件契合度高。redux这方面正好相反,但思想基本相同。
四、登录
登陆页面很简单,主要是一些布局:
jsx结构:
<ScrollView>
<Image style={sty.back} source={require('../../images/login/banner_2.png')}/>
<View style={[sty.back, sty.mask]}></View>
<View style={sty.loginView}>
<View style={sty.bannerWrap}>
<Image style={sty.bg} source={require('../../images/login/banner_1.png')}/>
<View style={sty.logoTextWrap}>
<Animated.Text style={[sty.logoText, {opacity: this.state.fadeAnim}]}>Demo</Animated.Text>
</View>
<View style={sty.copyRightWrap}>
<Text style={sty.copyRightText}>©2016</Text>
</View>
</View>
<View style={sty.inputWrap}>
<Text style={sty.inputTitle}>SIGN IN</Text>
<TextInput
{/* ... 账号 */}/>
<TextInput
{/* ... 密码 */}/>
<Animated.View style={{opacity: this.state.fadeAnim}}>
<TouchableOpacity
style={sty.loginBtn}
onPress={this.login.bind(this)}
>
<Text style={sty.loginBtnText}>登录</Text>
</TouchableOpacity>
</Animated.View>
</View>
<View style={sty.footer}>
<Image style={sty.footerLogo} source={require('../../images/login/react_native_logo.png')} />
<Text style={sty.footerText}>Powered by React-Native</Text>
</View>
</View>
</ScrollView>
const sty = StyleSheet.create({
back: {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
height: height - 24
},
mask: {
backgroundColor: '#2B75DE',
opacity: 0.2
},
loginView: {
height: height - 24
},
bannerWrap: { },
bg: {
width: 375 * refRat,
height: 235 * refRat
},
logoTextWrap: {
position: 'absolute',
bottom: 90
},
logoText: {
width: 375 * refRat,
textAlign: 'center',
fontSize: 40,
color: '#fff'
},
copyRightWrap: {
position: 'absolute',
bottom: 0,
paddingTop: 6,
paddingBottom: 6,
width: 375 * refRat,
borderTopWidth: onePx,
borderTopColor: '#fff',
opacity: 0.5
},
copyRightText: {
textAlign: 'center',
fontSize: 12,
color: '#fff'
},
inputWrap: {
alignItems: 'center'
},
inputTitle: {
paddingTop: 5,
paddingBottom: 20,
textAlign: 'center',
fontSize: 12,
color: '#fff',
opacity: 0.5
},
input: {
width: 230 * refRat,
textAlign: 'center',
color: '#fff',
borderBottomWidth: onePx,
borderBottomColor: '#fff',
},
loginBtn: {
marginTop: 30,
padding: 10,
width: 90 * refRat,
alignItems: 'center',
borderRadius: 20,
backgroundColor: '#FF8161'
},
loginBtnText: {
color: '#fff'
},
footer: {
position: 'absolute',
bottom: 0,
width: 375 * refRat,
alignItems: 'center'
},
footerLogo: {
width: 20 * refRat,
height: 20 * refRat
},
footerText: {
marginTop: 5,
textAlign: 'center',
fontSize: 12,
color: '#fff'
}
});
banner的文字和登录按钮有一个简单的淡入效果,这是通过Animated实现的,官方文档有更多的介绍。
登录以后跳转到TabView,使用的事导航器的restTo方法:
navigator.resetTo({
name: 'TabView',
component: TabView
});
它会跳转到新的场景,并重置整个路由栈。也就是说不能再直接通过pop出栈返回到登录页,必须指定跳转才行。这样可以避免在TabView进行了右滑等手势操作,无意间又回到了登录界面。
五、TabView
这是在github上找的一个第三方组件:react-native-tab-view,需要0.36以上版本支持。提供了顶部和底部tabbar,以及左右滑动来切换tab的容器,也支持点击tabbar按钮切换tab。tabbar点击自带有水波扩散你动画,类似于css3 scale实现的那种水波效果。
// 导入rn相关
// ...
// 导入需要的组件
// ...
export default class TabView extends Component {
constructor (props){
super(props); const {navigator} = props; this.state = {
index: 0,
routes: [
{key: '1',title: '搜图书'},
{key: '2',title: '电影排行'},
{key: '3',title: '关于'}
],
routeMap: {
1: <BookListView navigator={navigator} />,
2: <MovieListView navigator={navigator} />,
3: <AboutView navigator={navigator} />
}
};
} handleChangeTab (index){
this.setState({index});
} renderFooter (props){
return <TabBar tabStyle={{backgroundColor: '#6C6A6A'}} {...props} />;
} renderScene ({route, navigator}){
return this.state.routeMap[route.key] || null;
} render (){
return (
<TabViewAnimated
style={sty.container}
navigationState={this.state}
renderScene={this.renderScene.bind(this)}
renderFooter={this.renderFooter}
onRequestChangeTab={this.handleChangeTab.bind(this)}
/>
);
}
} const sty = StyleSheet.create({
// 样式
// ...
});
导入搜图书、电影排行和关于这三个视图组件,简单配置便可运行。具体可查看github上的介绍和示例代码。
六、搜图书
从最直观的列表开始介绍。列表采用ListView实现,ListView继承自ScrollView,扩展了一些功能,列表需要一个DataSource管理数据,我们取回的数据都必须用它改造一遍:
// 各种导入
// ... const ds = new ListView.DataSource({
// 更新策略(列表某项是否需要重新渲染):新旧值不相等
rowHasChanged: (v1, v2) => v1 !== v2
});
rowsHadChanged定义了怎么判断列表项是否有改变。虚拟DOM和diff算法是react的很巧妙的地方。新render出的虚拟DOM视图树会和当前的树进行比较,和得到不同地方,以类似于打补丁的方式打到当前的树上,也称之为'和解'过程。这就意味着只有真正改变了的地方才需要重新绘制,在有很多元素的时候能大幅提升渲染性能。
下面是BookListView类的结构:
// ... export default class BookListView extends Component {
constructor (props){
super(props);
this.state = {
showLoading: true,
scrollY: 0,
q: '红楼梦',
start: 0,
noMore : false,
isLoading: false,
data: [],
dataSource: ds.cloneWithRows([])
};
} async setListData (data){
await this.setState({
data: data,
dataSource: ds.cloneWithRows(data)
});
} async componentWillMount (){
let data = await this.getListData();
await this.setListData(data);
this.setState({showLoading: false});
} async getListData (){
this.setState({isLoading: true});
let {q, start} = this.state;
let data = await searchBook({q, start, count});
await this.setState({
start: start + count,
isLoading: false
});
return data.books;
} async listRefresh (){
await this.setState({start: 0,noMore : false});
let data = await this.getListData();
this.setListData(data);
if (!data.length) ToastAndroid.show("没有数据", ToastAndroid.SHORT);
} renderFooter (){
if (this.state.isLoading || this.state.data.length < 1) return null;
if (this.state.data.length < count) return <ListGetMore />;
return <ListGetMore isLoadAll={true}/>;
} async onEnd (){
if (this.state.isLoading || this.state.noMore) return; let data = await this.getListData();
if (data.length < count) this.setState({noMore: true});
let newList = this.state.data.concat(data);
this.setListData(newList);
} search (){
let {q} = this.state;
if (!q) return ToastAndroid.show("请输入书名", ToastAndroid.SHORT);
this.listRefresh();
} toDetail (data){
const {navigator} = this.props;
navigator.push({
name: 'BookDetailView',
component: BookDetailView,
params: {data}
});
} onScroll (e){
let scrollY = e.nativeEvent.contentOffset.y;
this.setState({scrollY});
} render (){
return (
<View>
<View
style={sty.searchWrap}>
<TextInput
style={sty.searchInput}
ref={(SearchInput) => {_SearchInput = SearchInput;}}
value={'' + this.state.q}
placeholder={'输入书名'}
autoCorrect={false}
clearButtonMode={'while-editing'}
underlineColorAndroid={'transparent'}
autoCapitalize={'none'}
onChangeText={(q) => this.setState({q})}
/>
<TouchableOpacity
style={sty.searchBtn}
onPress={() => {
_SearchInput.blur();
_ListView.scrollTo({y: 0, animated: false});
this.search();
}}
>
<Text style={sty.searchBtnText}>搜索</Text>
</TouchableOpacity>
</View>
<ListView
style={sty.listWrap}
ref={(ListView) => {_ListView = ListView;}}
enableEmptySections={true}
automaticallyAdjustContentInsets={false}
dataSource={this.state.dataSource}
renderRow={(rowData) => <BookListItem {...rowData} toDetail={this.toDetail.bind(this)}></BookListItem>}
renderFooter={this.renderFooter.bind(this)}
onEndReached={this.onEnd.bind(this)}
onEndReachedThreshold={50}
onScroll={this.onScroll.bind(this)}
scrollEventThrottle={5}
refreshControl={
<RefreshControl
onRefresh={this.listRefresh.bind(this)}
refreshing={this.state.isLoading}
colors={['#ff0000', '#00ff00', '#0000ff']}
enabled={true}
/>
}
/>
{this.state.scrollY > (height - 30 - 40 * refRat) ?
<ListToTop listView={_ListView}/> :
null}
{this.state.showLoading ?
<ActivityIndicator style={sty.loading} size={"large"} /> :
null}
</View>
);
}
}
在布局上ListView位于整个顶部搜索栏的下方,样式为flex布局,其内部子组件将按列排布。具体的属性和配置,以及dataSource数据集等在文档均有说明。
这里需要介绍下组件调用别的组件的方法:
BookListView作为导出的组件,它是由类中的state和方法,以及render方法返回的一个各种组件组成的复合组件,在实例中,我们点击了搜索按钮,输入框失去了焦点,让列表返回顶部,并触发了列表的搜索更新。都是通过refs这个属性来实现的。
<TextInput
// ...
ref={(SearchInput) => {_SearchInput = SearchInput;}}
<TouchableOpacity
// ...
style={sty.searchBtn}
onPress={() => {
_SearchInput.blur();
_ListView.scrollTo({y: 0, animated: false});
this.search();
}}
> // ...
<ListView
style={sty.listWrap}
ref={(ListView) => {_ListView = ListView;}} // ...
可以看到ListView这个组件类配置了一个ref属性,值为一个函数,入参即为这个ListView类在运行的时候实例化出的ListView对象,然后赋值给了_ListView这个变量,在点击搜索按钮的时候我们调用了_ListView的scrollTo方法来返回顶部,然后调用了BookListView类最后实例出的对象的search方法,也就是 this.search(); 。基于箭头函数的特性,this是在写的时候决定,因此它一定是指向BookListView对象的。如果是这样调用: <TouchableOpacity onPress={this.search.bind(this)} > ,就需要这个this通过es5的bind方法传入进去,强制要求search方法被调用的时候其内部this一定指向BookListView实例化出来的那个对象,因为这个search方法内部可能需要用到这个对象的属性和方法。如果不用bind方法来强行指定上下文环境,this指向的会是TouchableOpacity类实例化出的那个对象。这也是属于语法范畴。
ref属性可以不一定赋予一个函数作为值,一个字符串也是可行的。比如: ref={'ListView'} ,然后可以通过 this.refs['ListView'] 取到ListView这个对象,即可调用它的方法。当然,在使用时this一定要保证是指向BookListView对象的。
this的指向如果弄错,如果遇到这类报错,可以从这点开始排查,会经常出现’undefined is not a function‘这类报错。
基于React流程的setState方法是异步的(不受React控制的流程除外),这个一定要记住,如果在setState后直接获取state,值有可能还没有改变,要想保证改变,请使用es7的async/await特性,让异步操作用同步的方式来书写,其他异步方式也能解决。
列表的下拉刷新是通过配置refreshControl来实现的。
回到顶部按钮的显示与否是通过监听列表滚动的Y轴偏移来判断的,列表每次滚动会调用onScroll这个回调,从事件中获取到偏移,通过偏移量来决定按钮是否显示。由数据来驱动视图:
// ...
onScroll (e){
let scrollY = e.nativeEvent.contentOffset.y;
this.setState({scrollY});
}
// ...
<ListView
// ...
onScroll={this.onScroll.bind(this)}
scrollEventThrottle={5}
// ...
{this.state.scrollY > (height - 30 - 40 * refRat) ?
<ListToTop listView={_ListView}/> :
null}
注意:
1.jsx里不能食用if else 等,只支持一个语句,所以有判断的地方必须使用三元表达式。
2.scrollEventThrottle是节流控制,类似于jquery的debounce-throttle插件,可以避免每一次的scroll都执行onScroll回调带来的性能问题,毕竟我们一秒钟的滚动时间会触发很多很多次onScroll事件。
上拉加载更多是通过滚动到底部的检测来触发事件。官方文档中都有配置介绍。
列表项BookListItem是封装的一个业务组件,通过传入props来提供渲染需要的数据。很简单布局的一个组件,这里不再详细说。
跳转图书详情视图BookDetailView是通过push压栈的方式进行的,之所以没有使用resetTo方法,是因为希望进入详情以后能通过pop出栈便能返回上一个视图:
// ...
toDetail (data){
const {navigator} = this.props;
navigator.push({
name: 'BookDetailView',
component: BookDetailView,
params: {data}
});
}
// ...
豆瓣开放接口的简单封装,很简单,就2个接口,哈哈。使用了rn环境自带的fetch:
// common/model.js import {ToastAndroid} from 'react-native'; const _fetch = (url, param) => {
let qstring = '';
for (let key in param)
qstring += key + '=' + param[key] + '&';
url += '?' + qstring;
return fetch(url);
} const handle = async (url, param = {}) => {
try {
let response = await _fetch(url, param);
let res = await response.json();
return res;
} catch (error){
ToastAndroid.show('网络请求错误:' + error, ToastAndroid.LONG);
return {books: [],subjects: []};
}
} // 豆瓣开放API url
const domain = 'https://api.douban.com';
const douban = {
searchBook : domain + '/v2/book/search'
, movieTop250 : domain + '/v2/movie/top250'
}; /**
* 搜索图书
* @param {q 查询关键字 tag 查询标签 start 本次偏移 count 本次条数}
* @return {start 本次偏移 count 本次条数 total 总条数 books[] 图书集合}
*/
export const searchBook = param => handle(douban.searchBook, param);/**
* 电影Top250排行
* @param {start 本次偏移 count 本次条数}
* @return {start 本次偏移 count 本次条数 total 总条数 total 总条数 subjects[] 电影集合}
*/
export const movieTop250 = param => handle(douban.movieTop250, param);
七、电影排行
这个视图的列表相关组件以及详情组件与搜图书视图基本是一致的,只是少了搜索而已。
优化点:其实这两个视图的列表组件可以提取出公用的地方来抽象一次,封装为一个具有基本功能的公用List业务组件。搜图书列表和电影排行列表都可以继承自它,按需重写和扩展其他方法即可。
因为列表和详情基本与搜图书界面的功能基本一致,这里就只介绍一下webview内嵌豆瓣h5这里。从豆瓣取回的电影数据,有一个叫'alt'的字段存放了这个电影url,通过webview加载这个url,即可访问豆瓣的web页面:
<View>
<View style={sty.header}>
<TouchableOpacity
style={sty.backBtn}
onPress={this.back.bind(this)}
>
<Text style={sty.backBtnText}>{'<'}</Text>
</TouchableOpacity>
<Text numberOfLines={1} style={sty.headerText}>{this.props.title}</Text>
{this.state.canGoBack ?
<TouchableOpacity
style={sty.rightBtn}
onPress={this.directBack.bind(this)}
>
<Text style={sty.rightBtnText}>{'关闭'}</Text>
</TouchableOpacity> :
<Text style={sty.rightBtn}></Text>}
</View>
<WebView
style={sty.webView}
ref={'webview'}
automaticallyAdjustContentInsets={false}
source={{uri: this.props.url}}
javaScriptEnabled={true}
domStorageEnabled={true}
decelerationRate="normal"
startInLoadingState={true}
renderLoading={() => <ActivityIndicator style={sty.loading} size={"large"} />}
onNavigationStateChange={this.onNavigationStateChange.bind(this)}
onError={this.loadError.bind(this)}
/>
<Dialog
ref={'dialog'}
content={'刷新吗?'}
cancelAction={this.directBack.bind(this)}
okAction={this.reloadWebView.bind(this)}
/>
</View>
头部有三个元素:左边的后退按钮,中间的标题,右边的直接关闭按钮。
后端按钮可以控制webview的后退,只要webview的history还没有back到底,否则将直接通过整个app的导航组件回到电影详情界面:
async back (){
if (this.state.canGoBack){
this.refs['webview'].goBack();
} else {
this.directBack();
}
}
directBack (){
const {navigator} = this.props;
navigator.pop();
}
webview的每次history变化都会触发onNavigationStateChange事件,然后回调这个方法:
async onNavigationStateChange (navState){
var {canGoBack, canGoForward, title, loading} = navState;
await this.setState({
canGoBack,
title: loading ? '' : title
});
}
传入navState状态对象,我们可以取到canGoBack和canGoForward这两个布尔值,它们表示了当前webview的history状况,能否前进和后退。如果canGoBack为true,我们通过调用webview的back方法,可以实现history.back的功能。navState.loading为false表示加载完成,这时我们可以取到web页面的title作为header的title。
并且我们在state里维护了canGoBack这个状态值,当他为true的时候,会显示右侧的关闭按钮,点击这个按钮,可以直接回退到电影详情界面。好处在于:当我们在webview中点击web页面的连接前进了很多次之后,不想再不停的点击后退按钮,不论history有多少层都可以直接退回到上个场景:
这个虚拟机里面请求外网数据很缓慢,加载页面更慢...
八、关于
放了一个swiper组件,是一个第三方的组件:Github。下面放了一个登出按钮,点击以后弹出确任的对话框。点击确定,通过导航器的resetTo方法直接跳转到登录界面,并重置掉路由栈。
功能比较简单就不做过多介绍。
九、调试
ctrl+m或者打开菜单,点击'Debug JS Remotely',可以开启远程调试:
在js代码里console打出的信息都会在Console tab展示出来,报错和警报也会有。还Sources还可以打断点等。但是我开启远程调试后,有些时候非常卡,但帧数并不低。
除了菜单里的'Toggle Inspector'可以简易的查看一下元素以外,还可以安装react-devtools,下载地址:Github。也可以在chrome应用商店搜索安装(需要科学上网工具)。
这个调试插件我安装好以后,并没有使用起。即便在扩展管理里勾选了'允许访文件地址',在开启远程调试以后并没有探测到rn工程,但是访问豆瓣h5等使用react-js构建的站点时,可以嗅探到,并在chrome开发者工具里成功唤起了react-devtools的tab。Git上查看了issue,发现很多人也有这个问题,重新安装插件也没法解决...可能和chrome版本有关系,太高版本可能会出这个问题。
十、公共组件
这个demo抽象和封装了一些公共组件,但是没有提取完,还有优化点。这里介绍一下components目录下的Dialog对话框:
export default class Dialog extends Component {
constructor (props){
super(props); this.state = {
show: false
};
} async showDialog (){
await this.setState({show: true});
} async hideDialog (){
await this.setState({show: false});
} render (){
return (
<Modal
visible={this.state.show}
animationType='slide'
transparent={true}
onShow={() => {}}
onRequestClose={() => {}}
>
<View style={sty.modalStyle}>
<View style={sty.subView}>
<Text style={sty.titleText}>{this.props.title || '提示'}</Text>
<Text style={sty.contentText}>{this.props.content || '确定吗?'}</Text>
<View style={sty.horizontalLine}></View>
<View style={sty.buttonView}>
<TouchableHighlight
underlayColor='transparent'
style={sty.buttonStyle}
onPress={() => {
this.props.cancelAction && this.props.cancelAction();
this.hideDialog.bind(this)();
}}
>
<Text style={sty.buttonText}>取消</Text>
</TouchableHighlight>
<View style={sty.verticalLine}></View>
<TouchableHighlight
underlayColor='transparent'
style={sty.buttonStyle}
onPress={() => {
this.props.okAction && this.props.okAction();
this.hideDialog.bind(this)();
}}
>
<Text style={sty.buttonText}>确定</Text>
</TouchableHighlight>
</View>
</View>
</View>
</Modal>
);
}
}
十一、总结
1. windows下的安装环境的坑确实很多,而且这还只跑是在模拟器上,如果真机测试的话,不同机型、厂商应该会有适配的问题出现。mac下的表现应该要好得多,毕竟大家都说ios才是亲儿子。相信安卓方面以后还会不断的优化。如果需要一套代码同时跑安卓和ios两个平台,底层一定需要做组件封装,来屏蔽平台的差异。业务开发的时候,就不太需要考虑平台差异了。
2. 调试的提示信息有时候不太明确,需要挨着排查代码。
3. 布局和样式需要适应。
4. 组件使用上的限制文档没有明确提出,很多时候都是用到那里,那样写了,才发现不对。
5. html现在都讲究结构和样式分离,结构和逻辑分离。jsx又把我们拉回了以前的时代。
6. rn的生态圈还是很好的。
7. 其他 ...
以上希望对学习react native的同学有所帮助。不对的地方也请指出。
最后分享一个github上找到的一个不错的react native系列文章,包含作者原创和翻译的各种资料,原理、构架设计、性能优化、离线打包、增量更新都有介绍,入门rn以后可以看看,一定会有帮助的,可以基于此重构你的demo。
react native 入门实践的更多相关文章
- React Native入门——布局实践:开发京东client首页(一)
有了一些对React Native开发的简单了解,让我们从实战出发.一起来构建一个简单的京东client. 这个client是仿照之前版本号的京东client开发的Android版应用,来源于CSDN ...
- React Native入门教程 3 -- Flex布局
上一篇文章中介绍了基本组件的使用 React Native入门教程(笔记) 2 – 基本组件使用及样式 本节内容将继续沿用facebook官方例子介绍如何使用Flexbox布局把界面设计的多样化. 转 ...
- React Native入门教程2 -- 基本组件使用及样式
在上一篇文章中,我们学会了如何搭建React Native的环境(React Native入门教程(笔记) 1 – 开发环境搭建),不知道你们是否搭建好了呢,如果还没有,那么快动起小手,来体验RN带给 ...
- React Native入门教程 1 -- 开发环境搭建
有人问我为啥很久不更新博客..我只能说在学校宿舍真的没有学习的环境..基本上在宿舍里面很颓废..不过要毕业找工作了,我要渐渐把这个心态调整过来,就从react-native第一篇博客开始.话说RN也出 ...
- React Native 入门基础知识总结
中秋在家闲得无事,想着做点啥,后来想想,为啥不学学 react native.在学习 React Native 时, 需要对前端(HTML,CSS,JavaScript)知识有所了解.对于JS,可以看 ...
- React Native入门-刘望舒
React Native入门(一)环境搭建与Hello World React Native入门(二)Atom+Nuclide安装.配置与调试 React Native入门(三)组件的Props(属性 ...
- React Native入门指南
转载自:http://www.jianshu.com/p/b88944250b25 前言 React Native 诞生于 2015 年,名副其实的富二代,主要使命是为父出征,与 Apple 和 Go ...
- React Native 入门到原理(详解)
抛砖引玉(帮你更好的去理解怎么产生的 能做什么) 砖一.动态配置 由于 AppStore 审核周期的限制,如何动态的更改 app 成为了永恒的话题.无论采用何种方式,我们的流程总是可以归结为以下三部曲 ...
- 《React Native入门与实战》读书笔记(1)
ReactNative介绍 它的底层引擎是JavaScript Core,调用的是原生组件而非HTML5组件(HTML+CSS+JavaScript构建的组件).运行时,可以做到与Native App ...
随机推荐
- Jsonp跨域
Jsonp.html <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...
- Delphi之静态方法,虚方法virtual,动态dynamic,抽象abstract,消息
Delphi之静态方法,虚方法virtual,动态dynamic,抽象abstract,消息 http://www.cnblogs.com/zhwx/archive/2012/08/28/266055 ...
- 蜻蜓FM笔试题目,求两个点的最近父节点
这个博客写的特别好. http://blog.csdn.net/kangroger/article/details/40392925
- ng-repeat循环出来的部分调用同一个函数并且实现每个模块之间不能相互干扰
使用场景:用ng-repeat几个部分,每个部分调用同一个函数,但是每个模块之间的功能不能相互干扰 问题:在用repeat实现.content块repeat的时候打算这样做:新建一个空的数组(nmbe ...
- (RMQ版)LCA注意要点
inline int lca(int x,int y){ if(x>y) swap(x,y); ]][x]]<h[rmq[log[y-x+]][y-near[y-x+]+]])? rmq[ ...
- BZOJ4488: [Jsoi2015]最大公约数
Description 给定一个长度为 N 的正整数序列Ai对于其任意一个连续的子序列{Al,Al+1...Ar},我们定义其权值W(L,R )为其长度与序列中所有元素的最大公约数的乘积,即W(L,R ...
- C++中const的全面总结
C++中的const关键字的用法非常灵活,而使用const将大大改善程序的健壮性,本人根据各方面查到的资料进行总结如下,期望对朋友们有所帮助. Const 是C++中常用的类型修饰符,常类型是指使用类 ...
- Txt格式配置表无法解析的问题——BOM
今天再次遇到同一个问题:策划给来一个Txt格式配置表,我用解析类去读取,返回的结果为空.解析类参数是:主键key,文件名fileName,错误提示errorTip. 写读取语句的时候,主键key我是直 ...
- 利用http缓存数据
通过一个简单的ajax请求来详解http的缓存技术 register.html <!DOCTYPE> <html> <head> <title>http ...
- DNS域名服务器
DNS是用来解析域名的,IP就相当于地址一样,不知道IP就没法访问指定网络.要上PC6下载站,输入www.pc6.com, 得先问问DNS大哥www.pc6.com在哪,然后Dns告诉你后,就上去了w ...