ReactNative整理:《ReactNative系列》
前言
??前面兩篇文章我們已經(jīng)介紹了RN的基本知識(shí)和生命周期,這篇講講對(duì)RN中常用常見知識(shí)的理解裸诽,想看前面基礎(chǔ)文章的可以點(diǎn)下面的鏈接去看嫂用。
??我們可以將這些關(guān)鍵字兩兩分為一組,state
和props
丈冬;let
和const
嘱函;Promise
機(jī)制中的resolve
和reject
、then
和catch
埂蕊;async
和await
相互類比起來更容易理解往弓。
?ReactNative開發(fā)(一):簡(jiǎn)介及環(huán)境搭建
?ReactNative開發(fā)(二):組件生命周期詳解
一、狀態(tài)與屬性(state
蓄氧、props
)
?官方文檔的說明:
?props:組件在創(chuàng)建時(shí)可以用參數(shù)來定制函似。用于定制的參數(shù)就稱為props
。
?state:props是在父組件中指定喉童,而且一經(jīng)指定撇寞,在被指定的組件生命周期中不再改變。對(duì)于需要改變的數(shù)據(jù),我們需要用state
蔑担。
?通俗來講牌废,props
和state
是控制一個(gè)組件的兩種數(shù)據(jù)。屬性props
是組件生成時(shí)所帶的參數(shù)钟沛,組件自身內(nèi)部不可改變畔规;如果想要修改,只能通過外部修改參數(shù)達(dá)到效果恨统。狀態(tài)state
是組件自身私有的叁扫,是沒有辦法從外部傳入組件內(nèi)部的。React把組件看成是狀態(tài)機(jī)畜埋,會(huì)監(jiān)聽組件中state的變化莫绣,只要state發(fā)生了變化就會(huì)引起組件的重新render,更新DOM悠鞍。我們可以做個(gè)簡(jiǎn)單Demo幫助理解:
1对室、父組件:
import React, { Component } from 'react';
import {StyleSheet, Text, View, TouchableOpacity } from 'react-native';
// 父組件
export default class Parent extends Component {
constructor(props) {
super(props);
this.state = {
parent: '父組件初始值'
};
this.handleParentPress = this.handleParentPress.bind(this);
}
componentWillMount() {
console.log('-parent-WillMount-');
}
componentDidMount() {
console.log('-parent-DidMount-');
}
componentWillReceiveProps() {
console.log('-parent-WillReceiveProps-');
}
componentWillUpdate() {
console.log('-parent-WillUpdate-');
}
componentDidUpdate() {
console.log('-parent-DidUpdate-');
}
/** 按鈕點(diǎn)擊處理 **/
handleParentPress() {
this.setState({ parent: '父組件改變' });
}
render() {
console.log('-parent-render-');
return (
<View style={styles.parentContainer}>
<TouchableOpacity onPress={this.handleParentPress}>
<Text style={styles.contentText}>
{this.state.parent}
</Text>
</TouchableOpacity>
</View>
);
}
}
在這里把組件生命周期方法也加了進(jìn)來以便理解,通過log可以看到周期方法的運(yùn)行順序咖祭。在這里強(qiáng)調(diào)一下:如果組件render函數(shù)中存在對(duì)state
的使用掩宜,必須要在構(gòu)造函數(shù)中聲明this.state = {}
,否則運(yùn)行會(huì)報(bào)錯(cuò)么翰;如果沒有使用state
牺汤,不會(huì)影響運(yùn)行。
頁(yè)面運(yùn)行結(jié)果如下:
按鈕點(diǎn)擊后更新state:
可以看到兩次state
不同浩嫌,由開始的“父組件初始值”修改成了“父組件改變”檐迟,頁(yè)面刷新后文字也發(fā)生了改變。生命周期方法少了shouldComponentUpdate
码耐,建議最好不要隨意重寫這個(gè)方法追迟,除非很明確頁(yè)面刷新的判斷條件,否則容易導(dǎo)致state或者props改變之后頁(yè)面也不刷新骚腥;當(dāng)然敦间,這個(gè)方法是應(yīng)用性能優(yōu)化時(shí)的一個(gè)優(yōu)化點(diǎn)。如果重寫桦沉,可以返回默認(rèn)值return true
每瞒。
2、子組件:
class Child extends Component {
constructor(props) {
super(props);
this.state = {
text: props.childContent
};
this.handleChildPress = this.handleChildPress.bind(this);
}
componentWillMount() {
console.log('-child-WillMount-');
}
componentDidMount() {
console.log('-child-DidMount-');
}
componentWillReceiveProps(nextProps) {
console.log('-child-WillReceiveProps-');
console.log(this.props, '-this-');
console.log(nextProps, '--next--');
this.setState({ text: nextProps.childContent });
}
componentWillUpdate() {
console.log('-child-WillUpdate-');
}
componentDidUpdate() {
console.log('-child-DidUpdate-');
}
handleChildPress() {
console.log('-child-handleChildPress-');
this.setState({ text: '子組件變了'});
}
render() {
console.log('-child-render-');
console.log(this.props, '==child-props==');
return (
<View style={styles.childContainer}>
<TouchableOpacity onPress={this.handleChildPress}>
<Text style={styles.contentText}>
{this.state.text}
</Text>
</TouchableOpacity>
</View>
);
}
}
在父組件中添加一條state數(shù)據(jù)纯露,用來控制子組件屬性值剿骨,并在點(diǎn)擊父組件時(shí),修改這條state數(shù)據(jù):
// 父組件中構(gòu)造函數(shù)
this.state = {
parent: '父組件初始值',
childContent: '子組件初始值'
};
// 父組件點(diǎn)擊事件回調(diào)方法
handleParentPress() {
console.log('-parent-handleParentPress-');
this.setState({
parent: '父組件改變',
childContent: '子組件改變'
});
}
另外需要調(diào)整父組件的render函數(shù)埠褪,在其中加入子組件浓利,并傳入屬性值childContent
挤庇。
// 父組件render函數(shù)
render() {
console.log('-parent-render-');
console.log(this.state, '-parent--state-');
return (
<View style={styles.parentContainer}>
<TouchableOpacity onPress={this.handleParentPress}>
<Text style={styles.contentText}>
{this.state.parent}
</Text>
</TouchableOpacity>
<Child childContent={this.state.childContent}/>
</View>
);
}
注意:組件的構(gòu)造方法中存在props
,此時(shí)的props就是該組件的屬性值贷掖。所以嫡秕,我們可以通過構(gòu)造函數(shù)中的props,將子組件的屬性值childContent苹威,當(dāng)做其state的初始值this.state = { text: props.childContent }
用作頁(yè)面展示昆咽。
上面是運(yùn)行效果圖:
?注意兩個(gè)箭頭所指的日志,第一個(gè)是點(diǎn)擊父組件按鈕牙甫,第二個(gè)是點(diǎn)擊子組件按鈕掷酗;
(1)未點(diǎn)擊父組件前:可以看到調(diào)用生命周期方法時(shí),先調(diào)用父組件的componentWillMount再調(diào)用子組件的窟哺;而componentDidMount則是先調(diào)用子的泻轰,再調(diào)用父的。因?yàn)殇秩救肟谑歉附M件且轨,子組件是掛到父組件的render函數(shù)中的浮声,所以會(huì)先調(diào)用父組件的componentWillMount和render,但是componentDidMount是渲染完成時(shí)的回調(diào)旋奢,只有當(dāng)父組件中包含的所有子組件渲染完畢泳挥,父組件才算是渲染完畢,因此子組件調(diào)用componentDidMount方法在父組件之前至朗。
(2)點(diǎn)擊父組件按鈕后:更新了state值羡洁,傳入子組件的屬性值發(fā)生了改變,所以在子組件中調(diào)用了componentWillReceiveProps方法爽丹,
this.props
和nextProps
分別代表初始屬性值和修改過后的屬性值;在該方法中修改子組件的state值為修改后的屬性值辛蚊,可以看到子組件的顯示值變化粤蝎,而且沒有重復(fù)調(diào)用子組件的render函數(shù)。(3)點(diǎn)擊子組件按鈕后:更新了子組件的state值袋马,只調(diào)用了子組件自身的周期函數(shù)初澎,并沒有影響到父組件,不會(huì)引起父組件的渲染虑凛。
可以利用拆分子組件的方式減少全局渲染頻率碑宴,提高性能。
?總之桑谍,
state
和props
的深入理解和靈活使用會(huì)使頁(yè)面體驗(yàn)效果更好延柠,數(shù)據(jù)處理更便捷;同時(shí)也是性能優(yōu)化點(diǎn)锣披,盡量減少頁(yè)面狀態(tài)的刷新能降低頁(yè)面渲染資源開銷贞间,提升應(yīng)用性能贿条。
二、變量聲明(let
增热、const
)
?簡(jiǎn)述:let
和const
是ES6中新加入的命令整以,在ES5中用的是var
;它們的區(qū)別主要在于:var
定義的變量可以變量提升峻仇,沒有塊的概念公黑;let
和const
定義的變量只能在塊作用域內(nèi)訪問。let
定義的是變量摄咆,const
定義的是常量凡蚜,需要初始化賦值。const
定義基本類型時(shí)不可修改豆同,比如數(shù)字番刊、字符串;定義復(fù)合類型時(shí)可修改其中的值影锈,比如數(shù)組芹务。
Q:什么是變量提升?
A:是JS語法中的概念鸭廷,ES6之前是沒有塊作用域枣抱,只有全局作用域及函數(shù)作用域,變量提升是將變量聲明提升至所在作用域的最開始部分辆床。
下面用代碼幫助理解:
-
let
用法
function test() {
console.log(name, '-name-');
// 輸出 => undefined "-name-"
let name;
name = '姓名初始值';
console.log(name, '==name11==');
// 輸出 => 姓名初始值==name11==
name = '姓名賦值成功';
console.log(name, '==name22==');
// 輸出 => 姓名賦值成功 ==name22==
}
let
聲明變量佳晶,可以不用賦初始值;在變量的作用域內(nèi)讼载,可以進(jìn)行修改轿秧;不能在同一作用域內(nèi)重復(fù)聲明;在變量聲明前是不可用的咨堤。
-
const
用法
function test() {
console.log(name, '-name-');
// 輸出 => undefined "-name-"
const name = 'zhang';
const array = [];
// name = 'qwert';
// array = [0, 1, 2];
// 提示Attempt to assign to const or readonly variable
console.log(array, '==array11==');
// 輸出 => [] "==array11=="
array[0] = 0;
array[1] = 1;
array[2] = 3;
console.log(array, '==array22==');
// 輸出 => (3) [0, 1, 3] "==array22=="
}
const
聲明常量菇篡,必須賦初始值,并且基本類型賦值后不可改變一喘。與let
類似驱还,不能在同一作用域重復(fù)聲明,在聲明前不可用凸克。
注意:在聲明數(shù)組议蟆、map這類復(fù)合變量時(shí),是可以其修改內(nèi)容的萎战。上面例中我們可以看到咐容,定義數(shù)組array賦空值,如果用array = [1, 2, 3]
的方式是不可以修改數(shù)組的撞鹉,也不合語法疟丙;而用array[i] = 值
的方式卻能修改其內(nèi)容颖侄。這是因?yàn)榈谝环N賦值方式是相當(dāng)于將數(shù)組的地址賦給變量array后添,定義的數(shù)組地址不可修改酝静,所以不成功;而第二種賦值方式是在不修改變量地址的前提下為數(shù)組修改內(nèi)容扭仁,所以可以生效炊琉。
-
var
用法
{
var name = 'qwert';
var name = '1111';
}
// 輸出 => 1111 ==name==
console.log(name, '==name==');
(function abcd() {
var age = 15;
console.log(age, '==age==');
})();
// console.log(age, '==age00=='); 報(bào)錯(cuò)
可以看到var
聲明的變量可以跨塊訪問展蒂,但是不能跨函數(shù)訪問;而且在同一塊內(nèi)可以重復(fù)聲明苔咪。正因?yàn)榭梢钥鐗K訪問锰悼,同時(shí)可以重復(fù)聲明,所以有可能會(huì)引起不同塊區(qū)域中變量的相互影響团赏。
?對(duì)比let
箕般、const
和var
可以發(fā)現(xiàn):前兩者類似Java中的變量聲明方式,都是先聲明后使用舔清,不可重復(fù)聲明丝里;但是var
可以跨塊訪問,有可能引起變量值的覆蓋或混亂体谒,所以使用時(shí)需要特別注意杯聚。
三、Promise機(jī)制
- Promise簡(jiǎn)介
?Promise
是ES6中新增的編程方式抒痒,是異步編程的一種解決方案幌绍;它可以在異步操作中靈活的處理錯(cuò)誤;支持鏈?zhǔn)秸{(diào)用故响;從字面含義來看表示承諾傀广,承諾過一段時(shí)間會(huì)返回一個(gè)結(jié)果,同時(shí)它也是個(gè)對(duì)象彩届,從中可以獲取到異步操作的信息主儡。 - 三種狀態(tài)
Promise
有三種狀態(tài),分別是:
?1.pending
-- 等待狀態(tài)惨缆;
?2.fullfield
(或resolved
) -- 成功狀態(tài);
?3.rejected
-- 失敗狀態(tài)丰捷。
三種狀態(tài)的轉(zhuǎn)換途徑只有兩種坯墨,初始狀態(tài)是pending
:
?1. 任務(wù)完成:pending
-->resolved
(等待 --> 完成);
?2. 任務(wù)失敳⊥:pending
-->rejected
(等待 --> 失敗)捣染; - Promise的方法
Promise中方法可以用console.dir(Promise)
打印出來觀察:包含all
、race
停巷、reject
耍攘、resolve
方法榕栏,同時(shí)prototype中還有我們常見的then
、catch
蕾各、finally
方法扒磁。
Promise方法.jpg
- Promise創(chuàng)建
let promise = new Promise((resolve, reject) => {});
JS中提供了構(gòu)造函數(shù)去創(chuàng)建Promise
對(duì)象,需要傳的參數(shù)是用戶自定義的方法式曲,用來處理異步任務(wù)妨托;其中resolve
和reject
是JS提供的回調(diào)函數(shù),不需要自己定義實(shí)現(xiàn)吝羞。異步操作成功兰伤,就調(diào)用resolve
,將結(jié)果數(shù)據(jù)傳入resolve
钧排,Promise從pending
狀態(tài)變成resolve
敦腔;異步操作失敗,則調(diào)用reject
恨溜,需要將錯(cuò)誤對(duì)象當(dāng)參數(shù)傳進(jìn)去符衔,此時(shí),Promise從pending
狀態(tài)變成reject
筒捺。
-
then
方法
但凡異步操作柏腻,一般都是需要回調(diào)函數(shù)來處理接下來的業(yè)務(wù)的,而then
方法就是將原來的回調(diào)函數(shù)抽離出來系吭,在異步操作結(jié)束后五嫂,通過鏈?zhǔn)秸{(diào)用的方式執(zhí)行回調(diào);then
方法強(qiáng)大的地方在于它可以在內(nèi)部繼續(xù)寫Promise對(duì)象肯尺,并返回沃缘;也可以直接return
數(shù)據(jù)而不是Promise
對(duì)象。
?then
方法可以有兩個(gè)參數(shù)则吟,一個(gè)是成功resolve
的回調(diào)槐臀,第二個(gè)是失敗reject
的回調(diào)方法。
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('-start-');
// 取隨機(jī)數(shù) * 10
let random = Math.random() * 10;
// 向下取整
let count = Math.floor(random);
console.log(count, '-count-');
if (count < 5) { // 整數(shù)小于5判定為成功
resolve('--任務(wù)成功--');
} else { // 整數(shù)大于等于5判定為失敗
reject('==任務(wù)失敗==');
}
}, 1000);
});
promise.then(success => {
console.log(success, '--success--');
return '成功之后返回';
}, failed => {
console.log(failed, '--failed--')
}).then(data => {
console.log(data, '=data=')
});
結(jié)果輸出日志:
成功日志:
失敗日志:
-
catch
方法
catch
方法的作用主要有兩個(gè):一是用來指定reject
的回調(diào)氓仲;二是捕捉then
方法中的異常水慨,避免程序報(bào)錯(cuò)卡死。
修改上面代碼中的Promise調(diào)用方法:
promise.then(data => {
console.log(data, '=then=');
console.log(aaa);
}).catch(error => {
console.log(error, '=error=')
});
異步操作成功時(shí)敬扛,會(huì)鏈?zhǔn)綀?zhí)行then
方法晰洒,但是其中aaa
是未聲明的變量,所以會(huì)報(bào)錯(cuò):
異步操作失敗時(shí)啥箭,會(huì)調(diào)用
reject
谍珊,catch
方法中接收到拋出的錯(cuò)誤日志并打印:-
all
方法
all
方法為Promise提供了并行異步操作的能力急侥。簡(jiǎn)單來說砌滞,即可以同時(shí)執(zhí)行多個(gè)異步操作侮邀,all
方法所需要的參數(shù)是以需要執(zhí)行的異步操作為元素的數(shù)組。并且在所有異步任務(wù)執(zhí)行完畢后才執(zhí)行回調(diào)操作贝润。等到都執(zhí)行完后绊茧,會(huì)回調(diào)到then
方法中,data就是最終的返回結(jié)果题暖。
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('=執(zhí)行1=');
resolve('--任務(wù)1--');
}, 1000);
});
let promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('=執(zhí)行2=');
resolve('--任務(wù)2--');
}, 1000);
});
let promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('=執(zhí)行3=');
resolve('--任務(wù)3--');
}, 1000);
});
Promise.all([promise1, promise2, promise3]).then(data => {
console.log(data, '--執(zhí)行結(jié)果--');
}).catch(error => {
console.log(error, '--error--')
})
注意: 返回的結(jié)果也是數(shù)組按傅,結(jié)果元素的順序和傳入異步參數(shù)的順序一一對(duì)應(yīng);異步任務(wù)只要有任一個(gè)失敗都會(huì)回調(diào)catch方法胧卤。
-
race
方法
race
字面意思是賽跑唯绍,所以強(qiáng)調(diào)的是以速度最快的為準(zhǔn)執(zhí)行回調(diào)。race
的用法和all
類似枝誊,參數(shù)都是以異步操作為元素的數(shù)組况芒,不同的是:all
是所有異步操作執(zhí)行完后才會(huì)回調(diào)then
方法,以最慢的為準(zhǔn)叶撒;而race
是只要有一個(gè)執(zhí)行完就立刻回調(diào)then
绝骚,以最快的為準(zhǔn)。
將上面代碼中的all
改為race
祠够,并且調(diào)整后兩個(gè)異步操作的執(zhí)行延遲時(shí)間為1500压汪,時(shí)間相同的話表現(xiàn)不出來前后差異:
Promise.all([promise1, promise2, promise3]).then(data => {
console.log(data, '--執(zhí)行結(jié)果--');
}).catch(error => {
console.log(error, '--error--')
})
注意:第一個(gè)異步操作執(zhí)行完后回調(diào)then
后,其他未執(zhí)行完的異步操作會(huì)繼續(xù)執(zhí)行古瓤,不會(huì)中斷停止止剖。
?
Promise
常用的知識(shí)和方法這里就介紹完了,更深入的理解可以結(jié)合具體的項(xiàng)目來研究落君。
四穿香、異步與等待(async
和await
)
-
async
是異步的意思。用來標(biāo)識(shí)函數(shù)為異步函數(shù)绎速,往往函數(shù)中有耗時(shí)操作時(shí)會(huì)用到async
皮获,而異步函數(shù)在執(zhí)行時(shí)不會(huì)阻塞后面代碼運(yùn)行。 -
await
是等待的意思纹冤。必須在async
修飾的函數(shù)中使用洒宝,用來標(biāo)識(shí)耗時(shí)操作的語句;當(dāng)程序執(zhí)行到await
所在行時(shí)萌京,會(huì)阻塞并等待待德,直到異步任務(wù)得到返回結(jié)果。
代碼驗(yàn)證:
componentDidMount() {
console.log(new Date().toTimeString(), '==start==');
this.getImageData().then(data => {
console.log(data, new Date().toTimeString() + ' -data-');
});
console.log(new Date().toTimeString(), '==end==');
}
async getImageData() {
try {
let response = await fetch('http://www.pptbz.com/pptpic/UploadFiles_6909/201309/2013093019370302.jpg');
console.log(response, new Date().toTimeString() + ' --response--');
let path = response.url;
this.setState({ image: path });
return path;
} catch (error) {
console.log(error, '-error-');
}
}
輸出日志:
可以看到:從打印時(shí)間上來看枫夺,start日志和end日志基本沒有時(shí)間差,說明用
async
修飾的getImageData
方法沒有阻塞end日志的輸出绘闷;而response日志輸出時(shí)間要比end日志晚5秒左右橡庞,說明用await
修飾的fetch
語句在做耗時(shí)操作较坛,并且一直在等待,直到拿到返回值扒最,輸出日志丑勤;async
函數(shù)返回的是Promise
,因此可以在then
方法中得到返回值吧趣。
結(jié)尾
?以上介紹的技術(shù)點(diǎn)都是ReactNative中常見常用的法竞,是個(gè)人回顧整理的記錄,分享出來希望更多想學(xué)RN的同學(xué)看到强挫。僅供參考岔霸,如果看到有問題或錯(cuò)處,歡迎指出俯渤,互相交流4粝浮!