React的基础API介绍(二)

目录

    • useState
      • [useState 的基本原理](#useState 的基本原理)
        • [1. 状态在函数组件中的引入](#1. 状态在函数组件中的引入)
        • [2. useState 的工作机制](#2. useState 的工作机制)
        • [3. Hook 状态与组件渲染](#3. Hook 状态与组件渲染)
      • [useState 的使用方法](#useState 的使用方法)
        • [1. 基本用法](#1. 基本用法)
        • [2. 多个状态变量](#2. 多个状态变量)
        • [3. 更新状态](#3. 更新状态)
      • 注意事项与最佳实践
        • [1. 状态更新可能是异步的](#1. 状态更新可能是异步的)
        • [2. 不要直接修改状态](#2. 不要直接修改状态)
        • [3. 更新对象或数组状态](#3. 更新对象或数组状态)
        • [4. 避免闭包陷阱](#4. 避免闭包陷阱)
      • 进阶用法
        • [1. 自定义 Hook](#1. 自定义 Hook)
        • [2. 状态与副作用的结合](#2. 状态与副作用的结合)
      • [useState 与 useReducer 的对比](#useState 与 useReducer 的对比)
      • [为什么 Hook 应该始终在组件顶层调用,不能在条件、循环或嵌套函数中使用?](#为什么 Hook 应该始终在组件顶层调用,不能在条件、循环或嵌套函数中使用?)
        • [1. React 如何跟踪 Hooks](#1. React 如何跟踪 Hooks)
        • [2. 为什么需要保持 Hooks 的调用顺序一致](#2. 为什么需要保持 Hooks 的调用顺序一致)
        • [3. 示例解释](#3. 示例解释)
        • [4. 如何在条件情况下使用 Hooks](#4. 如何在条件情况下使用 Hooks)

useState

useState 是 React Hooks 中最基本也是最常用的一个 Hook,用于在函数组件中添加状态管理功能。它使得函数组件能够像类组件一样拥有内部状态,而无需编写类组件的样板代码。

useState 的基本原理

1. 状态在函数组件中的引入

在传统的 React 类组件中,状态(state)是通过 this.state 和 this.setState 来管理的。函数组件最初是无状态的,仅根据传入的 props 进行渲染。

useState 的引入改变了这一点,它允许在函数组件中引入状态。每次调用useState,都创建了一个状态变量和更新该状态的函数。

2. useState 的工作机制
  • 状态的持久化:useState 通过闭包的方式,在组件的多次渲染之间保持状态的持久化。
  • 状态更新:当调用状态更新函数时,React 会将新的状态值入队,并触发组件重新渲染。在下一次渲染时,useState 会返回更新后的状态值。
  • Hook 的调用顺序:React 依赖于 Hook 调用的顺序来正确地管理状态。因此,Hook 应该始终在组件顶层调用,不能在条件、循环或嵌套函数中使用。
3. Hook 状态与组件渲染
  • 组件重新渲染:当状态更新时,组件会重新渲染。函数组件会重新执行,生成新的 JSX。
  • 状态的稳定性:虽然组件函数会重新执行,但 useState 返回的状态在多次渲染之间是稳定的。React 内部通过一个链表结构来跟踪每个 Hook 的状态。

useState 的使用方法

1. 基本用法
javascript 复制代码
import React, { useState } from 'react';

function Counter() {
  // 声明一个新的状态变量 "count",初始值为 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>当前计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

export default Counter;

解释:

初始化状态:useState(0) 初始化了一个状态变量 count,初始值为 0。

状态变量:count 是当前的状态值。

更新函数:setCount 是更新状态的函数,调用它可以更新 count 的值并触发组件重新渲染。

2. 多个状态变量

可以在同一个组件中多次使用 useState,每个状态变量都是独立的。

javascript 复制代码
function UserProfile() {
  const [name, setName] = useState('李四');
  const [age, setAge] = useState(30);

  return (
    <div>
      <p>姓名:{name}</p>
      <p>年龄:{age}</p>
      <button onClick={() => setAge(age + 1)}>增加年龄</button>
    </div>
  );
}
3. 更新状态

直接赋值更新:

javascript 复制代码
setCount(newValue);

基于前一个状态更新(函数式更新):

当新的状态需要依赖之前的状态时,使用函数式更新。

javascript 复制代码
setCount(prevCount => prevCount + 1);

好处: 避免了状态更新的异步性带来的问题,确保基于最新的状态进行计算。

注意事项与最佳实践

1. 状态更新可能是异步的

不可立即获取更新后的状态: 调用 setState 后,状态并不会立即更新,新的状态会在下一次渲染时生效。

避免依赖立即更新的状态:

javascript 复制代码
// 错误示例
setCount(count + 1);
console.log(count); // 仍然是旧的 count 值

// 正确示例
setCount(prevCount => {
  const newCount = prevCount + 1;
  console.log(newCount); // 新的 count 值
  return newCount;
});
2. 不要直接修改状态

状态应该被视为不可变的。不要直接修改状态变量,而是创建新的状态值。

javascript 复制代码
// 错误示例
state.value = newValue;

// 正确示例
setState({ ...state, value: newValue });
3. 更新对象或数组状态

useState 不会自动合并更新对象或数组,需要手动合并。

javascript 复制代码
const [user, setUser] = useState({ name: '张三', age: 25 });

// 更新年龄
setUser(prevUser => ({ ...prevUser, age: prevUser.age + 1 }));
4. 避免闭包陷阱

在异步操作中,可能会捕获到旧的状态值。使用函数式更新可以避免这个问题。

javascript 复制代码
useEffect(() => {
  const timer = setTimeout(() => {
    // 使用函数式更新,确保获取最新的状态值
    setCount(prevCount => prevCount + 1);
  }, 1000);

  return () => clearTimeout(timer);
}, []);

进阶用法

1. 自定义 Hook

可以将状态逻辑封装到自定义 Hook 中,以便在多个组件之间复用。

javascript 复制代码
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(prev => prev + 1);
  const decrement = () => setCount(prev => prev - 1);

  return { count, increment, decrement };
}

// 在组件中使用
function Counter() {
  const { count, increment, decrement } = useCounter(10);

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={increment}>增加</button>
      <button onClick={decrement}>减少</button>
    </div>
  );
}
2. 状态与副作用的结合

useState 通常与 useEffect 一起使用,响应状态的变化执行副作用操作。

javascript 复制代码
function DataFetcher() {
  const [data, setData] = useState(null);

  useEffect(() => {
    let isMounted = true;

    fetchData().then(response => {
      if (isMounted) setData(response);
    });

    return () => {
      isMounted = false;
    };
  }, []);

  return <div>{data ? data.content : '加载中...'}</div>;
}

useState 与 useReducer 的对比

当状态逻辑较为复杂,或者状态更新依赖于前一个状态时,useReducer 可能是更好的选择。

javascript 复制代码
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'decrement':
      return { ...state, count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  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>
    </div>
  );
}

useReducer 的优势:

更清晰的状态管理:适用于复杂状态逻辑,状态更新逻辑集中在 reducer 函数中。

为什么 Hook 应该始终在组件顶层调用,不能在条件、循环或嵌套函数中使用?

1. React 如何跟踪 Hooks
  • Hooks 的调用顺序:React 通过组件中 Hooks 的调用顺序来跟踪每个 Hook 对应的状态。这意味着每次渲染时,Hooks 必须按照相同的顺序被调用。

  • Hook 链表:在内部,React 维护了一个 Hook 链表,记录了每个 Hook 在组件中的位置。当组件重新渲染时,React 依赖于这个调用顺序来正确地匹配当前的 Hook 与之前的状态。

2. 为什么需要保持 Hooks 的调用顺序一致
  • 状态与 Hook 位置关联:由于状态是与 Hook 调用的位置(即调用顺序)相关联的,如果 Hooks 的调用顺序发生变化,React 就无法正确地为每个 Hook 提供对应的状态。

  • 避免状态错位:如果在条件、循环或嵌套函数中调用 Hooks,可能会导致 Hooks 的调用数量或顺序在不同的渲染中发生变化,导致状态错位。

3. 示例解释
  • 错误示例:在条件语句中使用 Hook
javascript 复制代码
function MyComponent({ show }) {
  if (show) {
    useState(0);
  }
  // 其他代码
}

问题:

当 show 为 true 时,useState 被调用。

当 show 为 false 时,useState 未被调用。

这导致在不同的渲染中,Hooks 的调用数量和顺序发生了变化。

后果:

React 无法正确地匹配 Hook 与之前的状态。

可能会导致状态错位、错误的状态值,甚至引发难以调试的错误。

正确的做法

javascript 复制代码
function MyComponent({ show }) {
  const [state, setState] = useState(0);

  // 根据条件渲染内容
  if (show) {
    // 使用 state
  }

  // 其他代码
}

始终在顶层调用 useState,确保每次渲染时 Hooks 的调用顺序一致。

  • 错误示例:在循环中使用 Hook
javascript 复制代码
function MyComponent({ items }) {
  items.forEach(item => {
    useEffect(() => {
      // 对每个 item 进行副作用操作
    }, [item]);
  });

  // 其他代码
}

问题:

items 的数量可能会变化,导致 useEffect 的调用次数不一致。

Hooks 的调用顺序和数量在不同的渲染中不一致。

后果

React 无法正确地管理 Hooks,导致状态错位。

正确的做法

javascript 复制代码
function MyComponent({ items }) {
  // 使用一个 Hook 处理所有 items
  useEffect(() => {
    items.forEach(item => {
      // 对每个 item 进行副作用操作
    });
  }, [items]);

  // 其他代码
}

将 Hooks 调用放在顶层,不受循环或条件的影响。

4. 如何在条件情况下使用 Hooks

虽然不能在条件语句中调用 Hooks,但可以通过在 Hooks 内部处理条件逻辑来实现需求。

示例:

javascript 复制代码
function MyComponent({ show }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    if (show) {
      // 执行副作用
      fetchData().then(result => setData(result));
    }
  }, [show]);

  // 其他代码
}

解释:useEffect 始终被调用,但在内部根据 show 的值决定是否执行副作用操作。

相关推荐
男孩123 分钟前
react高阶组件及hooks
前端·javascript·react.js
m0_7482517223 分钟前
DataOps驱动数据集成创新:Apache DolphinScheduler & SeaTunnel on Amazon Web Services
前端·apache
珊珊来吃24 分钟前
EXCEL中给某一列数据加上双引号
java·前端·excel
胡西风_foxww1 小时前
【ES6复习笔记】Spread 扩展运算符(8)
前端·笔记·es6·扩展·运算符·spread
小林爱1 小时前
【Compose multiplatform教程08】【组件】Text组件
android·java·前端·ui·前端框架·kotlin·android studio
跨境商城搭建开发1 小时前
一个服务器可以搭建几个网站?搭建一个网站的流程介绍
运维·服务器·前端·vue.js·mysql·npm·php
hhzz1 小时前
vue前端项目中实现电子签名功能(附完整源码)
前端·javascript·vue.js
秋雨凉人心2 小时前
上传npm包加强
开发语言·前端·javascript·webpack·npm·node.js
时清云2 小时前
【算法】 课程表
前端·算法·面试
JoeChen.2 小时前
PostCSS插件——postcss-pxtorem结合动态调整rem实现字体自适应
javascript·ecmascript·postcss