React Native初探


前言

很久之前就想研究React Native了,但是一直沒有落地的機會,我一直認為一個技術要有落地的場景才有研究的意義,剛好最近迎來了新的APP,在可控的范圍內,我們可以在上面做任何想做的事情。

PS:任何新技術的嘗鮮都一定要控制在自己能控制的范圍內,失敗了會有可替換方案,不要引起不可逆的問題,這樣會給團隊造成災難性的后果。

事實上,RN經過一段時間發展,已經有充分數量的人嘗試過了,就我身邊就有幾批,褒貶也不一:

① 做UI快

② 還是有很多限制,不如原生Native

③ 入門簡單,能讓前端快速開發App

④ iOS&Android大部分代碼通用

⑤ code-push能做熱更新,但是用不好依舊坑

......

在得到一些信息后,可以看出,要用RN高效率的做出比較不錯的App是有可能的,單看投入度與最初設計是否合理,而且現在關於React Native的各種文檔是相當豐富的,所以這個階段想切入RN可能是一個不錯的選擇。

帶着試試不吃虧的想法,我們開始今天的學習,這里是一些比較優質的學習資料:

https://github.com/reactnativecn/react-native-guide

准備階段

搭建開發環境

http://reactnative.cn/docs/0.36/getting-started.html

官方的例子其實寫的很好了,我照着官方的例子能很好的跑起來,大家自己去看看吧

這里在運行時候要注意一下,我因為開啟了翻牆工具,一運行就crash,這里猜測是翻(科學上網法)牆工具對localhost造成了影響,導致不能讀取文件,這個可能涉及到RN底層實現,我們后面深入了再去做研究,這里關閉翻牆工具即可。

然后第二個問題,是http的圖片展示不出來,這里折騰了很久,卻發現后面的章節有了說明,app默認只支持https的鏈接,這里大家改下配置即可:

https://segmentfault.com/a/1190000002933776

RN中的js使用的是比較新的語法,這里也需要大家進行學習,我學習的感受是ES6提供了很多語法糖,但是有幾個東西也要注意。

Class

JavaScript之前的繼承全部是復寫原型鏈模擬實現的,作為大型應用框架,繼承是必不可少的,所以ES6直接將這塊API化了,我這里寫一個簡單的demo:

 1 class Animal {
 2     constructor(name) {
 3         this.name = name;
 4     }
 5     say() {
 6         console.log('我是' + this.name);
 7     }
 8 }
 9 
10 class Person extends Animal {
11     say() {
12         console.log('我是人類');
13         super.say();
14     }
15 }
16 
17 var p = new Person('葉小釵')
18 p.say();
1 /*
2  我是人類
3  我是葉小釵
4  */

Module

我們一般使用requireJS解決模塊化的問題,在ES6里面提出了Module功能在官方解決了模塊化的問題,這里優缺點不是我們考慮的重點,簡單了解下語法,兩個核心為:

① export

② import

ES6以一個文件為單位,一個文件可以多個輸出,這里以RN的一個引用為例:

1 import React, { Component } from 'react';
2 import {
3   AppRegistry,
4   StyleSheet,
5   Text,
6   View
7 } from 'react-native';
8 import styles from './static/style/styles.js';

可以假想,這里一定會有一個react文件,並且里面可能是這個樣式的:

export default class React......

expoet class Component ......

PS:一個文件只能有一個default

輸出的default一定會出現,不使用大括號包裹,其余部分隨意輸出,這里與我們使用require或有不同,需要注意。

應該說ES6提供了很多語法糖,有人喜歡,有人不喜歡,這個看愛好使用吧,比如=>箭頭函數。了解了以上關系,再配合ES6的一些文檔,基本可以寫RN的代碼了。

城市列表

拆分目錄

這里,我們做一個城市列表,真實的訪問接口獲取數據,然后渲染頁面,看看做出來效果如何。

首先,我們初始化一個RN項目:

react-native init Citylist

然后使用Xcode打開iOS中的項目,編譯運行:

 1 import React, { Component } from 'react';
 2 import {
 3   AppRegistry,
 4   StyleSheet,
 5   Text,
 6   View
 7 } from 'react-native';
 8 
 9 export default class Citylist extends Component {
10   render() {
11     return (
12       <View style={styles.container}>
13         <Text style={styles.welcome}>
14           Welcome to React Native!
15         </Text>
16         <Text style={styles.instructions}>
17           To get started, edit index.ios.js
18         </Text>
19         <Text style={styles.instructions}>
20           Press Cmd+R to reload,{'\n'}
21           Cmd+D or shake for dev menu
22         </Text>
23       </View>
24     );
25   }
26 }
27 
28 const styles = StyleSheet.create({
29   container: {
30     flex: 1,
31     justifyContent: 'center',
32     alignItems: 'center',
33     backgroundColor: '#F5FCFF',
34   },
35   welcome: {
36     fontSize: 20,
37     textAlign: 'center',
38     margin: 10,
39   },
40   instructions: {
41     textAlign: 'center',
42     color: '#333333',
43     marginBottom: 5,
44   },
45 });
46 
47 AppRegistry.registerComponent('Citylist', () => Citylist);
View Code

這里除了index.io.js,其他文件我們不必理睬,我們做的第一件事情是,將樣式文件剝離出去,新建static文件夾,加入images和style,將樣式文件移入style文件,新建style.js:

 1 import {
 2     StyleSheet
 3 } from 'react-native';
 4 
 5 export let styles = StyleSheet.create({
 6     container: {
 7         flex: 1,
 8         justifyContent: 'center',
 9         alignItems: 'center',
10         backgroundColor: '#F5FCFF',
11     },
12     welcome: {
13         fontSize: 20,
14         textAlign: 'center',
15         margin: 10,
16     },
17     instructions: {
18         textAlign: 'center',
19         color: '#333333',
20         marginBottom: 5,
21     },
22 });

然后首頁代碼再做一些改動:

 1 import React, { Component } from 'react';
 2 import {
 3   AppRegistry,
 4   Text,
 5   View
 6 } from 'react-native';
 7 
 8 import {styles} from './static/style/style';
 9 
10 
11 export default class Citylist extends Component {
12   render() {
13     return (
14       <View style={styles.container}>
15         <Text style={styles.welcome}>
16           Welcome to React Native!
17         </Text>
18         <Text style={styles.instructions}>
19           To get started, edit index.ios.js
20         </Text>
21         <Text style={styles.instructions}>
22           Press Cmd+R to reload,{'\n'}
23           Cmd+D or shake for dev menu
24         </Text>
25       </View>
26     );
27   }
28 }
29 
30 AppRegistry.registerComponent('Citylist', () => Citylist);

PS:這里有一個箭頭函數

1 () => Citylist
2 //===>
3 function () {
4   return Citylist;
5 }

靜態資源剝離后,我們先不處理其它的,我們來做數據請求。

數據請求

RN雖然內置了ajax庫,但是一般推薦使用RN自帶的Fetch,最簡單的使用是:

fetch('https://mywebsite.com/mydata.json')

PS:我們在學習RN的時候,也是在學習神馬方式是適合的,或者說熟悉使用合適的組件

請求一個接口是這樣寫的(使用promise):

1 fetch('https://apikuai.baidu.com/city/getstartcitys')
2 .then((response) => response.json())
3 .then((jsonData) => {
4   console.log(jsonData);
5 })
6 .catch((e) => {
7   console.log(e)
8 })

這里打開調試環境一看,輸出了我們要的數據:

一般來說,我們需要對數據請求應該封裝為一個底層庫,這里只做一些簡單改造,真實項目不會這樣做:

 1 export default class Citylist extends Component {
 2   getdata(url, suc, err) {
 3     return fetch(url)
 4       .then((response) => response.json())
 5       .then((data) => {
 6         if(data.errno == 0) {
 7           suc && suc(data.data)
 8         }
 9       })
10       .catch((e) => {
11           console.log(e)
12       });
13   }
14   render() {
15 
16     this.getdata('https://apikuai.baidu.com/city/getstartcitys', function(data) {
17       s = ''
18     });
19 
20     return (
21       <View style={styles.container}>
22         <Text style={styles.welcome}>
23           Welcome to React Native!
24         </Text>
25         <Text style={styles.instructions}>
26           To get started, edit index.ios.js
27         </Text>
28         <Text style={styles.instructions}>
29           Press Cmd+R to reload,{'\n'}
30           Cmd+D or shake for dev menu
31         </Text>
32       </View>
33     );
34   }
35 }

PS:這里的使用不一定正確,先完成功能再改進吧

我們取所有的城市cities,這個數據量很大,有1000多條記錄,也可以測試下拖動效率了,這里為類加入構造函數,因為列表是可變的,暫時把列表數據歸為state(react也不是太熟,如果有問題后續優化,先完成功能):

1 constructor(props) {
2   super(props);
3   this.state = {
4     cities: []
5   };
6 }
1 var scope = this;
2 //本來想使用箭頭函數的,但是了解不太清楚,demo時候暫時這樣吧
3 this.getdata('https://apikuai.baidu.com/city/getstartcitys', function(data) {
4   scope.state.citys = data.cities;
5 });

列表渲染

處理了數據問題后,我們開始做列表渲染,這里使用ListView組件,這個組件用以顯示一個垂直滾動列表,適合長列表,兩個必須的屬性是datasource和renderRow:

dataSource:列表數據源

renderRow:逐個解析數據源中的數據,然后返回一個設定好的格式來渲染

簡單書寫代碼:

  1 export default class Citylist extends Component {
  2   constructor(props) {
  3     super(props);
  4 
  5     this.ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
  6     this.state = {
  7       cities: this.ds.cloneWithRows([
  8           {cnname
  9               :
 10               "文山壯族苗族自治州",
 11               enname
 12                   :
 13                   "wszzmzzzz",
 14               extflag
 15                   :
 16                   "1",
 17               flag
 18                   :
 19                   "0",
 20               name
 21                   :
 22                   "wenshanzhuangzumiaozuzizhizhou",
 23               parentid
 24                   :
 25                   "28",
 26               regionid
 27                   :
 28                   "177",
 29               shortname
 30                   :
 31                   "文山",
 32               shownname
 33                   :
 34                   "文山",
 35               type
 36                   :
 37                   "2"},{cnname
 38               :
 39               "文山壯族苗族自治州",
 40               enname
 41                   :
 42                   "wszzmzzzz",
 43               extflag
 44                   :
 45                   "1",
 46               flag
 47                   :
 48                   "0",
 49               name
 50                   :
 51                   "wenshanzhuangzumiaozuzizhizhou",
 52               parentid
 53                   :
 54                   "28",
 55               regionid
 56                   :
 57                   "177",
 58               shortname
 59                   :
 60                   "文山",
 61               shownname
 62                   :
 63                   "文山",
 64               type
 65                   :
 66                   "2"},{cnname
 67               :
 68               "文山壯族苗族自治州",
 69               enname
 70                   :
 71                   "wszzmzzzz",
 72               extflag
 73                   :
 74                   "1",
 75               flag
 76                   :
 77                   "0",
 78               name
 79                   :
 80                   "wenshanzhuangzumiaozuzizhizhou",
 81               parentid
 82                   :
 83                   "28",
 84               regionid
 85                   :
 86                   "177",
 87               shortname
 88                   :
 89                   "文山",
 90               shownname
 91                   :
 92                   "文山",
 93               type
 94                   :
 95                   "2"}
 96       ])
 97     };
 98   }
 99   getdata(url, suc, err) {
100     return fetch(url)
101     .then((response) => response.json())
102     .then((data) => {
103       if(data.errno == 0) {
104         suc && suc(data.data)
105       }
106     })
107     .catch((e) => {
108         console.log(e)
109     });
110   }
111   componentDidMount(){
112     var scope = this;
113     this.getdata('https://apikuai.baidu.com/city/getstartcitys', function(data) {
114         console.log(data)
115 
116         scope.setState({
117             cities: scope.ds.cloneWithRows(data.cities)
118         });
119         //scope.state.citys = data.cities;
120         //this.getdata('https://apikuai.baidu.com/city/getstartcitys', (data) => {
121         //  this.state.citys = data.cities;
122         //});
123     });
124   }
125   render() {
126     return (
127       <View style={styles.container}>
128           <ListView
129               dataSource={this.state.cities}
130               renderRow={(rowData) => <Text>{rowData.cnname}</Text>}
131           />
132       </View>
133     );
134   }
135 }
View Code

然后就這樣了,雖然丑是丑點,但是還能看嘛,這里我們先不去理睬城市的排序,也不做搜索功能,我們先把布局處理下,他的丑陋我已經受不了了

樣式處理

現在我們開始處理這段樣式:

 1 import React, { Component } from 'react';
 2 import {
 3   AppRegistry,
 4   ListView,
 5   Text,
 6   View
 7 } from 'react-native';
 8 
 9 import {styles} from './static/style/style';
10 
11 export default class Citylist extends Component {
12   constructor(props) {
13     super(props);
14 
15     this.ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
16     this.state = {
17       cities: this.ds.cloneWithRows([])
18     };
19   }
20   getdata(url, suc, err) {
21     return fetch(url)
22     .then((response) => response.json())
23     .then((data) => {
24       if(data.errno == 0) {
25         suc && suc(data.data)
26       }
27     })
28     .catch((e) => {
29         console.log(e)
30     });
31   }
32   componentDidMount(){
33     var scope = this;
34     this.getdata('https://apikuai.baidu.com/city/getstartcitys', function(data) {
35         console.log(data)
36 
37         scope.setState({
38             cities: scope.ds.cloneWithRows(data.cities)
39         });
40         //scope.state.citys = data.cities;
41         //this.getdata('https://apikuai.baidu.com/city/getstartcitys', (data) => {
42         //  this.state.citys = data.cities;
43         //});
44     });
45   }
46   render() {
47     return (
48       <View style={styles.container}>
49           <ListView style={styles.listView} enableEmptySections={true}
50               dataSource={this.state.cities}
51               renderRow={(rowData) =>
52               <View style={styles.listItem} >
53                   <Text>{rowData.cnname}</Text>
54               </View>
55               }
56           />
57       </View>
58     );
59   }
60 }
61 
62 AppRegistry.registerComponent('Citylist', () => Citylist);
View Code
 1 import {
 2     StyleSheet
 3 } from 'react-native';
 4 
 5 export let styles = StyleSheet.create({
 6     container: {
 7         flex: 1,
 8         backgroundColor: '#F5FCFF',
 9     },
10     listView: {
11         marginTop: 30,
12         flex: 1,
13         borderBottomColor:'#CCCCCC',//cell的分割線
14         borderBottomWidth:1
15     },
16     listItem: {
17         paddingTop: 15,
18         paddingBottom: 15,
19         paddingLeft: 10,
20         flexDirection:'row',
21         borderBottomColor:'#CCCCCC',//cell的分割線
22         borderBottomWidth:1
23     }
24 });
View Code

事件綁定

然后,我們再為每行數據加上點擊事件,這里也做簡單一點,打印出當前行的值即可:

 1  onPressAction(data){  2     alert(data.cnname)
 3   }
 4   render() {
 5     return (
 6       <View style={styles.container}>
 7           <ListView style={styles.listView} enableEmptySections={true}
 8               dataSource={this.state.cities}
 9               renderRow={(rowData) =>
10               <View style={styles.listItem}  >
11                   <Text onPress={() => this.onPressAction(rowData)}>{rowData.cnname}</Text>
12               </View>
13               }
14           />
15       </View>
16     );
17   }

PS:我尼瑪,這個RN的學習,很大程度就是一個個API或者組件的熟悉,這塊不熟悉的話,做起來惱火的很

我這里開始想給Text設置邊框,怎么都不能成功,后面就加了一層View就好了,這種小細節需要多摸索,這個是最終的結構:

結語

作為一個demo的話,這個例子基本可以說明一些問題的,雖然我本意是想做成這個樣子的:)

通過這個例子,我們簡單的學習了下RN的開發模式,做出來的感受是Facebook很強大,做了一個體系性的東西,舉個例子來說(個人感受

之前我們做Hybrid的時候Header是Native提供的,大概做法是這樣的:

 1 //Native以及前端框架會對特殊tagname的標識做默認回調,如果未注冊callback,或者點擊回調callback無返回則執行默認方法
 2 //back前端默認執行History.back,如果不可后退則回到指定URL,Native如果檢測到不可后退則返回Naive大首頁
 3 //home前端默認返回指定URL,Native默認返回大首頁
 4 this.header.set({
 5     left: [
 6         {
 7             //如果出現value字段,則默認不使用icon
 8             tagname: 'back',
 9             value: '回退',
10             //如果設置了lefticon或者righticon,則顯示icon
11             //native會提供常用圖標icon映射,如果找不到,便會去當前業務頻道專用目錄獲取圖標
12             lefticon: 'back',
13             callback: function () { }
14         }
15     ],
16     right: [
17         {
18             //默認icon為tagname,這里為icon
19             tagname: 'search',
20             callback: function () { }
21         },
22     //自定義圖標
23         {
24             tagname: 'me',
25             //會去hotel頻道存儲靜態header圖標資源目錄搜尋該圖標,沒有便使用默認圖標
26             icon: 'hotel/me.png',
27             callback: function () { }
28         }
29     ],
30     title: 'title',
31     //顯示主標題,子標題的場景
32     title: ['title', 'subtitle'],
33 
34     //定制化title
35     title: {
36         value: 'title',
37         //標題右邊圖標
38         righticon: 'down', //也可以設置lefticon
39         //標題類型,默認為空,設置的話需要特殊處理
40         //type: 'tabs',
41         //點擊標題時的回調,默認為空
42         callback: function () { }
43     }
44 });

通過這個約定,我們的Native就會生成一系列headerUI:

而RN做了什么呢,他可能是實現了一個這樣的標簽(或者說是語法糖):

<Header title="" right="[]" ></Header>

然后RN會自己去解析這個標簽,生成上述的對象,然后生成Native的UI,這個我們其實也能做到,但是我們一個能做到,10個就不一定做得到了,RN牛的地方就牛在他提供了這么大一坨東西:

然后還有他一整套的樣式體系,非常之大手筆,而通過RN的完善約定,生成了一套NativeUI,應該說來體驗是非常高的,開發效率因為可以做到大部分iOS Android通用,雖然整體開發效率無法與Hybrid比肩,但絕對有其應用場景。

我們也有一些同事說了一些RN的問題,但是框架在發展,容器在優化,這些問題在某個時間點應該能解決的,總的說來,RN還是很有學習的價值,后面我可能會花很多功夫去進行落地!!!

為了匯集資源,這里引用這里的學習資源:https://github.com/reactnativecn/react-native-guide

React Native

React.js

ES6

系列教程

React Native探索系列教程

開源APP

研究源碼也是一個很好的學習方式

圖書

組件

由於已經有較好的組件庫網站,這里就不做總結。可以直接查看如下網站,過后可能精選一部分優質組件出來 :P

工具

資源網站

業界討論


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM