react hooks 的钩子函数

125 阅读3分钟

我的博客原文

@author: 郭瑞峰 @createTime: 2023/07/10 @updateTime: 2023/07/18

简介一下,前端react hooks是在react@16.8提出的新特性,为了简化 class 类组件,通过 function 函数式组件来实现原来 class 类组件。

先上结论

常用的钩子函数

不废话,直接说一下常用的钩子函数

  • useState: 用于在函数组件中添加状态
  • useEffect: 用于在组件挂载、更新或卸载时执行副作用操作
  • useCallback: 用于缓存函数实例,避免函数重复创建
  • useMemo: 用于缓存计算结果,避免重复计算
  • useRef: 用于在函数组件中创建 ref 对象

不太常用的钩子函数

  • useContext: 用于在组件中使用 React Context
  • useReducer: 用于在函数组件中使用 reducer 管理状态
  • useLayoutEffect: 类似于 useEffect,但会在浏览器 layout 之后同步执行

详细说明

useState

数据读写

import React, { useState } from 'react'

export default function () {
  const [count, setCount] = useState<number>(0)

  return (
    <div>
	  <button
		onClick={() => setCount(val => val + 1)}
	  >点了{ count }下</button>
    </div>
  )
}

useEffect

  • 在组件挂载、更新或卸载(生命周期)时执行操作,替代类组件中的生命周期方法
  • 订阅外部数据源变化时更新状态或重新渲染组件(简单来说就是监控数据)
  • 异步执行
import React, { useState, useEffect } from 'react'

export default function () {
  const [count, setCount] = useState<number>(0)

// 挂载、更新、卸载
  useEffect(() => {
    console.log('组件装载')
	return () => {
	  console.log('组件卸载了')
	}
  })

  return (
    <div>
      <p>count now is {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  )
}

useCallback

  • 性能优化
  • 防止父组件更新时无关紧要的子组件同步更新
import React, { useState, useCallback } from 'react'

function Child ({ onClick }) {
  return (<button onClick={onClick}>点我一下<button>)
}

function Parent () {
  const [count, setCount] = useState<number>(0)
  const addCount = useCallback(() => {
    setCount(val => val + 1)
  }, [count])
  return (<>
    <div>你点击了{ count }次按钮</div>
	<Child onClick={addCount} />
  </>)
}

useMemo

  • 通过将计算结果缓存起来,可以提高组件的渲染性能
import React, { useState, useEffect, useMemo } from 'react'

function App () {
  const [second, setSecond] = useState<number>(10)
  useEffect(() => {
    let timer: any // 这里应该number和空,我偷懒了 ㄟ( ▔, ▔ )ㄏ
    if (second > 0) {
	  clearTimeout(timer)
	  timer = setTimeout(() => {
	    setSecond(val => val - 1)
	  }, 1000)
	}
  }, [second])

  // 写一个有倒计时的按钮
  const timerButton = useMemo(() => {
    return (<button>{ second }s</button>)
  }, second)

  return (<>
    <div>
	{ /* 其他部分 */ }
	{ timerButton }
	</div>
  </>)
}

export default App

useRef

  • 保存 DOM 元素的引用
import React, { useRef } from 'react'

function App () {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleClick = () => {
    if (inputRef.current) {
      inputRef.current.focus()
    }
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleClick}>Focus Input</button>
    </div>
  );
};

export default App

useContext(不太常用)

  • 函数组件中获取上下文对象中的值,不必通过 props 属性逐层传递

注:该功能一般通过三方库实现全局状态管理(如redux或recoil)

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

interface Theme {
  backgroundColor: string
  textColor: string
}

const themes = {
  light: {
    backgroundColor: '#ffffff',
    textColor: '#000000'
  },
  dark: {
    backgroundColor: '#000000',
    textColor: '#ffffff'
  }
}

const ThemeContext = createContext<Theme>(themes.light)

const App = () => {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Header />
      <Main />
    </ThemeContext.Provider>
  )
}

const Header = () => {
  const theme = useContext(ThemeContext)

  return (
    <header style={{ backgroundColor: theme.backgroundColor, color: theme.textColor }}>
      <h1>Header</h1>
    </header>
  )
}

const Main = () => {
  const theme = useContext(ThemeContext)

  return (
    <main style={{ backgroundColor: theme.backgroundColor, color: theme.textColor }}>
      <h2>Main</h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
    </main>
  )
}

export default App

useReducer(不太常用)

  • 函数组件中管理状态
  • 组件的状态和更新逻辑分离开来管理

注:该功能一般通过三方库实现全局状态管理(如redux或recoil)

import React, { useReducer } from 'react';

type CounterState = {
  count: number
}

type CounterAction = {
  type: 'increment' | 'decrement',
  payload?: number
}

const initialState: CounterState = {
  count: 0
}

const reducer = (state: CounterState, action: CounterAction): CounterState => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + (action.payload || 1) }
    case 'decrement':
      return { count: state.count - (action.payload || 1) }
    default:
      throw new Error(`Unsupported action type: ${action.type}`)
  }
}

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

  const handleIncrement = () => {
    dispatch({ type: 'increment', payload: 5 })
  };

  const handleDecrement = () => {
    dispatch({ type: 'decrement' })
  };

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={handleIncrement}>+5</button>
      <button onClick={handleDecrement}>-1</button>
    </div>
  )
}

export default Counter

useLayoutEffect(有使用,但不频繁)

  • 与useEffect相似的
  • 同步操作
  • 可以用于获取dom尺寸等相关操作

缺点:同步执行,有可能会阻塞 UI 渲染

import React, { useState, useLayoutEffect, useRef } from 'react'

const Counter = () => {
  const [count, setCount] = useState<number>(0)
  const [rect, setRect] = useState<DOMRect | undefined>(undefined)

  const ref = useRef<HTMLDivElement>(null)

  useLayoutEffect(() => {
    if (ref.current) {
      setRect(ref.current.getBoundingClientRect())
    }
  }, [count])

  const handleIncrement = () => {
    setCount(count + 1)
  }

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleIncrement}>+1</button>
      <div ref={ref}>
        {rect && (
          <>
            <p>Width: {rect.width}</p>
            <p>Height: {rect.height}</p>
          </>
        )}
      </div>
    </div>
  )
}

export default Counter

blob-Draft-1689649104589.png