React Hooks 實用指南

前言

React Conf 2018會議中,Dan Abramov 介紹了 React Hooks。官方的描述爲html

Hook是一項新功能提案,可以讓您在不編寫類的狀況下使用狀態和其餘React功能。 它們目前處於React v16.7.0-alpha中。計劃將在 2019 Q1 推出到主版本中。前端

痛點

如下是React Hooks功能的動機,它解決了現有React中的一些問題react

組件之間很難共享狀態

React沒有一種將可重用的行爲附加到組件的方法(例如連接到store)。若是您使用過一段時間,您可能會使用render propsheight-order components組件解決這個問題。可是這些模式要求您再使用他們時重構組件,這會很麻煩。若是您使用React DevTools看一下您的程序,您會發現您的組件被各類組件所包裹這叫作包裝地域,好比:providers、comsumers、higher-order components、render props等。這裏有一個更深層的根本問題:React須要一個更好的方法來共享狀態邏輯。git

這就是Hooks,您能夠從組件中提取有狀態邏輯,以即可以獨立測試和重用。Hooks容許您不更改組件層次結構的狀況下重用有狀態邏輯。這樣就能夠輕鬆在多組件之間或與社區共享Hooksgithub

組件愈來愈複雜,變得難以理解

咱們常常不得不維護一些組件,這些組件一開始很簡單,隨着時間的延伸組件發展成一堆沒法管理的有狀態邏輯和一些反作用。每一個生命週期方法常常包含不相關的邏輯組合。舉個例子,組件可能會在componentDidMountcomponentDidUpdate中拉取一些數據。還有componentDidMount方法可能還包含一些事件監聽的不相關邏輯,而且再componentWillUnmount`中卸載監聽。可是徹底不相關的代碼會合併到一個方法中。是很容易引發bug和不一致性。redux

在不少狀況下,不能將這些組件拆分紅更小的組件由於邏輯遍及許多地方。對它們進行測試也很困難。這正是不少人將React和狀態管理庫結合使用的緣由。可是這更容易建立更多的抽象,要求您在許多不一樣的文件之間跳轉,重用組件將變得更加困難。api

爲了解決這個問題,Hooks容許您根據相關的功能將他們拆分爲一個更小的函數。而不是強制基於聲明周期函數進行拆分。您還能夠選擇使用reducer管理組件的本地狀態,使其更具可預測性。數組

類讓人和機器都混淆

除了使代碼重用和代碼組織更加困難外,咱們發現類(classes)可能成爲學習React的一大障礙。您必須瞭解它在JavaScript中是如何工做的,這與它在大多數語言中的工做方式有很大不一樣。您必須明白如何正確的綁定事件處理和還沒穩定的新語法,代碼很是冗長。你們可能很容易就會明白屬性(props)、狀態(state)、從上往下的數據流(top-down data flow)但類(classes)就很難理解。React中的函數和類組件之間的區別以及什麼時候使用每一個組件致使即便在經驗豐富的React開發人員之間也存在分歧。使用函數可使用prepack更好的優化代碼。可是使用類組件不能獲得更好的優化。bash

爲了解決這些問題,Hooks 容許您在沒有類的狀況下使用更多的React功能。服務器

useState

useState可讓您的函數組件也具有類組件的state功能

使用語法以下:

const [state, setState] = useState(initialState);

useState返回一個數組,一個是state的值,第二個是更新state的函數

在真實的程序中咱們能夠這樣使用:

function TestUseState() {
  const [count, setCount] = React.useState(0);

  return (
    <div> <p>useState api</p> <p>Count: {count} <button onClick={() => setCount(count + 1) }>自增</button></p> </div>
  )
}
複製代碼

使用 useState 須要注意一個事項,當你初始化是一個對象時。使用 setCount 時它不像類組件的 this.setState 會自動合併到 state 中。setCount 會使用當前的值覆蓋以前的 state。以下所示

function TestUseStateObject() {
  const [state, setState] = React.useState({
    count: 0,
    greeting: "Hello, World!",
  });
  const handleAdd = () => {
    setState({
      count: state.count + 1
    })
  }
  console.log('state > ', state)
  return (
    <div> <p>useStateObject api</p> <p>Count: {state.count} <button onClick={handleAdd}>自增</button></p> </div>
  )
}
複製代碼

1543423038609

咱們能夠看到,當點擊按鈕時 state 被替換成了 {count: 1}。若是想要在 state 中使用一個對象須要在更新值的時候把以前的值解構出來,以下所示:

setState({
      ...state,
      count: state.count + 1
    })
複製代碼

在函數中使用多個 state

function TestMultipleUseState() {
  const [count, setCount] = React.useState(0);
  const [name, setName] = React.useState('john');
  return (
    <div> <p>useState api</p> <p>Count: {count} - Name: {name}</p> </div>
  )
}
複製代碼

如須要在線測試請前往codepen useState

useEffect

默認狀況下 useEffect 在完成渲染後運行,咱們能夠在這裏獲取DOM和處理其餘反作用。但它還有兩種不一樣的運行階段稍候我會解釋。

function TestUseEffect() {
  const [count, setCount] = React.useState(0);
  
  React.useEffect(() => {
    console.log(`組件被更新,Count: ${count}`);
  });
  
  return (
    <div> <p>useEffect api</p> <p>Count: {count} <button onClick={() => setCount(count + 1) }>自增</button></p> </div>
  )
}
複製代碼

上面的 useEffect 在每次組件渲染後運行,每當咱們點擊自增按鈕都會執行一次。

可是若是上面的代碼在每次渲染後都執行,若是咱們在 useEffect 從服務器拉取數據。形成的結果就是每次渲染後都會從服務器拉取數據。或者是隻有某些 props 被更新後纔想執行 useEffect。那麼默認的 useEffect 就不是咱們想要執行方式,這時 useEffect 提供了第二個參數。

useEffect(didUpdate, [])

useEffect第二個參數爲一個數組。當咱們提供第二個參數時,只有第二個參數被更改 useEffect 纔會執行。利用第二個參數咱們能夠模擬出類組件的 componentDidMount 生命週期函數

function TestUseEffectListener() {
  const [count, setCount] = React.useState(0);
  
  React.useEffect(() => {
    console.log('componentDidMount fetch Data...');
  }, []);
  
  return (
    <div> <p>TestUseEffectListener</p> <p>Count: {count} <button onClick={() => setCount(count + 1) }>自增</button></p> </div>
  )
}
複製代碼

上面的代碼中 useEffect 只會執行一次,當您點擊自增 useEffect 也不會再次執行。

useEffect 第一個參數的函數中咱們能夠返回一個函數用於執行清理功能,它會在ui組件被清理以前執行,結合上面所學的知識使用 useEffect 模擬 componentWillUnmount 生命週期函數

function TestUseEffectUnMount() {
  const [count, setCount] = React.useState(0);
  
  React.useEffect(() => {
    return () => {
      console.log('componentUnmount cleanup...');
    }
  }, []);
  
  return (
    <div> <p>TestUseEffectUnMount</p> </div>
  )
}
複製代碼

上面的代碼中,當組件 TestUseEffectUnMount 將要銷燬時會,會執行 console.log('componentUnmount cleanup...') 代碼

如須要在線測試請前往codepen useEffect

useContext

useContext 可讓您在函數中使用 context,它有效的解決了之前 ProviderConsumer 須要額外包裝組件的問題

使用語法以下:

const context = useContext(Context);

如今讓咱們來看看實際應用中這個 useContext 是如何使用的,代碼以下:

function TestFuncContext() {
  const context = React.useContext(ThemeContext);

  return (
    <div style={context}>TestFuncContext</div>
  )
}
複製代碼

咱們能夠看到上面直接使用 React.useContext(ThemeContext) 就能夠得到 context,而在以前的版本中須要像這樣才能獲取 <Consumer>({vlaue} => {})</Consumer> ,這極大的簡化了代碼的書寫。

// 以前Consumer的訪問方式
function TestNativeContext() {
  return (
    <ThemeContext.Consumer> {(value) => { return ( <div style={value}>TestNativeContext</div> ) }} </ThemeContext.Consumer> ); } 複製代碼

如須要在線測試請前往codepen useContext

useReducer

useReduceruseState 的代提方案。當你有一些更負責的數據時可使用它。

使用語法以下:

const [state, dispatch] = useReducer(reducer, initialState)

第一個參數是一個 reduce 用來處理到來的 action,函數申明爲:(state, action) => ()。第二個參數是一個初始化的state常量。

在返回值 [state, dispatch] 中,state 就是你的數據。dispatch 能夠發起一個 action 到 reducer 中處理。

這個功能給個人感受就是組件本地的redux,感受仍是不錯。在設計一些複雜的數據結構是可使用

如今讓咱們來看看實際應用中這個 useReducer 是如何使用的,代碼以下:

function TestUseReducer() {
  const [state, setState] = React.useReducer((state, action) => {
    switch(action.type) {
      case 'update':
        return {name: action.payload}
      default:
        return state;
    }
  }, {name: ''});
  
  const handleNameChange = (e) => {
    setState({type: 'update', payload: e.target.value})
  }
  return (
    <div> <p>你好:{state.name}</p> <input onChange={handleNameChange} /> </div> ) } 複製代碼

當改變 input 中的值時會同時更新 state 中的數據,而後顯示在界面上

如須要在線測試請前往codepen useReducer

useCallback

useCallbackuseMemo 有些類似。它接收一個內聯函數和一個數組,它返回的是一個記憶化版本的函數。

使用語法以下:

const memoizedValue = useMemo(() => computeExpensiveValue(a), [a])

useCallback 的第一個參數是一個函數用來執行一些操做和計算。第二個參數是一個數組,當這個數組裏面的值改變時 useMemo 會從新執行更新這個匿名函數裏面引用到 a 的值。這樣描述可能有點不太好理解,下面看一個例子:

function TestUseCallback({ num }) {
  const memoizedCallback = React.useCallback(
    () => {
      // 一些計算
      return num;
    },
    [],
  );
  console.log('記憶 num > ', memoizedCallback())
  console.log('原始 num > ', num);
  return (
    <div> <p>TestUseCallback</p> </div>
  )
}
複製代碼

_6d371a9a-47a9-4a5c-ac22-28a456b4d4d5

若是咱們想監聽 num 值的更新從新作一些操做和計算,咱們能夠給第二個參數放入 num 值,像下面這樣:

function TestUseCallback({ num }) {
  const memoizedCallback = React.useCallback(
    () => {
      // 一些計算
      return num;
    },
    [num],
  );
  console.log('記憶 num > ', memoizedCallback())
  console.log('原始 num > ', num);
  return (
    <div> <p>TestUseCallback</p> </div>
  )
}
複製代碼

如須要在線測試請前往codepen useCallback

useRef

我以爲 useRef 的功能有點像類屬性,或者說您想要在組件中記錄一些值,而且這些值在稍後能夠更改。

使用語法以下:

const refContainer = useRef(initialValue)

useRef 返回一個可變的對象,對象的 current 屬性被初始化爲傳遞的參數(initialValue)。返回的對象將持續整個組件的生命週期。

一個保存input元素,並使其獲取焦點程序,代碼以下:

function TestUseRef() {
  const inputEl = React.useRef(null);
  const onButtonClick = () => {
    // 點擊按鈕會設置input獲取焦點
    inputEl.current.focus(); // 設置useRef返回對象的值
  };
  
  return (
    <div> <p>TestUseRef</p> <div> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>input聚焦</button> </div> </div> ) } 複製代碼

useRef 返回的對象您能夠在其餘地方設置好比: useEffect、useCallback等

如須要在線測試請前往codepen useRef

原文連接

感謝閱讀 🙏

最後作一個廣告,我建立了一個前端週刊每週五發布最新的技術文章和開源項目歡迎訂閱

相關文章
相關標籤/搜索