最初你可能會不喜歡 Hooks:
它們就像一段音樂,只有經過幾次用心聆聽才會慢慢愛上:
當你閱讀文檔時,不要錯過關于[最重要的部分](https://reactjs.org/docs/hooks-custom.html)——創造屬于你自己的 Hooks!太多的人糾結于反對我們的觀點(class 學習成本高等)以至于錯過了 Hooks 更重要的一面,Hooks 像`functional mixins`,可以讓你創造和搭建屬于自己的 Hook。
Hooks[受啟發于一些現有技術](https://reactjs.org/docs/hooks-faq.html#what-is-the-prior-art-for-hooks),但在 Sebastian 和團隊分享他的想法之后,我才知道這些。不幸的是,這些 API 和現在在用的之間的關聯很容易被忽略,通過這篇文章,我希望可以幫助更多的人理解 Hooks 提案中爭議較大的點。
**接下來的部分需要你知道 Hook API 的`useState`和如何寫自定義 Hook。如果你還不懂,可以看看早先的鏈接。**
(免責說明:文章的觀點僅代表個人想法,與 React 團隊無關。話題大且復雜,其中可能有錯誤的觀點。)
* * *
一開始當你學習時你可能會震驚,Hooks 重渲染時是依賴于固定順序調用的,這里有[說明](https://reactjs.org/docs/hooks-rules.html)。
這個決定顯然是有爭議的,這也是為什么會有人[反對我們的提案](https://www.reddit.com/r/reactjs/comments/9xs2r6/sebmarkbages_response_to_hooks_rfc_feedback/e9wh4um/)。我們會在恰當的時機發布這個提案,當我們覺得文檔和講座可以足夠好的描繪它時。
如果你在關注 Hooks API 的某些點,我建議你閱讀下 Sebastian 對 1000+ 評論 RFC 的[全部回復](https://github.com/reactjs/rfcs/pull/68#issuecomment-439314884),**足夠透澈但內容非常多,我可能會將評論中的每一段都變成自己的博客文章。(事實上,我已經[做過](https://overreacted.io/zh-hans/how-does-setstate-know-what-to-do/)一次!)**
我今天要關注一個具體部分。你可能還記得,每個 Hook 可以在組件里被多次使用,例如,我們可以用`useState`聲明[多個state](https://reactjs.org/docs/hooks-state.html#tip-using-multiple-state-variables):
~~~jsx
function Form() {
const [name, setName] = useState('Mary'); // State 變量 1 const [surname, setSurname] = useState('Poppins'); // State 變量 2 const [width, setWidth] = useState(window.innerWidth); // State 變量 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>
</>
);
}
~~~
注意我們用數組解構語法來命名`useState()`返回的 state 變量,但這些變量不會連接到 React 組件上。相反,這個例子中,**React 將`name`視為「第一個 state 變量」,`surname`視為「第二個 state 變量」,以此類推**。它們在重新渲染時用*順序調用*來保證被正確識別。[這篇文章](https://juejin.im/post/5bfa929551882524cb6f413b)詳細的解釋了原因。
表面上看,依賴于順序調用只是*感覺有問題*,直覺是一個有用的信號,但它有時會誤導我們 —— 特別是當我們還沒有完全消化困惑的問題。**這篇文章,我會提到幾個經常有人提出修改 Hooks 的方案,及它們存在的問題**。
* * *
這篇文章不會詳盡無遺,如你所見,我們已經看過十幾種至數百種不同的替代方案,我們一直在[考慮](https://github.com/reactjs/react-future)替換組件 API。
諸如此類的博客很棘手,因為即使你涉及了一百種替代方案,也有人強行提出一個來:「哈哈,你沒有想到*這個*!」
在實踐中,不同替代方案提到的問題會有很多重復,我不會列舉*所有*建議的 API(這需要花費數月時間),而是通過幾個具體示例展示最常見的問題,更多的問題就考驗讀者舉一反三的能力了。??
*這不是說 Hooks 就是完美的*,但是一旦你了解其他解決方案的缺陷,你可能會發現 Hooks 的設計是有道理的。
* * *
### [](https://overreacted.io/zh-hans/why-do-hooks-rely-on-call-order/#%E7%BC%BA%E9%99%B7-1%EF%BC%9A%E6%97%A0%E6%B3%95%E6%8F%90%E5%8F%96-custom-hook)缺陷 #1:無法提取 custom hook
出乎意料的是,大多數替代方案完全沒有提到[custom hooks](https://reactjs.org/docs/hooks-custom.html)。可能是因為我們在「motivation」文檔中沒有足夠強調 custom hooks,不過在弄懂 Hooks 基本原理之前,這是很難做到的。就像雞和蛋問題,但很大程度上 custom hooks 是提案的重點。
例如:有個替代方案是限制一個組件調用多次`useState()`,你可以把 state 放在一個對象里,這樣還可以兼容 class 不是更好嗎?
~~~jsx
function Form() {
const [state, setState] = useState({
name: 'Mary',
surname: 'Poppins',
width: window.innerWidth,
});
// ...
}
~~~
要清楚,Hooks 是允許這種風格寫的,你不必將 state 拆分成一堆 state 變量(請參閱參見問題解答中的[建議](https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables))。
但支持多次調用`useState()`的關鍵在于,你可以從組件中提取出部分有狀態邏輯(state + effect)到 custom hooks 中,同時可以單獨使用本地 state 和 effects:
~~~jsx
function Form() {
// 在組件內直接定義一些 state 變量
const [name, setName] = useState('Mary');
const [surname, setSurname] = useState('Poppins');
// 我們將部分 state 和 effects 移至 custom hook const width = useWindowWidth(); // ...
}
function useWindowWidth() {
// 在 custom hook 內定義一些 state 變量
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
// ...
});
return width;
}
~~~
如果你只允許每個組件調用一次`useState()`,你將失去用 custom hook 引入 state 能力,這就是 custom hooks 的關鍵。
### [](https://overreacted.io/zh-hans/why-do-hooks-rely-on-call-order/#%E7%BC%BA%E9%99%B7-2-%E5%91%BD%E5%90%8D%E5%86%B2%E7%AA%81)缺陷 #2: 命名沖突
一個常見的建議是讓組件內`useState()`接收一個唯一標識 key 參數(string 等)區分 state 變量。
和這主意有些出入,但看起來大致像這樣:
~~~jsx
// ?? 這不是 React Hooks API
function Form() {
// 我們傳幾種 state key 給 useState()
const [name, setName] = useState('name');
const [surname, setSurname] = useState('surname');
const [width, setWidth] = useState('width');
// ...
~~~
這試圖擺脫依賴順序調用(顯示 key),但引入了另外一個問題 —— 命名沖突。
當然除了錯誤之外,你可能無法在同一個組件調用兩次`useState('name')`,這種偶然發生的可以歸結于其他任意 bug,但是,當你使用一個*custom hook*時,你總會遇到想添加或移除 state 變量和 effects 的情況。
這個提議中,每當你在 custom hook 里添加一個新的 state 變量時,就有可能破壞使用它的任何組件(直接或者間接),因為*可能已經有同名的變量*位于組件內。
這是一個沒有[應變而優](https://juejin.im/post/5c665e44518825622f12e37c)的 API,當前代碼可能看起來總是「優雅的」,但應對需求變化時十分脆弱,我們應該從[錯誤](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html#mixins-cause-name-clashes)中吸取教訓。
實際中 Hooks 提案通過依賴順序調用來解決這個問題:即使兩個 Hooks 都用`name`state 變量,它們也會彼此隔離,每次調用`useState()`都會獲得獨立的 「內存單元」。
我們還有其他一些方法可以解決這個缺陷,但這些方法也有自身的缺陷。讓我們加深探索這個問題。
### [](https://overreacted.io/zh-hans/why-do-hooks-rely-on-call-order/#%E7%BC%BA%E9%99%B7-3%EF%BC%9A%E5%90%8C%E4%B8%80%E4%B8%AA-hook-%E6%97%A0%E6%B3%95%E8%B0%83%E7%94%A8%E4%B8%A4%E6%AC%A1)缺陷 #3:同一個 Hook 無法調用兩次
給`useState`「加key」的另一種衍生提案是使用像 Symbol 這樣的東西,這樣就不沖突了對吧?
~~~jsx
// ?? 這不是 React Hooks API
const nameKey = Symbol();
const surnameKey = Symbol();
const widthKey = Symbol();
function Form() {
// 我們傳幾種state key給useState()
const [name, setName] = useState(nameKey);
const [surname, setSurname] = useState(surnameKey);
const [width, setWidth] = useState(widthKey);
// ...
~~~
這個提案看上去對提取出來的`useWindowWidth`Hook 有效:
~~~jsx
// ?? 這不是 React Hooks API
function Form() {
// ...
const width = useWindowWidth(); // ...
}
/*********************
* useWindowWidth.js *
********************/
const widthKey = Symbol(); function useWindowWidth() { const [width, setWidth] = useState(widthKey); // ... return width;}
~~~
但如果嘗試提取出來的 input handling,它會失敗:
~~~jsx
// ?? 這不是 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); }, };}
~~~
(我承認`useFormInput()`Hook 不是特別好用,但你可以想象下它處理諸如驗證和 dirty state 標志之類,如[Formik](https://github.com/jaredpalmer/formik)。)
你能發現這個bug嗎?
我們調用`useFormInput()`兩次,但`useFormInput()`總是用同一個 key 調用`useState()`,就像這樣:
~~~jsx
const [name, setName] = useState(valueKey);
const [surname, setSurname] = useState(valueKey);
~~~
我們再次發生了沖突。
實際中 Hooks 提案沒有這種問題,因為**每次*調用*`useState()`會獲得單獨的state**。依賴于固定順序調用使我們免于擔心命名沖突。
### [](https://overreacted.io/zh-hans/why-do-hooks-rely-on-call-order/#%E7%BC%BA%E9%99%B7-4%EF%BC%9A%E9%92%BB%E7%9F%B3%E9%97%AE%E9%A2%98%E5%A4%9A%E5%B1%82%E7%BB%A7%E6%89%BF%E9%97%AE%E9%A2%98)缺陷 #4:鉆石問題(多層繼承問題)
從技術上來說這個和上一個缺陷相同,但它的臭名值得說說,甚至維基百科都有介紹。(有些時候還被稱為「致命的死亡鉆石」 —— cool!)
我們自己的 mixin 系統就[受到了傷害](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html#mixins-cause-name-clashes)。
比如`useWindowWidth()`和`useNetworkStatus()`這兩個 custom hooks 可能要用像`useSubscription()`這樣的 custom hook,如下:
~~~jsx
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;
}
~~~
這是一個真實可運行的示例。**custom hook 作者準備或停止使用另一個 custom hook 應該是要安全的,而不必擔心它是否已在鏈中某處「被用過了」**。
(作為反例,遺留的 React`createClass()`的 mixins 不允許你這樣做,有時你會有兩個 mixins,它們都是你想要的,但由于擴展了同一個 「base」 mixin,因此互不兼容。)
這是我們的 「鉆石」:??
~~~text
/ useWindowWidth() \ / useState() ?? Clash
Status useSubscription()
\ useNetworkStatus() / \ useEffect() ?? Clash
~~~
依賴于固定的順序調用很自然的解決了它:
~~~text
/ useState() ? #1. State
/ useWindowWidth() -> useSubscription()
/ \ useEffect() ? #2. Effect
Status
\ / useState() ? #3. State
\ useNetworkStatus() -> useSubscription()
\ useEffect() ? #4. Effect
~~~
函數調用不會有「鉆石」問題,因為它們會形成樹狀結構。??
### [](https://overreacted.io/zh-hans/why-do-hooks-rely-on-call-order/#%E7%BC%BA%E9%99%B7-5%EF%BC%9A%E5%A4%8D%E5%88%B6%E7%B2%98%E8%B4%B4%E7%9A%84%E4%B8%BB%E6%84%8F%E8%A2%AB%E6%89%93%E4%B9%B1)缺陷 #5:復制粘貼的主意被打亂
或許我們可以通過引入某種命名空間來挽救給 state 加「key」提議,有幾種不同的方法可以做到這一點。
一種方法是使用閉包隔離 state 的 key,這需要你在 「實例化」 custom hooks時給每個 hook 裹上一層 function:
~~~jsx
/*******************
* useFormInput.js *
******************/
function createUseFormInput() {
// 每次實例化都唯一 const valueKey = Symbol();
return function useFormInput() {
const [value, setValue] = useState(valueKey);
return {
value,
onChange(e) {
setValue(e.target.value);
},
};
}
}
~~~
這種作法非常繁瑣,Hooks 的設計目標之一就是避免使用高階組件和 render props 的深層嵌套函數。在這里,我們不得不在使用*任何*custom hook 時進行「實例化」 —— 而且在組件主體中只能單次使用生產的函數,這比直接調用 Hooks 麻煩好多。
另外,你不得不操作兩次才能使組件用上 custom hook。一次在最頂層(或在編寫 custom hook 時的函數里頭),還有一次是最終的調用。這意味著即使一個很小的改動,你也得在頂層聲明和render函數間來回跳轉:
~~~jsx
// ?? 這不是 React Hooks API
const useNameFormInput = createUseFormInput();const useSurnameFormInput = createUseFormInput();
function Form() {
// ...
const name = useNameFormInput(); const surname = useNameFormInput(); // ...
}
~~~
你還需要非常精確的命名,總是需要考慮「兩層」命名 —— 像`createUseFormInput`這樣的工廠函數和`useNameFormInput`、`useSurnameFormInput`這樣的實例 Hooks。
如果你同時調用兩次相同的 custom hook 「實例」,你會發生state沖突。事實上,上面的代碼就是這種錯誤 —— 發現了嗎? 它應該為:
~~~jsx
const name = useNameFormInput();
const surname = useSurnameFormInput(); // Not useNameFormInput!
~~~
這些問題并非不可克服,但我認為它們會比遵守[Hooks規則](https://reactjs.org/docs/hooks-rules.html)的阻力大些。
重要的是,它們打破了復制粘貼的小算盤。在沒有封裝外層的情況下這種 custom hook 仍然可以使用,但它們只可以被調用一次(這在使用時會產生問題)。不幸的是,當一個API看起來可以正常運行,一旦你意識到在鏈的某個地方出現了沖突時,就不得不把所有定義好的東西包起來了。
### [](https://overreacted.io/zh-hans/why-do-hooks-rely-on-call-order/#%E7%BC%BA%E9%99%B7-6%EF%BC%9A%E6%88%91%E4%BB%AC%E4%BB%8D%E7%84%B6%E9%9C%80%E8%A6%81%E4%B8%80%E4%B8%AA%E4%BB%A3%E7%A0%81%E6%A3%80%E6%9F%A5%E5%B7%A5%E5%85%B7)缺陷 #6:我們仍然需要一個代碼檢查工具
還有另外一種使用密鑰state來避免沖突的方法,如果你知道,可能會真的很生氣,因為我不看好它,抱歉。
這個主意就是每次寫 custom hook 時*組合*一個密鑰,就像這樣:
~~~jsx
// ?? 這不是 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);
},
};
}
~~~
和其他替代提議比,我最不喜歡這個,我覺得它沒有什么價值。
一個 Hook 經過多次調用或者與其他 Hook 沖突之后,代碼可能*意外產出*非唯一或合成無效密鑰進行傳遞。更糟糕的是,如果它是在某些條件下發生的(我們會試圖 「修復」 它對吧?),可能在一段時間后才發生沖突。
我們想提醒大家,記住所有通過密鑰來標記的 custom hooks 都很脆弱,它們不僅增加了運行時的工作量(別忘了它們要轉成*密鑰*),而且會漸漸增大 bundle 大小。**但如果說我們非要提醒一個問題,是哪個問題呢**?
如果非要在條件判斷里聲明 state 和 effects,這種方法可能是有作用的,但按過去經驗來說,我發現它令人困惑。事實上,我不記得有人會在條件判斷里定義`this.state`或者`componentMount`的。
這段代碼到底意味著什么?
~~~jsx
// ?? 這不是 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`時`count`是否被保留?或者由于`useState('count')`沒有被調用而重置`count`?
如果條件為保留 state,effect 又會發生什么?
~~~jsx
// ?? 這不是 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`*之前*運行,但一旦變成`true`,它會停止運行嗎?當`props.isActive`轉變為`false`時 interval 會重置嗎?如果是這樣,effect 與 state(我們說不重置時) 的行為不同令人困惑。如果 effect 繼續運行,那么 effect 外層的`if`不再控制 effect,這也令人感到困惑,我們不是說我們想要基于條件控制的 effects 嗎?
如果在渲染期間我們沒有「使用」 state 但 它卻被重置,如果有多個`if`分支包含`useState('count')`但只有其中一個會在給定時間里運行,會發生什么?這是有效的代碼嗎?如果我們的核心思想是 「以密鑰分布」,那為什么要 「丟棄」 它?開發人員是否希望在這之后從組件中提前`return`以重置所有 state 呢? 其實如果我們真的需要重置state,我們可以通過提取組件使其明確:
~~~jsx
function Counter(props) {
if (props.isActive) {
// 清晰地知道它有自己的 state
return <TickingCounter />;
}
return null;
}
~~~
無論如何這可能成為是解決這些困惑問題的「最佳實踐」,所以不管你選擇哪種方式去解釋,我覺得條件里*聲明*state 和 effect 的語義怎樣都很怪異,你可能會不知不覺的感受到。
如果還要提醒的是 —— 正確地組合密鑰的需求會變成「負擔」,它并沒有給我們帶來任何想要的。但是,放棄這個需求(并回到最初的提案)確實給我們帶來了一些東西,它使組件代碼能夠安全地復制粘貼到一個 custom hook 中,且不需要命名空間,減小 bundle 大小及輕微的效率提升(不需要Map查找)。
慢慢理解。
### [](https://overreacted.io/zh-hans/why-do-hooks-rely-on-call-order/#%E7%BC%BA%E9%99%B7-7%EF%BC%9Ahooks-%E4%B9%8B%E9%97%B4%E6%97%A0%E6%B3%95%E4%BC%A0%E5%80%BC)缺陷 #7:Hooks 之間無法傳值
Hooks 有個最好的功能就是可以在它們之間傳值。
以下是一個選擇信息收件人的模擬示例,它顯示了當前選擇的好友是否在線:
~~~jsx
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;
}
~~~
當改變收件人時,`useFriendStatus`Hook 就會退訂上一個好友的狀態,訂閱接下來的這個。
這是可行的,因為我們可以將`useState()`Hook 返回的值傳給`useFriendStatus()`Hook:
~~~jsx
const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);
~~~
Hooks之間傳值非常有用。例如:[React Spring](https://medium.com/@drcmda/hooks-in-react-spring-a-tutorial-c6c436ad7ee4)可以創建一個尾隨動畫,其中多個值彼此「跟隨」:
~~~jsx
const [{ pos1 }, set] = useSpring({ pos1: [0, 0], config: fast });
const [{ pos2 }] = useSpring({ pos2: pos1, config: slow });
const [{ pos3 }] = useSpring({ pos3: pos2, config: slow });
~~~
(這是[demo](https://codesandbox.io/s/ppxnl191zx)。)
在Hook初始化時添加默認參數或者將Hook寫在裝飾器表單中的提議,很難實現這種情況的邏輯。
如果不在函數體內調用 Hooks,就不可以輕松地在它們之間傳值了。你可以改變這些值結構,讓它們不需要在多層組件之間傳遞,也可以用`useMemo`來存儲計算結果。但你也無法在 effects 中引用這些值,因為它們無法在閉包中被獲取到。有些方法可以通過某些約定來解決這些問題,但它們需要你在心里「核算」輸入和輸出,這違背了 React 直接了當的風格。
在 Hooks 之間傳值是我們提案的核心,Render props 模式在沒有 Hooks 時是你最先能想到的,但像[Component Component](https://ui.reach.tech/component-component)這樣的庫,是無法適用于你遇到的所有場景的,它由于「錯誤的層次結構」存在大量的語法干擾。Hooks 用扁平化層次結構來實現傳值 —— 且函數調用是最簡單的傳值方式。
### [](https://overreacted.io/zh-hans/why-do-hooks-rely-on-call-order/#%E7%BC%BA%E9%99%B7-8%EF%BC%9A%E6%AD%A5%E9%AA%A4%E7%B9%81%E7%90%90)缺陷 #8:步驟繁瑣
有許多提議處于這種范疇里。他們盡可能的想讓React擺脫對 Hooks 的依賴感,大多數方法是這么做的:讓`this`擁有內置 Hooks,使它們變成額外的參數在React中無處不在,等等等。
我覺得[Sebastian的回答](https://github.com/reactjs/rfcs/pull/68#issuecomment-439314884)比我的描述,更能說服這種方式,我建議你去了解下「注入模型」。
我只想說這和程序員傾向于用`try`/`catch`捕獲方法中的錯誤代碼是一樣的道理,同樣對比 AMD由我們自己傳入`require`的「顯示」聲明,我們更喜歡`import`(或者 CommonJS`require`) 的 ES模塊。
~~~jsx
// 有誰想念 AMD?
define(['require', 'dependency1', 'dependency2'], function (require) {
var dependency1 = require('dependency1'),
var dependency2 = require('dependency2');
return function () {};
});
~~~
是的,AMD 可能更「誠實」 的陳述了在瀏覽器環境中模塊不是同步加載的,但當你知道了這個后,寫`define`三明治 就變成做無用功了。
`try`/`catch`、`require`和 React Context API都是我們更喜歡「環境」式體驗,多于直接聲明使用的真實例子(即使通常我們更喜歡直爽風格),我覺得 Hooks 也屬于這種。
這類似于當我們聲明組件時,就像從`React`抓個`Component`過來。如果我們用工廠的方式導出每個組件,可能我們的代碼會更解耦:
~~~jsx
function createModal(React) {
return class Modal extends React.Component {
// ...
};
}
~~~
但在實際中,這最后會變得多此一舉而令人厭煩。當我們真的想以某種方式抓React時,我們應該在模塊系統層面上實現。
## 摘自
[ 為什么順序調用對 React Hooks 很重要](https://overreacted.io/zh-hans/why-do-hooks-rely-on-call-order/)
- 文檔說明
- 大廠面試題
- HTML
- 001.如何遍歷一個dom樹
- 002.為什么操作DOM會很慢
- 003.瀏覽器渲染HTML的步驟
- 004.DOM和JavaScript的關系
- JS
- 001.數組扁平化并去重排序
- 002.高階函數
- 003.sort() 對數組進行排序
- 004.call 、 apply 和bind的區別
- 006.0.1+0.2為什么等于0.30000000000000004
- 011.var、let、const 的區別及實現原理?
- 010.new操作符都做了什么
- 009.a.b.c.d 和 a['b']['c']['d'],哪個性能更高?
- 016.什么是防抖和節流?有什么區別?如何實現?
- 017.['1', '2', '3'].map(parseInt) what & why ?
- 018.為什么 for 循環嵌套順序會影響性能?
- 019.介紹模塊化發展歷程
- 020.push輸出問題
- 021.判斷數組的三個方法
- 022.全局作用域中,用 const 和 let 聲明的變量不在 window 上,那到底在哪里?如何去獲取?
- 023.輸出以下代碼的執行結果并解釋為什么
- 024.ES6 代碼轉成 ES5 代碼的實現思路是什么
- 025.為什么普通 for 循環的性能遠遠高于 forEach 的性能,請解釋其中的原因。
- 026.數組里面有10萬個數據,取第一個元素和第10萬個元素的時間相差多少
- 027.變量類型
- 028.原型和原型鏈
- 029.作用域和閉包
- 030. 異步
- 031.ES6/7 新標準的考查
- 024.事件冒泡/事件代理
- 025.手寫 XMLHttpRequest 不借助任何庫
- 026.什么是深拷貝?
- 0027.克隆數組的方法
- 0028.ES6之展開運算符(...)
- 0029.arguments
- 0030. requestAnimationFrame
- 0031.遞歸爆棧問題與解決
- 021.簡單改造下面的代碼,使之分別打印 10 和 20
- 032.箭頭函數與普通函數
- 033.去除掉html標簽字符串里的所有屬性
- 034.查找公共父節點
- 035.Promise
- 0036.JSON.stringify ()
- CSS
- 001. BFC
- 002.介紹下 BFC、IFC、GFC 和 FFC
- 003.分析比較 opacity: 0、visibility: hidden、display: none 優劣和適用場景
- 004.怎么讓一個 div 水平垂直居中
- 005.重排重繪
- 006.inline/block/inline-block的區別
- 007.選擇器的權重和優先級
- 008.盒模型
- 009.清除浮動
- 010.flex
- 011.nth-child和nth-of-type的區別
- 0012.overflow
- 0013.CSS3中translate、transform和translation的區別和聯系
- 0014.flex
- 0015.px、em、rem
- 0016.width:100%
- 網絡
- 001.講解下HTTPS的工作原理
- 002.介紹下 HTTPS 中間人攻擊
- 003.談談你對TCP三次握手和四次揮手的理解
- 004.A、B 機器正常連接后,B 機器突然重啟,問 A 此時處于 TCP 什么狀態
- 005.簡單講解一下http2的多路復用
- 006. 介紹下 http1.0、1.1、2.0 協議的區別?
- 007.永久性重定向(301)和臨時性重定向(302)對 SEO 有什么影響
- 008.URL從輸入到頁面展示的過程
- 009.接口如何防刷
- 010.http狀態碼?
- 0111.跨域/如何解決?
- 012.cookie 和 localStorage 有何區別?
- 013.Fetch API
- 014.跨域Ajax請求時是否帶Cookie的設置
- 0015.協商緩存和強緩存
- 性能優化
- 001.前后端分離的項目如何seo
- 002.性能優化的方法
- 003.防抖和節流
- React
- 001.React 中 setState 什么時候是同步的,什么時候是異步的?
- 002.Virtual DOM 真的比操作原生 DOM 快嗎?談談你的想法。
- 003.Hooks 的特別之處
- 004.元素和組件有什么區別?
- 005.什么是 Pure Components?
- 006.HTML 和 React 事件處理有什么區別?
- 007.如何將參數傳遞給事件處理程序或回調函數?
- 008.如何創建 refs?
- 009.什么是 forward refs?
- 010.什么是 Virtual DOM?
- 011.什么是受控組件、非受控組件?
- 012.什么是 Fragments ?
- 013.為什么React元素有一個$$typeof屬性?
- 014.如何在 React 中創建組件?
- 015.React 如何區分 Class 和 Function?
- 016.React 的狀態是什么?
- 017.React 中的 props 是什么?
- 018.狀態和屬性有什么區別?
- 019.如何在 JSX 回調中綁定方法或事件處理程序?
- 020.什么是 "key" 屬性,在元素數組中使用它們有什么好處?
- 021.為什么順序調用對 React Hooks 很重要?
- 022.setState如何知道該做什么?
- 023.hook規則?
- 024.Hooks 與 Class 中調用 setState 有不同的表現差異么?
- 025.useEffect
- 026.fiber的作用
- 027.context的作用?
- 028.setState何時同步何時異步?
- 029.react性能優化
- 030.fiber
- 031.React SSR
- 異步
- 001.介紹下promise
- 002.Async/Await 如何通過同步的方式實現異步
- 003.setTimeout、Promise、Async/Await 的區別
- 004.JS 異步解決方案的發展歷程以及優缺點
- 005.Promise 構造函數是同步執行還是異步執行,那么 then 方法呢?
- 006.模擬實現一個 Promise.finally
- 012.簡單手寫實現promise
- 015.用Promise對象實現的 Ajax
- 007.簡單實現async/await中的async函數
- 008.設計并實現 Promise.race()
- 009.Async/await
- 010.珠峰培訓promise
- git
- 001.提交但沒有push
- 002.gitignore沒有作用?
- Node
- 001.用nodejs,將base64轉化成png文件
- Koa
- 001.koa和express的區別
- 數據庫
- redux
- 001.redux 為什么要把 reducer 設計成純函數
- 002.在 React 中如何使用 Redux 的 connect() ?
- 003.mapStateToProps() 和 mapDispatchToProps() 之間有什么區別?
- 004.為什么 Redux 狀態函數稱為 reducers ?
- 005.如何在 Redux 中發起 AJAX 請求?
- 006.訪問 Redux Store 的正確方法是什么?
- 007.React Redux 中展示組件和容器組件之間的區別是什么?
- 008.Redux 中常量的用途是什么?
- 009.什么是 redux-saga?
- 設計模式
- 公司題目
- 001.餓了么
- 001.div垂直水平居中(flex、絕對定位)
- 002.React子父組件之間如何傳值
- 003.Emit事件怎么發,需要引入什么
- 004.介紹下React高階組件,和普通組件有什么區別
- 005.一個對象數組,每個子對象包含一個id和name,React如何渲染出全部的name
- 006.在哪個生命周期里寫
- 007.其中有幾個name不存在,通過異步接口獲取,如何做
- 008.渲染的時候key給什么值,可以使用index嗎,用id好還是index好
- 009.webpack如何配sass,需要配哪些loader
- 010.配css需要哪些loader
- 011.如何配置把js、css、html單獨打包成一個文件
- 012.監聽input的哪個事件,在什么時候觸發
- 013.兩個元素塊,一左一右,中間相距10像素
- 014.上下固定,中間滾動布局如何實現
- 016.取數組的最大值(ES5、ES6)
- 017.apply和call的區別
- 018.ES5和ES6有什么區別
- 019.some、every、find、filter、map、forEach有什么區別
- 020.上述數組隨機取數,每次返回的值都不一樣
- 021.如何找0-5的隨機數,95-99呢
- 022.頁面上有1萬個button如何綁定事件
- 023.如何判斷是button
- 024.頁面上生成一萬個button,并且綁定事件,如何做(JS原生操作DOM)
- 025.循環綁定時的index是多少,為什么,怎么解決
- 026.頁面上有一個input,還有一個p標簽,改變input后p標簽就跟著變化,如何處理
- 瀏覽器相關
- 001.性能優化
- 002.web安全
- 003.獲取瀏覽器大小
- 004.從輸入 URL 到頁面加載完成的過程中都發生了什么事情?
- 后端
- 001.分布式
- zuku
- 字節
- webpack
- webpack的打包原理是什么
- Webpack-- 常見面試題
- webscoket