🚀 React Hooks通关秘籍,一次学会核心用法!

171 阅读9分钟

🚀 React Hooks,从useState到useEffect,一次学会核心用法!

你好,未来的React大神!👋

你是否曾经在学习 React 的过程中,被 Class 组件的 this 指向问题、生命周期方法搞得一头雾水?或者,你只是想在函数组件中实现一些状态管理和副作用操作,却发现无从下手?别担心!React Hooks 就是来解决这些问题的“超能力”!

本篇文章将带你深入了解 React Hooks,让你轻松掌握如何在函数组件中实现状态管理、副作用处理、跨组件通信等功能。无论你是 React 初学者,还是希望提升函数组件开发效率的开发者,都能从中受益匪。让我们一起揭开 Hooks 的神秘面纱吧!

🤔 什么是Hooks?为什么我们需要它?

在过去,React的函数组件就像“傻瓜相机”,只能负责展示UI,没有自己的“记忆”(状态)。如果想让组件拥有状态和生命周期,就必须使用更复杂的Class组件。

Hooks的出现,彻底改变了这一切!

你可以把Hooks想象成一系列特殊的“钩子”函数,它们能“钩”入React的底层特性,让你的函数组件也能拥有状态管理、副作用处理等强大的功能。它们的名字都以use开头,比如useStateuseEffect

一句话总结:Hooks让函数组件变得和Class组件一样强大,但代码更简洁、更易于理解!

🎣 第一个钩子:useState - 让组件拥有记忆

useState是你最先会接触,也是最常用的Hook。它的作用非常简单:为函数组件添加一个状态(State)

想象一下,你想做一个简单的计数器。你需要一个变量来“记住”当前的数字,并且当这个数字变化时,页面也要跟着更新。useState就能帮你做到!

怎么用?

 import { useState } from "react";
 ​
 function App() {
   // 调用 useState,传入初始值 1
   // 它返回一个数组:[当前状态, 更新状态的函数]
   let [num, setNum] = useState(1);
 ​
   return (
     <div>
       {/* 点击按钮时,调用 setNum 来更新状态 */}
       <button onClick={() => setNum(num + 1)}>{num}</button>
     </div>
   )
 }

代码解读:

  1. let [num, setNum] = useState(1);

    • 我们调用useState并传入1作为初始值。
    • 它返回一个包含两个元素的数组:第一个是当前的状态值num(初始为1),第二个是专门用来修改这个状态的函数setNum
    • 重点:你不能直接用num++来修改状态,因为那样React无法感知到变化。必须使用setNum函数来更新,这样React才会重新渲染组件,让页面显示最新的值。

🎬 第二个钩子:useEffect - 处理“副作用”

什么是“副作用”?在React中,除了渲染UI之外的操作,都可以看作是副作用。比如:

  • 发送网络请求(AJAX)
  • 手动修改DOM
  • 设置定时器或订阅

useEffect就是专门用来处理这些副作用的钩子。它能让你在组件渲染的不同阶段执行这些操作。

useEffect的4种经典用法:

  1. 组件每次加载(挂载和更新)都触发

     useEffect(() => {
       console.log("组件渲染了!");
     }); // 第二个参数不传
    
  2. 只在初次渲染时触发(常用于请求数据)

    这是非常有用的模式,比如在组件加载时从服务器获取初始化数据。

     // ...
     const [dataSource, setDataSource] = useState([])
     ​
     useEffect(() => {
         // 发送请求获取数据
         fetch("http://localhost:3001/data")
             .then(res => res.json())
             .then(data => {
                 setDataSource(data) // 用获取的数据更新状态
             })
     }, []) // 第二个参数是空数组,表示只执行一次
    
  3. 当某个特定状态变化时触发

    如果你希望在某个值(比如num)改变后执行特定操作,可以把它放进依赖数组里。

     useEffect(() => {
       console.log(`num 的值变成了: ${num}`)
     }, [num]); // 只有 num 变化时,才会执行
    
  4. 组件卸载时触发(用于清理操作)

    如果你的useEffect里创建了定时器或订阅,那么在组件消失时清理它们就非常重要,以防止内存泄漏。只需在useEffect的回调函数里返回一个新函数即可。

     useEffect(() => {
       let timer = setInterval(() => {
         console.log("定时器正在运行...");
       }, 1000)
     ​
       // 返回的这个函数就是清理函数
       return () => {
         console.log("组件卸载,清理定时器!");
         clearInterval(timer)
       }
     }, [])
    

🧠 更强大的状态管理:useReducer

当你的组件状态逻辑变得越来越复杂时,比如一个状态依赖另一个状态,或者有多种更新方式,只用useState可能会让代码变得混乱。这时,useReducer就派上用场了。

你可以把它看作是useState的“升级版”,它借鉴了Redux的思想,让你把更新逻辑从组件中抽离出来。

核心三要素:

  1. Reducer函数:一个纯函数,接收当前的stateaction,返回一个全新的state
  2. Action对象:一个描述“发生了什么”的普通对象,通常包含一个type字段。
  3. Dispatch函数:用来“派发”Action,触发Reducer函数执行。

示例:

 // 1. 定义 Reducer 函数
 function reducer(state, action) {
   switch(action.type) {
     case "add":
       // 重点:不能直接修改 state,必须返回一个新对象
       return { result: state.result + action.num };
     case "minus":
       return { result: state.result - action.num };
     default:
       return state;
   }
 }
 ​
 function App() {
   // 2. 使用 useReducer
   const [res, dispatch] = useReducer(reducer, {result: 0});
 ​
   return (
     <div>
       <h3>{res.result}</h3>
       {/* 3. 调用 dispatch 派发 action */}
       <button onClick={() => dispatch({type: "add", num: 2})}>+</button>
       <button onClick={() => dispatch({type: "minus", num: 1})}>-</button>
     </div>
   )
 }

提示:在一些代码中,你还看到了immer库的影子。immer可以让你用看似“直接修改”的方式来编写Reducer,而它底层会帮你处理不可变性,让代码更简洁。这是useReducer的一个绝佳搭档!

🤝 跨组件通信:useContext

想象一下,如果顶层组件有一个数据,需要传递给很深层的孙子组件,一层层地通过props传递会非常繁琐,这就是所谓的“道具钻孔”(Prop Drilling)。

useContext就是为了解决这个问题而生的!它能让你轻松实现跨组件数据共享,无论组件层级有多深。

如何使用?

  1. 创建一个Context对象

     import { createContext, useContext } from "react";
     ​
     const numContext = createContext();
    
  2. Provider包裹父组件,并通过value提供数据

     function App() {
       const num = 100;
       return (
         <numContext.Provider value={num}>
           <h1>父组件</h1>
           <Child1/>
         </numContext.Provider>
       )
     }
    
  3. 在任何子孙组件中用useContext获取数据

     function Child2() {
       const count = useContext(numContext);
       return (
         <div>
           <h3>孙子组件 --- {count}</h3> {/* 会显示 100 */}
         </div>
       )
     }
    

💡 useRef:获取 DOM 结构或保存可变值

useRef 是一个非常有用的 Hook,它主要有两个作用:

  1. 获取 DOM 元素:在函数组件中,如果你需要直接操作 DOM 元素(比如获取输入框的值、控制焦点等),useRef 可以帮助你。它会返回一个可变的 ref 对象,这个对象的 current 属性会指向被引用的 DOM 节点。

     import React, { useRef } from 'react';
     ​
     function MyInput() {
       const inputRef = useRef(null);
     ​
       const focusInput = () => {
         if (inputRef.current) {
           inputRef.current.focus();
         }
       };
     ​
       return (
         <div>
           <input type="text" ref={inputRef} />
           <button onClick={focusInput}>聚焦输入框</button>
         </div>
       );
     }
     ​
     export default MyInput;
    
  2. 保存可变值:除了获取 DOM,useRef 还可以用来保存任何可变的值,并且在组件重新渲染时,这个值不会丢失。这对于保存一些不需要触发组件重新渲染的变量非常有用,比如定时器的 ID、上一次的状态值等。

     import React, { useRef, useState, useEffect } from 'react';
     ​
     function CounterWithRef() {
       const [count, setCount] = useState(0);
       const prevCountRef = useRef();
     ​
       useEffect(() => {
         prevCountRef.current = count; // 在每次渲染后保存当前的 count 值
       });
     ​
       const prevCount = prevCountRef.current;
     ​
       return (
         <div>
           <p>当前 Count: {count}</p>
           <p>上次 Count: {prevCount}</p>
           <button onClick={() => setCount(count + 1)}>增加</button>
         </div>
       );
     }
     ​
     export default CounterWithRef;
    

⏱️ useLayoutEffect:在浏览器绘制前执行的副作用

useLayoutEffect 的作用和 useEffect 非常相似,都是用来处理副作用的。它们之间最主要的区别在于执行时机:

  • useEffect 在浏览器完成绘制之后执行。
  • useLayoutEffect 在浏览器执行 DOM 更新之后,但在浏览器绘制之前同步执行。

这意味着如果你需要在 DOM 更新后立即进行一些测量或操作 DOM 的操作(例如获取元素的尺寸、滚动位置等),并且这些操作会影响到后续的渲染,那么 useLayoutEffect 会更合适。因为它会在浏览器绘制之前完成,可以避免用户看到闪烁或不一致的 UI。

使用场景:

  • 获取 DOM 元素的布局信息。
  • 在 DOM 更新后立即修改 DOM 样式,以避免视觉上的闪烁。
 import React, { useLayoutEffect, useRef } from 'react';
 ​
 function MeasureBox() {
   const boxRef = useRef(null);
 ​
   useLayoutEffect(() => {
     if (boxRef.current) {
       console.log('Box 宽度:', boxRef.current.offsetWidth);
     }
   }, []);
 ​
   return (
     <div ref={boxRef} style={{ width: '100px', height: '100px', background: 'lightblue' }}>
       这是一个盒子
     </div>
   );
 }
 ​
 export default MeasureBox;

小贴士: 大多数情况下,你都应该优先使用 useEffect。只有当你遇到因为 useEffect 的异步执行导致视觉问题时,才考虑使用 useLayoutEffect

🎨 常用 UI 框架:让你的界面更美观

在 React 开发中,我们通常会结合一些 UI 框架来快速构建美观、一致的用户界面。这些框架提供了一系列预设的组件和样式,可以大大提高开发效率。README.md 中提到了几个常用的 UI 框架:

  1. Ant Design (antd) :一个广受欢迎的企业级 UI 设计语言和 React UI 组件库。它提供了丰富的组件,涵盖了从基础按钮到复杂表格、表单等各种场景,并且拥有完善的设计规范和文档。如果你在开发企业级应用,antd 是一个非常不错的选择。
  2. Element UI:虽然 Element UI 主要用于 Vue.js 生态系统,但在 React 生态中也有类似的组件库,例如 Ant DesignElement UI 以其简洁、美观的设计风格和丰富的组件而受到开发者喜爱。
  3. Vant:一个轻量、可靠的移动端 Vue 组件库。如果你正在开发移动端 React 应用,可以考虑使用 Ant Design Mobile 或其他专门为 React 设计的移动端 UI 库,它们提供了与 Vant 类似的功能和体验。

选择哪个 UI 框架取决于你的项目需求、团队偏好以及目标平台(PC 端还是移动端)。

🚀 实战演练:用Hooks做一个数据管理应用

App6.jsx中,我们看到了一个非常棒的综合案例。它使用了useState来保存表格数据,useEffect在初次加载时获取数据,并结合antd UI库,实现了一个包含搜索和删除功能的数据展示表格。这完美地展示了如何将我们学到的Hooks知识应用到实际项目中。

这个例子告诉我们:

  • useEffect的空数组依赖[]来获取初始数据。
  • 将获取的数据通过setDataSource存入state
  • 用户的交互(如搜索、删除)触发事件函数,在函数内部再次fetch数据或更新本地state来响应变化。

总结

恭喜你!你已经掌握了React Hooks最核心的几个概念。回顾一下:

  • useState:给组件添加状态。
  • useEffect:处理网络请求、定时器等副作用。
  • useReducer:管理复杂的状态逻辑。
  • useContext:轻松实现跨组件通信。
  • useRef:获取 DOM 结构或保存可变值。
  • useLayoutEffect:在浏览器绘制前执行的副作用。

现在,你已经拥有了使用函数组件构建强大React应用的基础。继续探索,动手实践,你的React技能一定会突飞猛进!

如果你对 React Hooks 还有任何疑问,或者想了解更多高级用法,欢迎在评论区留言交流!