前言
這篇文章,是接着之前的一篇入門文章寫的(雖然已經過去大半年了),本文的受衆仍然是React小白,熟悉React的同學可以不看了。上一篇文章鏈接:
上一篇文章主要介紹了JS的語言基礎和React的component生命週期。本文會接着上一篇文章,繼續講解React的基礎。
本文的React Native版本是0.0.40,開發IDE是Atom+Nuclide,IDE環境搭建可以參考我的這一篇文章。
如果你有足夠的時間,非常建議看看React的官方Getting start文檔。
準備工作
初始化一個RN的工程,
react-native init ReactBasics
然後,在iOS模擬器(安卓也可以)上運行這個工程。
react-native run-ios
本文所有的代碼修改都在index.ios.js
中(如果是安卓模擬器,修改index.andorid.js).
默認的ReactBasics代碼是這樣子的
//...
export default class ReactBasics extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit index.android.js
</Text>
<Text style={styles.instructions}>
Double tap R on your keyboard to reload,{'\n'}
Shake or press menu button for dev menu
</Text>
</View>
);
}
}
//...
我們修改爲這樣
export default class ReactBasics extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Let us learn React.
</Text>
</View>
);
}
}
然後,運行模擬器
react-native run-ios
看到的如下截圖:
React是啥?
React是一個Javascript框架,用來開發web應用。Web應用開發中,比較流行的有三個框架:
從名字上,就能看到React Native是基於React(都是Facebook出品)。React的設計思想是:
Declarative(交互式的)。
應用都是基於狀態的,應用會隨着數據的變化切換到不同的狀態。React將這種狀態抽象爲一個個View,這樣狀態的改變後,利用React就在不同的View之前切換。這樣,讓代碼更清晰可預測,頁方便測試。
Component-Based(基於組件)
把管理狀態的View封裝成Component,然後再把這些Component組合到一起來實現複雜的UI。
Learn Once, Write Anywhere(寫一次,到處跑)
React支持Web開發,Server開發(Node),同樣也支持本文提到的App開發(React Native)。
JSX
JSX是JavaScript語言的擴展,它並不改變JS本身語法。使用起來類似XML,React會對JSX的代碼進行編譯(編譯器是babel),生成JavaScript代碼,用來描述React中的Element如何渲染。
本文最初的這段代碼就是JSX語法。
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Let us learn React.
</Text>
</View>
);
}
其中
<Text style={styles.welcome}>
Let us learn React.
</Text>
會被編譯成
React.createElement(
Text,
{style: styles.welcome},
'Let us learn React.'
)
注意,使用JSX,一定要在scope中,能夠訪問到React和對應的Element。比如剛剛的例子,在代碼的最上面看到了這樣的import
import React, { Component } from 'react';
import {
//...
Text,
} from 'react-native';
另外,JSX編譯結果可以在線查看在線查看
Tips:JSX本身也是Javascript expression。所以,可以將其賦值給變量,放到if和for中,當然作爲參數傳入和返回值返回
如果你個Tag是空的,可以用/>
進行close。比如
<CustomComponent style={styles.welcome}/>
大小寫
JSX對大小寫開頭是敏感的
- 小寫字母開頭會被認爲是html內置標籤。比如
div
- 大寫字母開頭會被認爲是自己創建或者import的component。
所以,自定義的Components必須是大寫字母開頭的。
舉個例子:
如果上文的Text
改成小寫,
<text style={styles.welcome}>
Let us learn React.
</text>
會被編譯成爲
React.createElement(
"text",
{style: styles.welcome},
'Let us learn React.'
)
React在解析的時候,會認爲這和div
類似,是html內置標籤,引起錯誤。
JS代碼
JSX中的JS表達式要用大括號{/*這裏是JS代碼*/}
擴起來,不要加引號,加引號後React會認爲它是字符串。
比如,你可以這麼寫
<Text style={styles.welcome}>
{"Let" + " us" + " learn" + " react"}
</Text>
點擊Save,然後選中模擬器,command+R進行reload,仍然能夠正常顯示。
Children
這是本文最初的代碼
<View style={styles.container}>
<Text style={styles.welcome}>
Let us learn react
</Text>
</View>
可以看到,在JSX中可以嵌套Element形成一種層次結構。這種層次結構可以動態生成,比如
render() {
var textElement = <Text style={styles.welcome}>Let us learn react</Text>
return (
<View style={styles.container}>
{textElement}
</View>
);
}
JSX更多的學習資料,方便想要深入學習的同學:
Element
Element是你在屏幕上想要看到的東西,在React中,一個element就是一個對象。
在React中,element是不可變的。就像電影一樣,由一幀一幀組成的,一個element就像電影中的一幀。如果想要用戶看到變化,那麼就渲染下一幀。
聰明的你可能會問,這樣效率不是很低嗎?
事實就是,React只會更新變化的那部分,對於不變的視圖,是不會重新渲染的。
React強調函數式編程,不可變狀態是函數式編程的核心思想之一。不可變狀態能夠讓你的代碼更容易編寫,測試和維護。一個不可變的函數,在輸入一定的時候,輸出一定是一樣的。
Component
在React native開發中,Component
是一個非常重要的概念。它類似iOS中的UIView或者安卓中的View,將視圖分成一個個的小部分。
React native中,我們通常採用ES6 class來定義一個Component。
比如上文的代碼:
export default class ReactBasics extends Component {
render(){
//..
}
}
其中,render是實際的渲染函數。通常,使用JSX來返回想要看到的視圖。
React Native中的Component都是原生的Component,通過JS bridge來調用原生的Component來渲染。
這些Component分爲兩種:
- iOS/Android通用的。比如Navigator,Text,Image等等
- 平臺獨有的,比如NavigatorIOS,ProgressBarAndroid。
State/props
React的Component有兩個內置參數對象
- props,有React自動初始化,包含了傳遞給一個Component的參數。
- state,包含的參數對象應當用在render函數中,用作渲染。調用
this.setState()
會觸發上文提到的component重新渲染。
初始化
比如,我們對本文的代碼進行修改,新建一個Component
class GreatingComponent extends Component{
render(){
return (
<Text style={styles.welcome}>
Greating from {this.props.name}
</Text>
);
}
}
這個Component非常簡單:讀取this.props.name
,然後作爲Text顯示。
然後,我們使用這個Component,將ReactBasics
修改成如下
export default class ReactBasics extends Component {
render() {
return (
<View style={styles.container}>
<GreatingComponent name={"Leo"}/>
</View>
);
}
}
然後保存文件,選擇模擬器,command+R,重新加載。看到的界面如下:
通過這個例子,如何對Component初始化進行傳值已經很清楚了
<GreatingComponent name={"Leo"}/>
,初始化的時候,通過JSX的參數來傳遞值- 在
GreatingComponent
內部,通過this.props.name
來訪問這個值。
修改視圖狀態
React中,修改視圖狀態是通過
this.setState
觸發render重新調用,進而修改視圖的狀態。
我們繼續修改代碼,添加一個構造函數,對state進行初始化,然後GreatingComponent
初始化的時候,讀取this.state.name
。
在最上面的import中,添加一個TouchableOpacity
,然後在點擊事件中,調用this.setState
更新顯示的文字。
export default class ReactBasics extends Component {
constructor(props) {
super(props);
this.state = {name:"Tom"}
}
_onPressText(){
this.setState({name:"Jack"});
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={()=>this._onPressText()}>
<GreatingComponent name={this.state.name}/>
</TouchableOpacity>
</View>
);
}
}
然後,保存,reload模擬器。點擊下文字,效果如下。
setState
注意事項
- 不要直接修改state
//這樣並不會觸發重新渲染
this.state. name = 'Jack';
- setState修改可能是異步的。
React有可能會對多個this.setState進行收集,然後一起更新UI。所以,不要直接依賴於上一個狀態的結果。
所以,這樣是不對的
//此時上一個state可能還沒更新
this.setState({
counter: this.state.counter + this.props.increment,
});
如果依賴於上一個狀態,使用this.setState
第二個模式:
//這裏,把上一個state和當前參數作爲輸入
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
setState
是增量更新
比如
export default class ReactBasics extends Component {
constructor(props) {
super(props);
this.state = {firstName:"Tom", lastName:"Huang"}
}
_onPressText(){
this.setState({firstName:"Jack"});
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={()=>this._onPressText()}>
<GreatingComponent name={this.state.firstName +" " + this.state.lastName}/>
</TouchableOpacity>
</View>
);
}
}
可以看到
this.setState({firstName:"Jack"});
的效果,只是修改了firstName,lastName仍然保持不變。
Tips:上文onPress中的callBack採用了箭頭函數,除了箭頭函數,也可以使用function本身傳入
export default class ReactBasics extends Component {
constructor(props) {
super(props);
this.state = {firstName:"Tom", lastName:"Huang"}
this._onPressText = this._onPressText.bind(this);
}
_onPressText(){
this.setState({firstName:"Jack"});
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={this._onPressText}>
<GreatingComponent name={this.state.firstName +" " + this.state.lastName}/>
</TouchableOpacity>
</View>
);
注意這一行
this._onPressText = this._onPressText.bind(this);
因爲JS中,class的函數默認沒有bound。需要調用bind
來把this
傳入_onPressText
。
生命週期
關於生命週期,在上一篇文章裏已經講過了。這裏再簡單帶過一筆
使用ES6的class來創建的component生命週期如下:
第一次顯示
- componentWillMount 將要顯示
- componentDidMount 已經顯示
調用this.setState
- shouldComponentUpdate 是否要重新繪製
- componentWillUpdate 將要重新繪製
- componentDidUpdate
Component被移除
- componentWillUnmount
後續
最近一段時間,博客內容會集中在iOS進階,Swift和React Native三個方面。