初探React中函数组件和类组件的差异
特别声明:为感谢社区广大朋友对小站的支持,自2019年10月1日至2019年11月11日开通年费VIP通道,年费价格为 ¥299.00元。如果您喜欢小站的内容,可以点击开通会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!(^_^)
自从React Hooks的出来,社区讨论Hooks的越来越多。这并不是说React Hooks就优于类组件,但是使用Hooks来构建组件时有一个巨大的可用性提升,特别是因为这些函数组件可以通过React Hooks中的钩子函数来访问状态和生命周期。
今天我们就来一起聊聊如何将React的类组件转换为函数组件,用React Hooks中的钩子函数替换类组件中的setState
和生命周期方法,比如componentWillMount
、componentWillReceiveProps
等。
因此,让我们首先使用状态和生命周期方法构建一个基于类的React组件。也是大家最为熟悉的ToDoList
组件。该组件具备:
- 有一个文本输入框(
<input type="text" />
),用户可以在输入框中输入想要的内容 - 有一个“添加列表项”按钮(
button
),点击该按钮之后可以将文本输入框的内容添加到列表中(ToDoList
中) - 显示每个待办事项的列表清单
- 每个单独的列表项目都有一个相关联的复选框(
<input type="checkbox" />
),可以用来将列表项标记为已完成 - 列表项会存储到浏览器的缓存中(本地存储),并在应用程序启动时从本地存储中再次加载
我们的组件将使用state
、componentDidMount
、componentDidUpdate
和getDerivedStateFromProps
生命周期方法。其中一些生命周期方法(比如getDerivedStateFromProps
)将以一种非人为方式使用,以便能够演示有哪些Hooks的钩子函数可以替换这些生命周期的方法。
在开始之前,先来学习关于类和函数相关的知识点。
如何区分类和函数
作为Web开发者,经常和函数和类打交道。但要真正的理解和掌握他们也不是件易事,特别是对于初学JavaScript的同学更是如此。至少给我自己的感觉是如此。
在这里我们不会深入的去聊函数和类,因为要真正的聊透他们,都可以去写本书了。由于我们今天要聊React的类组件和函数组件,那么在开始之前很有必要的先了解一顶点有关于JavaScript的函数和类。先来看函数吧。
函数在JavaScript中被认为是第一类公民,在JavaScript中明确的创建函数的概念非常重要。
JavaScript语言似乎和其他编程语言不同,我们可以在JavaScript中以不同的方式来创建一个函数,常见的方式主要有:
用几个简单的示例代码来演示他们之间的不同:
// 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() { // 在对象上创建一个方法
return this.numbers.reduce(function(acc, num){ // Function Expression (使用该函数作为回调函数)
return acc + num
})
}
}
// Shorthand Method Definition
const Collection = { // 用于Object Literals和ES6 Class声明中
items: [],
// 使用函数名来定义
// 使用一对圆括号中的参数列表和一对花括号来分隔主体语句
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中的生成器函数返回这个生成器的迭代器对象
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中开始引入的,实质上是JavaScript现有的基于原型的继承的语法糖。实际上,类是特殊的函数,就像你能够定义的函数表达式和函数声明一样,类语法主要有两个组成部分:类表达式和类声明。
// 类声明
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
关键字在类声明或类表达式中用于创建一个类作为另一个类的子类:
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
如果子类中存在构造函数,则需要在使用this
之前首先调用super()
。也可以扩展传统折基于函数的“类”:
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()
如果你想更深入的了解有关于JavaScript中的函数和类相关的知识的话,可以花点时间阅读下面相关文章:
- 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的世界当中来。在React中我们可以以函数形式定义一个组件,比如像下面这样:
function SayHi() {
return <p>Hello, React</p>
}
也可以将SayHi
这个组件以类的形式来定义:
class SayHi extends React.Component {
render() {
return <p>Hello, React</p>
}
}
在当你要使用一个组件时,比如要使用SayHi
这个组件,并不会过多的关注它是以什么方式来定义(声明)的组件,只会关心如何使用:
<SayHi />
虽然使用者不会太过关注它是怎么创建的(以哪种方式创建的),但React自身对于怎么创建组件是较为关注也会在意其差别。
如果SayHi
是一个函数,React需要调用它:
// 你的代码
function SayHi() {
return <p>Hello, React</p>
}
// React内部
const result = SayHi(props) // » <p>Hello, React</p>
如果SayHi
是一个类,React需要先用new
操作符将其实例化,然后调用刚才生成实例的render
方法:
// 你的代码
class SayHi extends React.Component {
render() {
return <p>Hello, React</p>
}
}
// React内部
const instance = new SayHi(props) // » SayHi {}
const result = instance.render() // » <p>Hello, React</p>
无论哪种情况,React的最终目标是去获取渲染后的DOM节点,比如SayHi
组件,获取渲染后的DOM节点是:
<p>Hello, React</p>
具体需要取决于SayHi
组件是怎么定义的。
从上面的代码中你可能已经发现了,在调用类时,使用了new
关键字来调用:
// 如果SayHi是一个函数
const result = SayHi(props); // » <p>Hello, React</p>
// 如果SayHi是一个类
const instance = new SayHi(props) // » SayHi {}
const result = instance.render() // » <p>Hello, React</p>
那么JavaScript中的new
起什么作用呢?在ES6之前,JavaScript是没有类(class
)这样的概念。在这种情况之前如果要使用类这样的特性都是使用普通函数来模拟。即,在函数调用前加上new
关键字,就可以把任何函数当做一个类的构造函数来用:
function Fruit(name) {
this.name = name
}
const apple = new Fruit('apple') // » Fruit {name: "apple"}
const banana = Fruit('banana') // » undefined
JavaScript中的new
关键字会进行如下的操作:
- 创建一个空的对象,即
{}
- 链接该对象(即设置该对象的构造函数)到另一个对象
- 将创建的对象作为
this
的上下文 - 如果该函数没有返回对象,则返回
this
正如上面的示例来说:
- 调用
Fruit('apple')
时前面添加了new
关键字,这个时候JavaScript会知道Fruit
只是一个函数,同时也会假装它是一个构造函数。会创建一个空对象({}
)并把Fruit
中的this
指向那个对象,以便我们可以通过类似this.name
的形式去设置一些东西,然后把这个对象返回 - 调用
Fruit('banana')
时前面没有添加new
关键字,其中的this
会指向某个全局且无用的东西,比如window
或undefined
,因此代码会崩溃或者做一些像设置window.name
之类的傻事
也就是说:
// 和Fruit中的this是等效的对象
const apple = new Fruit('apple') // » Fruit {name: "apple"}
new
关键字同时也把放在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
关键字来模拟类的方式。有关于new
更多的介绍可以阅读:
如需转载,烦请注明出处:https://www.w3cplus.com/react/class-component-vs-function-component.html
如果文章中有不对之处,烦请各位大神拍正。如果你觉得这篇文章对你有所帮助,打个赏,让我有更大的动力去创作。(^_^)。看完了?还不过瘾?点击向作者提问!