箭头函数正确声明方法及React中addEventListener两种写法差异与重复触发问题咨询
嘿,我来帮你拆解这两个问题,结合你的React代码一起分析~
1. 如何正确声明箭头函数?
箭头函数是ES6里很实用的语法,正确的声明方式分几种常见场景:
- 带参数+多行代码块:如果函数有多行逻辑,用大括号包裹代码,参数用括号(单个参数可以省略括号):
// 多个参数 const sum = (a, b) => { const result = a + b; return result; }; // 单个参数可省略括号 const square = num => { return num * num; }; - 单行返回省略大括号:如果函数体只有一行返回语句,可以直接省略大括号和
return关键字,写法更简洁:const double = num => num * 2; const getGreeting = name => `Hello, ${name}!`; - 无参数场景:必须用空括号占位,不能省略:
const sayHello = () => console.log("Hello!"); - 返回对象的特殊写法:要把对象用小括号包裹,不然JS会把大括号当成代码块解析,导致语法错误:
const createUser = () => ({ id: 1, name: "John" });
另外要注意:箭头函数没有自己的this、arguments对象,也不能用作构造函数(不能用new调用)。这些特性在需要继承外层作用域this的时候很有用,但要避免在需要独立this的场景使用(比如对象的方法)。
2. 两种addEventListener写法的差异&你的代码问题
咱们先讲写法差异,再结合你的代码分析为什么会出现重复输出:
写法核心差异
document.addEventListener("click", (e) => handleClick(e));:每次执行这行代码时,都会创建一个全新的箭头函数实例,这个箭头函数的作用只是把事件对象e传递给handleClick。document.addEventListener("click", handleClick);:直接传递handleClick函数的引用,每次都是同一个函数实例,事件对象会自动作为参数传递给它,不需要额外包裹。
你的代码里的问题(重复输出的根源)
看你的useEffect代码:
useEffect(() => { document.addEventListener("click", (e) => handleClick(e)); localStorage.setItem("todos", JSON.stringify(todos)); return () => { document.removeEventListener("click", (e) => handleClick(e)); } })
这里有两个致命问题:
- 没有设置依赖项:这个
useEffect会在组件每次渲染时都执行(比如点击button改变changed状态、添加todo时,组件都会重新渲染)。 - 移除监听完全无效:每次添加的是一个新的箭头函数,而移除监听时又创建了另一个新的箭头函数——这两个箭头函数是完全不同的实例,所以
removeEventListener根本找不到之前添加的那个监听!
结果就是:每次组件渲染,都会多绑定一个点击监听,点击页面时所有绑定的监听都会触发,自然就会出现控制台输出重复的情况。
正确的修复写法
要解决这个问题,我们需要:
- 直接传递
handleClick的引用,确保添加和移除的是同一个函数 - 用
useCallback缓存handleClick,避免组件每次渲染都创建新的函数实例 - 给
useEffect设置正确的依赖项,控制它的执行时机
修改后的代码片段:
import React, {useState, useEffect, useCallback} from 'react' import TodoList from './TodoList' export default function App () { const [todos, setTodos] = useState([]); const [todoTitle, setTodoTitle] = useState(""); const [changed, setChanged] = useState(false); // 用useCallback缓存函数,确保每次渲染都是同一个引用 const handleClick = useCallback((e) => console.log("click", e.target), []); useEffect(() => { const raw = localStorage.getItem("todos") || JSON.stringify([]); setTodos(JSON.parse(raw)); }, []); useEffect(() => { // 直接传递handleClick的引用 document.addEventListener("click", handleClick); localStorage.setItem("todos", JSON.stringify(todos)); return () => { // 移除同一个引用的监听,现在能生效了 document.removeEventListener("click", handleClick); } }, [todos, handleClick]) // 依赖项:todos变化时更新localStorage,handleClick变化时重新绑定监听 const addTodo = event => { if (event.key === "Enter") { setTodos([ ...todos, { id: Date.now(), title: todoTitle, completed: false } ]) setTodoTitle(""); } } return ( <div className="container"> <h1>Todo app</h1> <div className="input-field"> <input type="text" value={todoTitle} onChange={e => setTodoTitle(e.target.value)} onKeyPress={addTodo} /> <label>Todo name</label> </div> <TodoList todos={todos} /> <button onClick = {() => setChanged(!changed)}>button</button> </div> ); }
这样修改后,每次只会绑定一个有效的监听,移除时也能精准清除,就不会出现重复输出的问题了。
内容的提问来源于stack exchange,提问作者Anthony
相关产品推荐
相关产品推荐

