極客時間-筆記:React Hooks 核心原理與實戰

認識react

  1. JSX 并不是一個新的模板語言,而可以認為是一個語法糖。
  2. React.createElement 作用就是創建一個組件的實例;參數:
  • 第一個參數表示組件的類型;
  • 第二個參數是傳給組件的屬性,也就是 props
  • 第三個以及后續所有的參數則是子組件。
React.createElement(
 "div",
 null,
 React.createElement(
   "button",
   { onClick: function onClick() {
       return setCount(count + 1);
     } },
   React.createElement(CountLabel, { count: count })
 )
);

理解hooks

  1. hooks的好處:簡化了邏輯復用。

如果用class組件實現邏輯復用需要封裝高階組件;有以下缺點:

  1. 代碼難理解,不直觀,很多人甚至寧愿重復代碼,也不愿用高階組件;
  2. 會增加很多額外的組件節點。每一個高階組件都會多一層節點,這就會給調試帶來很大的負擔。
  1. 在 Class 組件中,代碼是從技術角度組織在一起的,例如在 componentDidMount 中都去做一些初始化的事情。而在函數組件中,代碼是從業務角度組織在一起的,相關代碼能夠出現在集中的地方,從而更容易理解和維護。

內置 Hooks

  1. React 提供的 Hooks 其實非常少,一共只有 10 個,比如 useState、useEffect、useCallback、useMemo、useRef、useContext 等等。
  2. 副作用是指一段和當前執行結果無關的代碼。
  3. useEffect 讓我們能夠在下面四種時機去執行一個回調函數產生副作用:
  • 每次 render 后執行:不提供第二個依賴項參數。比如useEffect(() => {})。
  • 僅第一次 render 后執行:提供一個空數組作為依賴項。比如useEffect(() => {}, [])。
  • 第一次以及依賴項發生變化后執行:提供依賴項數組。比如useEffect(() => {}, [deps])。
  • 組件 unmount 后執行:返回一個回調函數。比如useEffect() => { return () => {} }, [])。
  1. Hooks 的使用規則包括以下兩個:
  • 只能在函數組件的頂級作用域使用;
  • 只能在函數組件或者其他 Hooks 中使用。
  1. useCallback解決事件處理函數的問題:
  • 不僅增加了系統的開銷
  • 每次創建新函數的方式會讓接收事件處理函數的組件,需要重新渲染。
  1. useCallback和useMemo做了同一件事情:建立了一個綁定某個結果到依賴數據的關系。只有當依賴變了,這個結果才需要被重新得到

用useMemo實現useCallback:
const myEventHandler = useMemo(() => {
// 返回一個函數作為緩存結果
return () => {
// 在這里進行事件處理
}
}, [dep1, dep2]);

問題:useCallback/useMemo 什么情況下使用?是所有情況?還是個別情況,例如計算量較大等情況?

  1. useRef:
  • 在多次渲染之間共享數據

使用 useRef 保存的數據一般是和 UI 的渲染無關的,因此當 ref 的值發生變化時,是不會觸發組件的重新渲染的,這也是 useRef 區別于 useState 的地方。

  • 保存某個 DOM 節點的引用

function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// current 屬性指向了真實的 input 這個 DOM 節點,從而可以調用 focus 方法
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
ref 這個屬性提供了獲得 DOM 節點的能力,并利用 useRef 保存了這個節點的應用。這樣的話,一旦 input 節點被渲染到界面上,那我們通過 inputEl.current 就能訪問到真實的 DOM 節點的實例了。

  1. useContext 定義全局狀態

和全局的變量去保存數據的區別?
就是為了能夠進行數據的綁定。當這個 Context 的數據發生變化時,使用這個數據的組件就能夠自動刷新。但如果沒有 Context,而是使用一個簡單的全局變量,就很難去實現了。

  1. useContext 的缺點:
  • 會讓調試變得困難,因為你很難跟蹤某個 Context 的變化究竟是如何產生的。
  • 讓組件的復用變得困難,因為一個組件如果使用了某個 Context,它就必須確保被用到的地方一定有這個 Context 的 Provider 在其父組件的路徑上。

所以在 React 的開發中,除了像 Theme、Language 等一目了然的需要全局設置的變量外,我們很少會使用 Context 來做太多數據的共享。需要再三強調的是,Context 更多的是提供了一個強大的機制,讓 React 應用具備定義全局的響應式數據的能力。

自定義hooks的使用場景

  • 抽取業務邏輯;
  • 封裝通用邏輯;
  • 監聽瀏覽器狀態;
  • 拆分復雜組件。

全局狀態管理:如何在函數組件中使用 Redux?

  1. 理解 Redux 的三個基本概念:State、Action 和 Reducer。
  • 其中 State 即 Store,一般就是一個純 JavaScript Object。
  • Action 也是一個 Object,用于描述發生的動作。
  • 而 Reducer 則是一個函數,接收 Action 和 State 并作為參數,通過計算得到新的 Store。

好處:1. 可預測性(Predictable):即給定一個初始狀態和一系列的 Action,一定能得到一致的結果,同時這也讓代碼更容易測試。2. 易于調試:可以跟蹤 Store 中數據的變化,甚至暫停和回放。因為每次 Action 產生的變化都會產生新的對象,而我們可以緩存這些對象用于調試。Redux 的基于瀏覽器插件的開發工具就是基于這個機制,非常有利于調試。

  1. React 和 Redux 共同使用時的單向數據流:
  2. 什么是異步action?Middleware 在 Action 真正到達 Reducer 之前提供的一個額外處理 Action 的機會:

Redux 中的 Action 不僅僅可以是一個 Object,它可以是任何東西,也可以是一個函數。利用這個機制,Redux 提供了 redux-thunk 這樣一個中間件,它如果發現接受到的 action 是一個函數,那么就不會傳遞給 Reducer,而是執行這個函數,并把 dispatch 作為參數傳給這個函數,從而在這個函數中你可以自由決定何時,如何發送 Action。

復雜狀態處理:如何保證狀態一致性?

  • 一個是狀態最小化原則,也就是說要避免冗余的狀態;
  • 另一個則是唯一數據源原則,避免中間狀態。

函數組件設計模式:如何應對復雜條件渲染場景?

  1. 容器模式:實現按條件執行 Hooks
  • 把條件判斷的結果放到兩個組件之中,確保真正 render UI 的組件收到的所有屬性都是有值的。
  • 還有一種做法,就是把判斷條件放到 Hooks 中去。
  1. 使用 render props 模式重用 UI 邏輯


    邏輯復用,自定義hook亦可

事件處理:如何創建自定義事件?

  1. React 原生事件的原理:合成事件(Synthetic Events)

由于虛擬 DOM 的存在,在 React 中即使綁定一個事件到原生的 DOM 節點,事件也并不是綁定在對應的節點上,而是所有的事件都是綁定在根節點上。然后由 React 統一監聽和管理,獲取事件后再分發到具體的虛擬 DOM 節點上。
在 React 17 之前,所有的事件都是綁定在 document 上的,而從 React 17 開始,所有的事件都綁定在整個 App 上的根節點上,這主要是為了以后頁面上可能存在多版本 React 的考慮。

  1. React 這么做的原因主要有兩個:
  • 虛擬 DOM render 的時候, DOM 很可能還沒有真實地 render 到頁面上,所以無法綁定事件。
  • React 可以屏蔽底層事件的細節,避免瀏覽器的兼容性問題。同時呢,對于 React Native 這種不是通過瀏覽器 render 的運行時,也能提供一致的 API。
  1. 由于瀏覽器事件的冒泡模型。無論事件在哪個節點被觸發, React 都可以通過事件的 srcElement這個屬性,知道它是從哪個節點開始發出的,這樣 React 就可以收集管理所有的事件,然后再以一致的 API 暴露出來。
  2. 雖然自定義事件和原生事件看上去類似,但是兩者的機制是完全不一樣的:
  • 原生事件是瀏覽器的機制;
  • 而自定義事件則是純粹的組件自己的行為,本質是一種回調函數機制。

路由管理:為什么每一個前端應用都需要使用路由機制?

  1. React Router 管理


    react router示例

這里需要注意,React Router 不僅支持瀏覽器,還支持 React Native,以及一些用 Web 實現的移動 App,所以它提供了多個 npm 模塊。

  1. BrowserRouter、Link、Route、Switch 等組件的用法及作用。
  • BrowserRouter:表示用標準的 URL 路徑去管理路由,比如 /my-page1 這樣的標準 URL 路徑。除此之外,還有 MemoryRouter,表示通過內存管理路由;HashRouter,標識通過 hash 管理路由。我們自己實現的例子其實就是用的 hash 來實現路由。
  • Link:定義一個導航鏈接,點擊時可以無刷新地改變頁面 URL,從而實現 React Router 控制的導航。
  • Route: 定義一條路由規則,可以指定匹配的路徑、要渲染的內容等等。
  • Switch:在默認情況下,所有匹配的 Route 節點都會被展示,但是 Switch 標記可以保證只有第一個匹配到的路由才會被渲染。
  1. 使用嵌套路由:實現二級導航頁面所謂嵌套路由,也稱為子路由,就是一個頁面組件內部,還需要通過 URL 上的信息來決定組件內部某個區域該如何顯示。
    嵌套路由示例
  2. 在 URL 中保存頁面狀態

一方面可以提升用戶體驗,另一方面也可以簡化頁面之間的交互

  1. 路由層面實現權限控制

我們完全可以利用前端路由的動態特性。你已經看到了,路由是通過 JSX 以聲明式的方式去定義的,這就意味著路由的定義規則是可以根據條件進行變化的,也就是所謂的動態路由。

jsx聲明routes

按需加載:如何提升應用打開速度?

  1. 如何實現按需加載?

使用 import 語句,定義按需加載的起始模塊語法是 import(someModule)。

webpack分包

react-loadable按需加載

  1. 使用 service worker 緩存前端資源
  • 緩存永遠不過期。你只要下載過一次,就永遠不需要再重新下載,除非主動刪除。
  • 永遠不會訪問過期的資源。換句話說,如果發布了一個新版本,那么你可以通過版本化的一些機制,來確保用戶訪問到的一定是最新的資源。

1.注冊 Service Worker


注冊service worker
  1. 在 Service Worker 安裝之后初始化緩存機制


    安裝
  2. 攔截請求
    攔截請求

    參考鏈接:cache mdn
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容