1 React基礎(chǔ)
1.1 環(huán)境準(zhǔn)備
1.1.1 cnmp使用
1.1.1.1 cnmp安裝
????????你可以使用我們定制的 cnpm(gzip壓縮支持) 命令行工具代替默認(rèn)的 npm:
$ npm install -g cnpm
--registry=https://registry.npm.taobao.org
????????或者你直接通過添加 npm 參數(shù) alias 一個新命令:
alias cnpm="npm --registry=https://registry.npm.taobao.org\
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npm.taobao.org/dist \
--userconfig=$HOME/.cnpmrc"
# Or alias it in .bashrc or .zshrc
$ echo '\n#alias for cnpm\nalias cnpm="npm--registry=https://registry.npm.taobao.org \
?--cache=$HOME/.npm/.cache/cnpm \
?--disturl=https://npm.taobao.org/dist \
? --userconfig=$HOME/.cnpmrc"' >>
~/.zshrc && source ~/.zshrc
1.1.1.2 安裝模塊
????????從 registry.npm.taobao.org安裝所有模塊. 當(dāng)安裝的時候發(fā)現(xiàn)安裝的模塊還沒有同步過來, 淘寶 NPM 會自動在后臺進(jìn)行同步, 并且會讓你從官方NPMregistry.npmjs.org進(jìn)行安裝. 下次你再安裝這個模塊的時候, 就會直接從 淘寶 NPM 安裝了.
$ cnpm install [name]
1.2 運行機理
1.2.1 render渲染方法
????????ReactDOM.render渲染方法是React的最基本方法,用于將模板轉(zhuǎn)為 HTML 語言遥金,并插入指定的 DOM 節(jié)點柄粹。
ReactDOM.render(
????<h1>Hello, world!</h1>,?
????document.getElementById('example')
);
????????上面代碼將一個 h1 標(biāo)題,插入 example 節(jié)點(查看 demo01)沧卢,運行結(jié)果如下咸包。
1.2.2 組件(component)
????????React允許將代碼封裝成組件(component)翰意,然后像插入普通 HTML 標(biāo)簽一樣战惊,在網(wǎng)頁中插入這個組件。React.createClass 方法就用于生成一個組件類(查看 demo04)但两。
var HelloMessage = React.createClass({
? render: function() {
? ? ? return <h1>Hello {this.props.name}</h1>;
? }
});
ReactDOM.render(
????<HelloMessage name="John" />? ,
? ? document.getElementById('example')
);
????????上面代碼中鬓梅,變量 HelloMessage 就是一個組件類。模板插入<HelloMessage />? 時谨湘,會自動生成 HelloMessage 的一個實例(下文的"組件"都指組件類的實例)绽快。所有組件類都必須有自己的 render 方法,用于輸出組件紧阔。
????????注意坊罢,組件類的第一個字母必須大寫,否則會報錯擅耽,比如HelloMessage不能寫成helloMessage活孩。另外,組件類只能包含一個頂層標(biāo)簽乖仇,否則也會報錯憾儒。
????????組件的用法與原生的 HTML 標(biāo)簽完全一致,可以任意加入屬性乃沙,比如<HelloMessage name="John" >起趾,就是 HelloMessage 組件加入一個 name 屬性,值為 John警儒。組件的屬性可以在組件類的 this.props 對象上獲取训裆,比如 name 屬性就可以通過 this.props.name 讀取。上面代碼的運行結(jié)果如下蜀铲。
????????添加組件屬性边琉,有一個地方需要注意,就是 class 屬性需要寫成 className 蝙茶,for 屬性需要寫成 htmlFor 艺骂,這是因為 class 和 for 是 JavaScript 的保留字。
1.2.3 this.props.children
????????this.props 對象的屬性與組件的屬性一一對應(yīng)隆夯,但是有一個例外钳恕,就是 this.props.children 屬性。它表示組件的所有子節(jié)點(查看 demo05)蹄衷。
var NotesList = React.createClass({
? render: function() {
??? return (
????????<ol>
? ? ? ? {
??????? ????React.Children.map(this.props.children, function (child) {
? ? ? ? ? ? ? ? return <li>{child}</li>;
??????? ????})
????? }
????????</ol>
??? );
? }
});
ReactDOM.render(
????<NotesList>
? ? ????<span>hello</span>
? ? ? ? <span>world</span>
????</NotesList>,
? ? document.body
);
????????上面代碼的 NoteList 組件有兩個 span 子節(jié)點忧额,它們都可以通過 this.props.children 讀取,運行結(jié)果如下愧口。
????????這里需要注意睦番, this.props.children 的值有三種可能:如果當(dāng)前組件沒有子節(jié)點,它就是 undefined ;如果有一個子節(jié)點,數(shù)據(jù)類型是 object 托嚣;如果有多個子節(jié)點巩检,數(shù)據(jù)類型就是 array 。所以示启,處理 this.props.children 的時候要小心兢哭。
????????React 提供一個工具方法 React.Children來處理 this.props.children 。我們可以用 React.Children.map 來遍歷子節(jié)點夫嗓,而不用擔(dān)心 this.props.children 的數(shù)據(jù)類型是 undefined 還是 object迟螺。更多的 React.Children 的方法,請參考官方文檔舍咖。
1.2.4 PropTypes
????????組件的屬性可以接受任意值矩父,字符串、對象排霉、函數(shù)等等都可以窍株。有時,我們需要一種機制郑诺,驗證別人使用組件時夹姥,提供的參數(shù)是否符合要求杉武。
????????組件類的PropTypes屬性辙诞,就是用來驗證組件實例的屬性是否符合要求(查看 demo06)。
var MyTitle = React.createClass({
? propTypes: {
??? title: React.PropTypes.string.isRequired,
? },
? render: function() {
???? return
????????<h1>{this.props.title}</h1>;
?? }
});
????????上面的Mytitle組件有一個title屬性轻抱。PropTypes 告訴 React飞涂,這個 title 屬性是必須的,而且它的值必須是字符串∑硭眩現(xiàn)在较店,我們設(shè)置 title 屬性的值是一個數(shù)值。
var data = 123;
ReactDOM.render(
????<MyTitle title={data} />,
? ? document.body
);
????????這樣一來容燕,title屬性就通不過驗證了梁呈。控制臺會顯示一行錯誤信息蘸秘。
Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.
????????更多的PropTypes設(shè)置官卡,可以查看官方文檔。
????????此外醋虏,getDefaultProps 方法可以用來設(shè)置組件屬性的默認(rèn)值寻咒。
var MyTitle = React.createClass({
? getDefaultProps: function () {
??? return {
????? title: 'Hello World'
??? };
? },
? render: function() {
???? return
????????<h1>{this.props.title}</h1>;
?? }
});
ReactDOM.render(
????<MyTitle />,
? ? document.body
);
????????上面代碼會輸出"Hello World"。
1.2.5 獲取真實的DOM節(jié)點
????????組件并不是真實的 DOM 節(jié)點颈嚼,而是存在于內(nèi)存之中的一種數(shù)據(jù)結(jié)構(gòu)毛秘,叫做虛擬 DOM (virtual DOM)。只有當(dāng)它插入文檔以后,才會變成真實的 DOM 叫挟。根據(jù) React 的設(shè)計艰匙,所有的 DOM 變動,都先在虛擬 DOM 上發(fā)生抹恳,然后再將實際發(fā)生變動的部分旬薯,反映在真實 DOM上,這種算法叫做 DOM?diff适秩,它可以極大提高網(wǎng)頁的性能表現(xiàn)绊序。
????????但是,有時需要從組件獲取真實 DOM 的節(jié)點秽荞,這時就要用到 ref 屬性(查看 demo07)骤公。
var MyComponent = React.createClass({
? handleClick: function() {
??? this.refs.myTextInput.focus();
? },
? render: function() {
??? return (
????????<div>
? ? ????????<input type="text" ref="myTextInput" />
????????????<input type="button" value="Focus the text input" onClick={this.handleClick} />
????????</div>
??? );
? }
});
ReactDOM.render(
????<MyComponent />,
? ? document.getElementById('example')
);
????????上面代碼中,組件 MyComponent 的子節(jié)點有一個文本輸入框扬跋,用于獲取用戶的輸入阶捆。這時就必須獲取真實的 DOM 節(jié)點,虛擬 DOM 是拿不到用戶輸入的钦听。為了做到這一點洒试,文本輸入框必須有一個 ref 屬性,然后 this.refs.[refName] 就會返回這個真實的 DOM 節(jié)點朴上。
????????需要注意的是垒棋,由于 this.refs.[refName] 屬性獲取的是真實 DOM ,所以必須等到虛擬 DOM 插入文檔以后痪宰,才能使用這個屬性叼架,否則會報錯。上面代碼中衣撬,通過為組件指定 Click 事件的回調(diào)函數(shù)乖订,確保了只有等到真實 DOM 發(fā)生 Click 事件之后,才會讀取 this.refs.[refName] 屬性具练。
????????React 組件支持很多事件乍构,除了 Click 事件以外,還有 KeyDown 扛点、Copy哥遮、Scroll 等,完整的事件清單請查看官方文檔占键。
1.2.6 this.state
????????組件免不了要與用戶互動昔善,React 的一大創(chuàng)新,就是將組件看成是一個狀態(tài)機畔乙,一開始有一個初始狀態(tài)君仆,然后用戶互動,導(dǎo)致狀態(tài)變化,從而觸發(fā)重新渲染 UI (查看 demo08)返咱。
var LikeButton = React.createClass({
? getInitialState: function() {
??? return {liked: false};
? },
? handleClick: function(event) {
??? this.setState({liked: !this.state.liked});
? },
? render: function() {
??? var text = this.state.liked ? 'like' : 'haven\'t liked';
??? return (
????????<p onClick={this.handleClick}>
??????? ????You{text}this. Click to toggle.
????????</p>
??? );
? }
});
ReactDOM.render(
????<LikeButton />,
? ? document.getElementById('example')
);
????????上面代碼是一個 LikeButton 組件钥庇,它的 getInitialState 方法用于定義初始狀態(tài),也就是一個對象咖摹,這個對象可以通過 this.state 屬性讀取评姨。當(dāng)用戶點擊組件,導(dǎo)致狀態(tài)變化萤晴,this.setState 方法就修改狀態(tài)值吐句,每次修改以后,自動調(diào)用 this.render 方法店读,再次渲染組件嗦枢。
????????由于 this.props 和 this.state 都用于描述組件的特性,可能會產(chǎn)生混淆屯断。一個簡單的區(qū)分方法是文虏,this.props 表示那些一旦定義,就不再改變的特性殖演,而 this.state 是會隨著用戶互動而產(chǎn)生變化的特性氧秘。
1.2.7 表單數(shù)據(jù)讀取
????????用戶在表單填入的內(nèi)容,屬于用戶跟組件的互動趴久,所以不能用 this.props 讀韧柘唷(查看 demo9)。
var Input = React.createClass({
? getInitialState: function() {
??? return {value: 'Hello!'};
? },
? handleChange: function(event) {
??? this.setState({value: event.target.value});
? },
? render: function () {
??? var value = this.state.value;
??? return (
????????<div>
????????????<input type="text" value={value} onChange={this.handleChange} />
????????????<p>{value}</p>
? ? ? ? </div>
??? );
? }
});
ReactDOM.render(<Input />,document.body);
????????上面代碼中朋鞍,文本輸入框的值已添,不能用 this.props.value 讀取,而要定義一個 onChange 事件的回調(diào)函數(shù)滥酥,通過 event.target.value 讀取用戶輸入的值。textarea 元素畦幢、select元素坎吻、radio元素都屬于這種情況,更多介紹請參考官方文檔宇葱。
1.2.8 組件的生命周期
????????組件的生命周期分成三個狀態(tài):
???? Mounting:已插入真實DOM
???? Updating:正在被重新渲染
???? Unmounting:已移出真實DOM
????????React 為每個狀態(tài)都提供了兩種處理函數(shù)瘦真,will 函數(shù)在進(jìn)入狀態(tài)之前調(diào)用,did 函數(shù)在進(jìn)入狀態(tài)之后調(diào)用黍瞧,三種狀態(tài)共計五種處理函數(shù)诸尽。
???? componentWillMount()
???? componentDidMount()
???? componentWillUpdate(object nextProps, object nextState)
???? componentDidUpdate(object prevProps, object prevState)
???? componentWillUnmount()
????????此外,React 還提供兩種特殊狀態(tài)的處理函數(shù)印颤。
????componentWillReceiveProps(object nextProps):已加載組件收到新的參數(shù)時調(diào)用
???? shouldComponentUpdate(object nextProps, object nextState):組件判斷是否重新渲染時調(diào)用
????????這些方法的詳細(xì)說明您机,可以參考官方文檔。下面是一個例子(查看 demo10)。
var Hello = React.createClass({
? getInitialState: function () {
??? return {
????? opacity: 1.0
??? };
? },
? componentDidMount: function () {
??? this.timer = setInterval(function () {
????? var opacity = this.state.opacity;
????? opacity-= .05;
????? if (opacity< 0.1) {
??????? opacity= 1.0;
????? }
????? this.setState({
??????? opacity: opacity
????? });
??? }.bind(this), 100);
? },
? render: function () {
??? return (
????????<div style={{opacity: this.state.opacity}}>
??????? ????Hello{this.props.name}
????????</div>
??? );
? }
});
ReactDOM.render(
????<Hello name="world" />,
? ? document.body
);
????????上面代碼在hello組件加載以后际看,通過 componentDidMount 方法設(shè)置一個定時器咸产,每隔100毫秒,就重新設(shè)置組件的透明度仲闽,從而引發(fā)重新渲染脑溢。
????????另外,組件的style屬性的設(shè)置方式也值得注意赖欣,不能寫成
style="opacity:{this.state.opacity};"
????????而要寫成
style={{opacity: this.state.opacity}}
????????這是因為 React 組件樣式是一個對象屑彻,所以第一重大括號表示這是 JavaScript 語法,第二重大括號表示樣式對象顶吮。
1.3 組件引用
1.4 工程構(gòu)建
1.4.1 安裝Node.js酱酬、RN
(一) 安裝命令行工具(只需要執(zhí)行一次,之后就可以直接從下面的第二部開始):
? sudo npm install react-native-cli -g
????查看安裝的版本:npm -v
1.4.2 利用RN命令創(chuàng)建工程
?? react-native initHelloWorld //創(chuàng)建一個HelloWorld工程
1.4.3 運行項目
???1. 找到創(chuàng)建的HelloWorld項目,雙擊HelloWorld.xcodeproj即可在xcode中打開項目云矫。xcodeproj是xcode的項目文件膳沽。
? ?2.使用終端命令運行項目:?
????????cd 該項目文件夾??
? ? ? ? react-native run-ios
? ?3.在WebStorm中運行,點擊右下角的圖標(biāo)让禀,選擇Terminal挑社,輸入react-nativerun-ios即可運行⊙沧幔或輸入npm start在模擬器打開的情況下運行痛阻。
2 開發(fā)技巧
2.1 樣式
2.1.1 聲明樣式
????????在React Native中聲明樣式的方法如下:
var styles = StyleSheet.create({
? base: {
? ? width: 38,
? ? height: 38,
? },
? background: {
? ? backgroundColor: '#222222',
? },
? active: {
? ? borderWidth: 2,
? ? borderColor: '#00ff00',
? },
});
2.1.2 使用樣式
????????所有的核心組件接受樣式屬性。
<Text style={styles.base} />
<View style={styles.background} />
????????它們也接受一系列的樣式腮敌。
<View style={[styles.base, styles.background]} />
????????行為與 Object.assign 相同:在沖突值的情況下阱当,從最右邊元素的值將會優(yōu)先,并且falsy值如 false 糜工, un defined 和 null 將被忽略弊添。一個常見的模式是基于某些條件有條件地添加一個樣式。
<View style={[styles.base, this.state.active && styles.active]} />
2.1.3 樣式傳遞
????????為了讓一個call site定制你的子組件的樣式捌木,你可以通過樣式傳遞油坝。使用View.propTypes.style 和 Text.propTypes.style ,以確保只有樣式被傳遞了刨裆。
var List = React.createClass({
? propTypes: {
??? style: View.propTypes.style,
??? elementStyle: View.propTypes.style,
? },
? render: function() {
??? return (
????????<View style={this.props.style}>
??????????? {elements.map((element) =>
????????????????<View style={[styles.element, this.props.elementStyle]} />
? ? ? ? ? ? )}
? ? ? ? ?</View>);
? }?
});
// ... in another file ...
2.2 手勢應(yīng)答系統(tǒng)
觸摸應(yīng)答系統(tǒng)在 ResponderEventPlugin.js中實現(xiàn)了澈圈。
2.2.1 TouchableHighlight和Touchable*
????????應(yīng)答系統(tǒng)在使用時可能是復(fù)雜的。所以我們?yōu)閼?yīng)該“可以輕擊的”東西提供了一個抽象的Touchable實現(xiàn)帆啃。這 使用了應(yīng)答系統(tǒng)瞬女,并且使你以聲明的方式可以輕松地識別輕擊交互。在網(wǎng)絡(luò)中任何你會用到按鈕或鏈接的地方使用TouchableHighlight努潘。
2.2.2 應(yīng)答器生命周期
? ??????是否接受觸摸事件:通過實施正確的處理方法诽偷,視圖可以成為接觸應(yīng)答器坤学。有兩種方法來詢問視圖是否想成為應(yīng)答器:
????? View.props.onStartShouldSetResponder:(evt) => true,——這個視圖是否在觸摸開始時想成為應(yīng)答器?
????? View.props.onMoveShouldSetResponder: (evt)=> true,——當(dāng)視圖不是應(yīng)答器時,該指令被在視圖上移動的;
? ??????觸摸調(diào)用:這個視圖想“聲明”觸摸響應(yīng)嗎?如果視圖返回true并且想成為應(yīng)答器渤刃,那么下述的一種情況就會發(fā)生:
????????View.props.onResponderGrant:(evt)=> { }——視圖現(xiàn)在正在響應(yīng)觸摸事件拥峦。這個時候要高亮標(biāo)明并顯示 給用戶正在發(fā)生的事情卖子。
????? View.props.onResponderReject:(evt)= > { }——其他的東西是應(yīng)答器并且不會釋放它。 如果視圖正在響應(yīng)玄柠,那么可以調(diào)用以下處理程序:
????? View.props.onResponderMove:(evt)= > { }——用戶正移動他們的手指;
????? View.props.onResponderRelease:(evt)= > { }——在觸摸最后被引發(fā)诫舅,即“touchUp”羽利;
????? View.props.onResponderTerminationRequest:(evt)= >true——其他的東西想成為應(yīng)答器刊懈。這種視圖應(yīng)該釋放應(yīng)答嗎?返回true就是允許釋放;
????? View.props.onResponderTerminate:(evt)= > { }——應(yīng)答器已經(jīng)從視圖獲取了虚汛∝依耍可能在調(diào)用onResponderTerminationRequest之后被其他視圖獲取,也可能是被操作系統(tǒng)在沒有請求的情況下獲取了(發(fā)生在iOS的control center/notificationcenter)蛋辈;
????????evt是一個綜合的觸摸事件将谊,有以下形式:
????? nativeEvent
????? changedTouches——自從上個事件之后,所有發(fā)生改變的觸摸事件的數(shù)組
????? identifier——觸摸的ID
????? locationX——觸摸相對于元素的X位置
????? locationY——觸摸相對于元素的Y位置
????? pageX——觸摸相對于屏幕的X位置
????? pageY——觸摸相對于屏幕的Y位置
????? target——接收觸摸事件的元素的節(jié)點id
????? timestamp——觸摸的時間標(biāo)識符逞频,用于速度計算
????? touches——所有當(dāng)前在屏幕上觸摸的數(shù)組
? ??捕捉ShouldSet處理程序
??? ????在冒泡模式眠砾,即最深的節(jié)點最先被調(diào)用,的情況下,onStartShouldSetResponder和 onMoveShouldSetResponder 被調(diào)用励堡。這意味著,當(dāng)多個視圖為 *ShouldSetResponder 處理程序返回true時刨疼,最深的組件會成為應(yīng)答 器。在大多數(shù)情況下亭畜,這是可取的迎卤,因為它確保了所有控件和按鈕是可用的。
????????然而劲藐,有時父組件會想要確保它成為應(yīng)答器樟凄。這可以通過使用捕獲階段進(jìn)行處理。在應(yīng)答系統(tǒng)從最深的組件冒泡時汰现,它將進(jìn)行一個捕獲階段叔壤,引發(fā) * ShouldSetResponderCapture 。所以如果一個父視圖要防止子視圖在觸摸開始時成為應(yīng)答器企软,它應(yīng)該有一個 onStartShouldSetResponderCapture 處理程序饭望,返回true铅辞。
View.props.onStartShouldSetResponderCapture: (evt) => true,
View.props.onMoveShouldSetResponderCapture: (evt) => true,
? ??PanResponder
????????更高級的手勢解釋,看看 PanResponder苇倡。
2.3 調(diào)用Native模塊(iOS)
2.3.1 iOS日歷模塊的例子
????????本指南將使用 iOS日歷API的例子囤踩。假設(shè)我們希望能夠從JavaScript訪問iOS日歷。
????????Native模塊只是一個Objectve-C類综慎,實現(xiàn)了 RCTBridgeModule 協(xié)議勤庐。如果你想知道,RCT是ReaCT的一個 簡稱米罚。
// CalendarManager.h
#import "RCTBridgeModule.h"
#import "RCTLog.h"
@interfaceCalendarManager : NSObject
@end
????????React Native不會向JavaScript公開任何 CalendarManager 方法录择,除非有明確的要求。幸運的是有了RCT_EXPORT 辨宠,這會非常簡單:
// CalendarManager.m
@implementation CalendarManager
-(void)addEventWithName: (NSString *)name location: (NSString *)location
{
????? RCT_EXPORT();
????? RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}
@end
????????現(xiàn)在從你的JavaScript文件中货裹,你可以像這樣調(diào)用方法:
???? var CalendarManager = require('NativeModules').CalendarManager;
???? CalendarManager.addEventWithName('BirthdayParty', '4 Privet Drive, Surrey');
????????注意,導(dǎo)出的方法名稱是從Objective-C選擇器的第一部分中生成的赋兵。有時它會產(chǎn)生一個非慣用的JavaScript名稱(就像在我們的例子中的那個)搔预。你可以通過為 RCT_EXPORT 提供一個可選參數(shù)更改名字拯田,如dEvent) 。
????????方法返回的類型應(yīng)該是 void 吭产。React Native橋是異步的,所以向JavaScript傳遞結(jié)果的唯一方法是使用回調(diào) 或emitting事件(見下文)鸭轮。
3 常見問題
3.1 配置類問題
3.1.1 Can not find module 'invariant'
????????解決方案:
npm install invariant -g