2TWDTL.

到目前为止,知情读者,你肯定瞥了一眼,撇去,或者至少书签了关于React 16.8最热门预期的特点:钩子。你可能会读或听到他们有多伟大,他们是多么糟糕,也许甚至可能令人困惑。你可能会问自己“我为什么要了解这一点?”而且你可能希望比这更好的答案“因为这是新事物”。如果你沿着几个钩子指南遵循,你可能会发现自己问“但为什么?我可以使用课程做同样的事情!“

82BCA13D-8A45-4F10-822B-34D567B52973
信用@lizandmollie.

如果这听起来很熟悉,那可能是因为它是我们每次所面临的每次都要学习新事物的同一个周期。学习新事物对于任何人来说都是困难的,并且重新学习你已经知道的东西可能是特别令人沮丧的。您本能的反应可能是在您已经知道的内容中框架新事物。当我第一次分享关于钩子的钩子的学习到Okcupid时,我制作了一个图表映射组件生命周期方法来挂钩替代方案,让我看起来有188bet金宝搏官网点像这个家伙:

tumblr_o16n2kblpx1ta3qyvo1_1280
作者的注意事项:向Okcupid Web团队道歉,以成为我的豚鼠188bet金宝搏官网

事实证明,这可能是学习钩子的非常令人困惑的方式!很多概念都不会很好地映射,或者在一种方法上看起来不必要地复杂化。而不是继续谈论我的失败,我会得到好东西。这并不是旨在成为关于钩子的一切的全面指南,但我希望一旦阅读阅读,你会感到有兴趣用钩子写第一个组件。在我的经验中,这是真正的秘密:它不一定点击,直到你开始为自己写作它们。没有进一步的ADO,这是学习钩子所知的单一最好的*方法。

*我的意思是没关系
**就是我,我已经发现了发布时间


累了:setstate.

有线:​​usestate.

我们将从基础开始。你可能已经看到这个钩子解释了,如果是这样,请随时跳到下一节,我们开始更深入地获得更多。

我们学到反应中的第一件事之一是制作一个有状态的组成部分。你,像我一样,学会通过扩展来编写一个组件React.Component.(或者更多,使用反应.Createclass.,但我们不谈论那些黑暗的日子)。你学会了使用this.setState({某些键:Somevalue})要修改组件状态,请记住您进入的键/值对setstate.用新的值覆盖旧状态,其他所有东西都会合并。哦,哦,毫无遗忘才能初始化状态对象,以便我们尝试时没有得到错误setstate.。当然,别忘了.bind.每个都在构造函数中修改状态的函数,或记得使用箭头函数语法在您的团队中的某人几年前安装了Babel插件。

tumblr_inline_p2dyybplrv1qgoj6i_540

让我们忘记所有这一切。让我们概述我们需要构建的内容,例如,简单的状态单击计数器组件:

  1. 我们需要知道当前的点击键(让我们打电话给这个CurrentCount.
  2. 我们需要一种递增点击计数的方法(让我们打电话给这个setcurrentcount.

如果我们想象一下,我们有这些先决条件,我们可能会写这样的东西:

导入从“反应”反应;const counter =()=> {return(
current count:{currenccount}

通常我们可能会达到setstate.将其转化为现实,这意味着必须将该微小功能组件重构为全吹的类组件。但是,这是我们第一次钩子进来的地方。

导入反应,{usestate}从“反应”;const计数器=()=> {const [currentcount,setcurrentcount] = useState(0);返回(
current count:{currenccount}

“嗯,刚刚发生了什么?!”你可能会问自己。寒意, 自己。这是一个钩子!挂钩允许功能组件钩入以前仅适用于类组件(如状态)的功能。

useestate.钩子是一个参数中的函数:初始状态(在这种情况下,0),并以该顺序在数组中返回到该州值的值和设置器。当您调用Setter时,会使用更新状态值重新渲染组件,就像您调用的那样setstate.

“为什么阵列破坏?”你问?嗯,这样你就可以命名值和Setter,无论你想要的何种何种。当然,你可以使用useestate.挂钩尽可能多的时间,因为您可以在组件中携带多次,因此如果需要,可以跟踪多条状态,而无需将状态表示转换为对象。我们将更多地了解到在下一节中为我们提供了影响的机会。

累了:一个持有国家的一个物体

有线:​​单独的疑虑状态。

一个整洁的事情之一useestate.是您的组件的状态表示没有要成为一个对象 - 它可以是一个数字,字符串,或者真的是您想要的(包括对象)。但这是添加新状态属性的意思是什么?让我们说你后来决定需要跟踪另一个有状态财产。当状态是一个对象时,这与添加另一个密钥一样简单。现在,它像添加另一个电话一样简单useestate.

导入反应,{usestate}从“反应”;const计数器=()=> {const [currentcount,setcurrentcount] = useState(0);const [isclicking,setisclicking] = Usestate(false);返回(
current count:{currenccount}是单击:{isclicking}

这也授予我们让我们将相关块的代码组合在一起的灵活性,而不是将可能的不相关状态变为一个setstate.称呼。例如,如果我想将一些事件处理程序移出返回块,我可以使用最合适的代码,如下所示:

导入反应,{usestate}从“反应”;const计数器=()=> {const [currentcount,setcurrentcount] = useState(0);const increntmentcounter =()=> setCurrentCount(CurrentCount + 1);const [isclicking,setisclicking] = Usestate(false);const onmousedown =()=> setisclicking(true);const onmouseup =()=> setisclicking(false)return(
current count:{currentcount}是点击:{isclicking}

很酷,对吗?在传统的类组件中,这些状态属性必须在单个对象中一起生活,并且状态的初始化和修改它的函数可能会在组件上传播,而不是与相关逻辑一起分组。对于一个简单的组件,优势可能很小,但对于较大的组件,它可以真正对组件的可读性产生差异。

累了:生命周期方法

有线:​​当您需要更改时更改的数据

所以我们学到了useestate.可以取代(以某种方式改善)setstate.。但是我们可以在课堂组件中使用生命周期方法做的所有其他强大的事情呢?这是在学习钩子的经验丰富的反应开发人员可以获得有点毛茸茸的地方。

生命周期方法是一种抽象,使我们就渲染了我们所在的组件的阶段。姓名ComponentDidMount.ComponentDidupdate., 和componentwillunmount.对我们那些已经使用它们多年来一直使用它们并且可靠地了解他们的何时以及为什么他们会跑的东西,这些东西可以非常令人困惑地学习。那就是在我的经验中,我发现我们通常以一些可预测的模式使用它们。如果这些声音熟悉,请举手:

  1. ComponentDidMount.componentwillunmount.用于附加/删除事件侦听器,或设置/清除超时。(例如:侦听文档滚动或按键事件以更改状态)
  2. ComponentDidMount.ComponentDidupdate.用于根据PRAC /状态改变加载某些东西。(例如:当我们登录页面时加载数据,并在状态更改时重新加载)
  3. ComponentDidMount.ComponentDidupdate.用于根据PROP / State Change重新计算某些DOM属性。(示例:在状态更改后滚动到元素的顶部)

通常,我们立即使用几种这些模式,哦,那些生命周期方法可以变得凌乱。相关逻辑通过必要性地分布在这些方法中的几种方法中,并且很难遵循kerfuffle发生的事情。但它不一定是这样!从根本上说,大多数这些模式都可以简化为:在发生某些事情时做点什么。谢天谢地,我们有几个可以帮助的钩子。

让我们说我们希望摆脱柜台组件中的按钮,而是只收听单击文档。而不是编写生命周期方法来设置和拆除那些事件处理程序,让我们使用一个名为的新钩子使用

导入反应,{Usestate,UseFeffect}从“反应”;const计数器=()=> {const [currentcount,setcurrentcount] = useState(0);const increntmentcounter =()=> setCurrentCount(CurrentCount + 1);使用(()=> {document.addeventListener(“单击”,increntmentCounter); return()=> {document.removeeventListener(“单击”,increntmentCounter);};},[递增Counter]);const [isclicking,setisclicking] = Usestate(false);const onmousedown =()=> setisclicking(true);const onmouseup =()=> setisclicking(false);使用(()=> {document.addeventlistener(“mousedown”,Onmousedown); document.addeventlistener(“mouseUp”,onmouseUp); return()=> {document.removeeventListener(“Mousedown”,OnMousedown); document.removeeventListener(“mouseup”,onmouseup);};},[onmousedown,onmouseup]);返回(
current count:{currentcount}是点击:{isclicking} );};

哇,那是很多。让我们专注于其中一个新的使用块。

使用effeffect(()=> {document.addeventListener(“单击”,increntrementCounter); return()=> {document.removeeventListener(“单击”,increntmentCounter);};});

这个整洁的小钩子可以棘手理解 - 它可能更容易凭借良好的老式的命名功能(我想念那些):

使用(函数setup(){document.addeventlistener(“单击”,inclymentCounter); return函数indown(){document.removeeventlistener(“单击”,increntmentCounter);};});

这更好了。基本上,我们告诉这些组件运行我们的设置()它渲染后的功能,并使用它后清理使用拆除函数,在下一渲染之前。例如,如果此组件呈现三次,则它将运行以下功能,如下所示:

  1. 使成为
  2. 设置()
  3. 使成为(x2)
  4. 拆除()
  5. 设置()
  6. 使成为(x3)
  7. 拆除()
  8. 设置()

…等等。但是,为了效率,我们可以选择通过使用第二个参数:

使用(函数setup(){document.addeventlistener(“单击”,increntrementCounter); return函数indown(){document.removeeventListener(“单击”,increntmentCounter);};},[递增Counter]);

此第二个参数是应该导致组件重新运行的项目列表设置拆除功能。通常此列表将包括您在内部的外部变量使用呼叫 - 在这种情况下,incrementcounter.。这让我们避免浪费的设置和拆除。甚至更有力地,它可以帮助我们阻止效果不止一次运行,只需将其传递一个空的值来更改。有用!

在上面的例子中,我们使用每次设置一些文档事件侦听器incrementcounter.changes, but we can use this same hook to run any sort of side effect we’d like—from subscribing and unsubscribing to a web socket, hitting an API for updated data when some prop changes, or any other prop or state driven action we might want to take.

这是值得注意的值得注意的是拆除返回值是完全可选的。我们并不总是有一些我们想要清理的东西,但有时我们愿意,我们可以将这种相关的逻辑保持在一起。在我们的副作用之后,它也可以有用的提醒清理,我们之前可能忘记了清理componentwillunmount.

累了:组件方法

有线:​​UseCallback.

“可是等等!”,你现在可能会说,“我们在渲染功能中定义了upirmentCounter!那将重新定义每一个渲染,所以我们的钩子每次都不会运行?“好吧,我没有意识到你正在对这个过度拉伸细小的示例组件进行如此密切关注。但你绝对是正确的!

导入反应,{Usestate,UseFeffect}从“反应”;const计数器=()=> {const [currentcount,setcurrentcount] = useState(0);const increntmentcounter =()=> setCurrentCount(CurrentCount + 1);使用(()=> {document.addeventListener(“单击”,increntmentCounter); return()=> {document.removeeventListener(“单击”,increntmentCounter);};},[递增Counter]);返回(
current count:{currentcount} );};

因为incrementcounter.在每个渲染中重新定义,在第二个参数中使用它使用并不真正给我们很多好处。谢天谢地,有一个钩子!

导入React,{Usestate,UseFeffect,UseCallback}从“React”;const计数器=()=> {const [currentcount,setcurrentcount] = useState(0);const increntmentcounter = useCallback(()=> setCurrentCount(CurrentCount + 1),[SetCurrentCount,CurrentCount],);使用(()=> {document.addeventListener(“单击”,increntmentCounter); return()=> {document.removeeventListener(“单击”,increntmentCounter);};},[递增Counter]);返回(
current count:{currentcount} );};

这是我最喜欢的之一,最普遍有用,钩子 - 即使你删除了整个钩子的概念,你也想把这个保持在你的工具腰带中。你给USECALLBALL.作为其第一个参数的函数,它返回它的忆起版本,只要第二个参数更改中的任何项目都会重新计算。

这是特别有用的,因为我们都知道将传递箭头函数下来,因为道具是没有bueno.,因为这可能导致浪费的纠正者。现在,修复它在包装箭头函数时可以简单USECALLBALL.钩!这是一种简单的方法来挤出一些改进的性能,并防止不必要的扭转。

累了:重新选择

有线:​​USEMEMO.

如果我没有谈论一个紧密的堂兄,我会在这一点上留下来USECALLBALL.,非常有用USEMEMO.钩。每次发现自己计算渲染块中昂贵的东西都会使用此钩子。例如,如果您的组件主体看起来像这样:

导入从“反应”反应;const mycomponent =({someObject})=> {const somenumber = object.keys(someobject).map((key)=>某种机管[value]).filter((value)=>值%2 === 0)。减少((总和,当前)=> SUM +电流,0)Const数组= [...新阵列(Somenumber)];返回(
{array.map(()=> )} );};

(这是一个易易于学习的例子,但当然,请直接告诉我,你没有在你的代码库中的某个地方看起来像那样,我会吃我的帽子。)你可能会加工这个昂贵的计算USEMEMO.喜欢:

导入反应,{usememo}从“反应”;const mycomponent =({someObject})=> {const数组= umemo(()=> {const somenumber = object.keys(someObject).map((key)=>某种机管[value]).filter((value)=>值%2 === 0).duce((sum,current)=> sum + current,0)return [...新阵列(somenumber)];},[someobject]);返回(
{array.map(()=> )} );};

现在,那个阵列会只要如果值的价值,请重新计算someObject.变化。这比在每一个渲染中重新计算它(虽然仍然缺乏效率,但这比重新计算它,因为它完全删除它,因为它非常糟糕)。在过去,图书馆喜欢重新选择给我们的工具以获得类似的性能优势,但现在您可以在不必导入其他库的情况下获得这些增益。

累了:高阶组分/混合

有线:​​自定义钩子

还有一件事。回到我们的替换实例(你以为我会让你的计数器组件从钩子 - 永远不会!),如果我们的某种不可理解的原因,想在第二个组件中将单击处理程序附加到文档中?也许在单击时生成随机数的。

导入React,{Usestate,UseFeffect,UseCallback}从“React”;constrameNunbergenerator =()=> {const [randomnumber,setrandomnumber] = useState();const getrandomnumber = useCallback(()=> setrandomnumber(4),//保证随机[setrandomnumber],);使用(()=> {document.addeventListener(“单击”,getRandomNumber); return()=> {document.removeeventListener(“单击”,getrandomnumber);};},[getrandomnumber]);返回(
随机数为:{randomnumber} );};

嗯,我们可以重新定义新组件内的逻辑,但这并不乐趣。如果我们感到聪明,我们可能会使用更高阶的组件,或者渲染原则这样做。但这是自定义钩子真正发光的地方。我们可以将共享逻辑摘要到自定义挂钩,我们可能会致电二手刑事员

导入React,{Usestate,UseFeffect,UseCallback}从“React”;函数使用使用cocumentClick(ondocumentClick){unmedeffect(()=> {document.addeventlistener(“单击”,ondocumentClick); return()=> {document.removeeventListener(“单击”,ondocumentClick);};},[ondocumentClick]);} const计数器=()=> {const [currentcount,setCurrentCount] = useState(0);const increntmentcounter = useCallback(()=> setCurrentCount(CurrentCount + 1),[SetCurrentCount,CurrentCount],);二手刑事员(incrementcounter);返回(
current count:{currentcount} );};constrameNunbergenerator =()=> {const [randomnumber,setrandomnumber] = useState();const getrandomnumber = useCallback(()=> setrandomnumber(4),//保证随机[setrandomnumber],);二手Contick(Getrandomnumber); return (
Random number is: {randomNumber}
); };

自定义钩子可以像你一样使用任何其他钩子。您的自定义钩子的名称应该始终开始使用但是,除此之外,您可以自由地在那里进行任何您喜欢的东西 - 包括使用其他钩子useestate.使用。您的组件永远不需要知道钩子的实现细节 - 只是它的API。这可以帮助将复杂的状态或副作用逻辑从组件中逻辑移动,并使逻辑可轻松可重复使用。您不一定要为所有钩子执行此操作,但是在需要时使组件逻辑可重用的更容易的方法。

例如,如果您发现自己经常从您的组件中进行API调用,您可能会写一个名为Useapi.

功能USEAPI(方法,端点,数据){const [insloading,setisloading] = useState(false);const [错误,setError] = Usestate(null);const [响应,setResponse] = Usestate(null);使用(async()=> {try {setiSloading(true); setResponse(null); setError(null); const res = await获取(端点,{方法,数据}); setiSloading(false); setResponse(Res);} catch(错误){setisloading(false); setError(错误);}},[方法,端点,数据]);返回{响应,错误,insloading,};}

这样,您可以从组件与API交谈一个一致的图层。如果您使用像GraphQL和Apollo这样的现代技术,就像我们开始在Okcupid一样,已经有些人188bet金宝搏官网伟大的开源项目为您提供一些这些强大的自定义钩子,以及越来越多的集合其他什锦的公用事业钩

警告一词⚠️

使用挂钩时需要记住一个主要规则:你的钩子必须每次组件渲染时都以相同的顺序声明。这意味着什么:钩子不能在条件返回或循环中定义内部条件。必须始终在缩进的“顶级”调用钩子。如果这似乎很奇怪,这是因为通过JavaScript标准,这是一个不寻常的限制。这是对语言之外的额外约束,但是一个必要的一个,以便反应能够以正确的方式保持状态。有关为什么存在此限制的更多信息,我建议您阅读Dan Abramov的博客文章反应过度。好消息是:有一个eslint.插入帮助你防止犯错误。


你迷上了吗?

React Hooks可以真正改变我们在组件内的州和状态更新的方式,这可能导致一些非常重要的重构机会。虽然它可能很诱人到“翻译”我们的组件1:1从类到挂钩,这通常可以限制福利钩可以提供。我希望这一指南帮助了对钩子力量的兴趣,以及帮助重新描述我们组件中一些常见模式的兴趣。优化我们现有代码的机会USECALLBALL.USEMEMO.不能夸大,因为他们可以在现有的功能组件中提供一些简单的性能。关于使用类组件更直观的何时何时使用类组件肯定会愤怒,但我认为至少,钩子为我们提供了一些非常强大的新工具,用于表达有状态组件,甚至优化无状态组件。