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 提供的鉤子前,我們需要從 react 中 import 鉤子。
這里我們使用 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)中刪除 name 和 year。
我們可以使用 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)行
}, [])
prop 或 state 值:
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é)合使用 useState、useffect 和 useRef 來跟蹤之前的狀態(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)前state和dispatch方法。
下面是計(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)行,提高性能。
useMemo 和 useCallback 鉤子類似。主要區(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ù)。