基础

Yuqi-666
Yuqi-666
· 4 分钟阅读时长
目录

react事件绑定

on + 事件名称 = { 事件处理程序 }

const clickHandler = () => {}
return (
	<button onClick={clickHandler}></button>
)

事件参数e的传入

const clickHandler = (e,name) => {}
return (
	<button onClick={(e) => clickHandler(e,"jack")}></button>
)

useState

创建状态变量

count [count,setCount] = useState(0)

count 状态变量 setCount 修改状态变量的方法

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

修改count 使用新count渲染UI

<button onClick={handleClick}>{count}</button>

修改对象状态

const [form,setForm] = useState({ name: 'jack'})

setForm传入全新的完整对象

const = changeForm = () => {
	setForm({
		...form,
		name:"john"
	})
}

受控表单绑定

useRef获取DOM

  1. useRef创建ref对象,并与JSX绑定
const inputRef = useRef(null)
<input type="text" ref={inputRef} />
  1. 在DOM可用时,通过inputRef.current拿到DOM对象

组件通信

父子通信

父传子

实现步骤:

  1. 父组件传数据 - 在子组件标签上绑定属性
//父组件
return (
	<div>
		<Son name={name} />
	</div>
)
  1. 子组件接收数据 - 子组件通过props参数接收数据
function Son (props){
	return <div>this is son, {props.name}</div>
}

props说明

prop children 当我们把内容嵌套在子组件标签中时,子组件会自动在名为children的prop属性中接收该内容

<Son>
    <span>this is span</span>
</Son>

子组件接收props时会有children传入<span />

子传父

父组件给子组件传递自己的get函数

function Parent() {
  // 父组件的状态(将被子组件修改)
  const [message, setMessage] = useState('等待子组件传递数据...');

  // 1. 定义回调函数(将传递给子组件)
  // 接收子组件传递的参数,并更新父组件状态
  const handleChildData = (childMsg) => {
    console.log('父组件收到子组件数据:', childMsg);
    setMessage(`子组件说:${childMsg}`);
  };

  return (
    <div style={{ padding: '20px', border: '1px solid #ccc' }}>
      <h3>父组件</h3>
      <p>子组件传递的数据:{message}</p>
      {/* 2. 通过 props 将回调函数传给子组件 */}
      <Child onSendData={handleChildData} />
    </div>
  );
}

子组件接收函数,调用函数传递数据

function Child({ onSendData }) { // 3. 接收父组件传递的回调函数
  const sendDataToParent = () => {
    // 4. 子组件触发回调函数,并传入要传递的数据(可以是任意类型:字符串、对象、数组等)
    onSendData('Hello 爸爸~ 我是子组件!');
  };

  return (
    <div style={{ padding: '10px', marginTop: '10px', border: '1px solid #f00' }}>
      <h4>子组件</h4>
      {/* 点击按钮触发传递数据 */}
      <button onClick={sendDataToParent}>向父组件传递数据</button>
    </div>
  ); 
}

export default Child;

兄弟通信

跨层通信

实现步骤:

  1. 使用createContext方法创建一个上下文对象Ctx
const MsgContext = createContext()
  1. 在顶层组件(App)中通过 Ctx.Provider 组件提供数据
function App(){
	const msg = "this is app msg"
	return(
	<div>
		<MsgContext.Provider value={msg}>
			<A />
		</MsgContext.Provider>
	</div>
	)
}
  1. 在底层组件(B)中通过 useContext 钩子函数获取消费数据
function B(){
	const msg = useContext(MsgContext)
	return(
	<div>
		{msg}
	</div>
	)
}

useEffect

useEffect 是 React Hooks 中用于处理副作用的核心 Hook,它允许你在函数组件中执行数据获取、订阅、DOM 操作、清理定时器等“副作用”操作,替代了类组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 三个生命周期方法。

一、核心概念

  • 副作用:指组件渲染之外的操作(如网络请求、DOM 操作、订阅/监听、定时器/计时器等)。
  • 依赖数组:控制 useEffect 何时执行的关键,决定了副作用的触发时机。
  • 清理函数:可选的返回函数,用于清除副作用(如取消订阅、清除定时器),避免内存泄漏。

二、基本语法

useEffect(() => {
  // 副作用逻辑(如数据请求、DOM 操作、定时器等)

  // 可选:清理函数(组件卸载或依赖变化前执行)
  return () => {
    // 清理操作(如清除定时器、取消订阅)
  };
}, [依赖数组]); // 控制触发时机的依赖项列表

三、常见用法(按依赖数组分类)

useEffect 的行为完全由依赖数组决定,以下是 4 种核心用法:

1. 无依赖数组:每次渲染后执行(不推荐)

useEffect(() => {
  console.log("每次组件渲染后都会执行");
});
  • 触发时机:组件首次渲染 + 每次状态/Props 变化导致的重新渲染后。
  • 风险:可能导致无限循环(如副作用中修改了依赖的状态),除非明确需要,否则避免使用。

2. 空依赖数组:仅首次渲染后执行(替代 componentDidMount)

import { useEffect } from "react";

function MyComponent() {
  useEffect(() => {
    console.log("组件首次渲染后执行(只跑一次)");
    // 常见场景:初始化数据请求、创建订阅、启动定时器
    const timer = setInterval(() => console.log("定时器运行中"), 1000);

    // 清理函数:组件卸载前执行(替代 componentWillUnmount)
    return () => {
      clearInterval(timer); // 清除定时器,避免内存泄漏
      console.log("组件卸载,清理副作用");
    };
  }, []); // 空数组 = 仅依赖组件挂载/卸载

  return <div>Hello useEffect</div>;
}
  • 触发时机:仅首次渲染后执行一次。
  • 清理函数:仅在组件卸载前执行一次(用于清除副作用)。

3. 有依赖数组:依赖变化时执行(替代 componentDidUpdate)

import { useState, useEffect } from "react";

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // 依赖 userId,当 userId 变化时重新请求数据
    const fetchUser = async () => {
      const res = await fetch(`/api/users/${userId}`);
      const data = await res.json();
      setUser(data);
    };

    fetchUser();

    // 清理函数:userId 变化前或组件卸载前执行
    return () => {
      console.log("清理上一次的请求(如取消请求)");
    };
  }, [userId]); // 依赖数组:仅当 userId 变化时,重新执行副作用

  return <div>{user ? user.name : "加载中..."}</div>;
}
  • 触发时机:
    • 首次渲染后执行一次;
    • 依赖数组中的任意一项发生变化时,先执行上一次的清理函数,再执行新的副作用逻辑。
  • 关键:依赖数组必须包含副作用中使用的所有外部变量(状态、Props、局部变量等),否则会导致闭包陷阱(使用旧值)。

4. 清理函数的完整场景

清理函数的核心作用是“消除副作用的残留影响”,常见场景:

  • 清除定时器/计时器(setInterval/setTimeout);
  • 取消网络请求(AbortController);
  • 移除 DOM 事件监听(removeEventListener);
  • 取消订阅(如 Redux 订阅、WebSocket 订阅)。

示例:取消网络请求 + 移除事件监听

useEffect(() => {
  // 1. 网络请求(支持取消)
  const controller = new AbortController();
  const signal = controller.signal;

  const fetchData = async () => {
    try {
      const res = await fetch("/api/data", { signal });
      const data = await res.json();
      console.log(data);
    } catch (err) {
      if (err.name !== "AbortError") console.error(err);
    }
  };

  fetchData();

  // 2. DOM 事件监听
  const handleScroll = () => console.log("滚动了");
  window.addEventListener("scroll", handleScroll);

  // 清理函数:依赖变化/组件卸载时执行
  return () => {
    controller.abort(); // 取消网络请求
    window.removeEventListener("scroll", handleScroll); // 移除事件监听
  };
}, [依赖项]);

四、关键注意事项

1. 依赖数组必须完整

副作用中使用的所有外部变量(状态、Props、组件内定义的函数/变量)都必须加入依赖数组,否则 React 会警告,且可能导致逻辑错误(闭包陷阱)。

❌ 错误示例(遗漏依赖):

const [count, setCount] = useState(0);
const msg = `当前计数:${count}`;

useEffect(() => {
  console.log(msg); // msg 依赖 count,但未加入依赖数组
}, []); // 警告:缺少依赖 msg/count

✅ 正确示例:

useEffect(() => {
  const msg = `当前计数:${count}`;
  console.log(msg);
}, [count]); // 依赖 count,count 变化时重新执行

2. 避免无限循环

如果副作用中修改了依赖数组中的变量,会导致:依赖变化 → 副作用执行 → 依赖再变化 → 副作用再执行… 无限循环。

❌ 错误示例(无限循环):

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

useEffect(() => {
  setCount(count + 1); // 副作用修改了依赖 count
}, [count]); // count 变化 → 副作用执行 → 无限循环

✅ 解决:使用函数式更新(不依赖外部 count):

useEffect(() => {
  setCount(prev => prev + 1); // 函数式更新,不依赖外部 count
}, []); // 空数组,仅执行一次

3. 清理函数的执行时机

清理函数不是只在组件卸载时执行!当依赖数组变化时,会先执行上一次副作用的清理函数,再执行新的副作用逻辑。

示例:

useEffect(() => {
  console.log("副作用执行(userId:", userId, ")");
  return () => console.log("清理(旧 userId:", userId, ")");
}, [userId]);
  • userId 从 1 变为 2 时,执行顺序:
    1. 清理(旧 userId:1)
    2. 副作用执行(userId:2)

4. useEffect 是异步的

useEffect 的副作用逻辑是在组件渲染完成后异步执行的,不会阻塞 DOM 渲染(区别于 useLayoutEffect,后者是同步执行)。

示例:

useEffect(() => {
  console.log("useEffect 执行(异步)");
});
console.log("组件渲染同步代码");

// 执行顺序:组件渲染同步代码 → useEffect 执行(异步)

五、与类组件生命周期的对应关系

useEffect 用法类组件生命周期等价
useEffect(() => {}, [])componentDidMount + componentWillUnmount
useEffect(() => {})componentDidMount + componentDidUpdate
useEffect(() => { return () => {} }, [deps])componentDidUpdate(依赖变化) + componentWillUnmount

总结

  • useEffect 用于处理组件的副作用,核心是依赖数组清理函数
  • 依赖数组控制触发时机:空数组(仅挂载)、有值(依赖变化)、无数组(每次渲染)。
  • 清理函数用于清除副作用残留,避免内存泄漏。
  • 必须保证依赖数组完整,避免闭包陷阱和无限循环。

掌握 useEffect 是 React 函数组件开发的核心,实际开发中需结合数据请求、事件监听、定时器等场景灵活使用。