Bootstrap

React的基础API介绍(一)

useEffect

useEffect 是 React 中的一个 Hook,用于在函数组件中处理副作用操作,例如数据获取、订阅事件、手动修改 DOM 等。以下是 useEffect 的一些主要特性和优点:

  1. 替代生命周期方法
    模拟类组件的生命周期:useEffect 可以模拟类组件的 componentDidMount、componentDidUpdate 和 componentWillUnmount 方法,允许在组件的不同阶段执行相应的逻辑。
useEffect(() => {
  // componentDidMount 和 componentDidUpdate 的逻辑
  return () => {
    // componentWillUnmount 的逻辑
  };
}, [dependencies]);
  1. 副作用管理
    处理副作用:useEffect 在组件渲染后执行,用于处理需要在渲染后发生的副作用,例如更新 DOM、设置订阅、发送网络请求等。

清理副作用:
a. 组件卸载时(Unmount):当组件从界面上被移除时,React 会调用 useEffect 返回的清除函数。这确保了任何与该组件相关的订阅、计时器或其他副作用在组件不再存在时被正确清理。

b. 在重新执行 effect 之前:如果 useEffect 的依赖项数组中的某个值发生了变化,React 会在重新执行 effect 之前调用上一次 effect 的清除函数。这避免了副作用的累积,并确保每次 effect 执行前的环境是干净的。

useEffect(() => {
  const subscription = someService.subscribe();
  return () => {
    subscription.unsubscribe();
  };
}, []);
  1. 依赖项数组
    精确控制 effect 执行时机:依赖项数组 [dependencies] 允许你指定 effect 何时重新执行。只有当数组中的依赖项发生变化时,effect 才会被触发。空数组的话只执行一次,相当于componentDidMount

避免不必要的更新:通过正确设置依赖项数组,可以避免不必要的 effect 执行,提高组件性能。

useEffect(() => {
  // 仅当 count 变化时执行
}, [count]);
  1. 多次使用
    拆分逻辑:可以在同一个组件中多次使用 useEffect,每个 effect 钩子可以处理不同的副作用逻辑,保持代码清晰。
useEffect(() => {
  // 处理订阅逻辑
}, []);

useEffect(() => {
  // 处理数据获取逻辑
}, [dataId]);
  1. 与闭包配合
    访问最新的 state 和 props:useEffect 内部的函数会捕获到当前渲染周期的 state 和 props,确保副作用操作基于最新的数据。
  2. 支持异步操作
    处理异步任务:useEffect 可以轻松处理异步操作,如数据获取和定时器。
useEffect(() => {
  async function fetchData() {
    const result = await axios.get('/api/data');
    setData(result.data);
  }
  fetchData();
}, []);
  1. 减少样板代码
    简化代码结构:相比于类组件的生命周期方法,useEffect 在函数组件中使用更加简洁,减少了样板代码,使代码更易读。

优点总结

简洁性:使函数组件能够方便地管理副作用,减少了对类组件的依赖。
可读性:通过拆分不同的 useEffect,逻辑更清晰,代码更易于维护。
性能优化:通过依赖项数组,避免了不必要的副作用执行,提高了应用性能。
一致性:提供了统一的方式来处理组件的副作用,减少了认知负担。

注意事项

  1. 正确使用依赖项数组:确保在依赖项数组中包含所有在 effect 中使用的外部变量,防止因为闭包导致的状态不一致问题。
  2. 避免无限循环:如果不慎遗漏依赖项,或者依赖项数组变化过于频繁,可能导致 effect 无限执行,造成性能问题。
  3. 清理函数的重要性:对于订阅、计时器等副作用,一定要在清理函数中进行清理,防止内存泄漏。

示例代码

import React, { useState, useEffect } from 'react';

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    // 订阅聊天信息
    const subscription = ChatAPI.subscribeToRoom(roomId, (newMessage) => {
      setMessages((prevMessages) => [...prevMessages, newMessage]);
    });

    // 清理函数,取消订阅
    return () => {
      ChatAPI.unsubscribeFromRoom(roomId, subscription);
    };
  }, [roomId]); // 当 roomId 变化时,重新订阅

  return (
    <div>
      {messages.map((message) => (
        <p key={message.id}>{message.content}</p>
      ))}
    </div>
  );
}

在上述示例中:

使用了 useEffect 来订阅和取消订阅聊天信息。
依赖项数组 [roomId] 确保当房间 ID 变化时,重新执行 effect。
返回的清理函数确保在组件卸载或 roomId 变化时,取消之前的订阅。

useEffetct是如何拿到变量count最新的值?

示例:

function Counter() {  
  const [count, setCount] = useState(0);    

  useEffect(() => {   
    document.title = `You clicked ${count} times`;  
  });  

  return (    
    <div>      
      <p>You clicked {count} times</p>      
      <button onClick={() => setCount(count + 1)}>        
        Click me      
      </button>   
    </div>  
  );
}

useEffect 中的函数能够读取到最新的 count 状态值,主要是由于 JavaScript 中的闭包机制和 React 函数组件的渲染逻辑。

  1. 每次渲染都会创建新的函数作用域
    新的渲染上下文:在 React 中,每当组件的状态或属性发生变化时,组件都会重新渲染。对于函数组件来说,这意味着组件函数会被重新执行,生成新的渲染结果。
    新的变量和函数:每次渲染都会创建新的作用域,其中的变量(如 count)和函数(如传递给 useEffect 的函数)都是新的。
  2. 闭包捕获最新的状态值
    闭包机制:JavaScript 中,函数会捕获其外部作用域中使用的变量。这意味着在每次渲染时,useEffect 中的函数都会捕获到当次渲染时的 count 值。
    最新的 count 值:由于 count 是在组件函数执行时定义的,每次渲染都会得到最新的状态值。useEffect 中的函数在被调用时,使用的就是这个最新的 count 值。
  3. useEffect 的执行时机
    在渲染后执行:useEffect 中的函数会在组件渲染到屏幕后执行。即使它在渲染期间定义,但执行是在 DOM 更新之后。
    每次渲染后执行:在您的示例中,由于没有指定依赖项数组,useEffect 会在每次渲染后执行,因此每次都会使用最新的 count 值。

综合解释

  1. 函数组件的特性:函数组件在每次渲染时都会重新执行函数体,生成新的 count、setCount,以及新的 useEffect 中的函数。
  2. 闭包捕获:useEffect 中的函数在定义时,捕获了当次渲染的 count 值。当 effect 被执行时,它使用的就是这个捕获的值。
  3. 最新的状态值:由于每次渲染都会生成新的 count,useEffect 中的函数总是能够获取到最新的 count 值。

useLayoutEffect 与 useEffect

  1. useLayoutEffect:

在浏览器完成布局和绘制之前同步执行。
适用于需要读取布局信息并同步修改 DOM 的场景。
会阻塞浏览器绘制,影响性能,谨慎使用。

  1. useEffect:

在浏览器绘制之后异步执行。
不会阻塞渲染,更常用。
示例场景:

使用 useLayoutEffect:测量 DOM 元素的尺寸或位置,然后立即修改布局。

createRef和useRef

  1. createRef
    创建方式:React.createRef()。

适用于:主要在 类组件(Class Components) 中使用。

示例:

import React from 'react';

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }

  componentDidMount() {
    // 通过 this.myRef.current 访问 DOM 元素或子组件实例
    console.log(this.myRef.current);
  }

  render() {
    return <div ref={this.myRef}>Hello World</div>;
  }
}

每次调用 createRef() 都会返回一个新的 Ref 对象。这意味着如果在函数组件中使用 createRef,每次渲染都会创建一个新的 Ref,而不是持久化的。
适用于类组件:在类组件的构造函数中创建一次 Ref,并在整个组件生命周期中保持一致。

  1. useRef
    创建方式:const refContainer = useRef(initialValue)。

适用于:函数组件(Function Components),因为它是一个 Hook。

示例:

import React, { useRef, useEffect } from 'react';

function MyComponent() {
  const myRef = useRef(null);

  useEffect(() => {
    // 通过 myRef.current 访问 DOM 元素或保存任何可变值
    console.log(myRef.current);
  }, []);

  return <div ref={myRef}>Hello World</div>;
}

useRef 返回的 Ref 对象在整个组件生命周期中是持久的。无论组件如何重新渲染,useRef 返回的对象始终指向同一个引用。
不仅可以用于获取 DOM 元素,还可以用于保存任意可变值,类似于在类组件中使用实例属性。
不会在组件重新渲染时重新初始化。

特性createRefuseRef
适用组件类型类组件(Class Components)函数组件(Function Components)
调用时机通常在构造函数中调用一次在函数组件内的任意位置(遵循 Hook 规则)
每次渲染是否创建新 Ref是,每次调用都会创建新的 Ref 对象否,useRef 返回的对象在整个生命周期中保持不变
用途获取 DOM 元素或子组件实例获取 DOM 元素、保存可变值、存储任何可变数据
可持久化的可变值否,主要用于引用 DOM 或组件实例是,可用于存储任意可变值,不会触发重新渲染
;