我嘗試重現 React 的 useState() Hook 并失去了工作機會
在 React 中創建自定義鉤子(hooks)是一個非常有價值的練習,甚至可以說是許多開發者的“啊哈”時刻。
劇透警告:第一次嘗試是在 Revolut 的一次高壓面試中,結果并不如意。
在這篇文章中,帶你一起回顧我重現著名的 useState() 鉤子的歷程,稱它為 useMyState()。同時,我也會給出一些建議,幫助你下次面試時避免失敗。
讓我們一起來探討哪里出了問題,我是如何重新拾起信心的,以及你如何避免犯同樣的錯誤。準備好迎接一些高級 React 的樂趣吧!??
夢寐以求的工作和艱難的面試
拿到 Revolut 的面試機會感覺就像中了彩票。我滿懷激動和信心,因為我花了數周時間打磨我的 React 和 TypeScript 技能。
我以為我已經掌握了一切——但面試讓我意識到,我的信心過于自滿。這個面試,我既興奮又緊張,然而很快,這次經歷變成了一次讓我永生難忘的挫敗。
在順利通過一些初步面試輪次后,我遇到了一個我沒有完全預料到的挑戰:“你能從頭開始重現 useState() 鉤子嗎?”
這個問題看似簡單,但其背后的意義深遠。我知道 useState() 在高層次上的工作原理,但在面試現場,面對時鐘滴答作響,要從頭構建它卻是另一回事。
任務:在壓力下重現 useState()
面試官解釋說,我需要創建一個自定義鉤子,模仿 React 的 useState() 的行為。
這個鉤子應該存儲狀態,允許狀態更新,并在狀態變化時觸發重新渲染。
聽起來很熟悉,對吧?我曾經讀過這類內容,甚至考慮過如何實現。但現在,在面試壓力下,一切似乎變得更加復雜。
關鍵問題是:我從未實際在現場實現過類似的東西。
概念在我腦海中很清晰,但在面對一個可能決定我職業生涯的人時,執行起來卻完全不同。
最終,我沒有完成這個任務……但后來我決定在沒有時間壓力的情況下再試一次,結果是這樣的。
為什么要重現 useState()?
你可能會問,“為什么要重現一個已經完美運作的東西?”嗯,這正是我為什么要做的原因——因為它確實運作得很好。
理解 useState() 的底層工作原理有助于你更好地掌握 React 的“魔法”。
此外,構建你自己的工具——即使它們已經存在——也是一種非常有成就感的體驗。
入門:useMyState() 背后的概念
在開始編碼之前,讓我們明確我們想要實現的目標。React 中的 useState() 鉤子:
- 返回一個狀態變量和一個用于更新該狀態的函數。
- 每當狀態更新時,重新渲染組件。
我們的 useMyState() 鉤子應該實現相同的功能。本質上,我們需要一種方式來存儲狀態,更新它,并在發生變化時觸發重新渲染。聽起來很簡單,對吧?嗯,這既簡單又復雜。
構建模塊:useState() 的底層工作原理
首先,讓我們討論一下 React 如何在內部管理狀態。當你調用 useState() 時,React 在內部通過一個稱為“鉤子狀態隊列”的機制來跟蹤你的狀態。
每當你調用更新函數(我們稱之為 setState)時,React 并不會立即更新狀態。相反,它會調度一次重新渲染。
在這次重新渲染期間,React 根據隊列計算新的狀態。
要復制這種行為,我們需要:
- 將狀態存儲在一個可以跨渲染持續存在的地方。
- 提供一個函數來更新這個狀態并觸發重新渲染。
創建 useMyState():編寫一些代碼!
我們可以這樣開始。首先,我們需要創建一個組件級別的狀態存儲。
由于 React 并不提供一種直接的方式來在渲染之間保持變量的持久性(除了使用類似 useState() 或 useRef() 的鉤子),我們需要使用 useRef() 來實現這一點。
function useMyState(initialValue) {
const stateRef = React.useRef(initialValue);
const [, forceRender] = React.useReducer(x => x + 1, 0);
const setState = (newValue) => {
stateRef.current = newValue;
forceRender();
};
return [stateRef.current, setState];
}
細分解釋
- useRef(initialValue): 這是我們的狀態所在。useRef() 可以在渲染之間保持值,而不會導致重新渲染。
- useReducer(x => x + 1, 0): 這是一個巧妙的技巧,用來強制組件重新渲染。我們不關心 x 的值,只是調用 forceRender() 會使 React 重新渲染我們的組件。
- setState(newValue): 我們更新 stateRef 的 current 屬性,然后觸發一次重新渲染。
就這樣!通過這三部分,我們就有了自己的 useState() 版本。但我們怎么知道它是否有效呢?讓我們測試一下。
測試 useMyState(): 讓它工作
讓我們看看這個鉤子的實際效果。這里有一個簡單的計數器組件,使用我們的 useMyState():
function Counter() {
const [count, setCount] = useMyState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
當你點擊“Increment”按鈕時,計數器應該增加,組件會重新渲染以反映新的計數。這就像使用 useState() 一樣。很棒吧?
處理多個狀態:擴展 useMyState()
現在,如果我們需要在同一個組件中管理多個狀態會怎么樣?React 的 useState() 可以優雅地處理這個問題,我們的 useMyState() 也可以。
function MultipleStatesComponent() {
const [count, setCount] = useMyState(0);
const [text, setText] = useMyState('Hello');
return (
<div>
<p>{count}</p>
<p>{text}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setText('World')}>Change Text</button>
</div>
);
}
這個組件管理兩個獨立的狀態變量,useMyState() 像 useState() 一樣處理它們。
總結
React 的 useState() 鉤子經過高度優化、經過實戰檢驗,能夠處理我們簡單的 useMyState() 可能忽略的邊緣情況。
盡管我未能在面試中重現它,但我能夠在一個平靜的環境中自己重新實現它,下次面試時,我會準備得更好。
重現這樣的工具并不是為了取代它們,而是為了學習它們的工作原理,從而更好地使用它們。
誰知道呢?你也許會在一個副項目中找到 useMyState() 的用武之地,或者只是把它當作一個有趣的技巧留在你的工具箱里。
祝你好運!