原文: Why Do React Hooks Rely on Call Order?
譯文: 為什么react hooks依賴于調(diào)用順序
在React Conf 2018,react團隊介紹了Hooks渐排。
如果你想了解Hooks是什么妨蛹,以及他解決了什么問題。請查看我們的演講介紹以及后續(xù)寫的一些文章去了解他。
你第一次看到他的時候可能不會喜歡他养叛。
但是我想說的是种呐,他需要經(jīng)過一番品味之后才會發(fā)現(xiàn)其中的美妙。
當(dāng)你閱讀文檔時弃甥,請一定要閱讀最重要的這一頁爽室,這是關(guān)于自定義Hook的!在這些文檔里淆攻,很多人都會過于關(guān)注一些他們不同意的部分(比如學(xué)習(xí)class太難)阔墩,但是他們沒有注意到Hooks的重點。這個重點就是Hooks就像functional mixins
卜录,可以讓你創(chuàng)建和組合成你自己的抽象化概念戈擒。
Hooks受到一些現(xiàn)有技術(shù)的影響,但是在Sebastian與團隊分享他的想法之前艰毒,我沒有看到任何類似的東西筐高。不幸的是這很容易忽略特定的API選擇和此設(shè)計開放的有價值屬性之間的聯(lián)系。這篇文章丑瞧,我希望能幫助更多人理解Hooks提案中最具爭議性的方面的理由柑土。
本文假設(shè)你已經(jīng)知道什么是useState
Hook API并且知道如何寫一個自定義的hook。如果你不知道绊汹,請查看之前的鏈接稽屏。另外,請記住西乖,鉤子是實驗性的狐榔,你現(xiàn)在大可不必學(xué)習(xí)它們!
筆者:如果不了解
Hooks API
, 又不想去看原文的获雕,可以推薦你看hooks中文系列
免責(zé)聲明:這是個人帖子薄腻,并不一定反映React團隊的意見。它很大届案,話題很復(fù)雜庵楷,我可能在某個地方有犯過錯誤。
當(dāng)你了解Hooks時楣颠,第一個感覺到驚訝也可能是最大的驚訝是它們一直依賴于重新渲染之間的調(diào)用索引尽纽。這有一些其他的含義。
這個定案顯然是有爭議的童漩。這就是為什么弄贿,根據(jù)我們的原則,我們會在文檔和語言描述的足夠好的時候才會發(fā)一個提案讓大家給一個公平的機會去選擇睁冬。
如果你關(guān)注Hooks API的設(shè)計方面挎春,我鼓勵你閱讀Sebastian’s對1,000多條評論RFC討論的全部回答看疙。 這很深入,但是信息量過多直奋。我可能會將此評論的每一段都變成自己的博客文章能庆。 (事實上??,我已經(jīng)做過一次=畔摺)
我今天要關(guān)注一個特定的部分搁胆。你可能還記得,每個Hook可以在一個組件中使用多次邮绿。例如渠旁,我們可以通過重復(fù)調(diào)用useState
來聲明多個狀態(tài)變量:
function Form() {
const [name, setName] = useState('Mary'); // State variable 1
const [surname, setSurname] = useState('Poppins'); // State variable 2
const [width, setWidth] = useState(window.innerWidth); // State variable 3
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
});
function handleNameChange(e) {
setName(e.target.value);
}
function handleSurnameChange(e) {
setSurname(e.target.value);
}
return (
<>
<input value={name} onChange={handleNameChange} />
<input value={surname} onChange={handleSurnameChange} />
<p>Hello, {name} {surname}</p>
<p>Window width: {width}</p>
</>
);
}
請注意,我們使用數(shù)組解構(gòu)語法來命名useState
狀態(tài)變量船逮,但這些名稱不會傳遞給React顾腊。相反的,在這個例子中挖胃,React將name
視為“第一個狀態(tài)變量”杂靶,將surname
視為“第二個狀態(tài)變量”,依此類推酱鸭。 他們的call index(調(diào)用索引)使他們在重新渲染之間具有穩(wěn)定的標(biāo)識吗垮。對于這個,在這篇文章中有更好的描述凹髓。
表面看來烁登,依賴于調(diào)用牽引只是感覺不對。直覺是一種不錯的指引蔚舀,但它可能會產(chǎn)生誤導(dǎo) -- 特別是如果我們沒有完全內(nèi)化我們正在解決的問題饵沧。 在這篇文章中,我將為Hooks采用一些他們建議的常用的替代的設(shè)計赌躺,并展示它們不可以在什么地方捷泞。
這篇文章不會很全面的做到。根據(jù)你的計算寿谴,我們已經(jīng)看到了十幾種到數(shù)百種不同的替代方案。在過去的五年里失受,我們一直在思考替代組件的API讶泰。
像這樣的博客文章很棘手,因為即使你覆蓋了一百個替代品拂到,也有人可以調(diào)整一個并說:“哈痪署,你沒想到這個!”
在實踐中兄旬,不同的替代方案易于在其缺點中重疊狼犯。我會用典型的例子展示最常見的缺陷余寥,而不是枚舉所有建議的API(這需要幾個月)。通過這些問題對其他可能的API進(jìn)行分類可能是讀者的一種練習(xí)悯森。 ??
這并不是說Hooks是完美的宋舷。但是一旦你熟悉了其他解決方案的缺陷,你可能會發(fā)現(xiàn)Hooks設(shè)計有一定道理瓢姻。
缺陷 #1: 無法提取自定義Hook
令人驚訝的是祝蝠,許多替代方案根本不允許自定義Hook。也許我們并沒有在“動機”文檔中充分強調(diào)自定義Hooks幻碱。在原函數(shù)被充分理解之前很難做到绎狭。所以這是一個雞與蛋的問題。但定制hook在很大程度上是提案的重點褥傍。
例如儡嘶,替代禁止在組件中調(diào)用多個useState。你將狀態(tài)保存在一個對象中恍风。這適用于類蹦狂,對吧?
function Form() {
const [state, setState] = useState({
name: 'Mary',
surname: 'Poppins',
width: window.innerWidth,
});
// ...
}
要清楚邻耕,Hooks確實允許這種風(fēng)格鸥咖。你不必將狀態(tài)拆分為一堆狀態(tài)變量(請參閱常見問題解答中的建議)。
但支持多個useState
調(diào)用的重點是兄世,你可以從組件中提取有狀態(tài)邏輯(state+effect)的一部分到自定義Hook中啼辣,也可以獨立使用本地state和effect:
function Form() {
// Declare some state variables directly in component body
const [name, setName] = useState('Mary');
const [surname, setSurname] = useState('Poppins');
// We moved some state and effects into a custom Hook
const width = useWindowWidth();
// ...
}
function useWindowWidth() {
// Declare some state and effects in a custom Hook
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
// ...
});
return width;
}
如果每個組件只允許一次useState
調(diào)用,則會失去自定義Hook引入本地狀態(tài)的能力御滩。這是定制Hooks的重點鸥拧。
缺陷 #2 名字沖突
一個常見的建議是讓useState
接受一個唯一標(biāo)識key
, 組件中特定狀態(tài)變量的參數(shù)(例如字符串)。
這個想法可能有一些變動削解,但它們大致如下:
// ?? This is NOT the React Hooks API
function Form() {
// We pass some kind of state key to useState()
const [name, setName] = useState('name');
const [surname, setSurname] = useState('surname');
const [width, setWidth] = useState('width');
// ...
這試圖避免依賴于call index(調(diào)用索引), 但引入了另一個問題 - 名稱沖突富弦。
當(dāng)然,除了錯誤之外氛驮,你可能不會在同一個組件中兩次調(diào)用useState('name')腕柜。這可能會有意外發(fā)生,但我們可以爭論這些任何錯誤矫废。但是盏缤,當(dāng)處理自定義Hook時,很可能需要添加或刪除狀態(tài)變量和效果(effects)蓖扑。
有了這個提議唉铜,每當(dāng)你在自定義Hook中添加一個新的狀態(tài)變量時,你就有可能破壞使用它的任何組件(直接或傳遞)律杠,因為它們可能已經(jīng)為自己的狀態(tài)變量使用了相同的名稱潭流。
這是未針對更改進(jìn)行優(yōu)化的API的示例竞惋。當(dāng)前代碼可能總是看起來“優(yōu)雅”,但是有要求需要變化的時候顯得非常脆弱灰嫉。我們應(yīng)該從錯誤中吸取教訓(xùn)拆宛。
實際上,Hooks提議通過依賴于調(diào)用順序來解決這個問題:即使兩個Hook使用name
狀態(tài)變量熬甫,它們也會彼此隔離胰挑。每個useState
調(diào)用都有自己的“內(nèi)存單元”。
我們還有其他一些方法可以解決這個缺陷椿肩,但它們也有自己的問題瞻颂。讓我們更細(xì)致地探討這個問題。
缺陷 #3 不可以調(diào)用同樣的Hooks兩次
另一種useState的提案是使用Symbol郑象,這樣就不會有沖突贡这,是吧?
// ?? This is NOT the React Hooks API
const nameKey = Symbol();
const surnameKey = Symbol();
const widthKey = Symbol();
function Form() {
// We pass some kind of state key to useState()
const [name, setName] = useState(nameKey);
const [surname, setSurname] = useState(surnameKey);
const [width, setWidth] = useState(widthKey);
// ...
這個提案似乎像提取useWindowWidth
Hook:
// ?? This is NOT the React Hooks API
function Form() {
// ...
const width = useWindowWidth();
// ...
}
/*********************
* useWindowWidth.js *
********************/
const widthKey = Symbol();
function useWindowWidth() {
const [width, setWidth] = useState(widthKey);
// ...
return width;
}
但是我們嘗試提取對于輸入的處理厂榛,那就不行了:
// ?? This is NOT the React Hooks API
function Form() {
// ...
const name = useFormInput();
const surname = useFormInput();
// ...
return (
<>
<input {...name} />
<input {...surname} />
{/* ... */}
</>
)
}
/*******************
* useFormInput.js *
******************/
const valueKey = Symbol();
function useFormInput() {
const [value, setValue] = useState(valueKey);
return {
value,
onChange(e) {
setValue(e.target.value);
},
};
}
你能發(fā)現(xiàn)這個Bug嗎盖矫?
我們兩次調(diào)用useFormInput但我們的useFormInput總是使用相同的鍵調(diào)用useState。所以我們有效地做了類似的事情:
const [name, setName] = useState(valueKey);
const [surname, setSurname] = useState(valueKey);
這就是我們再次發(fā)生沖突的方式击奶。
實際上辈双,Hooks提議沒有這個問題,因為每次調(diào)用useState都會獲得自己的隔離狀態(tài)柜砾。依賴調(diào)用索引可以使我們免于擔(dān)心名稱沖突湃望。
缺陷 #4 Diamond問題
這在技術(shù)上與前一個相同,但值得一提的是它的臭名昭著痰驱。它甚至在維基百科也有所描述证芭。
我們自己的mixin系統(tǒng)遭受了它。
像useWindowWidth和useOnlineStatus這樣的兩個自定義Hook可能想要使用相同的自定義Hook担映,例如useSubscription:
function StatusMessage() {
const width = useWindowWidth();
const isOnline = useNetworkStatus();
return (
<>
<p>Window width is {width}</p>
<p>You are {isOnline ? 'online' : 'offline'}</p>
</>
);
}
function useSubscription(subscribe, unsubscribe, getValue) {
const [state, setState] = useState(getValue());
useEffect(() => {
const handleChange = () => setState(getValue());
subscribe(handleChange);
return () => unsubscribe(handleChange);
});
return state;
}
function useWindowWidth() {
const width = useSubscription(
handler => window.addEventListener('resize', handler),
handler => window.removeEventListener('resize', handler),
() => window.innerWidth
);
return width;
}
function useNetworkStatus() {
const isOnline = useSubscription(
handler => {
window.addEventListener('online', handler);
window.addEventListener('offline', handler);
},
handler => {
window.removeEventListener('online', handler);
window.removeEventListener('offline', handler);
},
() => navigator.onLine
);
return isOnline;
}
這是一個完全有效的用例废士。對于自定義Hook作者來說,啟動或停止使用另一個自定義Hook應(yīng)該是安全的蝇完,而不必?fù)?dān)心它是否已在鏈中某處“已經(jīng)使用”官硝。 實際上,除非你在每次更改時使用Hook審核每個組件短蜕,否則你永遠(yuǎn)無法了解整個鏈泛源。
這是我們的“鉆石”:??
/ useWindowWidth() \ / useState() ?? Clash
Status useSubscription()
\ useNetworkStatus() / \ useEffect() ?? Clash
依賴于調(diào)用順序的方案可以解決:
/ useState() ? #1. State
/ useWindowWidth() -> useSubscription()
/ \ useEffect() ? #2. Effect
Status
\ / useState() ? #3. State
\ useNetworkStatus() -> useSubscription()
\ useEffect() ? #4. Effect
函數(shù)調(diào)用沒有“鉆石”問題,因為它們形成了一個樹忿危。 ??
缺陷#5:復(fù)制粘貼破壞事物
許我們可以通過引入某種命名空間來挽救關(guān)鍵的state提案。有幾種不同的方法可以做到這一點没龙。
一種方法是使用閉包隔離狀態(tài)鍵铺厨。這將要求您“實例化”自定義Hook并在每個Hook周圍添加一個函數(shù)包裝器:
/*******************
* useFormInput.js *
******************/
function createUseFormInput() {
// Unique per instantiation
const valueKey = Symbol();
return function useFormInput() {
const [value, setValue] = useState(valueKey);
return {
value,
onChange(e) {
setValue(e.target.value);
},
};
}
}
這種做法相當(dāng)?shù)暮荨?Hooks的設(shè)計目標(biāo)之一是避免使用高階組件和渲染props所普遍存在的深層嵌套功能樣式缎玫。在這里,我們必須在使用之前“實例化”任何自定義Hook解滓,并在組件的主體中使用生成的函數(shù)赃磨。這并不比無條件地調(diào)用Hook簡單得多。
此外洼裤,您必須重復(fù)兩次組件中使用的每個自定義Hook邻辉。一旦進(jìn)入頂級范圍(或者在我們編寫自定義Hook時在函數(shù)范圍內(nèi)),并且在實際調(diào)用站點一次腮鞍。這意味著即使是小的更改值骇,您也必須在渲染和頂級聲明之間跳轉(zhuǎn):
// ?? This is NOT the React Hooks API
const useNameInput = createUseFormInput();
const useSurnameInput = createUseFormInput();
function Form() {
// ...
const name = useNameFormInput();
const surname = useNameFormInput();
// ...
}
你還需要非常精確地說出他們的名字。你總是會有“兩個級別”的名字 - 像createUseFormInput這樣的工廠和像useNameFormInput和useSurnameFormInput這樣的實例化Hook移国。
如果你兩次調(diào)用相同的自定義Hook“實例”吱瘩,你會發(fā)生狀態(tài)沖突。事實上迹缀,上面的代碼有這個錯誤 - 你注意到了嗎使碾?它應(yīng)該是:
const name = useNameFormInput();
const surname = useSurnameFormInput(); // Not useNameFormInput!
這些問題并非不可克服,但我認(rèn)為它們會比遵循“鉤子規(guī)則”有更多的問題祝懂。
重要的是票摇,它們打破了復(fù)制粘貼的期望。在沒有額外的封裝包裝的情況下提取自定義Hook仍然可以使用這種方法砚蓬,但只能在您調(diào)用它兩次之前矢门。這就是它產(chǎn)生沖突的時候。)當(dāng)一個API看起來有效但是當(dāng)你意識到在鏈條的某個地方存在沖突時怜械,會強迫你把所有的東西包裹起來颅和,這是不幸的。
缺陷#6:我們?nèi)匀恍枰粋€Linter
有另一種方法可以避免與鍵控狀態(tài)發(fā)生沖突缕允。如果你知道它峡扩,你可能真的很生氣我仍然沒有承認(rèn)它!抱歉障本。
我們的想法是每次編寫自定義Hook時都可以編寫密鑰教届。像這樣的東西:
// ?? This is NOT the React Hooks API
function Form() {
// ...
const name = useFormInput('name');
const surname = useFormInput('surname');
// ...
return (
<>
<input {...name} />
<input {...surname} />
{/* ... */}
</>
)
}
function useFormInput(formInputKey) {
const [value, setValue] = useState('useFormInput(' + formInputKey + ').value');
return {
value,
onChange(e) {
setValue(e.target.value);
},
};
}
出于不同的選擇,我最不喜歡這種方法驾霜。我不認(rèn)為這是有價值的案训。
傳遞非唯一或組合嚴(yán)密的密鑰的代碼會在多次調(diào)用Hook或與另一個Hook發(fā)生沖突之前工作。更糟糕的是粪糙,如果它是有條件的(我們試圖“修復(fù)”無條件的通話要求强霎,對吧?)蓉冈,我們甚至可能在以后遇到?jīng)_突城舞。
記住在自定義Hooks的所有層中傳遞密鑰似乎很不穩(wěn)定轩触,我們想要為此提供lint。他們會在運行時添加額外的工作(不要忘記他們需要作為鍵)家夺,并且每個都是針對包大小的剪紙脱柱。但是,如果我們不得不去皮拉馋,我們解決了什么問題榨为?
如果有條件地聲明狀態(tài)和效果是非常可取的煌茴,這可能是有意義的随闺。但在實踐中我發(fā)現(xiàn)它令人困惑。事實上景馁,我不記得有人要求有條件地定義this.state或componentDidMount板壮。
這段代碼到底意味著什么?
// ?? This is NOT the React Hooks API
function Counter(props) {
if (props.isActive) {
const [count, setCount] = useState('count');
return (
<p onClick={() => setCount(count + 1)}>
{count}
</p>;
);
}
return null;
}
props.isActive
為false
時合住,是否保留計數(shù)绰精?或者是否因為沒有調(diào)用useState('count')
而重置?
如果條件狀態(tài)得到保留透葛,那么effect呢笨使?
// ?? This is NOT the React Hooks API
function Counter(props) {
if (props.isActive) {
const [count, setCount] = useState('count');
useEffect(() => {
const id = setInterval(() => setCount(c => c + 1), 1000);
return () => clearInterval(id);
}, []);
return (
<p onClick={() => setCount(count + 1)}>
{count}
</p>;
);
}
return null;
}
它絕對不能在props.isActive是true第一次出現(xiàn)之前運行。但一旦它成為true僚害,它是否會停止運行硫椰?當(dāng)props.isActive為false時,間隔是否重置萨蚕?如果是這樣靶草,那令人困惑的 是,這種效果與狀態(tài)(我們說不會重置)的行為不同岳遥。如果效果繼續(xù)運行奕翔,那么如果在效果之外實際上不會使效果成為條件,那就太令人困惑了浩蓉。我們不是說我們想要條件去處理effect嗎派继?
如果在渲染期間我們沒有“使用”它時,狀態(tài)卻被重置捻艳,如果多個if分支包含useState('count')但在任何給定時間只運行一個會發(fā)生什么驾窟?這是有效的代碼嗎?開發(fā)人員是否期望從組件中提前返回以重置所有狀態(tài)认轨?如果我們真的想要重置狀態(tài)绅络,我們可以通過提取組件使其明確:
function Counter(props) {
if (props.isActive) {
// Clearly has its own state
return <TickingCounter />;
}
return null;
}
無論如何,這可能成為避免這些令人困惑的問題的“最佳實踐”。因此恩急,無論你選擇哪種方式來回答這些問題节视,我認(rèn)為有條件地聲明狀態(tài)和效果本身的語義最終會變得奇怪,以至于你可能想要對它進(jìn)行抨擊假栓。
如果我們無論如何都需要lint,正確組成鍵的要求就變成了“負(fù)載”霍掺。它并沒有給我們帶來任何我們想要做的事情匾荆。但是,放棄這個要求(并回到最初的提案)確實給我們帶來了一些東西杆烁。它使復(fù)制粘貼組件代碼成為一個自定義的Hook安全牙丽,沒有命名空間,減少了包大小的紙張切割兔魂,并解鎖了一個稍微高效的實現(xiàn)(不需要Map查找)烤芦。
小事累加也變得繁瑣。
缺陷#7:無法在掛鉤之間傳遞值
Hooks的最佳功能之一是可以在它們之間傳遞值析校。
以下是消息收件人選擇器的一個假設(shè)示例构罗,該示例顯示當(dāng)前選擇的朋友是否在線:
const friendList = [
{ id: 1, name: 'Phoebe' },
{ id: 2, name: 'Rachel' },
{ id: 3, name: 'Ross' },
];
function ChatRecipientPicker() {
const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);
return (
<>
<Circle color={isRecipientOnline ? 'green' : 'red'} />
<select
value={recipientID}
onChange={e => setRecipientID(Number(e.target.value))}
>
{friendList.map(friend => (
<option key={friend.id} value={friend.id}>
{friend.name}
</option>
))}
</select>
</>
);
}
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
const handleStatusChange = (status) => setIsOnline(status.isOnline);
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
當(dāng)更改收件人時,我們的useFriendStatus Hook將取消訂閱上一位朋友的狀態(tài)智玻,并訂閱下一位朋友遂唧。
這是有效的,因為我們可以將useState Hook的返回值傳遞給useFriendStatus Hook:
const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);
在Hooks之間傳遞值非常強大吊奢。例如盖彭,React Spring允許您創(chuàng)建一個相互“跟隨”的多個值的尾隨動畫:
const [{ pos1 }, set] = useSpring({ pos1: [0, 0], config: fast });
const [{ pos2 }] = useSpring({ pos2: pos1, config: slow });
const [{ pos3 }] = useSpring({ pos3: pos2, config: slow });
demo在這里。
將Hook初始化放入默認(rèn)參數(shù)值或在裝飾器表單中編寫Hook的提議使得很難表達(dá)這種邏輯页滚。
如果在函數(shù)體中沒有調(diào)用Hooks召边,則不能再在它們之間輕松傳遞值,在不創(chuàng)建多層組件的情況下轉(zhuǎn)換這些值裹驰,或者添加useMemo()來記憶中間計算隧熙。也無法在效果中輕松引用這些值,因為它們無法在閉包中捕獲它們邦马。有些方法可以解決這些問題贱鼻,但是它們要求您在精神上“匹配”輸入和輸出。這很棘手滋将,違反了React的直接風(fēng)格邻悬。
在Hooks之間傳遞價值是我們提案的核心。渲染props模式是你在沒有Hooks的情況下最接近它的方法随闽,但是如果沒有像Component Component那樣由于“錯誤的層次結(jié)構(gòu)”而具有大量語法干擾的東西父丰,你就無法獲得全部好處。鉤子將該層次結(jié)構(gòu)扁平化為傳遞值 - 函數(shù)調(diào)用是最簡單的方法。
缺陷#8:太多儀式
有許多提案屬于這一保護傘蛾扇。大多數(shù)人試圖避免Hooks對React的依賴感攘烛。有很多種方法可以做到這一點:通過制作內(nèi)置的Hooks于this
,使它們成為一個額外的參數(shù)镀首,你必須通過一切坟漱,等等。
我認(rèn)為Sebastian’s的回答比我描述的更好地解決了這個問題更哄,所以我鼓勵你查看它的第一部分(“注入模型”)芋齿。
我只想說程序員傾向于選擇try / catch進(jìn)行錯誤處理,以便通過每個函數(shù)傳遞錯誤代碼成翩。這就是為什么我們更喜歡帶有導(dǎo)入(或CommonJS要求)的ES模塊到AMD的“顯式”定義的原因觅捆,其中require被傳遞給我們。
// Anyone miss AMD?
define(['require', 'dependency1', 'dependency2'], function (require) {
var dependency1 = require('dependency1'),
var dependency2 = require('dependency2');
return function () {};
});
是的麻敌,對于模塊實際上并未在瀏覽器環(huán)境中同步加載的事實栅炒,AMD可能更“誠實”。但是一旦你了解到這一點术羔,編寫define
sandwitch就會成為一種無意識的苦差事赢赊。
try / catch,require和React Context API是我們希望如何為我們提供一些“環(huán)境”處理程序的實用示例聂示,而不是通過每個級別顯式線程化它--即使一般來說我們重視顯性域携。我認(rèn)為Hooks也是如此。
這類似于我們定義組件時鱼喉,我們只是從React中獲取Component秀鞭。如果我們?yōu)槊總€組件導(dǎo)出工廠,我們的代碼可能會與React脫鉤:
function createModal(React) {
return class Modal extends React.Component {
// ...
};
}
但在實踐中扛禽,這最終只是一個惱人的間接锋边。當(dāng)我們真的想要用其他東西存根React時,我們總是可以在模塊系統(tǒng)級別那樣做编曼。
這同樣適用于Hooks豆巨。盡管如此,正如Sebastian’s的回答所提到的那樣掐场,技術(shù)上可以將從反應(yīng)中導(dǎo)出的Hook重定向到不同的實現(xiàn)往扔。 (我之前的一篇文章提到過。)
強加更多儀式的另一種方法是制作Hooksmonadic(元)或添加像React.createHook()這樣的一流概念熊户。除了運行時開銷之外萍膛,任何添加包裝器的解決方案都會失去使用普通函數(shù)的巨大好處:它們就像調(diào)試一樣容易。
普通函數(shù)允許您使用調(diào)試器進(jìn)入和退出嚷堡,中間沒有任何庫代碼蝗罗,并且可以準(zhǔn)確地查看值如何在組件體內(nèi)流動。間接使這很困難。在精神上類似于高階組件(“裝飾器”鉤子)或渲染props的解決方案有著同樣的問題串塑。間接的使靜態(tài)類型變得復(fù)雜沼琉。
正如我之前提到的,這篇文章并非旨在詳盡無遺桩匪。不同的提案還有其他有趣的問題打瘪。其中一些更加模糊(例如,與并發(fā)或高級編譯技術(shù)相關(guān))傻昙,并且可能是未來另一篇博客文章的主題瑟慈。
鉤子也不完美,但它是解決這些問題的最佳權(quán)衡屋匕。還有一些我們仍然需要修復(fù)的東西,并且存在著使用Hook比使用類更尷尬的東西借杰。這也是另一篇博文的主題过吻。
無論我是否覆蓋了您最喜歡的替代提議,我希望這篇文章能夠幫助我們了解我們的思維過程以及我們在選擇API時考慮的標(biāo)準(zhǔn)蔗衡。正如您所看到的纤虽,很多(例如確保復(fù)制粘貼,移動代碼绞惦,添加和刪除依賴項按預(yù)期工作)與優(yōu)化更改有關(guān)逼纸。我希望React用戶會欣賞這些方面。