react教程要點記錄

描述UI

  • React 組件是一段可以 使用標簽進行擴展 的 JavaScript 函數。
  • React 組件是常規的 JavaScript 函數,但 組件的名稱必須以大寫字母開頭
  • 同一文件中,有且僅有一個默認導出,但可以有多個具名導出!
  • 為什么需要一個根標簽
    • JSX 雖然看起來很像 HTML,但在底層其實被轉化為了 JavaScript 對象,你不能在一個函數中返回多個對象,除非用一個數組把他們包裝起來。這就是為什么多個 JSX 標簽必須要用一個父元素或者 Fragment 來包裹。
  • 切勿將數字放在 && 左側.左側為0則會渲染為0
  • key 需要滿足的條件
    • key 值在兄弟節點之間必須是唯一的。 不過不要求全局唯一,在不同的數組中可以使用相同的 key。
    • key 值不能改變,否則就失去了使用 key 的意義!所以千萬不要在渲染時動態地生成 key。
  • 為什么需要key
    • React 里需要 key 和文件夾里的文件需要有文件名的道理是類似的。一個精心選擇的 key 值所能提供的信息遠遠不止于這個元素在數組中的位置。即使元素的位置在渲染的過程中發生了改變,它提供的 key 值也能讓 React 在整個生命周期中一直認得它。
    • 請不要在運行過程中動態地產生 key,像是 key={Math.random()} 這種方式。這會導致每次重新渲染后的 key 值都不一樣,從而使得所有的組件和 DOM 元素每次都要重新創建。這不僅會造成運行變慢的問題,更有可能導致用戶輸入的丟失。
  • 純函數的基本定義:
    • 只負責自己的任務。 它不會更改在該函數調用前就已存在的對象或變量。
    • 輸入相同,輸出也相同。 在輸入相同的情況下,對純函數來說應總是返回相同的結果。
  • React 假設你編寫的所有組件都是純函數。React 的渲染過程必須自始至終是純粹的。組件應該只返回它們的 JSX,而不改變在渲染前,就已存在的任何對象或變量 — 這將會使它們變得不純粹!
  • React 提供了 “嚴格模式”,在嚴格模式下開發時,它將會調用每個組件函數兩次。通過重復調用組件函數,嚴格模式有助于找到違反這些規則的組件。
  • 副作用:包括更新屏幕、啟動動畫、更改數據等,它們被稱為 副作用。它們是 “額外” 發生的事情,與渲染過程無關。
  • 在 React 中,副作用通常屬于 事件處理程序。事件處理程序是 React 在你執行某些操作(如單擊按鈕)時運行的函數。即使事件處理程序是在你的組件 內部 定義的,它們也不會在渲染期間運行! 因此事件處理程序無需是純函數
  • 如果你用盡一切辦法,仍無法為副作用找到合適的事件處理程序,你還可以調用組件中的 useEffect 方法將其附加到返回的 JSX 中。這會告訴 React 在渲染結束后執行它。然而,這種方法應該是你最后的手段

添加交互

  • 事件處理函數是執行副作用的最佳位置,數據的改變使用state存儲

  • 為什么需要useState

    • 局部變量無法在多次渲染中持久保存。當 React 再次渲染這個組件時,它會從頭開始渲染——不會考慮之前對局部變量的任何更改。
    • 更改局部變量不會觸發渲染。 React 沒有意識到它需要使用新數據再次渲染組件。
  • Hooks ——以 use 開頭的函數——只能在組件或自定義 Hook 的最頂層調用。

  • 如果你渲染同一個組件兩次,每個副本都會有完全隔離的 state

  • React 把更改提交到 DOM 上

    • 對于初次渲染, React 會使用 appendChild() DOM API 將其創建的所有 DOM 節點放在屏幕上。
    • 對于重渲染, React 將應用最少的必要操作(在渲染時計算!),以使得 DOM 與最新的渲染輸出相互匹配。
  • React 僅在渲染之間存在差異時才會更改 DOM 節點

  • 在渲染完成并且 React 更新 DOM 之后,瀏覽器就會重新繪制屏幕

  • 在一個 React 應用中一次屏幕更新都會發生以下三個步驟:

    1. 觸發
    • 組件的 初次渲染。
    • 組件(或者其祖先之一)的 狀態發生了改變
    1. 渲染。在您觸發渲染后,React 會調用您的組件來確定要在屏幕上顯示的內容。“渲染中” 即 React 在調用您的組件。
    • 在進行初次渲染時, React 會調用根組件。
    • 對于后續的渲染, React 會調用內部狀態更新觸發了渲染的函數組件。
    1. 提交。在渲染(調用)您的組件之后,React 將會修改 DOM。
    • 對于初次渲染, React 會使用 appendChild() DOM API 將其創建的所有 DOM 節點放在屏幕上。
    • 對于重渲染, React 將應用最少的必要操作(在渲染時計算!),以使得 DOM 與最新的渲染輸出相互匹配。
  • 設置 state 會觸發渲染(當你調用 useState 時)

  • 渲染會及時生成一張快照

    • “正在渲染” 就意味著 React 正在調用你的組件——一個函數。你從該函數返回的 JSX 就像是 UI 的一張及時的快照。它的 props、事件處理函數和內部變量都是 根據當前渲染時的 state 被計算出來的。
    • 返回的 UI “快照”是可交互的。它其中包括類似事件處理函數的邏輯,這些邏輯用于指定如何對輸入作出響應。React 隨后會更新屏幕來匹配這張快照,并綁定事件處理函數。因此,按下按鈕就會觸發你 JSX 中的點擊事件處理函數。
    • state 實際上“活”在 React 本身中——就像被擺在一個架子上!——位于你的函數之外。當 React 調用你的組件時,它會為特定的那一次渲染提供一張 state 快照。你的組件會在其 JSX 中返回一張包含一整套新的 props 和事件處理函數的 UI 快照 ,其中所有的值都是 根據那一次渲染中 state 的值 被計算出來的!
  • 設置 state 只會為下一次渲染變更 state 的值。

  • 一個 state 變量的值永遠不會在一次渲染的內部發生變化, 即使其事件處理函數的代碼是異步的。

  • React 會使 state 的值始終”固定“在一次渲染的各個事件處理函數內部。 你無需擔心代碼運行時 state 是否發生了變化。

  • 變量和事件處理函數不會在重渲染中“存活”。每個渲染都有自己的事件處理函數。

  • 每個渲染(以及其中的函數)始終“看到”的是 React 提供給這個 渲染的 state 快照。

  • 過去創建的事件處理函數擁有的是創建它們的那次渲染中的 state 值。

  • React 會等到事件處理函數中的 所有 代碼都運行完畢再處理你的 state 更新。

  • 在下次渲染前多次更新同一個 state,需要傳入一個更新函數,如setNumber(n => n + 1),而不是像 setNumber(number + 1) 這樣傳入 下一個 state 值

  • 設置 state 不會更改現有渲染中的變量,但會請求一次新的渲染。

  • React 會在事件處理函數執行完成之后處理 state 更新。這被稱為批處理。

  • 要在一個事件中多次更新某些 state,你可以使用 setNumber(n => n + 1) 更新函數。

  • 把所有存放在 state 中的 JavaScript 對象都視為只讀的,為了真正地 觸發一次重新渲染你需要創建一個新對象并把它傳遞給 state 的設置函數

  • 不要直接修改一個對象,而要為它創建一個 新 版本,并通過把 state 設置成這個新版本來觸發重新渲染。

  • 你可以使用這樣的 {...obj, something: 'newValue'} 對象展開語法來創建對象的拷貝。

狀態管理

  • 只要一個組件還被渲染在 UI 樹的相同位置,React 就會保留它的 state。 如果它被移除,或者一個不同的組件被渲染在相同的位置,那么 React 就會丟掉它的 state。
  • 相同位置的相同組件會使得 state 被保留下來
  • 對 React 來說重要的是組件在 UI 樹中的位置,而不是在 JSX 中的位置!
  • 當你在相同位置渲染不同的組件時,組件的整個子樹都會被重置
  • 永遠要將組件定義在最上層(函數組件外)并且不要把它們的定義嵌套起來,否則每次渲染出的都是不同的組件,狀態會丟失
  • 在相同位置重置 state
    1. 將組件渲染在不同的位置。同一個組件在第一個位置與第二個位置交替展示時會重置state
    2. 使用 key 來重置 state。即使兩個 <Counter /> 會出現在 JSX 中的同一個位置,它們也不會共享 state
    • 使用key后,React 將重新創建 DOM 元素,而不是復用它們。
  • 為被移除的組件保留 state
    1. 用 CSS 把其他組件隱藏起來。這些組件就不會從樹中被移除了,所以它們的內部 state 會被保留下來。這種解決方法對于簡單 UI 非常有效。但如果要隱藏的樹形結構很大且包含了大量的 DOM 節點,那么性能就會變得很差。
    2. 你可以進行狀態提升并在父組件中保存每個收件人的草稿消息。這是最常見的解決方法
    3. 你也可以使用localStorage存儲草稿信息
    4. 為多個 不同位置的 相同組件 指定key可以保留state
  • 把 useState 轉化為 useReducer:
    1. 通過事件處理函數 dispatch actions(派發action對象);
    2. 編寫一個 reducer 函數,它接受傳入的 state 和一個 action對象(通常包含type字段),并返回一個新的 state;
    3. 使用 useReducer 替換 useState;
  • reducer 必須是一個純函數——它應該只計算下一個狀態。而不應該 “做” 其它事情,包括向用戶顯示消息。這應該在事件處理程序中處理。(為了便于捕獲這樣的錯誤,React 會在嚴格模式下多次調用你的 reducer。
  • Context 可以讓父節點,甚至是很遠的父節點都可以為其內部的整個組件樹提供數據。
    1. 通過 export const MyContext = createContext(defaultValue) 創建并導出 context。
    2. 在無論層級多深的任何子組件中,把 context 傳遞給 useContext(MyContext) Hook 來讀取它。
    3. 在父組件中把 children 包在 <MyContext.Provider value={...}> 中來提供 context。
  • 在 React 中,覆蓋來自上層的某些 context 的唯一方法是將子組件包裹到一個提供不同值的 context provider 中。
  • 不同的 React context 不會覆蓋彼此。你通過 createContext() 創建的每個 context 都和其他 context 完全分離,只有使用和提供 那個特定的 context 的組件才會聯系在一起。一個組件可以輕松地使用或者提供許多不同的 context。

應急方案

  • 當你希望組件“記住”某些信息,但又不想讓這些信息觸發新的渲染時,你可以使用 ref
  • 當一條信息用于渲染時,將它保存在 state 中。當一條信息僅被事件處理器需要,并且更改它不需要重新渲染時,使用 ref 可能會更高效。
  • 何時使用ref
    • 存儲timeout ID
    • 存儲和操作DOM 元素
    • 存儲不需要被用來計算 JSX 的其他對象。
    • 不要在渲染過程中讀取或寫入 ref.current,ref 本身是一個普通的 JavaScript 對象
  • 給未知長度的列表中每項綁定ref
    • 將函數傳遞給 ref 屬性。這稱為 ref 回調。當需要設置 ref 時,React 將傳入 DOM 節點來調用你的 ref 回調,并在需要清除它時傳入 null 。這使你可以維護自己的數組或 Map,并通過其索引或某種類型的 ID 訪問任何 ref。
  • 訪問另一個組件的 DOM 節點
    • 一個組件可以指定將它的 ref “轉發”給一個子組件,子組件是使用 forwardRef 聲明的。 這讓從父組件接收的ref作為第二個參數傳入組件,第一個參數是 props
  • 使用ref后,父組件可以調用子組件dom節點的所有方法,使用命令句柄(useImperativeHandle)可以只暴露一部分 API
  • 通常,你將從事件處理器訪問 refs。
  • 你可以強制 React 同步更新(“刷新”)DOM(添加一個元素后滾動至該元素)。 為此,從 react-dom 導入 flushSync 并將 state 更新包裹 到 flushSync 調用中
  • useEffect 會把它包裹的代碼放到屏幕更新渲染之后執行
    -為什么依賴數組中可以省略 ref 或者 set函數?
  • React 總是在執行下一輪渲染的 Effect 之前清理(執行effect中return出的清理函數)上一輪渲染的 Effect。
  • 如果一個值可以基于現有的 props 或 state 計算得出,不要把它作為一個 state,而是在渲染期間直接計算這個值
  • 你可以使用 useMemo Hook 緩存(或者說 記憶(memoize))一個昂貴的計算(用該hook包裹一個耗時的函數,以避免每次渲染期間的重新計算)。
  • 當 props 變化時重置所有 state,應當給該組件設置key屬性,key變化,react會自動重置該組件
  • 當 props 變化時重置部分 state
    • 在渲染期間更新組件時,React 會丟棄已經返回的 JSX 并立即嘗試重新渲染。為了避免非常緩慢的級聯重試,React 只允許在渲染期間更新 同一 組件的狀態。如果你在渲染期間更新另一個組件的狀態,你會看到一條報錯信息。
  • 如果某些邏輯必須在 每次應用加載時執行一次,而不是在 每次組件掛載時執行一次,可以添加一個頂層變量(寫在組件函數之外)來記錄它是否已經執行過了
  • 訂閱外部 store,useSyncExternalStore
  • 數據請求中,為了修復競態條件下返回值set錯誤的問題,你需要添加一個 清理函數 來忽略較早的返回結果
  useEffect(() => {
    let ignore = false;
    fetchResults(query, page).then(json => {
      if (!ignore) {
        setResults(json);
      }
    });
    return () => {
      ignore = true;
    };
  }, [query, page]);
  • 每個 Effect 表示一個獨立的同步過程。抵制將與 Effect 無關的邏輯添加到已經編寫的 Effect 中(代碼中的每個 Effect 應該代表一個獨立的同步過程),僅僅因為 這些邏輯 需要與 Effect 同時運行(應重新寫一個effect函數來同步 這些邏輯,它的的好處是,刪除一個 Effect 不會影響另一個 Effect 的邏輯)。
  • 組件內部的所有值(包括 props、state 和組件體內的變量)都是響應式的。任何響應式值都可以在重新渲染時發生變化,所以需要將響應式值包括在 Effect 的依賴項中。
  • 避免將對象和函數作為依賴項。如果在渲染過程中創建對象和函數,然后在 Effect 中讀取它們,它們將在每次渲染時都不同。這將導致 Effect 每次都重新同步
  • 避免禁用檢查工具。為了不違反規則,修復代碼總是值得的!
useEffect(() => {
  // ...
  // ?? 避免這樣禁用靜態代碼分析工具:
  // eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);
  • 使用 useEffectEvent (實驗性的功能)這個特殊的 Hook 從 Effect 中提取非響應式邏輯。Effect Event。它是 Effect 邏輯的一部分,但是其行為更像事件處理函數。它內部的邏輯不是響應式的,不需要添加到effect的依賴中,Effect Event 讓你在 Effect 響應性和不應是響應式的代碼間“打破鏈條”, Effect Event 讀取的是最新的 props 和 state 。
  • 事件處理函數內部的邏輯是非響應式的(用戶的特定操作,才會執行)。
  • Effect 內部的邏輯是響應式的(切換聊天室,需要自動斷開之前的,連接最新的)。
  • 你可以將非響應式邏輯從 Effect 移到 Effect Event 中。
  • 只在 Effect 內部調用 Effect Event。
  • 不要將 Effect Event 傳給其他組件或者 Hook。
  • 注意,你不能“選擇” Effect 的依賴。每個被 Effect 所使用的響應式值,必須在依賴中聲明。依賴是由 Effect 的代碼決定的。每個 Effect 應該代表一個獨立的同步過程,不應該同步兩個不同的、不相關的東西,應將他們拆分在不同的effect中
  • 是否在讀取一些狀態來計算下一個狀態?使用state的更新函數寫法
  • 你想讀取一個值而不對其變化做出“反應”嗎?或者來自 props 的事件處理程序的處理,將非響應式邏輯(props的事件處理程序)移至 Effect Event 中(非響應式邏輯應該在事件中)
  • 盡量避免對象和函數依賴。將它們移到組件外(常量對象或者函數)、effect外(在effect外對對象或者函數解構,使effect依賴解構出來的基本類型的值)、 Effect 內(在effect中創建對象或者函數)
  • 自定義 Hook 共享的只是狀態邏輯而不是狀態本身。對 Hook 的每個調用完全獨立于對同一個 Hook 的其他調用
  • 不要創建像 useMount 這樣的自定義 Hook。保持目標具體化。

api

  • 即使一個組件被記憶化(memo)了,當它自身的狀態發生變化時,它仍然會重新渲染
  • 即使組件已被記憶化(memo),當其使用的 context 發生變化時,它仍將重新渲染
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容