來(lái)源:派臣科技|時(shí)間:2020-11-14|瀏覽:次
語(yǔ)言的魔力是咒語(yǔ),芝麻開(kāi)門(mén),等等,但是在一個(gè)故事里的咒語(yǔ)在下一個(gè)故事里就沒(méi)有魔力了。真正的神奇之處在于懂得哪些詞語(yǔ),什么時(shí)候起作用,為什么起作用;訣竅在于學(xué)會(huì)訣竅。
自從在React 16.8中正式穩(wěn)定了鉤子已經(jīng)有一段時(shí)間了,伴隨它們而來(lái)的是一種完全不同的理解我們應(yīng)用程序工作方式的方式。這既是一件好事,也是一件壞事:鉤子更接近于React編程模型,有助于避免某些微妙而令人困惑的bug,但一些開(kāi)發(fā)人員也擔(dān)心React會(huì)成為一個(gè)黑盒子。這些擔(dān)憂完全有道理;鉤子通??雌饋?lái)很“神奇”,因?yàn)榇蠖鄶?shù)復(fù)雜性都隱藏在React的內(nèi)部。
這種“神奇”的感覺(jué)主要是因?yàn)閔ook是基于一些現(xiàn)有技術(shù)和編程語(yǔ)言研究,而許多開(kāi)發(fā)人員只是不熟悉。理解hook的一些動(dòng)機(jī)和靈感可以幫助建立一個(gè)心理模型來(lái)了解幕后發(fā)生了什么。雖然有幾個(gè)來(lái)源的影響,最初的鉤子建議,可以說(shuō)最重要的是代數(shù)效應(yīng)的概念。
注意,本文不介紹如何使用鉤子或內(nèi)部鉤子是如何工作的。這只是考慮鉤子的一種方式。關(guān)于如何使用它們的更多信息,我建議從文檔開(kāi)始。
在深入研究代數(shù)效應(yīng)的細(xì)節(jié)之前,讓我們先退一步。
我們?yōu)槭裁葱枰^子?
類組件似乎工作得很好,為什么要添加另一種編寫(xiě)組件的方式,至少表面上,做同樣的事情?
React的核心原則之一是,應(yīng)用程序的用戶界面純粹是應(yīng)用程序狀態(tài)的函數(shù)。這里,“state”可以指本地組件狀態(tài)和全局狀態(tài)的任何組合,比如Redux存儲(chǔ)。當(dāng)狀態(tài)更改并通過(guò)組件樹(shù)傳播時(shí),輸出表示狀態(tài)更改后的新UI。當(dāng)然,這是對(duì)實(shí)際更新如何發(fā)生的具體細(xì)節(jié)的抽象,因?yàn)镽eact處理實(shí)際的協(xié)調(diào)和必要的DOM更新,但這個(gè)核心原則意味著,至少在理論上,我們的UI總是與數(shù)據(jù)同步的。
當(dāng)然,這并不總是正確的。類組件暴露了某些場(chǎng)景,如果我們不能在生命周期方法中有效地處理這些狀態(tài)更改,這些場(chǎng)景允許我們忽略狀態(tài)更改。丹·阿布拉莫夫(Dan Abramov)寫(xiě)了一篇關(guān)于與此相關(guān)的常見(jiàn)陷阱的優(yōu)秀文章,值得一讀以了解更多細(xì)節(jié)。簡(jiǎn)而言之,類組件使用不同的生命周期方法來(lái)處理副作用,但這將副作用映射到DOM操作,而不是狀態(tài)更改。這意味著,雖然UI的可視化元素可能會(huì)響應(yīng)狀態(tài)更改,但副作用可能不會(huì)。
因?yàn)轭惤M件必須做這些內(nèi)部更新,以同步它們的內(nèi)部狀態(tài)時(shí),道具改變,它們的定義是不純的。等等,你可能會(huì)說(shuō),UI是一個(gè)狀態(tài)函數(shù)。
精確。這就是鉤子發(fā)揮作用的地方。
鉤子代表了看待效果的另一種方式。與其考慮組件的整個(gè)生命周期,掛鉤允許我們將關(guān)注點(diǎn)縮小到當(dāng)前狀態(tài)。然后,我們可以聲明要在其中運(yùn)行效果的狀態(tài),確保這些狀態(tài)更改反映在效果中。當(dāng)然,一個(gè)“效果”可以是很多事情,從用useState處理狀態(tài),發(fā)出網(wǎng)絡(luò)請(qǐng)求或者用useEffect手動(dòng)更新DOM,或者用useCallback計(jì)算昂貴的回調(diào)函數(shù)。
但是我們?nèi)绾卧谝粋€(gè)純函數(shù)中推斷這些副作用呢?我很高興你問(wèn)了!
代數(shù)效應(yīng)的介紹
代數(shù)效應(yīng)是通過(guò)定義一個(gè)效應(yīng)、一組操作和一個(gè)效應(yīng)處理程序(負(fù)責(zé)處理如何實(shí)現(xiàn)效應(yīng)的語(yǔ)義)來(lái)在純上下文中推理計(jì)算效應(yīng)的一種通用方法。1代數(shù)效應(yīng)泛化在許多潛在的用途上,如輸入和輸出、處理狀態(tài)、異步/等待,以及更多。
這有點(diǎn)抽象,所以讓我們寫(xiě)一些代碼來(lái)看看這在實(shí)踐中是如何工作的。遺憾的是,JavaScript實(shí)際上并不支持代數(shù)效果,盡管React可能在內(nèi)部模仿它們。雖然有一些不同的語(yǔ)言2支持代數(shù)效果,但我們將使用Eff,這是一種專門(mén)圍繞代數(shù)效果設(shè)計(jì)的函數(shù)式編程語(yǔ)言。不用擔(dān)心,大多數(shù)人不會(huì)知道Eff,所以我將在后面解釋一些語(yǔ)法。
代數(shù)效果的一個(gè)常見(jiàn)用例是處理有狀態(tài)計(jì)算。記住,效果只是在具有一組操作的接口中。
讓我們把它分解一下。我們有一個(gè)具有三個(gè)分支的處理程序,它們都返回一個(gè)函數(shù)。該函數(shù)將用于處理某些效果(或缺少效果)。
第一個(gè)分支,y ->有趣的currentState -> (y, currentState),表示沒(méi)有效果,當(dāng)我們到達(dá)我們正在處理的代碼塊的末尾時(shí)就會(huì)發(fā)生這種情況(我們很快就會(huì)看到)。這里的y是函數(shù)的返回值,所以它只返回內(nèi)部返回值和狀態(tài)的元組。
第二個(gè)和第三個(gè)分支匹配我們的效果,但是有一個(gè)可疑的論點(diǎn),k,這里是一個(gè)延續(xù),它表示在我們執(zhí)行效果之后的剩余計(jì)算。
GOTO,但更好的在我的心里,我是一個(gè)像神諭的人;我的創(chuàng)建設(shè)置標(biāo)簽,我的方法執(zhí)行跳轉(zhuǎn)。然而,這是一種非常強(qiáng)大的goto指令。如果您的頭發(fā)在這一點(diǎn)上變綠了,不要擔(dān)心,因?yàn)槟赡苤惶幚硌永m(xù)的用戶,而不是概念本身。
這個(gè)小gem來(lái)自于GNU Smalltalk延續(xù)文檔。對(duì)于有些人來(lái)說(shuō),對(duì)GOTO的引用可能會(huì)讓您感到有些厭惡,但是延續(xù)仍然作為控制流而存在是有原因的,這是關(guān)于上下文的。GOTO更危險(xiǎn)的方面之一是被扔進(jìn)了一個(gè)無(wú)效的上下文中,但是使用延續(xù),您實(shí)際上是在存儲(chǔ)一個(gè)動(dòng)態(tài)進(jìn)程,因此變量、指針等都將是有效的。
因?yàn)檠永m(xù)表示運(yùn)行中的整個(gè)過(guò)程,所以它們本質(zhì)上是效果發(fā)生時(shí)調(diào)用堆棧的快照。當(dāng)我們得到一個(gè)效果時(shí),就好像我們?cè)谟?jì)算中按下一個(gè)巨大的暫停鍵,直到我們正確地處理這個(gè)效果。呼叫continue k4就像再次按下播放按鈕一樣。
當(dāng)我們開(kāi)始這個(gè)計(jì)算時(shí),我們首先從state獲得用戶,它運(yùn)行處理程序中的第二個(gè)分支。在這一點(diǎn)上,我們已經(jīng)按下了pause按鈕,所以當(dāng)我們從state得到這個(gè)函數(shù)時(shí),函數(shù)已經(jīng)停止運(yùn)行。處理程序返回一個(gè)函數(shù),該函數(shù)調(diào)用continue k currentState,用currentState的值恢復(fù)我們的計(jì)算。
每次執(zhí)行一個(gè)效果時(shí),都會(huì)發(fā)生相同的流。按暫停,做一些工作,按播放。
這里,親愛(ài)的讀者,是代數(shù)效應(yīng)的力量真正閃耀的地方。你看,我們?nèi)绾伪3譅顟B(tài)并不重要。當(dāng)然,現(xiàn)在它只是內(nèi)存中的一個(gè)對(duì)象,但如果它在數(shù)據(jù)庫(kù)中呢?如果它存儲(chǔ)在瀏覽器的localStorage中呢?據(jù)celebrate所知,這些都是一樣的。如果需要,我們可以使用存儲(chǔ)鍵值存儲(chǔ)中的狀態(tài)的redisState處理程序交換狀態(tài)處理程序。
在JavaScript中,您的代碼必須知道什么是同步的,什么不是。如果將來(lái)發(fā)生更改,并且狀態(tài)是異步處理的,我們將需要開(kāi)始處理承諾,這將需要對(duì)涉及此函數(shù)的所有內(nèi)容進(jìn)行更改。但是使用代數(shù)效果,我們可以完全停止當(dāng)前進(jìn)程,直到效果完成,而不是維護(hù)一個(gè)運(yùn)行的進(jìn)程,該進(jìn)程包含對(duì)另一個(gè)進(jìn)程的引用。
當(dāng)然,狀態(tài)不是我們能用代數(shù)效應(yīng)處理的唯一東西。假設(shè)我們有一些網(wǎng)絡(luò)請(qǐng)求,我們想要?jiǎng)?chuàng)建或清理,我們想要執(zhí)行,但我們只想在函數(shù)完成后執(zhí)行。我們稱之為延遲效應(yīng)。
此時(shí),我相信你們會(huì)想"太好了,我們可以隨時(shí)暫停執(zhí)行。這和鉤子有什么關(guān)系?好吧,我們?cè)贓ff中列出的兩個(gè)效果存在于React中,只是它們的名字不同:state處理程序(意料之中)反映了useState,而我們的defer處理程序工作起來(lái)很像一個(gè)簡(jiǎn)化的useEffect。前面的示例與用戶界面沒(méi)有直接關(guān)系,但是暫停和恢復(fù)過(guò)程的心理模型,以及在延續(xù)之后的調(diào)度效果,是理解鉤子和React未來(lái)的核心。
反應(yīng)中的代數(shù)效應(yīng)
讓我們把注意力轉(zhuǎn)回反應(yīng)。之前我們討論了為什么需要鉤子,但問(wèn)題是我們?nèi)绾慰创^子。回想一下我們最初將代數(shù)效果定義為一組操作和一組效果處理程序。這里的操作是我們的鉤子(例如useState, useEffect,等等),并在渲染期間處理這些效果。
由于鉤子的一些規(guī)則,我們知道效果處理程序是反應(yīng)呈現(xiàn)周期的一部分。例如,如果您嘗試在一個(gè)React組件之外調(diào)用useEffect,您可能會(huì)在無(wú)效鉤子調(diào)用的一行中得到一個(gè)錯(cuò)誤。鉤子只能在函數(shù)組件的內(nèi)部調(diào)用。類似地,如果你在沒(méi)有正確處理的情況下在Eff中執(zhí)行一個(gè)效果,你會(huì)看到運(yùn)行時(shí)錯(cuò)誤:未捕獲的效果延遲。雖然我們必須自己在Eff中設(shè)置處理程序,但在React中,它們是作為渲染周期的一部分設(shè)置的。
為什么這很重要呢?理解React對(duì)何時(shí)以及如何運(yùn)行您的效果的實(shí)現(xiàn)負(fù)責(zé)是很重要的,因?yàn)樗试S我們?cè)赗eact中隱藏大量的復(fù)雜性。例如,useEffect的關(guān)鍵用途之一是作為調(diào)度器。特別是對(duì)于計(jì)算開(kāi)銷較大的ui(比如復(fù)雜的動(dòng)畫(huà)),調(diào)度工作單元是非常復(fù)雜的,并且React需要能夠決定什么工作是最高優(yōu)先級(jí)的。在更高的級(jí)別上,React可以暫停和恢復(fù)單個(gè)組件的呈現(xiàn),這同樣可以對(duì)屏幕上的組件或響應(yīng)用戶輸入的組件進(jìn)行優(yōu)先級(jí)排序。安德魯·克拉克(Andrew Clark)寫(xiě)了一篇關(guān)于纖維如何起作用及其設(shè)計(jì)目標(biāo)的精彩概述,但這篇關(guān)于日程安排的小文章在這里尤為重要:
基于推的方法要求應(yīng)用程序(你,程序員)決定如何安排工作?;诶姆椒ㄔ试S框架(React)更聰明地為您做出這些決定。
通過(guò)允許反應(yīng)分離效果和渲染,我們?cè)试S它減輕我們的一些復(fù)雜性。隨著React越來(lái)越多地轉(zhuǎn)向懸念和并發(fā)模式,這一點(diǎn)將變得越來(lái)越重要。
結(jié)論
當(dāng)我們對(duì)工具的思維模型與它的工作方式不一致時(shí),通常會(huì)產(chǎn)生最痛苦的bug。對(duì)于許多React開(kāi)發(fā)者來(lái)說(shuō),我認(rèn)為我們很難看到grok在調(diào)用useState時(shí)發(fā)生了什么。我的希望是,對(duì)代數(shù)效應(yīng)的理解至少為鉤子在幕后所做的工作提供了一個(gè)稍微好一點(diǎn)的模型。當(dāng)然,值得重申的是,這并不是說(shuō)鉤子就是這樣工作的——只是試著去理解它們。
這篇文章并沒(méi)有過(guò)多地探討React的內(nèi)部工作原理,但希望它能提供對(duì)鉤子和效果更普遍的更好的直覺(jué)。代數(shù)效應(yīng)是編程語(yǔ)言研究中最近才出現(xiàn)的一個(gè)領(lǐng)域,至少我自己知道,要更好地理解它們需要大量的閱讀。如果你想深入研究代數(shù)效應(yīng)的背后,我在下面列出了一些建議。
盡管一些社區(qū)的投訴反應(yīng)成為一個(gè)黑盒,重要的是要記住,新工具的存在是有原因的,而更廣泛的鉤子和反應(yīng)的主要目標(biāo)是保護(hù)我們免受一定的復(fù)雜性,我們不想處理,讓我們專注于構(gòu)建更好的ui和取悅用戶。