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基础&入门教程:初步使用Flexbox布局
在上篇中,笔者分享了部分安装并调试React Native应用过程里的一点经验,如果还没有看过的同学请点击<React Native基础&入门教程:调试React Native应用的一小 ...
- React Native小白入门学习路径——五
React Native小白入门学习路径--五 序 "哦天呐!" 这句话成了我在实验室的口头禅, 老师可能觉得我们都是大神吧,都还在看着基础就给布置了那么多任务:写一个RN的TDD ...
- React Native小白入门学习路径——二
万万没想到,RN组仅剩的一个学长也走了,刚进实验室没几天就被告知这样的事情,一下子还真的有点接受不了,现在RN组就成了为一个没有前辈带的组了,以后学习就更得靠自己了吧.唉,看来得再努力一点了. 这一周 ...
- React Native 从入门到原理一
React Native 从入门到原理一 React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原理的文章却 ...
- React Native开发入门
目录: 一.前言 二.什么是React Native 三.开发环境搭建 四.预备知识 五.最简单的React Native小程序 六.总结 七.参考资料 一.前言 虽然只是简单的了解了一下Reac ...
- React Native 从入门到原理
React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原理的文章却寥寥无几. 本文分为两个部分:上半部分用通 ...
- React Native小白入门学习路径——三
迷茫,真的迷茫. 或许是自己努力的还不够吧,在学习的过程中遇到了很多问题,自己尝试借助搜索引擎解决问题,无奈国内的教程写的还很基础,涉及到稍微具体一点的问题时讲解就比较少更新也比较慢,绝大多数还是很早 ...
- React Native新手入门
前言 React Native是最近非常火的一个话题,想要学习如何使用它,首先就要知道它是什么. 好像面对一个新手全面介绍它的文章还不多,我就归纳一下所有的资料和刚入门的小伙伴一起来认识它~ 将从以下 ...
- 给所有开发者的React Native详细入门指南
建议先下载好资料后,再阅读本文.demo代码和资料下载 目录 一.前言 二.回答一些问题 1.为什么写此教程 2.本文适合哪些人看 3.如何使用本教程 4.需要先学习JavaScript.HTML.C ...
随机推荐
- PHP常用日期加减计算方法实例
PHP常用日期加减计算方法实例 实例总结了PHP常用日期加减计算方法.分享给大家供大家参考,具体如下: PHP 标准的日期格式 date("Y-m-d H:i:s"); PHP 简 ...
- hadoop2-MapReduce详解
本文是对Hadoop2.2.0版本的MapReduce进行详细讲解.请大家要注意版本,因为Hadoop的不同版本,源码可能是不同的. 以下是本文的大纲: 1.获取源码2.WordCount案例分析3. ...
- node03
1.express处理post请求 借助body-parse中间件,其实最终我们也不会使用这个 对于get请求,无需中间件,用req.query即可返回相应的数据 但是post我们尝试借助中间件处理 ...
- [LeetCode] Score of Parentheses 括号的分数
Given a balanced parentheses string S, compute the score of the string based on the following rule: ...
- synchronized 与 volatile 原理 —— 内存屏障的重要实践
单例模式的双重校验锁的实现: 第一种: private static Singleton _instance; public static synchronized Singleton getInst ...
- Java Fileupload
fileupload FileUpload 是 Apache commons下面的一个子项目,用来实现Java环境下面的文件上传功能,与常见的SmartUpload齐名. 组件 1.FileUpLoa ...
- [运维工具]linux下远程桌面rdesktop安装和使用
依然是解压 configure make make install 这些步骤 rdesktop -f 16 192.168.16.90 -f是全屏,退出全屏是CRTL+ALT+ENTER 记录一个li ...
- Ubuntu 16.04下安装搜狗输入法
在确保更新了国内镜像源的前提下: 安装sogou输入法步骤 一.安装fcitx键盘输入法系统(系统已安装的可忽略此步骤) 1.添加以下源 sudo add-apt-repository ppa:fci ...
- [python] bluepy 一款python封装的BLE利器
1.bluepy 简介 bluepy 是github上一个很好的蓝牙开源项目,其地址在 LINK-1, 其主要功能是用python实现linux上BLE的接口. This is a project t ...
- .NET程序员所需要注意的网站资源
一个程序员 需要 对 技术 和 行业 两方面同时具有极大热情和注意力才能让自己在一个新的台阶. 有些程序员是对技术有着极大的热情但是行业完全不注意,这样我感觉只能成为一个专家,并不能让自己真正的质变, ...