自從React Hooks的出來,社區(qū)討論Hooks的越來越多。愛掏網(wǎng) - it200.com這并不是說React Hooks就優(yōu)于類組件,但是使用Hooks來構(gòu)建組件時有一個巨大的可用性提升,特別是因為這些函數(shù)組件可以通過React Hooks中的鉤子函數(shù)來訪問狀態(tài)和生命周期。愛掏網(wǎng) - it200.com
今天我們就來一起聊聊如何將React的類組件轉(zhuǎn)換為函數(shù)組件,用React Hooks中的鉤子函數(shù)替換類組件中的setState
和生命周期方法,比如componentWillMount
、componentWillReceiveProps
等。愛掏網(wǎng) - it200.com
因此,讓我們首先使用狀態(tài)和生命周期方法構(gòu)建一個基于類的React組件。愛掏網(wǎng) - it200.com也是大家最為熟悉的ToDoList
組件。愛掏網(wǎng) - it200.com該組件具備:
- 有一個文本輸入框(
),用戶可以在輸入框中輸入想要的內(nèi)容
- 有一個“添加列表項”按鈕(
button
),點擊該按鈕之后可以將文本輸入框的內(nèi)容添加到列表中(ToDoList
中) - 顯示每個待辦事項的列表清單
- 每個單獨的列表項目都有一個相關(guān)聯(lián)的復選框(
),可以用來將列表項標記為已完成
- 列表項會存儲到瀏覽器的緩存中(本地存儲),并在應(yīng)用程序啟動時從本地存儲中再次加載
我們的組件將使用state
、componentDidMount
、componentDidUpdate
和getDerivedStateFromProps
生命周期方法。愛掏網(wǎng) - it200.com其中一些生命周期方法(比如getDerivedStateFromProps
)將以一種非人為方式使用,以便能夠演示有哪些Hooks的鉤子函數(shù)可以替換這些生命周期的方法。愛掏網(wǎng) - it200.com
在開始之前,先來學習關(guān)于類和函數(shù)相關(guān)的知識點。愛掏網(wǎng) - it200.com
作為Web開發(fā)者,經(jīng)常和函數(shù)和類打交道。愛掏網(wǎng) - it200.com但要真正的理解和掌握他們也不是件易事,特別是對于初學JavaScript的同學更是如此。愛掏網(wǎng) - it200.com至少給我自己的感覺是如此。愛掏網(wǎng) - it200.com
在這里我們不會深入的去聊函數(shù)和類,因為要真正的聊透他們,都可以去寫本書了。愛掏網(wǎng) - it200.com由于我們今天要聊React的類組件和函數(shù)組件,那么在開始之前很有必要的先了解一頂點有關(guān)于JavaScript的函數(shù)和類。愛掏網(wǎng) - it200.com先來看函數(shù)吧。愛掏網(wǎng) - it200.com
函數(shù)在JavaScript中被認為是第一類公民,在JavaScript中明確的創(chuàng)建函數(shù)的概念非常重要。愛掏網(wǎng) - it200.com
JavaScript語言似乎和其他編程語言不同,我們可以在JavaScript中以不同的方式來創(chuàng)建一個函數(shù),常見的方式主要有:
用幾個簡單的示例代碼來演示他們之間的不同:
// Function Declaration
function Greeting(user) {
console.log(`Hello, ${user}`)
}
Greeting('@w3cplus') // ? Hello, @w3cplus
// Function Expression
const Greeting = function(user) { // 作為對象分配給變量
console.log(`Hello, ${user}`)
}
const Methods = {
numbers: [1, 2, 8],
// Function Expression
sum: function() { // 在對象上創(chuàng)建一個方法
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ù)名來定義
// 使用一對圓括號中的參數(shù)列表和一對花括號來分隔主體語句
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ù)返回這個生成器的迭代器對象
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
類是ES6中開始引入的,實質(zhì)上是JavaScript現(xiàn)有的基于原型的繼承的語法糖。愛掏網(wǎng) - it200.com實際上,類是特殊的函數(shù),就像你能夠定義的函數(shù)表達式和函數(shù)聲明一樣,類語法主要有兩個組成部分:類表達式和類聲明。愛掏網(wǎng) - it200.com
// 類聲明
class Rectangle {
constructor(height, width) {
this.height = height
this.width = width
}
}
// 類表達式
// 匿名類
let Rectangle = class {
constructor(height, width) {
this.height = height
this.width = width
}
}
// 命名類
let Rectangle = class Rectangle {
constructor(height, width) {
this.height = height
this.width = width
}
}
而且還可以使用extends
關(guān)鍵字在類聲明或類表達式中用于創(chuàng)建一個類作為另一個類的子類:
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
如果子類中存在構(gòu)造函數(shù),則需要在使用this
之前首先調(diào)用super()
。愛掏網(wǎng) - it200.com也可以擴展傳統(tǒng)折基于函數(shù)的“類”:
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ù)和類相關(guān)的知識的話,可以花點時間閱讀下面相關(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的世界當中來。愛掏網(wǎng) - it200.com在React中我們可以以函數(shù)形式定義一個組件,比如像下面這樣:
function SayHi() {
return Hello, React
}
也可以將SayHi
這個組件以類的形式來定義:
class SayHi extends React.Component {
render() {
return Hello, React
}
}
在當你要使用一個組件時,比如要使用SayHi
這個組件,并不會過多的關(guān)注它是以什么方式來定義(聲明)的組件,只會關(guān)心如何使用:
雖然使用者不會太過關(guān)注它是怎么創(chuàng)建的(以哪種方式創(chuàng)建的),但React自身對于怎么創(chuàng)建組件是較為關(guān)注也會在意其差別。愛掏網(wǎng) - it200.com
如果SayHi
是一個函數(shù),React需要調(diào)用它:
// 你的代碼
function SayHi() {
return Hello, React
}
// React內(nèi)部
const result = SayHi(props) // ? Hello, React
如果SayHi
是一個類,React需要先用new
操作符將其實例化,然后調(diào)用剛才生成實例的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
無論哪種情況,React的最終目標是去獲取渲染后的DOM節(jié)點,比如SayHi
組件,獲取渲染后的DOM節(jié)點是:
Hello, React
具體需要取決于SayHi
組件是怎么定義的。愛掏網(wǎng) - it200.com
從上面的代碼中你可能已經(jīng)發(fā)現(xiàn)了,在調(diào)用類時,使用了new
關(guān)鍵字來調(diào)用:
// 如果SayHi是一個函數(shù)
const result = SayHi(props); // ? Hello, React
// 如果SayHi是一個類
const instance = new SayHi(props) // ? SayHi {}
const result = instance.render() // ? Hello, React
那么JavaScript中的new
起什么作用呢?在ES6之前,JavaScript是沒有類(class
)這樣的概念。愛掏網(wǎng) - it200.com在這種情況之前如果要使用類這樣的特性都是使用普通函數(shù)來模擬。愛掏網(wǎng) - it200.com即,在函數(shù)調(diào)用前加上new
關(guān)鍵字,就可以把任何函數(shù)當做一個類的構(gòu)造函數(shù)來用:
function Fruit(name) {
this.name = name
}
const apple = new Fruit('apple') // ? Fruit?{name: "apple"}
const banana = Fruit('banana') // ? undefined
JavaScript中的new
關(guān)鍵字會進行如下的操作:
- 創(chuàng)建一個空的對象,即
{}
- 鏈接該對象(即設(shè)置該對象的構(gòu)造函數(shù))到另一個對象
- 將創(chuàng)建的對象作為
this
的上下文 - 如果該函數(shù)沒有返回對象,則返回
this
正如上面的示例來說:
- 調(diào)用
Fruit('apple')
時前面添加了new
關(guān)鍵字,這個時候JavaScript會知道Fruit
只是一個函數(shù),同時也會假裝它是一個構(gòu)造函數(shù)。愛掏網(wǎng) - it200.com會創(chuàng)建一個空對象({}
)并把Fruit
中的this
指向那個對象,以便我們可以通過類似this.name
的形式去設(shè)置一些東西,然后把這個對象返回 - 調(diào)用
Fruit('banana')
時前面沒有添加new
關(guān)鍵字,其中的this
會指向某個全局且無用的東西,比如window
或undefined
,因此代碼會崩潰或者做一些像設(shè)置window.name
之類的傻事
也就是說:
// 和Fruit中的this是等效的對象
const apple = new Fruit('apple') // ? Fruit?{name: "apple"}
new
關(guān)鍵字同時也把放在Fruit.prototype
上的東西放到了apple
對象上:
function Fruit(name) {
this.name = name
}
Fruit.prototype.SayHi = function () {
console.log(`Hi,我想吃${this.name}`)
}
const apple = new Fruit('蘋果')
apple.SayHi() // ? Hi,我想吃蘋果
這就是在JavaScript中如何通過new
關(guān)鍵字來模擬類的方式。愛掏網(wǎng) - it200.com有關(guān)于new
更多的介紹可以閱讀:
- JavaScript’s new