1. 程式人生 > 實用技巧 >React - Hook隨想

React - Hook隨想

  Hook 是react 16.8新增的特性。它可以讓你在不編寫Class的情況下使用state以及其他的React特性

  1.HOOKS是什麼?->為了擁抱函數語言程式設計。

  2.Hooks帶來的變革,讓函式元件有了狀態和其他的react特性,可以替代class。

  我們愈發提倡寫一些無狀態元件,但是正是因為如此,我們在某些情況下必須使用到state來維護我們元件的資料,諸如我們需要去從某些後端介面獲取回的列表資料、但是我們為了維護方便吧我們的專案拆分更詳盡、粒度更小,此時我們最外層最大的元件如果是函式元件的話就無法在react理念中把資料存到元件上。正是這種具體的需求促使Hook的產 生。

import React, { useState } from 'react';

function Example() {
  // 宣告一個新的叫做 “count” 的 state 變數
  const [count, setCount] = useState(0); // useState函式返回一個數組->[state,set方法自定義名字 xxx也可以] (初始值initValue)

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

  這裡我們引用官網的基本事例來動態的呈現一個最簡單的計數器需求。

  這裡我們需要注意:react是沒有計劃讓hook+function完全代替class component,沒有計劃移除class,朋友們還是請放心

  HOOK解決了什麼問題

  1、在元件之間複用狀態邏輯很難

React沒有提供可複用性行為“附加”到組建的途徑(例如,把元件連線到store)。如果你使用過React一段時間,你也許會熟悉一些解決此類問題的方案,比如 render props 和 告誡元件。

export default Wrap = (Com) => (props) => {
  const [lang,setLang] = useState('zh')
  useEffect(() => {
    if( getCookie('lang') === 'en' ){
      setLang('en')
    }
  },[]); 
  return (
    <IntlProvider locale={lang} messages={lang==='en'?en_US:zh_CN}>
      <Com {...props}/>
    </IntlProvider>
  );
};

例如,我們借鑑一段可能會碰到的一種國際化的需求。我們不想去每個頁面都做一次,可以封裝一個高階元件出來。

但這是一個比較明朗的高階元件,如果需求更多,可能會很麻煩,自己封裝的程式碼可能使你的程式碼難以理解。如果你在React DevTOOLS中觀察過React應用,你會發現provider consumers highComponent render props等其他抽象層組成的元件形成“巢狀地獄”。儘管我們可以在DevTools過濾掉它們,但這說明了一個更深層次的問題:React需要為共享邏輯提供更好的原生途徑。

你可以在使用Hook從元件中提取狀態邏輯,使得這些邏輯可以單獨測試並複用,Hook使你在無需求改元件結構的情況下複用狀態邏輯,這是的元件間或社群內共享Hook變得更便捷

複雜元件變得難以理解

我們經常維護一些元件,元件起初很簡單,但是逐漸會被狀態邏輯和副作用充斥。每個生命週期常常包含一些不相關的邏輯。例如,元件常常在componentDidMount和componentDidUpdate中獲取資料。但是,同一個componentDidMount中可能也包含很多其它的邏輯,如設定事件監聽,而之後需在componentWillUnmount中清除。相互關聯且需要對照修改的程式碼被進行了拆分,而完全不相關的程式碼卻在同一個方法中組合在一起。如此很容易產生bug,並且導致邏輯不一致。在多數情況下,不可能將元件拆分為更小的粒度,因為狀態邏輯無處不在。這也給測試帶來了一定挑戰。同時,這也是很多人將React與狀態管理庫結合使用的原因之一。但是,這往往會引入了很多抽象概念,需要你在不同的檔案之間來回切換,使得複用變得更加困難。為了解決這個問題,Hook將元件中相互關聯的部分拆分成更小的函式(比如設定訂閱或請求資料),而並非強制按照生命週期劃分。你還可以使用reducer來管理元件的內部狀態,使其更加可預測。我們將在使用EffectHook中對此展開更多討論。難以理解的class除了程式碼複用和程式碼管理會遇到困難外,我們還發現class是學習React的一大屏障。你必須去理解JavaScript中this的工作方式,這與其他語言存在巨大差異。還不能忘記繫結事件處理器。沒有穩定的語法提案,這些程式碼非常冗餘。大家可以很好地理解props,state和自頂向下的資料流,但對class卻一籌莫展。即便在有經驗的React開發者之間,對於函式元件與class元件的差異也存在分歧,甚至還要區分兩種元件的使用場景。另外,React已經發布五年了,我們希望它能在下一個五年也與時俱進。就像Svelte,Angular,Glimmer等其它的庫展示的那樣,元件預編譯會帶來巨大的潛力。尤其是在它不侷限於模板的時候。最近,我們一直在使用Prepack來試驗componentfolding,也取得了初步成效。但是我們發現使用

class元件會無意中鼓勵開發者使用一些讓優化措施無效的方案。class也給目前的工具帶來了一些問題。例如,class不能很好的壓縮,並且會使熱重載出現不穩定的情況。因此,我們想提供一個使程式碼

更易於優化的API。

為了解決這些問題,Hook使你在非class的情況下可以使用更多的React特性。從概念上講,React元件一直更像是函式。而Hook則擁抱了函式,同時也沒有犧牲React的精神原則。Hook提供了問題的解決方案,無需學習複雜的函式式或響應式程式設計技術。

使用Effect HOOK

此處執行一些副作用。 --> 資料獲取,設定訂閱以及手動更改React元件中的DOM都屬於副作用,不管你知不知道這些操作,或副作用這個名字,應該都在元件中使用過他們。

importReact,{useState,useEffect} from "react";
exportdefaultfunctionHookPage(props){
  //宣告⼀一個叫“count”的state變數量,初始化為0
  const [count, setCount] = useState(0);
  //與componentDidMount和componentDidUpdate相似
  useEffect(() => {
    //更更新title
    document.title = `Youclicked${count}times`;
  });
  return (
    <div>
      <h3>HookPage</h3>
      <p>{count}</p>
      <buttonn onClick={() => setCount(count + 1)}>add</button>
    </div>
  );
}

在函式元件主體內(咋合理指在React渲染階段)改變dom、新增訂閱、設定定時器、記錄日誌以及執行其他包含副作用的操作都是不被允許的,因為這可能會產生莫名奇妙的bug並破壞UI的一致性。

使用useEffect完成副作用操作。賦值給useEffect的函式會在元件渲染到螢幕之後執行。你可以把effect看作從React的純函式式世界通往命令式世界的逃生通道。

預設情況下,effect會在每輪渲染結束後執行,但你可以選擇讓他只在某些值改變的時候才執行。(第二個引數陣列的使用,[xxcount])

effect的條件執⾏

預設情況下,effect會在每輪元件渲染完成後執⾏。這樣的話,⼀旦effect的依賴發⽣變化,它就會被
重新建立。
然而,在某些場景下這麼做可能會矯枉過正。⽐比如,在上⼀章節的訂閱示例例中,我們不需要在每次元件
更新時都建立新的訂閱,⽽而是僅需要在sourceprops改變時重新建立。
要實現這⼀點,可以給useEffect傳遞第⼆個引數,它是effect所依賴的值陣列。更新後的示例例如
下:

import React, { useState, useEffect } from"react";
export default functionHookPage(props){
  //宣告⼀一個叫“count”的state變數量,初始化為0
  const [count, setCount] = useState(0);
  const [date, setDate] = useState(newDate());
  //與componentDidMount和componentDidUpdate相似
  useEffect(() => {
    //更更新title
    document.title = `Youclicked${count}times`;
  }, [count]);
  useEffect(() => {
    consttimer = setInterval(() => {
      setDate(newDate());
    }, 1000);
  }, []);
  return (
    <div>
      <h3>HookPage</h3>
      <p>{count}</p>
      <buttononClick={() => setCount(count + 1)}>add</button>
    <p>{date.toLocaleTimeString()}</p>
</div >
);
}

清除Effect

通常,元件解除安裝時需要清楚effect建立的諸如訂閱或計數器ID等資源。要實現這一點,useEfeect函式需返回一個清除函式,以防記憶體洩露,清除函式會在元件解除安裝前執行

useEffect(()=>{
 consttimer=setInterval(()=>{
 setDate(newDate());
 },1000);
 return()=>clearInterval(timer);
 },[]);