初探React中函数组件和类组件的差异

自从React Hooks的出来,社区讨论Hooks的越来越多。这并不是说React Hooks就优于类组件,但是使用Hooks来构建组件时有一个巨大的可用性提升,特别是因为这些函数组件可以通过React Hooks中的钩子函数来访问状态和生命周期。

今天我们就来一起聊聊如何将React的类组件转换为函数组件,用React Hooks中的钩子函数替换类组件中的 setState 和生命周期方法,比如 componentWillMountcomponentWillReceiveProps 等。

因此,让我们首先使用状态和生命周期方法构建一个基于类的React组件。也是大家最为熟悉的 ToDoList 组件。该组件具备:

  • 有一个 文本输入框( <input type="text" /> ,用户可以在输入框中输入想要的内容
  • 有一个 “添加列表项”按钮( button ,点击该按钮之后可以将文本输入框的内容添加到列表中( ToDoList 中)
  • 显示每个待办事项的列表清单
  • 每个单独的列表项目都有一个相关联的复选框( <input type="checkbox" /> ),可以用来将列表项标记为已完成
  • 列表项会存储到浏览器的缓存中(本地存储),并在应用程序启动时从本地存储中再次加载

我们的组件将使用 statecomponentDidMountcomponentDidUpdategetDerivedStateFromProps 生命周期方法。其中一些生命周期方法(比如 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中的函数和类相关的知识的话,可以花点时间阅读下面相关文章:

我们回到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 会指向某个全局且无用的东西,比如 windowundefined ,因此代码会崩溃或者做一些像设置 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 更多的介绍可以阅读:

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章