react解构赋值_解构React钩子背后的魔力

react解构赋值

React v16.8.0已发布,这意味着React Hooks现在可以在React的稳定版本中使用。 自从我第一次开始使用alpha发行版中的钩子以来,这就是我一直期待的东西。 我相信,钩子将在React组件的编写和组合方式方面带来巨大的变化,并且这是一个更好的变化。

如果您还没有听说过React钩子,那么您可能应该去阅读React文档站点上关于钩子的概述 。 接下来,您可能还想阅读Dan Abramov 撰写的有关钩子的出色文章

当心:这里有魔术!

我最近一直在谈论钩子,很多次,有人指出,尽管钩子似乎是解决副作用以及React组件中状态管理问题的一个很好的解决方案,但它似乎有点像黑匣子,发生了无法辨认的魔术,使其真正起作用。

这些人中大多数人认为钩子是魔术是因为以下原因:

  • 在每个渲染期间,必须以相同的静态顺序调用挂钩。
  • 只能在其他挂钩或功能组件中调用挂钩。
  • useState挂钩似乎用来跟踪组件的state ,而该state从未传递给它。

虽然魔术在表演中可能很有趣,但是在编写软件时当然并不有趣,而且对于您的一生来说,无法弄清楚为什么您的库在做奇怪,出乎意料的事情并且违背了所有逻辑。 因此,让我们解构魔力,看看在每种情况下幕后到底发生了什么。

让UnMagicking开始!

在每个渲染期间,必须以相同的静态顺序调用挂钩。

人们注意到的第一个因素是(如“挂钩规则”中所述),挂钩对其调用顺序非常挑剔。 具体来说,React要求在每个渲染器上,调用挂钩的顺序必须相同。 这意味着您不能在循环或条件内调用挂钩,而必须在最顶层调用它们。

这背后的原因很简单。 考虑如下在函数组件中定义的一些钩子:

const [firstName, setFirstName] = useState('John');
const [lastName, setLastName] = useState('Doe');
const [age, setAge] = useState(28);

如您所见,这是对名为useState的函数的简单ES6调用。 useState函数曾经获得的唯一信息是传递给它的初始值(虽然不完全准确,但是我们稍后会useState )。 然后, setState调用返回[value, setter]形式的数组。

在后续渲染中,相同的useState方法将返回该特定属性的更新状态值。 为此, useState方法必须以某种方式跟踪所请求的每个属性。 但是,绝对没有地方将任何类型的key传递给useState方法。

那么useState究竟如何知道要为哪个调用返回什么状态? 好useState使用render函数内部的调用序列号来跟踪状态。 因此,在上面的示例中,对useState的第一次调用将始终返回firstName及其设置器的值,第二次调用将始终返回lastName及其设置器的值,依此类推。

如果像我一样,您可能会认为,必须有一种更好的方法来实现此目的,而不是依赖于呼叫顺序,这似乎是很棘手的。 塞巴斯蒂安·马克巴奇(Sebastian Markbage)对React Hooks RFC留下了极好的评论 ,并提出了其他建议。 您可能也应该阅读。

无论如何,它的不足之处在于,如果您使用钩子,则绝对必须在每个渲染器上以相同的顺序调用钩子,否则会发生坏事,而React则想在以下情况下获取或设置某个状态值:您想要的完全不同。

一种简单的理解方法是将useState视为维护将调用顺序链接到状态值的键-值映射。 在第一个渲染上, useState将键值映射初始化为:

const [firstName, setFirstName] = useState('John'); // internal state: {0: 'John'}
const [lastName, setLastName] = useState('Doe'); // internal state: {0: 'John', 1: 'Doe'}
const [age, setAge] = useState(28); // internal state: {0: 'John', 1: 'Doe', 2: 28}

在第二个渲染器上, useState已经具有一个键-值映射,因此无需在其上设置值,而只是根据调用顺序检索值。 请注意, useState实际上并不维护简单的键-值映射,实际的实现要比这复杂一些。 但是,出于理解呼叫顺序为何重要的目的,您可以将其视为有效的键值映射。

只能在其他挂钩或功能组件中调用挂钩。

钩子的另一个主要规则是,您只能从React函数组件或其他钩子中调用一个钩子。 如果您考虑一下,这是完全有道理的-如果您的钩子必须直接或间接获取或设置组件的状态或触发副作用,那么它需要链接到特定的React组件,该状态所属的国家。 否则,React将如何知道您的钩子试图访问哪个组件的状态?

到目前为止,一切似乎都很好。 那里真的没有什么神奇的东西吗? 好吧,如果您想了解魔术,则需要打破此规则,并从常规Javascript函数内部调用一个钩子。

function catchMeIfYouCan(){
const [firstName, setFirstName] = useState('John');
};

立即运行此代码将引发错误:

Uncaught Error: Hooks can only be called inside the body of a function component.

现在等一下! 如果useState调用只是一个普通的函数调用,而Javascript中的函数实际上不能在分析调用该方法的方法上做很多事情,那么React如何知道useState方法是从React组件外部调用的? 魔法? 并非如此,但是由于这与我们列表中的下一个不可思议的项目相关,因此我将同时解决这两个问题。

useState挂钩似乎用来跟踪组件的state ,而该state从未传递给它。

考虑StudentTeacher两个组成部分,两者都有状态属性firstNamelastNameage

class StudentComponent extends React.Component{
constructor(props){
super(props);
this.state = {
firstName: 'Bobby',
lastName: 'Tables',
age: 8
}
}
render(){
return <div>{this.state.firstName} {this.state.lastName} ({this.state.age})</div>
}
}
class TeacherComponent extends React.Component{
constructor(props){
super(props);
this.state = {
firstName: 'John',
lastName: 'Keating',
age: 34
}
}
render(){
return <div>{this.state.firstName} {this.state.lastName} ({this.state.age})</div>
}
}

在上面的示例中,在两个组件中, state都是实例属性,因此可以从任何类方法中访问状态。 由于它是一个实例属性,而不是一个通用共享对象,因此这些值也被正确封装,并且TeacherComponent无法访问StudentComponent的状态,反之亦然。

现在,使用useState挂钩将这些组件转换为对应的功能组件。

function StudentComponent(props){
const [firstName, setFirstName] = useState('Bobby');
const [lastName, setLastName] = useState('Tables');
const [age, setAge] = useState(8);
    return <div>{firstName} {lastName} ({age})</div>
};
function TeacherComponent(props){
const [firstName, setFirstName] = useState('John');
const [lastName, setLastName] = useState('Keating');
const [age, setAge] = useState(34);
    return <div>{firstName} {lastName} ({age})</div>
};

如果您在此处注意到,则在这两个组件中,我们都在调用相同的useState方法,该方法是从React库中导入的方法,并且在两个组件之间共同共享。 就像我们之前讨论的一样, useState仅使用调用顺序来跟踪状态值。

鉴于此,如果我们首先呈现StudentComponent ,设置firstNamelastNameage状态属性,然后紧随其后呈现TeacherComponent ,则TeacherComponent应该能够访问StudentComponent设置的状态值,对吗?

好吧,实际上没有。 即使在使用钩子时,React也确保始终正确封装state值,并且不会被错误的组件访问。 否则,生活将不尽如人意,每个组件都可以访问所有其他组件的状态,对吗?

但是鉴于我们不再使用this来封装组件实例中的状态,React如何为我们执行这种封装? 答案实际上很简单。 但是在开始之前,我们需要稍微绕过React的内部。 更确切地说,是React-DOM的内部。

使用React Fiber更好的渲染性能

在最初的React版本中,渲染组件是作为单个连续动作发生的。 在React应用程序中,对应用程序状态的更改会触发整个应用程序的重新渲染。 但是,实际上,对于任何非平凡的应用程序,重新渲染整个应用程序都会导致糟糕的性能。

为了提高性能,React执行了很多优化。 这些优化大多数发生在Reconciliation阶段。 协调基本上是React通过将每个组件实例上的render方法与在浏览器上构建的实际DOM同步而建立的虚拟DOM进行同步的过程。

在React Fiber(React v16中发布)之前,React依赖于调用堆栈来管理渲染。 每当必须重新渲染组件时,都会将其添加到调用堆栈中,并且一旦堆栈上的其他项目完成渲染,该组件就会进行渲染。 但是,尽管堆栈中有物品,但无法完成其他工作。 渲染会阻塞所有其他功能,并且渲染无法分解成较小的块并运行。

为了解决这个问题,引入了光纤。 Fiber基本上是对调用堆栈的自定义重新实现,它允许将大型任务拆分为较小的工作单元,根据需要暂停和恢复这些工作单元,以及在不再需要时删除它们。

为了实现这一目标,Fiber在内部将React组件的每个实例称为“ fiber”对象。 纤维对象是一个JavaScript对象,其中包含有关组件,其输入和输出的信息。 它类似于堆栈框架,但也对应于组件的实例。

这实际上意味着,组件的每个实例在React中都内部表示为“纤维”,而这些纤维具有React用来跟踪stateprops类的许多属性。

那么,这与将state封装在功能组件中有什么关系呢?

每个组件实例都是一根纤维,并且纤维具有许多内部特定于React的属性。 在类组件和功能组件中,组件的实际实例都表示为纤维,在React内部无法访问。

当我们在类组件中调用this.setState时,Fiber会创建一个updateAction并将该动作加入this.setState ,而不是立即将部分状态合并为现有状态。 将操作放入队列的队列特定于每个单独的光纤,并且将在光纤上执行的大多数操作都放入队列,而不是直接运行。 使动作排队可使Fibre安排和控制执行。

当我们在函数组件中调用useState钩子,或使用useState钩子返回的setter方法时,会发生完全相同的事情。 将要执行的相应操作放入代表组件此特定实例的光纤上。

类组件和功能组件之间的唯一区别是,如何识别与特定组件实例相对应的特定光纤。 在类组件中,类组件实例本身具有_reactInternalFiber属性,该属性直接提供相应的光纤。 最初构建组件时由React设置。

但是,在功能组件中,不存在这样的键。 回到我们的例子:

function StudentComponent(props){
const [firstName, setFirstName] = useState('Bobby');
const [lastName, setLastName] = useState('Tables');
const [age, setAge] = useState(8);
    return <div>{firstName} {lastName} ({age})</div>
};
function TeacherComponent(props){
const [firstName, setFirstName] = useState('John');
const [lastName, setLastName] = useState('Keating');
const [age, setAge] = useState(34);
    return <div>{firstName} {lastName} ({age})</div>
};

React如何在两个组件实例之间封装数据? 到目前为止,我们知道解决方案涉及为每个组件实例维护单个光纤,并且在首次构建组件时就设置了该光纤。 但是useState如何知道哪个组件正在尝试访问状态并返回适当的状态?

好了,React用来识别调用组件实例的解决方案非常简单。 在React中,在任何给定的时间点,当前只能渲染一个组件。 React还确切地知道组件渲染何时开始以及何时结束。 因此,当一个组件实例即将开始渲染时,React会设置一个标志currentlyRenderingFiber ,指向该实例。 组件渲染完成后,该标志将为空。

由于hook函数只能从函数组件内部调用,而函数组件基本上只是类组件的render方法,因此可以确保每当调用hook时,我们都将位于render的中间,并且currentlyRenderingFiber位于RenderingFiber将指向正在渲染的光纤。 组件实例的state作为该光纤上的属性维护,而useState用来获取正确组件实例的状态。

还记得我们从普通的Javascript函数调用钩子时看到的错误吗?

Uncaught Error: Hooks can only be called inside the body of a function component.

那么React如何知道这不是从函数组件内部调用的呢? 那么,阵营只设置currentlyRenderingFiberdispatcher和组件时呈现为一个功能组件的一些其他物品。 当您从普通的javascript对象调用该挂钩时,未设置这些项,这就是React引发错误的时间。

魔法消失了!

因此,这基本上就是React Hooks背后所有魔力的全部。 既然我们确切地知道了幕后发生的事情,我们应该能够更好地了解钩子,它们如何工作以及为什么我们需要遵循特殊的规则来编写钩子。

我很想听听您对我在这篇文章中所写内容的看法。 因此,请在下面发表您的评论。

最初发布于 asleepysamurai.com

翻译自: https://hackernoon.com/deconstructing-the-magic-behind-react-hooks-33ca987e5307

react解构赋值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值