React中对象类型useState导致按钮重复渲染的解决方法
解决对象类型useState下按钮重复渲染的问题
问题背景
使用原始类型(如字符串)的useState时,配合useCallback不会触发按钮重复渲染;但改用对象类型useState后,每次更新对象属性都会生成新的对象引用,导致依赖该对象的useCallback重新创建,最终触发用memo包裹的按钮组件重复渲染。需要在保持对象类型useState的同时,实现输入时按钮不重渲染的效果,还要能向按钮传递String、Object、Array、Func等属性并完成字段校验。
解决方案
1. 缩小useCallback的依赖范围,仅依赖用到的对象属性
不要把整个对象作为useCallback的依赖,而是只依赖校验时实际用到的属性。这样只有当目标属性真正变化时,回调函数才会重新创建,避免不必要的更新。
import { useCallback, useState } from "react"; import Button from "./components/Button"; import "./styles.css"; export default function App() { const [userData, setUserData] = useState({ email: "", password: "" }); // 仅依赖userData.email,而非整个userData对象 const validateFields = useCallback(() => { if (userData.email.trim() === "") { alert("邮箱不能为空"); return false; } return true; }, [userData.email]); const handleAPI = useCallback(() => { if (validateFields()) { console.log("执行API请求"); } }, [validateFields]); return ( <div className="App"> <input value={userData.email} onChange={(event) => setUserData((prev) => ({ ...prev, email: event.target.value })) } /> <Button onClick={handleAPI} /> </div> ); } // Button组件保持不变 import { memo } from "react"; const Button = ({ onClick }) => { console.log("------------Button render---------------"); return <button onClick={onClick}>默认按钮</button>; }; export default memo(Button);
2. 使用useRef缓存对象,避免依赖变化
如果需要用到对象的多个属性,或者不想频繁修改依赖列表,可以用useRef把对象缓存起来,在useCallback里直接访问ref.current,这样回调函数的依赖可以为空,不会因为对象更新而重新创建。
import { useCallback, useState, useRef, useEffect } from "react"; import Button from "./components/Button"; import "./styles.css"; export default function App() { const [userData, setUserData] = useState({ email: "", password: "" }); const userDataRef = useRef(userData); // 同步userData到ref useEffect(() => { userDataRef.current = userData; }, [userData]); // 依赖为空,不会因为userData更新而重新创建 const validateFields = useCallback(() => { const { email } = userDataRef.current; if (email.trim() === "") { alert("邮箱不能为空"); return false; } return true; }, []); const handleAPI = useCallback(() => { if (validateFields()) { console.log("执行API请求"); } }, [validateFields]); return ( <div className="App"> <input value={userData.email} onChange={(event) => setUserData((prev) => ({ ...prev, email: event.target.value })) } /> <Button onClick={handleAPI} /> </div> ); }
3. 用useMemo处理传递给按钮的非函数属性
如果需要向按钮传递Object、Array这类引用类型属性,要配合useMemo缓存它们,避免每次渲染生成新的引用,触发按钮重渲染。比如传递用户信息对象:
// 在App组件中 const buttonUserData = useMemo(() => ({ email: userData.email, password: userData.password }), [userData.email, userData.password]); // 传递给Button <Button onClick={handleAPI} userInfo={buttonUserData} />
4. 拆分useState(可选方案)
如果场景允许,可以把对象中的属性拆分成单独的useState,这样每个属性的更新不会影响其他属性的引用,从根源上减少依赖变化导致的重渲染:
const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); // 此时validateFields的依赖仅为email const validateFields = useCallback(() => { if (email.trim() === "") { alert("邮箱不能为空"); return false; } return true; }, [email]);
内容的提问来源于stack exchange,提问作者Granizo
相关产品推荐
相关产品推荐

