0
点赞
收藏
分享

微信扫一扫

React Hooks 使用指南:从 useState 到自定义 Hook

在 React Hooks 出现之前,函数组件总给人一种 "能力不足" 的感觉 —— 不能拥有自己的状态,也无法处理副作用。Hooks 的出现彻底改变了这一点,让函数组件能够轻松实现状态管理、生命周期控制等复杂功能。本文从实际开发场景出发,带你掌握从基础 Hook 到自定义 Hook 的完整用法。

一、useState:让函数组件拥有状态

useState是最基础也最常用的 Hook,它让函数组件能够管理自己的状态。

比如实现一个计数器:

import { useState } from 'react';function Counter() {  // 声明一个count状态,初始值为0  // setCount是更新状态的函数  const [count, setCount] = useState(0);  return (    <div>      <p>你点击了{count}次</p>      <button onClick={() => setCount(count + 1)}>        点击我      </button>    </div>  );}

useState的参数是初始状态,返回值是一个数组,第一个元素是当前状态,第二个是更新状态的函数。

更新状态时可以使用函数形式,确保依赖最新状态:

// 处理异步更新或依赖前一次状态的情况<button onClick={() => setCount(prevCount => prevCount + 1)}>  点击我</button>

这种函数形式在处理倒计时、累加器等场景特别有用,避免因闭包导致的状态更新问题。

二、useEffect:处理副作用

组件中除了渲染 UI 之外的操作(如请求数据、订阅事件、操作 DOM)都叫副作用,这些工作都应该放在useEffect中处理。

基础用法:组件挂载和更新时执行

import { useState, useEffect } from 'react';function UserProfile({ userId }) {  const [user, setUser] = useState(null);  // 当userId变化时,重新获取用户数据  useEffect(() => {    // 定义获取数据的函数    const fetchUser = async () => {      const response = await fetch(`/api/users/${userId}`);      const data = await response.json();      setUser(data);    };    fetchUser();  }, [userId]); // 依赖数组:只有userId变化时才重新执行  if (!user) return <div>加载中...</div>;  return (    <div>      <h1>{user.name}</h1>      <p>{user.email}</p>    </div>  );}

useEffect的第一个参数是副作用函数,第二个参数是依赖数组:

  • 依赖数组为空[]:只在组件挂载时执行一次
  • 依赖数组有值[a, b]:组件挂载时执行,且 a 或 b 变化时重新执行
  • 没有依赖数组:每次组件渲染都会执行

清理副作用

有些副作用需要清理(如事件监听、定时器),可以在副作用函数中返回一个清理函数:

function WindowSize() {  const [size, setSize] = useState({ width: 0, height: 0 });  useEffect(() => {    // 定义处理函数    const handleResize = () => {      setSize({        width: window.innerWidth,        height: window.innerHeight      });    };    // 监听窗口大小变化    window.addEventListener('resize', handleResize);    // 初始化执行一次    handleResize();    // 返回清理函数    return () => {      window.removeEventListener('resize', handleResize);    };  }, []); // 空依赖数组:只执行一次  return (    <div>      窗口宽度:{size.width},高度:{size.height}    </div>  );}

清理函数会在组件卸载时执行,避免内存泄漏。

三、useContext:跨组件传值

当组件层级较深时,用props传值很繁琐,useContext可以轻松实现跨组件数据共享。

首先创建上下文:

// 创建上下文import { createContext, useContext, useState } from 'react';const ThemeContext = createContext();// 提供上下文的组件export function ThemeProvider({ children }) {  const [theme, setTheme] = useState('light');  return (    <ThemeContext.Provider value={{ theme, setTheme }}>      {children}    </ThemeContext.Provider>  );}// 自定义Hook简化使用export function useTheme() {  return useContext(ThemeContext);}

然后在任意子组件中使用:

import { useTheme } from './ThemeContext';function ThemedButton() {  const { theme, setTheme } = useTheme();  return (    <button       style={{         background: theme === 'light' ? '#fff' : '#333',        color: theme === 'light' ? '#333' : '#fff'      }}      onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}    >      当前主题:{theme},点击切换    </button>  );}

这种方式避免了 "props drilling"( props 层层传递)的问题,特别适合主题切换、用户登录状态等全局数据的管理。

四、自定义 Hook:封装复用逻辑

当多个组件需要用到相同的逻辑时,可以把这些逻辑封装成自定义 Hook,这是 Hooks 最强大的特性之一。

示例:封装表单处理逻辑

import { useState } from 'react';// 自定义Hook:处理表单输入function useForm(initialValues) {  const [values, setValues] = useState(initialValues);  const handleChange = (e) => {    const { name, value } = e.target;    setValues(prev => ({ ...prev, [name]: value }));  };  const resetForm = () => setValues(initialValues);  return { values, handleChange, resetForm };}// 使用自定义Hookfunction LoginForm() {  const { values, handleChange, resetForm } = useForm({    username: '',    password: ''  });  const handleSubmit = (e) => {    e.preventDefault();    console.log('提交表单:', values);    // 调用API提交数据...  };  return (    <form onSubmit={handleSubmit}>      <div>        <label>用户名:</label>        <input          name="username"          value={values.username}          onChange={handleChange}        />      </div>      <div>        <label>密码:</label>        <input          name="password"          type="password"          value={values.password}          onChange={handleChange}        />      </div>      <button type="submit">登录</button>      <button type="button" onClick={resetForm}>重置</button>    </form>  );}

自定义 Hook 必须以use开头,这样 React 才能识别并应用 Hooks 的规则。

另一个示例:处理本地存储

// 自定义Hook:同步localStorage的状态function useLocalStorage(key, initialValue) {  // 从localStorage读取初始值  const [value, setValue] = useState(() => {    const storedValue = localStorage.getItem(key);    return storedValue ? JSON.parse(storedValue) : initialValue;  });  // 当值变化时同步到localStorage  useEffect(() => {    localStorage.setItem(key, JSON.stringify(value));  }, [key, value]);  return [value, setValue];}// 使用function Settings() {  const [darkMode, setDarkMode] = useLocalStorage('darkMode', false);  return (    <div>      <label>        <input          type="checkbox"          checked={darkMode}          onChange={(e) => setDarkMode(e.target.checked)}        />        开启深色模式      </label>    </div>  );}

这个 Hook 让状态自动同步到本地存储,刷新页面后状态不会丢失,非常实用。

五、使用 Hooks 的注意事项

  1. 只能在函数组件或自定义 Hook 中使用:不能在普通函数、循环、条件语句中调用 Hooks。
  2. 遵循调用顺序:Hooks 的调用顺序必须稳定,不要在if、for中调用:

// 错误做法if (count > 0) {  useEffect(() => {    // ...  });}// 正确做法useEffect(() => {  if (count > 0) {    // ...  }});

  1. 依赖数组要完整:useEffect的依赖数组要包含所有用到的外部变量,否则可能导致闭包问题。

可以安装eslint-plugin-react-hooks插件,自动检测这些问题:

// .eslintrc.jsmodule.exports = {  plugins: ['react-hooks'],  rules: {    'react-hooks/rules-of-hooks': 'error', // 检查Hook调用规则    'react-hooks/exhaustive-deps': 'warn' // 检查依赖数组  }};

总结

React Hooks 彻底改变了 React 组件的编写方式:

  • useState让函数组件拥有状态
  • useEffect处理各种副作用,替代类组件的生命周期
  • 自定义 Hook 实现逻辑复用,让代码更简洁

学习 Hooks 的关键是理解 "副作用隔离" 和 "逻辑复用" 这两个核心思想。刚开始使用时可能会遇到闭包陷阱、依赖数组设置错误等问题,多写多练就能慢慢掌握。

记住,Hooks 不是要完全替代类组件,而是提供了一种更简洁、更灵活的方式来编写 React 组件。在实际项目中,根据场景选择合适的写法,才能发挥 React 的最大威力。

举报

相关推荐

0 条评论