React Hooks

及时更新中……

1.useState状态钩子

1.1使用场景

当一个数据需要响应式呈现到页面上时,需要使用 useState 状态声明钩子来告知 React 监听数据,从而实现页面的数据动态更新

1.2使用示例

import { useState } from 'react'

const myComponent = () => {
  const [count, setCount] = useState<number>(0)
  const handleClick1 = () => setCount(count + 1)       // 等效
  const handleClick2 = () => setCount(pre => pre + 1)  // 等效
  return <button onClick={handleClick1}>count: {count}</button>
}

声明方法为 const [val, setVal] = useState<T>(default) ,该方法会使用泛型 T 作为限制, default 作为初始值,返回的 val 为数据本身,而 setVal 为更新数据的方法

当状态改变时,直接改动 val 是无效的,需要把新的 val 通过 setVal 作为参数发送,从而达到改变数据的效果

setVal 函数也可以接受一个函数 func 作为参数,React 会调用 func 并将原有的 val 作为参数传递,将 func 的返回值作为新的数据

1.3注意事项

1.3.1 修改状态会导致组件重新执行

当组件的一个状态被修改时,React 会调用方法重新渲染组件

import { useState } from 'react'

const myComponent = () => {
  const [count, setCount] = useState<number>(0)
  console.log('render')
  const handleClick = () => setCount(count + 1)
  return <button onClick={handleClick}>count: {count}</button>
}

每当按钮被点击时,状态更改,组件重新渲染,控制台输出 render ,按钮内部的数据变更

若 handleClick 函数未使用 setCount 而是直接修改 count 本身,则当点击按钮时,不会触发重新渲染,控制台不会再次输出 render ,按钮内部的数据也不会变更,但 count 变量本身是已经改变了的

1.3.2 修改状态是异步操作

setVal 函数会将内容存放于微队列中,在同步代码执行结束后再执行

同时, setVal 函数会自动合并,多次调用一次执行,执行后再进行重新渲染,以防多次不必要的重新渲染

import { useState } from 'react'

const myComponent = () => {
  const [count, setCount] = useState<number>(0)
  const handleClick = () => {
    setCount(count + 1)
    setCount(count + 1)
    setCount(count + 1)
    console.log(count)
  }
  return <button onClick={handleClick}>count: {count}</button>
}

如上代码,在按钮点击时,虽然执行了三次 setCount ,但由于 JavaScript 的预先计算,若本次 count 为 0 ,则等效于执行了三次 setCount(1) ,故每次点击按钮, count 的值只会自增 1 ,而非预想中的自增 3

同时,由于 setVal 函数的异步性, console.log(count) 在控制台的输出也是先前而非更新后的 count 值

对于上述代码,符合预期的解决方式是使用 setVal 函数的回调函数参数

import { useState } from 'react'

const myComponent = () => {
  const [count, setCount] = useState<number>(0)
  const handleClick = () => {
    setCount(pre => pre + 1)
    setCount(pre => pre + 1)
    setCount(pre => {
      console.log(pre + 1)
      return pre + 1
    })
  }
  return <button onClick={handleClick}>count: {count}</button>
}

由于 setVal 函数的回调函数参数也是异步调用的,回调函数参数接收参数 pre 时,状态 count 已被上一次状态修改函数修改完毕,故取到的是新值,理所当然地,控制台输出等操作也会输出正确的新值

1.3.3 状态具有缓存能力

每一个状态都被缓存在 Fiber 节点的 memoizedState 属性中,而非组件内部

不然每次组件重新渲染时 useState 钩子接收到的默认值就会覆盖原有状态,此钩子就无任何意义了

2.useEffect外部作用钩子

2.1使用场景

当需要在组件渲染后执行一些副作用操作时(如请求数据、DOM 操作、订阅事件、定时器等),使用 useEffect 来声明副作用逻辑

常见的使用场景包括:

  • 数据获取(API请求)
  • 订阅外部数据源
  • 手动操作DOM元素
  • 设置定时器或计时器
  • 记录日志或分析数据
  • 与第三方库集成

2.2使用示例

import { useState, useEffect } from 'react'

const MyComponent = () => {
  const [count, setCount] = useState<number>(0)
  
  // 组件挂载时和count变化时执行
  useEffect(() => {
    document.title = `点击了 ${count} 次`
    console.log('副作用执行')
  }, [count]) // 依赖数组中包含count
  
  return (
    <button onClick={() => setCount(count + 1)}>
      点击次数: {count}
    </button>
  )
}

useEffect 接受两个参数:一个副作用函数 effect 和一个依赖数组 deps 。当 deps 中的数据变化时,副作用函数会被重新执行

依赖数组的不同情况:

  • useEffect(fn, []) :仅在组件挂载时执行一次,组件卸载时执行清理函数
  • useEffect(fn, [dep1, dep2]) :在组件挂载和依赖项变化时执行
  • useEffect(fn) :在每次渲染后都执行(不推荐,可能导致无限循环)

2.3注意事项

2.3.1清理副作用

副作用函数可以返回一个清理函数 cleanup ,当组件卸载或依赖更新时,React 会执行清理逻辑

MyComponent.tsx

import { useState, useEffect } from 'react'

export const MyComponent = () => {
    const [isOnline, setIsOnline] = useState<boolean>(navigator.onLine)

    useEffect(() => {
        // 设置事件监听器
        const handleOnline = () => setIsOnline(true)
        const handleOffline = () => setIsOnline(false)

        window.addEventListener('online', handleOnline)
        window.addEventListener('offline', handleOffline)

        console.log('添加了事件监听')

        // 返回清理函数
        // 1.事件监听器
        // 2.定时器
        // 3.订阅
        // 4.手动DOM操作
        // 5.WebSocket连接
        return () => {
            window.removeEventListener('online', handleOnline)
            window.removeEventListener('offline', handleOffline)
            console.log('移除了事件监听')
        }
    }, [])
    // 空依赖数组,仅在组件挂载和卸载时执行
    // 监听器的生命周期 = 组件的生命周期

    return (
        <div>
            当前网络状态: {isOnline ? '在线' : '离线'}
        </div>
    )
}

App.tsx

import { useState } from 'react'
import { MyComponent } from './components/Example'

const App = () => {
  const [showComponent, setShowComponent] = useState(true)

  return (
    <div>
      <button onClick={() => setShowComponent(!showComponent)}>
        {showComponent ? '卸载组件' : '挂载组件'}
      </button>

      {showComponent && <MyComponent />}
    </div>
  )
}

export default App

常见的清理逻辑有取消订阅、清除定时器等,避免内存泄漏或重复注册

2.3.2依赖项缺失问题

如果在副作用中使用了组件内的变量或状态,但没有将其添加到依赖数组中,可能会导致闭包陷阱,使用的是旧值而非最新值

2.3.3 避免无限循环

如果在副作用中更新了依赖数组中包含的状态,会导致无限循环:副作用执行 → 状态更新 → 组件重新渲染 → 副作用再次执行

3.useContext上下文钩子

3.1使用场景

用于在组件树中跨层级传递数据,而不必层层通过 props 传递。适用于以下场景:

  • 全局主题设置(明/暗模式)
  • 用户认证信息共享
  • 语言偏好设置
  • 全局状态管理
  • 路由信息共享

3.2使用示例

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

// 创建上下文
const ThemeContext = createContext<'light' | 'dark'>('light')

// 父组件提供上下文
const App = () => {
  const [theme, setTheme] = useState<'light' | 'dark'>('light')
  
  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light')
  }
  
  return (
    <ThemeContext.Provider value={theme}>
      <div>
        <button onClick={toggleTheme}>切换主题</button>
        <ThemedButton />
      </div>
    </ThemeContext.Provider>
  )
}

// 子组件消费上下文
const ThemedButton = () => {
  const theme = useContext(ThemeContext)
  
  const style = {
    background: theme === 'dark' ? '#333' : '#fff',
    color: theme === 'dark' ? '#fff' : '#333',
    padding: '10px',
    borderRadius: '4px'
  }
  
  return (
    <button style={style}>
      当前主题: {theme}
    </button>
  )
}

使用 Context 需要三个步骤:

  1. 通过 createContext 创建上下文,可以提供默认值
  2. 父组件使用 Context.Provider 提供数据,通过 value 属性传递
  3. 子组件通过 useContext 钩子获取上下文数据

3.3注意事项

3.3.1性能考虑

当 Provider 的 value 变化时,所有消费该 Context 的组件都会重新渲染,即使它们没有使用变化的部分。可以通过拆分 Context 或使用 memo 优化

3.3.2 默认值的局限性

Context 的默认值只有在组件树中没有匹配的 Provider 时才会使用。如果有 Provider 但 value 是 undefined,消费组件获取的值也是 undefined 而非默认值

4.useRef引用钩子

4.1使用场景

useRef 有两个主要用途:

  1. 获取组件内部的 DOM 节点引用
  2. 在多次渲染之间存储一个不会触发重新渲染的值

适用于以下场景:

  • 访问和操作 DOM 元素(如聚焦、测量尺寸)
  • 存储前一个状态值
  • 保存定时器 ID、动画帧 ID 等
  • 存储不需要触发重新渲染的数据
  • 计数器或标志(如渲染次数)

4.2使用示例

import { useRef, useState, useEffect } from 'react'

const TextInputWithFocusButton = () => {
  // 创建一个ref来存储input元素的引用
  const inputRef = useRef<HTMLInputElement>(null)
  
  // 创建一个ref来存储渲染次数(不会触发重新渲染)
  const renderCountRef = useRef<number>(0)
  
  const [text, setText] = useState<string>('')
  
  // 点击按钮时聚焦输入框
  const handleFocus = () => {
    // 使用ref访问DOM元素
    if (inputRef.current) {
      inputRef.current.focus()
    }
  }
  
  useEffect(() => {
    // 每次渲染后增加计数
    renderCountRef.current += 1
    console.log(`组件已渲染 ${renderCountRef.current} 次`)
  })
  
  return (
    <div>
      <input
        ref={inputRef}
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="请输入文本"
      />
      <button onClick={handleFocus}>聚焦输入框</button>
      <p>当前文本: {text}</p>
      <p>渲染次数: {renderCountRef.current}</p>
    </div>
  )
}

useRef 接收一个初始值参数,返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数

返回的 ref 对象在组件的整个生命周期内保持不变,即使组件重新渲染,ref 对象也不会改变

4.3注意事项

4.3.1 修改不会触发重新渲染

修改 ref.current 值不会导致组件重新渲染,这是它与 state 的主要区别

4.3.2 DOM引用时机

当用于引用 DOM 元素时,ref 对象的 .current 属性在组件挂载时设置为对应的 DOM 元素,在卸载时设置为 null

4.3.3 与状态的区别

与 useState 不同, useRef 是可变的,可以直接修改 .current 属性,且修改不会触发重新渲染

5.useReducer值管理钩子

5.1使用场景

当状态逻辑较复杂或状态之间有依赖时,可以使用 useReducer 来替代 useState 

适用于以下场景:

  • 状态逻辑复杂,包含多个子值
  • 下一个状态依赖于之前的状态
  • 需要集中管理相关状态的更新逻辑
  • 状态更新涉及深层嵌套对象
  • 需要在多个组件间共享状态逻辑

5.2使用示例

import { useReducer } from 'react'

// 定义状态类型和动作类型
type State = { count: number }
type Action = 
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'reset' }

// 定义reducer函数
const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    case 'reset':
      return { count: 0 }
    default:
      return state
  }
}

const Counter = () => {
  // 使用useReducer,传入reducer函数和初始状态
  const [state, dispatch] = useReducer(reducer, { count: 0 })
  
  return (
    <div>
      <p>计数: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>增加</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>减少</button>
      <button onClick={() => dispatch({ type: 'reset' })}>重置</button>
    </div>
  )
}

useReducer 接收三个参数:

  1. reducer 函数:接收当前状态和动作,返回新状态
  2. 初始状态:可以是值或初始化函数
  3. 初始化函数(可选):用于惰性初始化状态

返回当前状态和 dispatch 方法。通过 dispatch 触发状态更新,传入的动作会被传递给 reducer 函数

5.3注意事项

5.3.1 与Redux的区别

useReducer 是 Redux 模式的简化版,但不包含中间件、组合 reducer、时间旅行调试等高级功能

5.3.2 性能优化

与 useState 不同,如果 dispatch 的动作没有导致状态变化(返回相同的状态引用),React 不会触发重新渲染

5.3.3 状态共享

可以结合 useContext 实现跨组件的状态共享,作为简易的状态管理解决方案

6. useMemo值缓存钩子

6.1 使用场景

当一个计算过程开销较大时,可以通过 useMemo 缓存计算结果,避免每次渲染都重新计算。适用于以下场景:

  • 复杂计算或数据处理(如排序、过滤大型数组)
  • 昂贵的格式化操作
  • 需要保持对象引用稳定以避免子组件重新渲染
  • 作为其他 Hook 的依赖项时,避免不必要的重新计算

6.2 使用示例

缓存计算结果

import { useState, useMemo } from 'react'

const ExpensiveCalculation = () => {
  const [count, setCount] = useState<number>(0)
  const [wordIndex, setWordIndex] = useState<number>(0)
  
  const words = ['React', 'JavaScript', 'TypeScript', 'Hooks', 'Components']
  
  // 模拟昂贵的计算过程
  const expensiveComputation = (num: number) => {
    console.log('执行昂贵计算...')
    // 模拟耗时操作
    let result = 0
    for (let i = 0; i < 1000000; i++) {
      result += num
    }
    return result
  }
  
  // 使用useMemo缓存计算结果,只有当count变化时才重新计算
  const computedValue = useMemo(() => {
    return expensiveComputation(count)
  }, [count])
  
  return (
    <div>
      <p>当前计数: {count}</p>
      <p>计算结果: {computedValue}</p>
      <p>当前单词: {words[wordIndex]}</p>
      
      <button onClick={() => setCount(count + 1)}>增加计数</button>
      <button onClick={() => setWordIndex((wordIndex + 1) % words.length)}>
        切换单词 (不会触发重新计算)
      </button>
    </div>
  )
}

useMemo 接收两个参数:

  1. 创建值的工厂函数
  2. 依赖数组:只有当依赖项变化时,才会重新计算值

返回值是缓存的计算结果,在依赖不变的情况下,多次渲染时返回相同的值引用

6.3 注意事项

6.3.1 不要过度使用

只有当计算真正昂贵或需要保持引用稳定时才使用 useMemo ,否则可能因为额外的记忆化开销而适得其反

6.3.2 依赖项管理

依赖数组应包含工厂函数中使用的所有组件内的变量和状态,确保在相关数据变化时重新计算

6.3.3 与React.memo的区别

useMemo 缓存组件内的计算结果,而 React.memo 缓存整个组件的渲染结果

Life's a struggle, I'll conquer it.
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇