一、 前言
隨著 react 最新的一個(gè)大版本中,給我們帶來了 Hooks:React v16.8: The One With Hooks,從而將 Function component 的能力提高了一大截,成功的擁有了可以與 Class component 抗衡的能力。但話說回來,雖然 Hooks 看起來很美好,最近也有不少文章都講解了Hooks這一“黑魔法”,但技術(shù)的不斷演進(jìn),本身就是一個(gè)解決以往所存在問題的過程,因此我個(gè)人認(rèn)為著眼于現(xiàn)在,回望過去,去看一看 react component 的發(fā)展之路,去看看 Class component 以及 Function component 為什么會出現(xiàn)以及它們出現(xiàn)的意義,所要解決的問題,也對于我們?nèi)媪私?react 是很有幫助的。
從 react component 的發(fā)展歷程上來看,它主要是經(jīng)歷了一下三個(gè)階段:
- createClass Component
- Class Component
- Function Component
這個(gè)三個(gè)階段也是react的組件不斷走向輕量級的一個(gè)過程。其中 Class Component 完全替代了 createClass Component 成為了現(xiàn)在我們開發(fā) react 組件的主流,而 Function Component 也在 Hooks 推出后磨刀霍霍,準(zhǔn)備大干一場。下面就讓我們?nèi)タ纯慈叩木唧w情況吧~
注:這篇文章整體只是對React Component的發(fā)展歷程的一個(gè)概括或者說是我自己學(xué)習(xí)后的一個(gè)整理,想要詳細(xì)了解,還請看看我在文章貼的那些鏈接。
二、 createClass Component
說實(shí)話,createClass Component 我也沒用過,因?yàn)槲医佑|到 react 的時(shí)候已經(jīng)是2017年下半年了,那時(shí)候 ES6 已經(jīng)大行其道,class component 也已經(jīng)完全取代了 createClass Component。但現(xiàn)在看來 createClass Component 的語法也很簡單,并不復(fù)雜:
import React from 'react'
const MyComponent = React.createClass({
// 通過proTypes對象和getDefaultProps()方法來設(shè)置和獲取props
propTypes: {
name: React.PropTypes.string
},
getDefaultProps() {
return {
}
},
// 通過getInitialState()方法返回一個(gè)包含初始值的對象
getInitialState(){
return {
sayHello: 'Hello Srtian'
}
}
render() {
return (
<p></p>
)
}
})
export default MyComponent
react.createClass的語法并不復(fù)雜,它通過 createClass 來創(chuàng)建一個(gè)組件,并通過propTypes和getDefaultProps來獲取props,通過通過getInitialState()方法返回一個(gè)包含初始值的對象,雖然從現(xiàn)在看來還是有點(diǎn)麻煩,但總體上來看代碼也比較清晰,跟現(xiàn)在的 Class Component差別并不是太大。但 react.createClass 自從 react 15.5版本就不再為 react 官方所推介,而是想讓大家的使用 class component 來代替它。而且在 react 16版本發(fā)布后,createClass 更是被廢棄,當(dāng)我們使用它的時(shí)候,會提示報(bào)錯(cuò),也就是說,在 react 團(tuán)隊(duì)看來 createClass 已經(jīng)完全沒有存在的必要了。
其實(shí) Class Component 完全替代 React.createClass 并不是說 React.createClass 有多壞,相反它還有一些 class Component 所沒有的特性。它的廢棄是由于ES6的出現(xiàn),新增了 class 這一語法糖,讓我們在 JavaScript 的開發(fā)中可以直接使用 extends 來擴(kuò)展我們的對象,因此為了與標(biāo)準(zhǔn)的ES6接軌,原有的只在 react 中使用的 createClass 自然而然也成為了被拋棄的對象。但 class Component 在剛出現(xiàn)的時(shí)候也仍然存在的不小的爭議,因?yàn)檫@兩者還是存在一定的差別的,比如當(dāng)時(shí)在Stack Overflow便出現(xiàn)了關(guān)于這兩者的討論,感興趣的朋友可以去看看:
https://stackoverflow.com/questions/30668464/react-component-vs-react-createclass
總的來說,除了語法上存在差異外,Class Component 和 React.createClass 的區(qū)別主要是以下兩點(diǎn)(詳情可以看看上面的回答):
- React.createClass 會正確綁定 this,而 React.Component 則不行,我們需要在 constructor 里面使用 bind 或者直接使用箭頭函數(shù)來綁定 this。
- React.Component 不能使用 React mixins 特性,這一方面我們可以使用高階組件來彌補(bǔ)。
三、Class Component
Class Component創(chuàng)建的方式也很簡單,就是普通的ES6的class的語法,通過extends來創(chuàng)建一個(gè)新的對象來創(chuàng)建react組件,下面是使用class Component創(chuàng)建一個(gè)組件的例子(由于為了給后面聊一聊hooks,所以在這里我使用了antd的例子)
class Modal extends React.Component {
state = { visible: false }
showModal = () => {
this.setState({
visible: true,
});
}
handleOk = (e) => {
console.log(e);
this.setState({
visible: false,
});
}
handleCancel = (e) => {
console.log(e);
this.setState({
visible: false,
});
}
render() {
return (
<div>
<Button type="primary" onClick={this.showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<p>this is a modal</p>
</Modal>
</div>
);
}
}
上面就是antd中一個(gè)簡單的 modal 組件的例子,其內(nèi)部就是通過維護(hù) visible 的狀態(tài)來控制這個(gè) modal 是否顯示。我們可以看到,其中的一些方法都是使用箭頭函數(shù)的方式來將 this 綁定到正確的屬性。(具體為什么要這么做,不清楚的朋友可以看看下面這篇文章:)
而類似于上面的這種組件,也是近兩年來我們在日常開發(fā)中使用最多的組件開發(fā)的方式。那為什么到了現(xiàn)在,我們又開始要強(qiáng)調(diào)使用 Function Component 來進(jìn)行開發(fā)了呢?主要是由于 Class Component 所開發(fā)的組件仍然存在以下一些問題:
- this 綁定的問題:
我們前面也提到了,我們在使用原本的 React.createClass 時(shí)并不需要去考慮this綁定的問題,而現(xiàn)在我們卻要時(shí)刻注意使用bind或者箭頭函數(shù)來讓this正確綁定,同時(shí)也讓一些新上手react的同學(xué)的上手成本有所提升。雖然這不是React的鍋,但這方面的問題仍然客觀存在。 - 嵌套地獄: 這種情況則多發(fā)生于需要用到Context的場景下,在這種場景下,數(shù)據(jù)是同步的,因?yàn)樾枰ㄖ滤杏幸玫綌?shù)據(jù)的地方,因此我們就需要通過render-props 的形式定義在Context.Consumer的children中,而使用到越多的Context 就會導(dǎo)致嵌套層級越多,這很容易讓人看代碼看的一臉懵逼。比如這樣:
<FirstContext.Consumer>
{first => (
<SecondContext.Consumer>
{second => (
<ThirdContext.Consumer>
{third => (
<Component />
)}
</ThirdContext.Consumer>
)}
</SecondContext.Consumer>
)}
</FirstContext.Consumer>
- Life-cycles 的問題:生命周期函數(shù)也是我們在日常開發(fā)所經(jīng)常使用到的東西。雖然生命周期函數(shù)用起來很方便,但一旦組件的邏輯變得復(fù)雜起來,這些生命周期函數(shù)也會變得難以理解和維護(hù);同時(shí)如何讓這些生命周期函數(shù)與react渲染有效結(jié)合也是一個(gè)不小的問題,這往往可能會讓一些剛上手的人摸不著頭腦。此外使用這些生命周期函數(shù)時(shí)也可能會出現(xiàn)一些預(yù)料之外的事情發(fā)生(比如在某些生命周期函數(shù)中進(jìn)行數(shù)據(jù)請求,而導(dǎo)致組件被重復(fù)渲染多次的問題等等,這些都是有可能發(fā)生的)
詳細(xì)可以去看看知乎上的這個(gè)回答:https://www.zhihu.com/question/300049718
四、Function Component
看到這里,大家對class Component所存在的一些問題也算是有一些了解了,但為什么它還能橫行如此之久,一直占據(jù)著主流的地位呢?其本質(zhì)上就是因?yàn)闆]有競爭對手嘛,F(xiàn)unction Component 長期沒有內(nèi)部狀態(tài)管理機(jī)制,只能通過外部來管理狀態(tài),因此組件的可測試性非常的高,寫起來也簡潔明了,符合現(xiàn)在前端函數(shù)式的大潮流,是個(gè)好同志。但也正是因?yàn)闆]有狀態(tài)管理機(jī)制,所以無法和Class Component相抗衡,畢竟一旦組件內(nèi)部的邏輯變得復(fù)雜之后,內(nèi)部的狀態(tài)管理機(jī)制是必須的。
因此 React 團(tuán)隊(duì)基于 Function Component 提出 Hooks 的概念,用以解決 Function Component 的內(nèi)部狀態(tài)管理,同時(shí)也希望通過 Hooks 來解決 Class Component 所存在的問題。下面就是使用 Hooks 針對 antd 中的 modal 進(jìn)行的改寫,大家可以自行感受一下:
const Modal = () => {
const [visible , changeVisible] = useState(false)
return (
<div>
<Button type="primary" onClick={()=>changeVisible(true)}>open</Button>
<Modal
title="Basic Modal"
visible={visible}
onOk={()=>changeVisible(false)}
onCancel={()=>changeVisible(false)}
>
<p>this is a modal</p>
</Modal>
</div>
)
}
我們可以看到,基于 Function Component 與 Hooks 所編寫出來的組件代碼是相當(dāng)簡潔明了的,也直接避免了我們上面所提到的 this 指向的問題。而對于上面所提到的嵌套地獄以及 Life-cycles 的問題,Hooks也提供了 useContext 和 useEffect(這個(gè)倒還是存在一些問題) 來解決,在這里我也不詳細(xì)說了,詳情可以去看官方文檔或者是 Dan 的博客:
好了,看到這里我想大家都以為上面 Class Component 的問題都已經(jīng)得到圓滿解決了,F(xiàn)unction Component好像已經(jīng)圓滿了,我們只管放心的使用它就好了。但世界上哪有這么好的事情,F(xiàn)unction Component 仍然存在著下面幾個(gè) tip 是我們在使用前要知道的:
- Function Component 與 Class Component 表現(xiàn)不同,這塊不清楚的可以直接去看Dan的文章,他對這方面做了很明白的闡述:
https://overreacted.io/how-are-function-components-different-from-classes/
- 使用useState需要注意的是,它的執(zhí)行順序要在每次 render 時(shí)必須保持一致,不可以進(jìn)判斷和循環(huán),必須寫在最前面,關(guān)于這一點(diǎn)看視頻:
- Function Component 中,外部對與函數(shù)式組件的操作只能通過 props 來進(jìn)行控制,不能通過函數(shù)式組件內(nèi)部暴露方法來對組件進(jìn)行操作。
參考資料:
- https://ultimatecourses.com/blog/react-create-class-versus-component
- https://overreacted.io/how-are-function-components-different-from-classes/
- https://www.youtube.com/watch?v=dpw9EHDh2bM
- https://overreacted.io/a-complete-guide-to-useeffect/
- https://www.zhihu.com/question/300049718
- https://stackoverflow.com/questions/30668464/react-component-vs-react-createclass
- http://taobaofed.org/blog/2018/11/27/hooks-and-function-component/
- https://www.freecodecamp.org/news/this-is-why-we-need-to-bind-event-handlers-in-class-components-in-react-f7ea1a6f93eb/