自從React Hooks的出來(lái),社區(qū)討論Hooks的越來(lái)越多。愛(ài)掏網(wǎng) - it200.com這并不是說(shuō)React Hooks就優(yōu)于類(lèi)組件,但是使用Hooks來(lái)構(gòu)建組件時(shí)有一個(gè)巨大的可用性提升,特別是因?yàn)檫@些函數(shù)組件可以通過(guò)React Hooks中的鉤子函數(shù)來(lái)訪問(wèn)狀態(tài)和生命周期。愛(ài)掏網(wǎng) - it200.com
今天我們就來(lái)一起聊聊如何將React的類(lèi)組件轉(zhuǎn)換為函數(shù)組件,用React Hooks中的鉤子函數(shù)替換類(lèi)組件中的setState
和生命周期方法,比如componentWillMount
、componentWillReceiveProps
等。愛(ài)掏網(wǎng) - it200.com
因此,讓我們首先使用狀態(tài)和生命周期方法構(gòu)建一個(gè)基于類(lèi)的React組件。愛(ài)掏網(wǎng) - it200.com也是大家最為熟悉的ToDoList
組件。愛(ài)掏網(wǎng) - it200.com該組件具備:
- 有一個(gè)文本輸入框(
),用戶可以在輸入框中輸入想要的內(nèi)容
- 有一個(gè)“添加列表項(xiàng)”按鈕(
button
),點(diǎn)擊該按鈕之后可以將文本輸入框的內(nèi)容添加到列表中(ToDoList
中) - 顯示每個(gè)待辦事項(xiàng)的列表清單
- 每個(gè)單獨(dú)的列表項(xiàng)目都有一個(gè)相關(guān)聯(lián)的復(fù)選框(
),可以用來(lái)將列表項(xiàng)標(biāo)記為已完成
- 列表項(xiàng)會(huì)存儲(chǔ)到瀏覽器的緩存中(本地存儲(chǔ)),并在應(yīng)用程序啟動(dòng)時(shí)從本地存儲(chǔ)中再次加載
我們的組件將使用state
、componentDidMount
、componentDidUpdate
和getDerivedStateFromProps
生命周期方法。愛(ài)掏網(wǎng) - it200.com其中一些生命周期方法(比如getDerivedStateFromProps
)將以一種非人為方式使用,以便能夠演示有哪些Hooks的鉤子函數(shù)可以替換這些生命周期的方法。愛(ài)掏網(wǎng) - it200.com
在開(kāi)始之前,先來(lái)學(xué)習(xí)關(guān)于類(lèi)和函數(shù)相關(guān)的知識(shí)點(diǎn)。愛(ài)掏網(wǎng) - it200.com
作為Web開(kāi)發(fā)者,經(jīng)常和函數(shù)和類(lèi)打交道。愛(ài)掏網(wǎng) - it200.com但要真正的理解和掌握他們也不是件易事,特別是對(duì)于初學(xué)JavaScript的同學(xué)更是如此。愛(ài)掏網(wǎng) - it200.com至少給我自己的感覺(jué)是如此。愛(ài)掏網(wǎng) - it200.com
在這里我們不會(huì)深入的去聊函數(shù)和類(lèi),因?yàn)橐嬲牧耐杆麄儯伎梢匀?xiě)本書(shū)了。愛(ài)掏網(wǎng) - it200.com由于我們今天要聊React的類(lèi)組件和函數(shù)組件,那么在開(kāi)始之前很有必要的先了解一頂點(diǎn)有關(guān)于JavaScript的函數(shù)和類(lèi)。愛(ài)掏網(wǎng) - it200.com先來(lái)看函數(shù)吧。愛(ài)掏網(wǎng) - it200.com
函數(shù)在JavaScript中被認(rèn)為是第一類(lèi)公民,在JavaScript中明確的創(chuàng)建函數(shù)的概念非常重要。愛(ài)掏網(wǎng) - it200.com
JavaScript語(yǔ)言似乎和其他編程語(yǔ)言不同,我們可以在JavaScript中以不同的方式來(lái)創(chuàng)建一個(gè)函數(shù),常見(jiàn)的方式主要有:
用幾個(gè)簡(jiǎn)單的示例代碼來(lái)演示他們之間的不同:
// Function Declaration
function Greeting(user) {
console.log(`Hello, ${user}`)
}
Greeting('@w3cplus') // ? Hello, @w3cplus
// Function Expression
const Greeting = function(user) { // 作為對(duì)象分配給變量
console.log(`Hello, ${user}`)
}
const Methods = {
numbers: [1, 2, 8],
// Function Expression
sum: function() { // 在對(duì)象上創(chuàng)建一個(gè)方法
return this.numbers.reduce(function(acc, num){ // Function Expression (使用該函數(shù)作為回調(diào)函數(shù))
return acc + num
})
}
}
// Shorthand Method Definition
const Collection = { // 用于Object Literals和ES6 Class聲明中
items: [],
// 使用函數(shù)名來(lái)定義
// 使用一對(duì)圓括號(hào)中的參數(shù)列表和一對(duì)花括號(hào)來(lái)分隔主體語(yǔ)句
add(...items) {
this.items.push(...items)
},
get(index) {
return this.items[index]
}
}
// Arrow Function
let empty = () =>{}
let simple = a => a > 15 ? 15 : a
let max = (a, b) => a > b ? a : b
let numbers = [1, 2, 3, 4]
let sum = numbers.reduce((a, b) => a + b)
let even = numbers.filter(v => v % 2 == 0)
let double = numbers.map(v => v * 2)
primise.then( a => {
// ...
}).then(b => {
// ...
})
// Generator Function
// JavaScript中的生成器函數(shù)返回這個(gè)生成器的迭代器對(duì)象
function* indexGenerator() {
var index = 0
while(true) {
yield index++
}
}
const indexGenerator = function* () {
var index = 0
while(true) {
yield index++
}
}
const obj = {
*indexGenerator() {
var index = 0
while(true) {
yield index++
}
}
}
// Function Constructor
const sum = new Function('a', 'b', 'return a + b')
sum(1, 2) // ? 3
類(lèi)是ES6中開(kāi)始引入的,實(shí)質(zhì)上是JavaScript現(xiàn)有的基于原型的繼承的語(yǔ)法糖。愛(ài)掏網(wǎng) - it200.com實(shí)際上,類(lèi)是特殊的函數(shù),就像你能夠定義的函數(shù)表達(dá)式和函數(shù)聲明一樣,類(lèi)語(yǔ)法主要有兩個(gè)組成部分:類(lèi)表達(dá)式和類(lèi)聲明。愛(ài)掏網(wǎng) - it200.com
// 類(lèi)聲明
class Rectangle {
constructor(height, width) {
this.height = height
this.width = width
}
}
// 類(lèi)表達(dá)式
// 匿名類(lèi)
let Rectangle = class {
constructor(height, width) {
this.height = height
this.width = width
}
}
// 命名類(lèi)
let Rectangle = class Rectangle {
constructor(height, width) {
this.height = height
this.width = width
}
}
而且還可以使用extends
關(guān)鍵字在類(lèi)聲明或類(lèi)表達(dá)式中用于創(chuàng)建一個(gè)類(lèi)作為另一個(gè)類(lèi)的子類(lèi):
class Animal {
constructor(name) {
this.name = name
}
sayHi() {
console.log(this.name)
}
}
class Dog extends Animal {
sayHi() {
console.log(`${this.name} barks.`)
}
}
let dog = new Dog('Mitzie')
dog.sayHi() // ? Mitzie barks
如果子類(lèi)中存在構(gòu)造函數(shù),則需要在使用this
之前首先調(diào)用super()
。愛(ài)掏網(wǎng) - it200.com也可以擴(kuò)展傳統(tǒng)折基于函數(shù)的“類(lèi)”:
function Animal(name) {
this.name = name
}
Animal.prototype.sayHi = function() {
console.log(this.name)
}
class Dog extends Animal {
sayHi() {
super.sayHi()
console.log(`${this.name} barks.`)
}
}
let dog = new Dog('Mitzie')
dog.sayHi()
如果你想更深入的了解有關(guān)于JavaScript中的函數(shù)和類(lèi)相關(guān)的知識(shí)的話,可以花點(diǎn)時(shí)間閱讀下面相關(guān)文章:
- 6 Ways to Declare JavaScript Functions
- Understanding JavaScript Functions
- How To Define Functions in JavaScript
- Curry and Function Composition
- Understanding JavaScript Callbacks and best practices
- Understanding Classes in JavaScript
- Understanding Prototypes and Inheritance in JavaScript
- A Deep Dive into Classes
- A Guide To Prototype-Based Class Inheritance In JavaScript
- Understanding Public and Private Fields in JavaScript Class
- 3 ways to define a JavaScript class
- Object-oriented JavaScript: A Deep Dive into ES6 Classes
- Demystifying Class in JavaScript
- Javascript Classes — Under The Hood
- JavaScript engine fundamentals: Shapes and Inline Caches
- Understanding "Prototypes" in JavaScript
- Advanced TypeScript Concepts: Classes and Types
- A Beginner's Guide to JavaScript's Prototype
我們回到React的世界當(dāng)中來(lái)。愛(ài)掏網(wǎng) - it200.com在React中我們可以以函數(shù)形式定義一個(gè)組件,比如像下面這樣:
function SayHi() {
return Hello, React
}
也可以將SayHi
這個(gè)組件以類(lèi)的形式來(lái)定義:
class SayHi extends React.Component {
render() {
return Hello, React
}
}
在當(dāng)你要使用一個(gè)組件時(shí),比如要使用SayHi
這個(gè)組件,并不會(huì)過(guò)多的關(guān)注它是以什么方式來(lái)定義(聲明)的組件,只會(huì)關(guān)心如何使用:
雖然使用者不會(huì)太過(guò)關(guān)注它是怎么創(chuàng)建的(以哪種方式創(chuàng)建的),但React自身對(duì)于怎么創(chuàng)建組件是較為關(guān)注也會(huì)在意其差別。愛(ài)掏網(wǎng) - it200.com
如果SayHi
是一個(gè)函數(shù),React需要調(diào)用它:
// 你的代碼
function SayHi() {
return Hello, React
}
// React內(nèi)部
const result = SayHi(props) // ? Hello, React
如果SayHi
是一個(gè)類(lèi),React需要先用new
操作符將其實(shí)例化,然后調(diào)用剛才生成實(shí)例的render
方法:
// 你的代碼
class SayHi extends React.Component {
render() {
return Hello, React
}
}
// React內(nèi)部
const instance = new SayHi(props) // ? SayHi {}
const result = instance.render() // ? Hello, React
無(wú)論哪種情況,React的最終目標(biāo)是去獲取渲染后的DOM節(jié)點(diǎn),比如SayHi
組件,獲取渲染后的DOM節(jié)點(diǎn)是:
Hello, React
具體需要取決于SayHi
組件是怎么定義的。愛(ài)掏網(wǎng) - it200.com
從上面的代碼中你可能已經(jīng)發(fā)現(xiàn)了,在調(diào)用類(lèi)時(shí),使用了new
關(guān)鍵字來(lái)調(diào)用:
// 如果SayHi是一個(gè)函數(shù)
const result = SayHi(props); // ? Hello, React
// 如果SayHi是一個(gè)類(lèi)
const instance = new SayHi(props) // ? SayHi {}
const result = instance.render() // ? Hello, React
那么JavaScript中的new
起什么作用呢?在ES6之前,JavaScript是沒(méi)有類(lèi)(class
)這樣的概念。愛(ài)掏網(wǎng) - it200.com在這種情況之前如果要使用類(lèi)這樣的特性都是使用普通函數(shù)來(lái)模擬。愛(ài)掏網(wǎng) - it200.com即,在函數(shù)調(diào)用前加上new
關(guān)鍵字,就可以把任何函數(shù)當(dāng)做一個(gè)類(lèi)的構(gòu)造函數(shù)來(lái)用:
function Fruit(name) {
this.name = name
}
const apple = new Fruit('apple') // ? Fruit?{name: "apple"}
const banana = Fruit('banana') // ? undefined
JavaScript中的new
關(guān)鍵字會(huì)進(jìn)行如下的操作:
- 創(chuàng)建一個(gè)空的對(duì)象,即
{}
- 鏈接該對(duì)象(即設(shè)置該對(duì)象的構(gòu)造函數(shù))到另一個(gè)對(duì)象
- 將創(chuàng)建的對(duì)象作為
this
的上下文 - 如果該函數(shù)沒(méi)有返回對(duì)象,則返回
this
正如上面的示例來(lái)說(shuō):
- 調(diào)用
Fruit('apple')
時(shí)前面添加了new
關(guān)鍵字,這個(gè)時(shí)候JavaScript會(huì)知道Fruit
只是一個(gè)函數(shù),同時(shí)也會(huì)假裝它是一個(gè)構(gòu)造函數(shù)。愛(ài)掏網(wǎng) - it200.com會(huì)創(chuàng)建一個(gè)空對(duì)象({}
)并把Fruit
中的this
指向那個(gè)對(duì)象,以便我們可以通過(guò)類(lèi)似this.name
的形式去設(shè)置一些東西,然后把這個(gè)對(duì)象返回 - 調(diào)用
Fruit('banana')
時(shí)前面沒(méi)有添加new
關(guān)鍵字,其中的this
會(huì)指向某個(gè)全局且無(wú)用的東西,比如window
或undefined
,因此代碼會(huì)崩潰或者做一些像設(shè)置window.name
之類(lèi)的傻事
也就是說(shuō):
// 和Fruit中的this是等效的對(duì)象
const apple = new Fruit('apple') // ? Fruit?{name: "apple"}
new
關(guān)鍵字同時(shí)也把放在Fruit.prototype
上的東西放到了apple
對(duì)象上:
function Fruit(name) {
this.name = name
}
Fruit.prototype.SayHi = function () {
console.log(`Hi,我想吃${this.name}`)
}
const apple = new Fruit('蘋(píng)果')
apple.SayHi() // ? Hi,我想吃蘋(píng)果
這就是在JavaScript中如何通過(guò)new
關(guān)鍵字來(lái)模擬類(lèi)的方式。愛(ài)掏網(wǎng) - it200.com有關(guān)于new
更多的介紹可以閱讀:
- JavaScript’s new