思考一個(gè)問題:
在DOM結(jié)構(gòu)中的某一個(gè)div元素藻治,是一個(gè)對(duì)象嗎载矿?
如果第一時(shí)間無法確定的同學(xué)可以在瀏覽器的Console中創(chuàng)建并查看一個(gè)div元素限书,如下圖虫蝶。
很顯然,所有的DOM元素其實(shí)都是一個(gè)對(duì)象倦西,而每當(dāng)我們?cè)趆tml中寫入一個(gè)div元素時(shí)能真,其實(shí)就是相當(dāng)于new了一個(gè)div的實(shí)例。
<div></div>
// like
new div()
我想很多人其實(shí)沒有意識(shí)到這一點(diǎn)扰柠,在React中粉铐,也運(yùn)用了類似的理念。當(dāng)我們學(xué)習(xí)React時(shí)卤档,理解到這個(gè)知識(shí)點(diǎn)非常重要蝙泼,因?yàn)樵趆tml中,我們不需要?jiǎng)?chuàng)建DOM元素裆装,而在React中則不同踱承,我們通常情況下會(huì)創(chuàng)建自己的組件,然后在jsx中實(shí)例化哨免,這簡直像極了DOM元素的使用。
那么理解了DOM元素的這個(gè)點(diǎn)琢唾,并且知道其實(shí)React元素其實(shí)是非常類似的,那么現(xiàn)在我們思考一下我們會(huì)如何創(chuàng)建React組件呢盾饮?很顯然采桃,React組件實(shí)際上就是一個(gè)對(duì)象懒熙,在ES6的語法中,我們使用class
語法來創(chuàng)建對(duì)象普办。因此首先大概會(huì)如下寫:
class ReactDemo {
}
每一個(gè)React組件都內(nèi)含了許多共同的邏輯工扎,例如生命周期,狀態(tài)等衔蹲,因此必然會(huì)有基類來實(shí)現(xiàn)這些共通的邏輯肢娘,所以我們會(huì)繼承他 React.Component
。
class ReactDemo extends React.Component {
}
我們需要進(jìn)一步學(xué)習(xí)的舆驶,則就是這些組件共通的特性橱健,他們分別是:
- 生命周期
- state
- props
- ref
學(xué)完了這些知識(shí)點(diǎn),你就可以大膽的宣告React你已經(jīng)學(xué)會(huì)啦 ~
生命周期函數(shù) render
生命周期中沙廉,最常見的就是render拘荡。顧名思義,它就是用來渲染模板語言jsx的撬陵。狀態(tài)<state>與屬性<props>每次改動(dòng)珊皿,它都會(huì)執(zhí)行一次,因此它是執(zhí)行次數(shù)最多的生命周期方法巨税,同時(shí)通過優(yōu)化代碼邏輯亮隙,減少它的執(zhí)行次數(shù),也是優(yōu)化React程序性能的最常用手段垢夹。因?yàn)樵跓o意識(shí)中溢吻,你的邏輯可能會(huì)讓render無意義的執(zhí)行更多次。
因此創(chuàng)建組件的下一步果元,就是渲染模板語言
class ReactDemo extends React.Component {
render() {
return (
<div>hello world!</div>
)
}
}
就這樣促王,我們已經(jīng)創(chuàng)建了一個(gè)最簡單的組件。我們已經(jīng)可以將組件ReactDemo
運(yùn)用起來了而晒。
import { render } from 'react-dom';
import ReactDemo from './ReactDemo';
render(<ReactDemo />, document.querySelector('#root'));
生命周期函數(shù) componentWillMount
當(dāng)?shù)谝淮蝦ender執(zhí)行完蝇狼,則表示組件已經(jīng)完全創(chuàng)立。
因此如果我們想要在組件創(chuàng)建完成之前對(duì)組件做一些事情倡怎,那么我們可以使用componentWillMount
迅耘,該組件的應(yīng)用場景相對(duì)比較少,我通常用它來初始化一些無副作用的數(shù)據(jù)监署。
無副作用:不會(huì)引發(fā)組件render函數(shù)的執(zhí)行
class ReactDemo extends React.Component {
currentKey = ''
componentWillMount() {
this.currentKey = this.props.key || 'tony'
}
render() {
return (
<div>hello {this.currentKey}!</div>
)
}
}
生命周期函數(shù) componentDidMount
當(dāng)組件第一次render執(zhí)行完颤专,會(huì)通過componentDidMount
告訴我們。意味著此時(shí)組件處于一個(gè)相對(duì)穩(wěn)定的狀態(tài)钠乏,我們可以處理更多的邏輯栖秕。例如獲取網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)等。
class ReactDemo extends React.Component {
componentDidMount() {
axios.get('xxxxx').then(res => {
// todo
})
}
render() {
return (
<div>hello wrold!</div>
)
}
}
生命周期函數(shù) componentWillUnmount
當(dāng)我們切換頁面或者需要隱藏掉一些組件時(shí)晓避,組件會(huì)被銷毀簇捍,在銷毀之前只壳,componentWillUnmount
會(huì)告訴我們。在這個(gè)鉤子函數(shù)中暑塑,我們常常會(huì)手動(dòng)處理一些掃尾工作吼句,例如取消某些事件監(jiān)聽,清除定時(shí)器事格,重置存儲(chǔ)在閉包中的引用等惕艳。
class ReactDemo extends React.Component {
componentDidMount() {
document.addEventListener(...)
}
componentWillUnmount() {
document.removeEventListener(...)
}
render() {
return (
<div>hello wrold!</div>
)
}
}
需要注意的是,willMount, didMount, WillUnmount三個(gè)生命周期函數(shù)在組件的整個(gè)生命周期中僅執(zhí)行一次分蓖。
理解了這三個(gè)函數(shù)的使用尔艇,基本已經(jīng)可以應(yīng)對(duì)大多數(shù)場景。因此生命周期函數(shù)暫時(shí)先介紹到這里么鹤,后續(xù)再介紹其他的生命周期函數(shù)终娃。
我們想要讓一個(gè)組件呈現(xiàn)出來的樣子與邏輯是可控的,那么就必然會(huì)引入狀態(tài)管理的機(jī)制蒸甜。一個(gè)組件通常受控于內(nèi)部狀態(tài)state與外部狀態(tài)props
每一個(gè)狀態(tài)的改動(dòng)棠耕,都會(huì)引起render函數(shù)執(zhí)行,這一點(diǎn)非常重要柠新。
內(nèi)部狀態(tài)state
每個(gè)組件實(shí)例的內(nèi)部狀態(tài)僅僅只需要管理當(dāng)前實(shí)例窍荧,因此state必然是掛載在構(gòu)造函數(shù)中,因此我們通常使用如下的方式初始化state
不懂這句的朋友就必須回到<前端基礎(chǔ)進(jìn)階>中去補(bǔ)充基礎(chǔ)知識(shí)了恨憎,這個(gè)點(diǎn)非常重要
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
bar: true
}
}
render() {
return (
<div>{this.state.bar ? 'true' : 'false'}</div>
)
}
}
正因?yàn)閟tate掛載中構(gòu)造函數(shù)中蕊退,知道babel編譯結(jié)果的同學(xué)應(yīng)該會(huì)明白,state的初始化還可以簡寫成如下方式:
class Demo extends React.Component {
state = {
bar: true
}
render() {
return (
<div>{this.state.bar ? 'true' : 'false'}</div>
)
}
}
如上所示憔恳,在使用時(shí)瓤荔,我們通過this.state
來訪問state的值。
除此之外钥组,我們還可以通過this.setState
的方式來改變state的值输硝。
class Demo extends React.Component {
state = {
data: []
}
componentDidMount() {
axios.get('/group/users').then(res => {
this.setState({
data: res.data
})
})
}
render() {
const { data } = this.state;
return (
<div>
{data.map((item, key) => (
<div key={key}>{item.name}</div>
))}
</div>
)
}
}
在這個(gè)例子中,render函數(shù)會(huì)執(zhí)行兩次程梦,第一次為組件初始化時(shí)点把,data沒有數(shù)據(jù)。第二次是接口請(qǐng)求成功后屿附,我們手動(dòng)修改了state的值郎逃,引發(fā)一次render的修改。
在這里我們可以總結(jié)幾個(gè)非常有意思的點(diǎn):
- 當(dāng)我們想要改變頁面時(shí)拿撩,不再直接修改DOM元素衣厘,而僅僅只是修改了state中的數(shù)據(jù)。這個(gè)思維的轉(zhuǎn)變也是React的核心所在压恒,它直接改變了我們前端的開發(fā)思維與開發(fā)模式
- render會(huì)因?yàn)闋顟B(tài)的變化而多次執(zhí)行影暴,而狀態(tài)的變化不可預(yù)測,因此計(jì)算量大的邏輯通常不會(huì)放在render中來執(zhí)行探赫,否則會(huì)帶來更高的性能消耗
- 當(dāng)組件狀態(tài)首次穩(wěn)定了之后我們才會(huì)嘗試去修改它型宙,因此通常我們會(huì)將數(shù)據(jù)請(qǐng)求放在
componentDidMount
中執(zhí)行 - setState是一個(gè)異步操作。明白事件循環(huán)的同學(xué)知道了這個(gè)點(diǎn)之后坑定能夠瞬間明白它可能會(huì)帶來的麻煩與小坑伦吠。所以妆兑,如果還不知道事件循環(huán)的同學(xué)得回過頭去補(bǔ)補(bǔ)知識(shí)了
大家還可以深入思考一下,為什么setState需要異步的方式毛仪,如果就弄成同步的方式會(huì)引發(fā)什么麻煩搁嗓?<面試必備>
外部狀態(tài)props
<div className="foo" index="1" dataset="xxx"></div>
熟練使用html的同學(xué)對(duì)上面例子中的class, index, dataset
肯定不會(huì)陌生。當(dāng)我們用這種方式嘗試從外部控制組件的呈現(xiàn)時(shí)箱靴,就需要用到props腺逛。在div組件的內(nèi)部,我們可以使用如下的方式訪問外部傳進(jìn)來的參數(shù)衡怀。
this.props.className
this.props.index
this.props.dataset
我們可以使用defaultProps
來設(shè)置props的默認(rèn)值
例如我們想要?jiǎng)?chuàng)建一個(gè)方塊棍矛,在創(chuàng)建時(shí)我們可以隨意定制它的顏色,長寬抛杨。那么我們可以簡單如下實(shí)現(xiàn)够委。
class Rect extends React.Component {
static defaultProps = {
color: 'red',
width: '10px',
height: '10px'
}
render() {
const { color, width, height } = this.props;
const _style = {
color, width, height
}
return (
<div style={_style} />
)
}
}
<Rect />
<Rect color="orange" width="20px" height="40px" />
我們不能直接在內(nèi)部修改props的狀態(tài)
傳入到組件中的props發(fā)生變化時(shí),render函數(shù)仍然會(huì)執(zhí)行一次
生命周期函數(shù) componentWillReceiveProps
每次接收到新的props之前<組件初始化除外>怖现,componentWillReceiveProps
都會(huì)執(zhí)行一次茁帽,因此我們可以利用它來判斷某些關(guān)鍵的props是否發(fā)生了變化。
在該函數(shù)中屈嗤,舊的props使用this.props
訪問潘拨,而新的props會(huì)通過參數(shù)傳入
componentWillReceiveProps(nextProps) {
if (this.props.bar === nextProps.bar) {
return;
}
}
需要警惕的是,許多人常常在想不到其他辦法的情況下會(huì)在該函數(shù)中根據(jù)props來修改內(nèi)部狀態(tài)state恢共。這是一個(gè)非常危險(xiǎn)的行為战秋。特別是當(dāng)前組件擁有大量子元素時(shí),這種行為會(huì)給你的代碼性能帶來嚴(yán)峻的考驗(yàn)讨韭,甚至可能是異常災(zāi)難脂信。
// 例如隨便摘抄一個(gè)例子
componentWillReceiveProps: function(nextProps) {
this.setState({
likesIncreasing: nextProps.likeCount > this.props.likeCount
});
}
我通常只在沒有什么子元素的組件中使用這個(gè)生命周期函數(shù),其他場景會(huì)想辦法避開這個(gè)雷區(qū)透硝。
另外update相關(guān)的幾個(gè)生命周期如果使用不善也會(huì)存在性能問題狰闪,因此也不建議大家在實(shí)踐中使用,非要使用時(shí)請(qǐng)千萬要謹(jǐn)慎