欢迎来到3672js教程,我们关注js教程、js框架、js代码特效等。

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

3672Js.Com2019-09-18 12:03 来源:未知 阅读:7972 关注度5

初探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中的函数和类相关的知识的话,可以花点时间阅读下面相关文章:

  • 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会指向某个全局且无用的东西,比如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更多的介绍可以阅读:

  • JavaScript’s new
剩余80%内容付费后可查看 本部分为付费内容,支付后可查看 支付3.99元 已支付?输入手机号查看 包月会员登录 购买包月会员推荐

本站文章为3672js教程网友分享投稿,版权归原作者,欢迎任何形式的转载,但请务必注明出处。同时文章内容如有侵犯了您的权益,请联系我们处理。
评论已被关闭
{dede:include filename="foot.htm"/}