React Hooks

Hooks 在 16.8 版本中被添加到 React,允許函數(shù)組件訪問狀態(tài)和其他 React 特性。因此,通常不再需要類組件。

盡管 Hooks 通常會(huì)替換類組件,但沒有計(jì)劃從 React 中刪除類。

什么是 Hooks?

Hooks 允許我們“掛鉤”到 React 特性,例如狀態(tài)和生命周期方法。

import React, { useState } from 'react'
import ReactDOM from 'react-dom'

function FavoriteColor() {
  const [color, setColor] = useState('red')

  return (
    <>
      <h1>我最喜歡的顏色是 {color}!</h1>
      <button type='button' onClick={() => setColor('blue')}>
        Blue
      </button>
      <button type='button' onClick={() => setColor('red')}>
        Red
      </button>
      <button type='button' onClick={() => setColor('pink')}>
        Pink
      </button>
      <button type='button' onClick={() => setColor('green')}>
        Green
      </button>
    </>
  )
}

ReactDOM.render(<FavoriteColor />, document.getElementById('root'))

使用 React 提供的鉤子前,我們需要從 reactimport 鉤子。

這里我們使用 useState 鉤子來跟蹤應(yīng)用程序狀態(tài)。狀態(tài)通常指需要跟蹤的應(yīng)用程序數(shù)據(jù)或?qū)傩浴?/p>

掛鉤規(guī)則

鉤子有 3 條規(guī)則:

  • Hooks 只能在 React 函數(shù)組件內(nèi)部調(diào)用。
  • Hooks 只能在組件的頂層調(diào)用。
  • Hooks 不能是有條件的

注意:鉤子在 React 類組件中不起作用。

下面我們來看看 React 提供的一些 Hooks。

useState Hooks

React useState 鉤子允許我們跟蹤函數(shù)組件中的狀態(tài)。狀態(tài)通常指應(yīng)用程序中需要跟蹤的數(shù)據(jù)或?qū)傩浴?/p>

導(dǎo)入 useState

要使用 useState 鉤子,我們首先需要將 import 它到我們的組件中。

import { useState } from 'react'

我們從 react 中解構(gòu) useState,因?yàn)樗且粋€(gè)命名導(dǎo)出。

初始化 useState

我們通過在函數(shù)組件中調(diào)用 useState 來初始化狀態(tài)。

useState 接受初始狀態(tài)并返回兩個(gè)值:

  • 當(dāng)前狀態(tài)。
  • 更新狀態(tài)的函數(shù)。
import { useState } from 'react'

function FavoriteColor() {
  const [color, setColor] = useState('')
}

第一個(gè)值 color 是我們當(dāng)前的狀態(tài)。第二個(gè)值 setColor 是用于更新狀態(tài)的函數(shù)。這些名稱是變量,可以任意命名。

最后,我們將初始狀態(tài)設(shè)置為空字符串:useState('')

讀取狀態(tài)

我們現(xiàn)在可以在組件中的任何位置包含我們的狀態(tài)。

import { useState } from 'react'
import ReactDOM from 'react-dom'

function FavoriteColor() {
  const [color, setColor] = useState('red')

  return <h1>我最喜歡的顏色是 {color}!</h1>
}

ReactDOM.render(<FavoriteColor />, document.getElementById('root'))

更新狀態(tài)

為了更新我們的狀態(tài),我們使用定義好的 setColor 狀態(tài)更新程序函數(shù)。

import { useState } from 'react'
import ReactDOM from 'react-dom'

function FavoriteColor() {
  const [color, setColor] = useState('red')

  return (
    <>
      <h1>我最喜歡的顏色是 {color}!</h1>
      <button type='button' onClick={() => setColor('blue')}>
        Blue
      </button>
    </>
  )
}

ReactDOM.render(<FavoriteColor />, document.getElementById('root'))

注意:我們不應(yīng)該直接更新狀態(tài)。例如:不允許使用 color="red"。

狀態(tài)可以持有什么

useState 鉤子可以用來跟蹤字符串、數(shù)字、布爾值、數(shù)組、對(duì)象以及它們的任意組合!

我們可以創(chuàng)建多個(gè)狀態(tài) Hook 來跟蹤單個(gè)值。

import { useState } from 'react'
import ReactDOM from 'react-dom'

function User() {
  const [name, setName] = useState('O.O')
  const [age, setAge] = useState(20)
  const [year, setYear] = useState(1998)

  return (
    <>
      <h1>個(gè)人信息</h1>
      <p>
        我叫{name},今年{age}歲,生于{year}年。
      </p>
    </>
  )
}

ReactDOM.render(<User />, document.getElementById('root'))

或者,我們可以只使用一個(gè)狀態(tài)并包含一個(gè)對(duì)象!

import { useState } from 'react'
import ReactDOM from 'react-dom'

function User() {
  const [user, setUser] = useState({
    name: 'O.O',
    age: 20,
    year: 1998
  })

  return (
    <>
      <h1>個(gè)人信息</h1>
      <p>
        我叫{user.name},今年{user.age}歲,生于{user.year}年。
      </p>
    </>
  )
}

ReactDOM.render(<User />, document.getElementById('root'))

由于我們現(xiàn)在正在跟蹤單個(gè)對(duì)象,因此在渲染組件時(shí)需要引用該對(duì)象,然后引用該對(duì)象的屬性。(例如:user.name

更新狀態(tài)中的對(duì)象和數(shù)組

當(dāng)狀態(tài)更新時(shí),整個(gè)狀態(tài)都會(huì)被覆蓋。

如果我們只想更新用戶的年齡呢?

如果我們只調(diào)用 setUser({ age: 18 }),這將從我們的狀態(tài)中刪除 nameyear。

我們可以使用 JavaScript 擴(kuò)展運(yùn)算符來幫助我們。

import { useState } from 'react'
import ReactDOM from 'react-dom'

function User() {
  const [user, setUser] = useState({
    name: 'O.O',
    age: 20,
    year: 1998
  })

  const updateUser = () => {
    setUser((previousState) => {
      return { ...previousState, age: 18 }
    })
  }

  return (
    <>
      <h1>個(gè)人信息</h1>
      <p>
        我叫{user.name},今年{user.age}歲,生于{user.year}年。
      </p>
      <button onClick={updateUser}>18</button>
    </>
  )
}

ReactDOM.render(<User />, document.getElementById('root'))

因?yàn)槲覀冃枰獱顟B(tài)的當(dāng)前值,所以我們將一個(gè)函數(shù)傳遞給 setUser 函數(shù)。此函數(shù)接收上一個(gè)值。

然后我們返回一個(gè)對(duì)象,展開 previousState 并僅覆蓋 age。

useEffect Hooks

useEffect Hook 允許您在組件中執(zhí)行副作用。副作用的一些示例如:獲取數(shù)據(jù)、直接更新 DOM 和定時(shí)器。

useEffect 接受兩個(gè)參數(shù)。第二個(gè)參數(shù)是可選的。

以定時(shí)器為例,使用 setTimeout() 計(jì)算初始渲染后的 1 秒:

import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    setTimeout(() => {
      setCount((count) => count + 1)
    }, 1000)
  })

  return <h1>我已經(jīng)渲染了 {count} 次!</h1>
}

ReactDOM.render(<Timer />, document.getElementById('root'))

可是等等??!它一直在計(jì)數(shù),即使它應(yīng)該只計(jì)數(shù)一次!

useEffect 在每個(gè)渲染上運(yùn)行。這意味著當(dāng)計(jì)數(shù)發(fā)生變化時(shí),會(huì)發(fā)生渲染,然后觸發(fā)另一個(gè)效果。

這不是我們想要的。有幾種方法可以控制副作用何時(shí)運(yùn)行。

我們應(yīng)該始終包含接受數(shù)組的第二個(gè)參數(shù)。我們可以選擇將依賴項(xiàng)傳遞給該數(shù)組中的 useEffect。

沒有任何依賴:

useEffect(() => {
  // 在每個(gè)渲染上運(yùn)行
})

一個(gè)空數(shù)組:

useEffect(() => {
  // 僅在第一次渲染時(shí)運(yùn)行
}, [])

propstate 值:

useEffect(() => {
  // 在第一次渲染和任何依賴項(xiàng)值更改時(shí)運(yùn)行
}, [prop, state])

所以,為了解決這個(gè)問題,讓我們只在初始渲染上運(yùn)行這個(gè)效果。

import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    setTimeout(() => {
      setCount((count) => count + 1)
    }, 1000)
  }, []) // <- 在此處添加空括號(hào)

  return <h1>我渲染了 {count} 次!</h1>
}

ReactDOM.render(<Timer />, document.getElementById('root'))

下面是一個(gè)依賴于變量的 useEffect 鉤子的示例。如果 count 變量更新,效果將再次運(yùn)行:

import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

function Counter() {
  const [count, setCount] = useState(0)
  const [calculation, setCalculation] = useState(0)

  useEffect(() => {
    setCalculation(() => count * 2)
  }, [count]) // <- 在這里添加 count 變量

  return (
    <>
      <p>總數(shù): {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>+</button>
      <p>計(jì)算: {calculation}</p>
    </>
  )
}

ReactDOM.render(<Counter />, document.getElementById('root'))

如果存在多個(gè)依賴項(xiàng),則應(yīng)將它們包含在 useEffect 依賴項(xiàng)數(shù)組中。

清理 Effect

有些效果需要清理以減少內(nèi)存泄漏。

超時(shí)、訂閱、事件監(jiān)聽器和其他不再需要的效果應(yīng)該被處理。

我們通過在 useEffect 鉤子的末尾包含一個(gè)返回函數(shù)來實(shí)現(xiàn)這一點(diǎn)。

import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    let timer = setTimeout(() => {
      setCount((count) => count + 1)
    }, 1000)

    return () => clearTimeout(timer)
  }, [])

  return <h1>我渲染了 {count} 次!</h1>
}

ReactDOM.render(<Timer />, document.getElementById('root'))

注意:要清除定時(shí)器,我們必須為其命名。

useContext Hooks

React Context 是一種全局管理狀態(tài)的方法。

與單獨(dú)使用 useState 相比,它可以與 useState 鉤子一起使用,在深度嵌套的組件之間更容易地共享狀態(tài)。

問題

狀態(tài)應(yīng)由堆棧中需要訪問狀態(tài)的最高父組件持有。

舉例來說,我們有許多嵌套組件。堆棧頂部和底部的組件需要訪問狀態(tài)。

要在沒有上下文的情況下實(shí)現(xiàn)這一點(diǎn),我們需要將狀態(tài)作為 props 傳遞給每個(gè)嵌套組件。這被稱為 prop drilling。

import { useState } from 'react'
import ReactDOM from 'react-dom'

function Comp1() {
  const [user, setUser] = useState('O.O')

  return (
    <>
      <h1>{`Hello ${user}!`}</h1>
      <Comp2 user={user} />
    </>
  )
}

function Comp2({ user }) {
  return (
    <>
      <h1>組件 2</h1>
      <Comp3 user={user} />
    </>
  )
}

function Comp3({ user }) {
  return (
    <>
      <h1>組件 3</h1>
      <Comp4 user={user} />
    </>
  )
}

function Comp4({ user }) {
  return (
    <>
      <h1>組件 4</h1>
      <Comp5 user={user} />
    </>
  )
}

function Comp5({ user }) {
  return (
    <>
      <h1>組件 5</h1>
      <h2>{`Hello ${user} again!`}</h2>
    </>
  )
}

ReactDOM.render(<Comp1 />, document.getElementById('root'))

即使組件 2-4 不需要狀態(tài),但它們也必須傳遞狀態(tài)才能到達(dá)組件 5。

解決方案

解決方案是創(chuàng)建上下文。

要?jiǎng)?chuàng)建上下文,必須導(dǎo)入 createContext 并對(duì)其進(jìn)行初始化:

import { useState, createContext } from 'react'
import ReactDOM from 'react-dom'

const UserContext = createContext()

接下來,我們將使用 Context Provider 來包裝需要狀態(tài)上下文的組件樹。

Context Provider

在 Context Provider 中包裝子組件并提供狀態(tài)值。

function Comp1() {
  const [user, setUser] = useState('O.O')

  return (
    <UserContext.Provider value={user}>
      <h1>{`Hello ${user}!`}</h1>
      <Comp2 user={user} />
    </UserContext.Provider>
  )
}

現(xiàn)在,該樹中的所有組件都可以訪問用戶上下文。

使用 useContext Hooks

為了在子組件中使用上下文,我們需要使用 useContext 鉤子訪問它。

首先,導(dǎo)入 useContext

import { useState, createContext, useContext } from 'react'

然后,您可以訪問所有組件中的用戶上下文:

function Comp5() {
  const user = useContext(UserContext)

  return (
    <>
      <h1>組件 5</h1>
      <h2>{`Hello ${user} again!`}</h2>
    </>
  )
}

完整示例:

import { useState, createContext, useContext } from 'react'
import ReactDOM from 'react-dom'

const UserContext = createContext()

function Component1() {
  const [user, setUser] = useState('O.O')

  return (
    <UserContext.Provider value={user}>
      <h1>{`Hello ${user}!`}</h1>
      <Comp2 user={user} />
    </UserContext.Provider>
  )
}

function Comp2() {
  return (
    <>
      <h1>組件 2</h1>
      <Comp3 />
    </>
  )
}

function Comp3() {
  return (
    <>
      <h1>組件 3</h1>
      <Comp4 />
    </>
  )
}

function Comp4() {
  return (
    <>
      <h1>組件 4</h1>
      <Comp5 />
    </>
  )
}

function Comp5() {
  const user = useContext(UserContext)

  return (
    <>
      <h1>組件 5</h1>
      <h2>{`Hello ${user} again!`}</h2>
    </>
  )
}

ReactDOM.render(<Component1 />, document.getElementById('root'))

useRef Hooks

useRef 鉤子允許在渲染之間持久化值。

它可用于存儲(chǔ)在更新時(shí)不會(huì)導(dǎo)致重新渲染的可變值,也可用于直接訪問 DOM 元素。

不會(huì)導(dǎo)致重新渲染

如果我們?cè)噲D計(jì)算應(yīng)用程序使用 useState 鉤子渲染的次數(shù),我們將陷入無限循環(huán),因?yàn)檫@個(gè)鉤子本身會(huì)導(dǎo)致重新渲染。

為了避免這種情況,我們可以使用 useRef 鉤子。

import { useState, useEffect, useRef } from 'react'
import ReactDOM from 'react-dom'

function App() {
  const [inputValue, setInputValue] = useState('')
  const count = useRef(0)

  useEffect(() => {
    count.current = count.current + 1
  })

  return (
    <>
      <input
        type='text'
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <h1>渲染次數(shù): {count.current}</h1>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

useRef() 只返回一項(xiàng)。它返回一個(gè)名為 current 的對(duì)象。

初始化 useRef 時(shí),我們?cè)O(shè)置初始值為 useRef(0)。它其實(shí)類似于 const count= { current:0 },我們可以使用 count 訪問 count.current。

訪問 DOM 元素

通常,我們希望讓 React 處理所有 DOM 操作。

但在某些情況下,可以使用 useRef 而不會(huì)引起問題。

在 React 中,我們可以向元素添加 ref 屬性,以便直接在 DOM 中訪問它。

import { useRef } from 'react'
import ReactDOM from 'react-dom'

function App() {
  const inputElement = useRef()

  const focusInput = () => inputElement.current.focus()

  return (
    <>
      <input type='text' ref={inputElement} />
      <button onClick={focusInput}>聚焦 input</button>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

跟蹤狀態(tài)變化

useRef 鉤子還可用于跟蹤先前的狀態(tài)值。

這是因?yàn)槲覀兡軌蛟阡秩局g持久化 useRef 值。

import { useState, useEffect, useRef } from 'react'
import ReactDOM from 'react-dom'

function App() {
  const [inputValue, setInputValue] = useState('')
  const previousInputValue = useRef('')

  useEffect(() => {
    previousInputValue.current = inputValue
  }, [inputValue])

  return (
    <>
      <input
        type='text'
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <h2>當(dāng)前值: {inputValue}</h2>
      <h2>先前值: {previousInputValue.current}</h2>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

這一次,我們結(jié)合使用 useStateuseffectuseRef 來跟蹤之前的狀態(tài)。

useEffect 中,每次通過在 input 字段中輸入文本來更新 inputValue 時(shí),我們都會(huì)更新 useRef 當(dāng)前值。

useReducer Hooks

useReducer 鉤子類似于 useState 鉤子。它允許自定義狀態(tài)邏輯。

如果您發(fā)現(xiàn)自己在跟蹤依賴于復(fù)雜邏輯的多個(gè)狀態(tài),useReducer 可能會(huì)很有用。

useReducer 鉤子接受兩個(gè)參數(shù):

  • reducer函數(shù)包含自定義狀態(tài)邏輯,initialState 可以是一個(gè)簡(jiǎn)單的值,但通常會(huì)包含一個(gè)對(duì)象。
  • useReducer 鉤子返回當(dāng)前 statedispatch 方法。

下面是計(jì)數(shù)器使用 useReducer 的示例:

import { useReducer } from 'react'
import ReactDOM from 'react-dom'

const initialTodos = [
  {
    id: 1,
    title: 'Todo 1',
    complete: false
  },
  {
    id: 2,
    title: 'Todo 2',
    complete: false
  }
]

const reducer = (state, action) => {
  switch (action.type) {
    case 'COMPLETE':
      return state.map((todo) => {
        if (todo.id === action.id) {
          return { ...todo, complete: !todo.complete }
        } else {
          return todo
        }
      })
    default:
      return state
  }
}

function Todos() {
  const [todos, dispatch] = useReducer(reducer, initialTodos)

  const handleComplete = (todo) => {
    dispatch({ type: 'COMPLETE', id: todo.id })
  }

  return (
    <>
      {todos.map((todo) => (
        <div key={todo.id}>
          <label>
            <input
              type='checkbox'
              checked={todo.complete}
              onChange={() => handleComplete(todo)}
            />
            {todo.title}
          </label>
        </div>
      ))}
    </>
  )
}

ReactDOM.render(<Todos />, document.getElementById('root'))

這就是跟蹤 todo 完成狀態(tài)的邏輯。

通過添加更多操作,添加、刪除和完成 todo 的所有邏輯都可以包含在單個(gè) useReducer 鉤子中。

useCallback Hooks

React useCallback Hook 返回一個(gè)已記憶的回調(diào)函數(shù)。

這使我們能夠隔離資源密集型函數(shù),以便它們不會(huì)在每次渲染時(shí)自動(dòng)運(yùn)行。

useCallbackHook 僅在其依賴項(xiàng)之一更新時(shí)運(yùn)行,提高了性能。

問題

使用 useCallback 的一個(gè)原因是防止組件重新渲染,除非其 props 已更改。

在本例中,您可能會(huì)認(rèn)為 todos 組件不會(huì)重新渲染,除非 todos 發(fā)生更改:

// main.js
import { useState } from 'react'
import ReactDOM from 'react-dom'
import Todos from './Todos'

const App = () => {
  const [count, setCount] = useState(0)
  const [todos, setTodos] = useState([])

  const increment = () => {
    setCount((c) => c + 1)
  }
  const addTodo = () => {
    setTodos((t) => [...t, 'New Todo'])
  }

  return (
    <>
      <Todos todos={todos} addTodo={addTodo} />
      <hr />
      <div>
        次數(shù): {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

Todos.js 組件:

import { memo } from 'react'

const Todos = ({ todos, addTodo }) => {
  console.log('子渲染')
  return (
    <>
      <h2>Todos List</h2>
      {todos.map((todo, index) => {
        return <p key={index}>{todo}</p>
      })}
      <button onClick={addTodo}>添加 Todo</button>
    </>
  )
}

export default memo(Todos)

嘗試運(yùn)行它并單擊 + 按鈕。

您會(huì)注意到,即使 todos 沒有更改,Todos 組件也會(huì)重新渲染。

為什么這不起作用?我們使用的是 memo,所以 Todos 組件不應(yīng)該重新渲染,因?yàn)楫?dāng) count增加時(shí),todos 狀態(tài)和 addTodo 函數(shù)都沒有改變。

這是因?yàn)樗^的“參照平等”。

每次組件重新渲染時(shí),都會(huì)重新創(chuàng)建其函數(shù)。因此,addTodo 函數(shù)實(shí)際上發(fā)生了變化。

解決方案

為了解決這個(gè)問題,我們可以使用 useCallback 鉤子來防止函數(shù)被重新創(chuàng)建,除非有必要。

使用 useCallback 鉤子可以防止 Todos 組件不必要地重新渲染:

import { useState, useCallback } from 'react'
import ReactDOM from 'react-dom'
import Todos from './Todos'

const App = () => {
  const [count, setCount] = useState(0)
  const [todos, setTodos] = useState([])

  const increment = () => {
    setCount((c) => c + 1)
  }
  const addTodo = useCallback(() => {
    setTodos((t) => [...t, 'New Todo'])
  }, [todos])

  return (
    <>
      <Todos todos={todos} addTodo={addTodo} />
      <hr />
      <div>
        次數(shù): {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

Todos.js 組件:

import { memo } from 'react'

const Todos = ({ todos, addTodo }) => {
  console.log('子渲染')
  return (
    <>
      <h2>Todos List</h2>
      {todos.map((todo, index) => {
        return <p key={index}>{todo}</p>
      })}
      <button onClick={addTodo}>新增 Todo</button>
    </>
  )
}

export default memo(Todos)

useMemo Hooks

React useMemo 鉤子返回一個(gè)已記憶的值,它僅在其中一個(gè)依賴項(xiàng)更新時(shí)運(yùn)行,提高性能。

useMemouseCallback 鉤子類似。主要區(qū)別在于 useMemo 返回一個(gè)已記憶的值, useCallback 返回一個(gè)已記憶的函數(shù)。

性能

useMemo 鉤子可以用來防止昂貴的、資源密集型的函數(shù)不必要地運(yùn)行。

在本例中,我們有一個(gè)在每個(gè)渲染上運(yùn)行的昂貴函數(shù)。

更改計(jì)數(shù)或添加 todo 時(shí),您會(huì)注意到執(zhí)行延遲。

import { useState } from 'react'
import ReactDOM from 'react-dom'

const App = () => {
  const [count, setCount] = useState(0)
  const [todos, setTodos] = useState([])
  const calculation = expensiveCalculation(count)

  const increment = () => {
    setCount((c) => c + 1)
  }
  const addTodo = () => {
    setTodos((t) => [...t, 'New Todo'])
  }

  return (
    <div>
      <div>
        <h2>Todos List</h2>
        {todos.map((todo, index) => {
          return <p key={index}>{todo}</p>
        })}
        <button onClick={addTodo}>新增 Todo</button>
      </div>
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
        <h2>昂貴的計(jì)算</h2>
        {calculation}
      </div>
    </div>
  )
}

const expensiveCalculation = (num) => {
  console.log('計(jì)算...')
  for (let i = 0; i < 1000000000; i++) {
    num += 1
  }
  return num
}

ReactDOM.render(<App />, document.getElementById('root'))

使用 useMemo

為了解決這個(gè)性能問題,我們可以使用 useMemoHook 來記憶 expensiveCalculation 函數(shù)。這將導(dǎo)致該函數(shù)僅在需要時(shí)運(yùn)行。

useMemoHook 接受第二個(gè)參數(shù)來聲明依賴項(xiàng)。昂貴的函數(shù)只會(huì)在其依賴關(guān)系發(fā)生變化時(shí)運(yùn)行。

在下面的示例中,昂貴的函數(shù)只會(huì)在 count 更改時(shí)運(yùn)行,而不是在添加待辦事項(xiàng)時(shí)運(yùn)行。

import { useState, useMemo } from 'react'
import ReactDOM from 'react-dom'

const App = () => {
  const [count, setCount] = useState(0)
  const [todos, setTodos] = useState([])
  const calculation = useMemo(() => expensiveCalculation(count), [count])

  const increment = () => {
    setCount((c) => c + 1)
  }
  const addTodo = () => {
    setTodos((t) => [...t, 'New Todo'])
  }

  return (
    <div>
      <div>
        <h2>Todos List</h2>
        {todos.map((todo, index) => {
          return <p key={index}>{todo}</p>
        })}
        <button onClick={addTodo}>新增 Todo</button>
      </div>
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
        <h2>昂貴的計(jì)算</h2>
        {calculation}
      </div>
    </div>
  )
}

const expensiveCalculation = (num) => {
  console.log('計(jì)算...')
  for (let i = 0; i < 1000000000; i++) {
    num += 1
  }
  return num
}

ReactDOM.render(<App />, document.getElementById('root'))

自定義 Hooks

Hooks 是可重用的函數(shù)。

當(dāng)您有需要在多個(gè)組件中使用相同的組件邏輯時(shí),我們可以將該邏輯提取到自定義 Hook。

自定義 Hooks 以 use 開頭。本節(jié)將編寫一個(gè) useFetch 示例。

自定義 useFetch 鉤子

在下面的代碼中,我們?cè)?Home 組件中獲取數(shù)據(jù)并顯示它。

我們將使用 JSONPlaceholder 服務(wù)來獲取假數(shù)據(jù)。

使用 JSONPlaceholder 服務(wù)獲取假 Todos 列表,并在頁面上顯示標(biāo)題:

import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

const Home = () => {
  const [data, setData] = useState(null)

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/todos')
      .then((res) => res.json())
      .then((data) => setData(data))
  }, [])

  return (
    <>
      {data &&
        data.map((item) => {
          return <p key={item.id}>{item.title}</p>
        })}
    </>
  )
}

ReactDOM.render(<Home />, document.getElementById('root'))

其他組件也可能需要獲取邏輯,因此我們將其提取到自定義 Hook 中。

將獲取邏輯移動(dòng)到一個(gè)新文件以用作自定義 Hook:

// useFetch.js
import { useState, useEffect } from 'react'

const useFetch = (url) => {
  const [data, setData] = useState(null)

  useEffect(() => {
    fetch(url)
      .then((res) => res.json())
      .then((data) => setData(data))
  }, [url])

  return [data]
}

export default useFetch
// main.js
import ReactDOM from 'react-dom'
import useFetch from './useFetch'

const Home = () => {
  const [data] = useFetch('https://jsonplaceholder.typicode.com/todos')

  return (
    <>
      {data &&
        data.map((item) => {
          return <p key={item.id}>{item.title}</p>
        })}
    </>
  )
}

ReactDOM.render(<Home />, document.getElementById('root'))

我們創(chuàng)建了一個(gè)名為 useFetch.js 的新文件,其中包含一個(gè)名為 useFetch 的函數(shù),該函數(shù)包含獲取數(shù)據(jù)所需的所有邏輯。

我們刪除了硬編碼的 URL,并將其替換為可以傳遞給自定義鉤子的 URL 變量。

最后,我們從鉤子中返回?cái)?shù)據(jù)。

main.js 中,我們導(dǎo)入 useFetch 鉤子,并像其他鉤子一樣使用它。這就是我們傳遞 URL 以從中獲取數(shù)據(jù)的地方。

現(xiàn)在我們可以在任何組件中重用這個(gè)自定義鉤子,從任何 URL 獲取數(shù)據(jù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容