React自定义Hook问题:useTags返回undefined及代码性能咨询
问题解决与代码性能评估
一、点击按钮返回undefined的原因及解决方法
核心问题
- Hook状态完全隔离:你在
App组件和TagList组件中分别调用了useTags,这是两个独立的Hook实例,它们的tags状态互不共享,App里的tags和TagList中操作的tags根本不是同一个值。 - 参数缺失导致初始值异常:
App中调用useTags()时未传入data参数,useTags里的data默认是undefined,因此useState(data)初始化的tags就是undefined,后续也没有触发更新(useEffect依赖JSON.stringify(data),初始data为undefined,无变化则不会执行setTags)。
解决方案:状态提升
将tags状态提升到App组件中,让App和TagList共享同一状态:
修改后的App.js
import { useState, useEffect } from "react"; import { TagList, useTags } from "./TagList"; export default function App() { const [data, setData] = useState([]); // 在App中初始化useTags并传入data const [tags, updateTags] = useTags(data); const clickHandler = () => { alert(JSON.stringify(tags)); }; useEffect(() => { setTimeout(() => { setData(["java", "javascript"]); }, 3000); }, []); console.log('[App.js] rendered'); return ( <div className="App"> {/* 将共享的tags和更新方法传给TagList */} <TagList data={data} tags={tags} updateTags={updateTags} /> <button onClick={clickHandler}>click</button> </div> ); }
修改后的TagList.js
import { memo, useEffect, useState } from "react"; export const useTags = (initialData) => { // 处理initialData为undefined的情况,默认初始化为空数组 const [tags, setTags] = useState(initialData || []); const updateTags = (newTags) => { // 支持函数式更新,避免闭包导致的stale状态问题 if (typeof newTags === 'function') { setTags(prev => newTags(prev)); } else { setTags(newTags); } }; useEffect(() => { if (initialData) { setTags(initialData); } }, [initialData]); // 直接用数组作为依赖,移除不必要的JSON.stringify return [tags, updateTags]; }; const Tag = memo(({ tags, tagRemoveHandler, errIndex = -1 }) => { return ( <div className="taglist-tags"> {tags.map((tag, index) => ( // 用tag+index组合作为key,避免重复tag导致的key冲突 <div className={errIndex === index ? "taglist-tag error" : "taglist-tag"} key={`${tag}-${index}`}> <span className="taglist-tag-text">{tag}</span> <span className="taglist-tag-remove" onClick={() => tagRemoveHandler(tag)}> <span className="icon-close"></span> </span> </div> ))} </div> ); }); // 给TagList添加memo,减少不必要的重渲染 export const TagList = memo(({ data, tags, updateTags }) => { const [tag, setTag] = useState(""); const [errIndex, setErrIndex] = useState(-1); useEffect(() => { let timer; if (errIndex !== -1) { timer = setTimeout(() => { setErrIndex(-1); }, 3000); } return () => clearTimeout(timer); }, [errIndex]); const keyDownListener = (e) => { if (e.key === "Enter" || e.code === "Comma") { e.preventDefault(); const trimmedTag = tag.trim(); if (!trimmedTag) return; // 过滤空标签 const index = tags.indexOf(trimmedTag); if (index > -1) { return setErrIndex(index); } updateTags(prev => [...prev, trimmedTag]); setTag(""); } }; const changeHandler = (e) => { setTag(e.target.value); }; const tagRemoveHandler = (t) => { updateTags(prev => prev.filter((tag) => tag !== t)); }; return ( <> <div style={{ marginBottom: 20 }}> <h2>Create tags here.</h2> <label>press enter to create a tag</label> </div> <div className="taglist"> <Tag tags={tags} tagRemoveHandler={tagRemoveHandler} errIndex={errIndex} /> <textarea className="taglist-input" value={tag} onKeyDown={keyDownListener} onChange={changeHandler} /> </div> </> ); });
二、代码性能评估与优化点
原代码的性能问题
- 冗余的JSON.stringify计算:
useTags中用JSON.stringify(data)作为useEffect依赖,每次渲染都会执行序列化操作,对大数组来说会增加不必要的计算开销。 - 状态冗余导致数据不同步:
App和TagList各自维护tags状态,既浪费内存,又引发了数据不一致的问题。 - 未使用memo减少重渲染:
TagList未包裹memo,当App组件重渲染时(比如setData触发),TagList会被强制重渲染,即使传入的props没有变化。 - key值不唯一:Tag组件用
tag作为key,若存在重复tag会导致React渲染异常,无法正确识别元素更新。 - 函数式更新使用不当:
tagRemoveHandler中依赖当前tags变量执行过滤,可能因闭包问题拿到过期的状态值。
优化后的性能提升
- 移除JSON.stringify依赖:直接用
initialData作为useEffect依赖,React会对比数组引用,仅当data实际更新时才触发状态同步。 - 状态提升消除冗余:共享同一
tags状态,避免重复维护,同时确保数据同步。 - memo优化重渲染:
TagList和Tag都添加了memo,仅当props变化时才重渲染,减少不必要的渲染次数。 - 唯一key值避免渲染异常:用
${tag}-${index}作为key,确保即使tag重复,key也唯一,保证React渲染的正确性。 - 正确使用函数式更新:
updateTags支持函数式更新,确保每次更新都基于最新状态,规避闭包带来的stale值问题。
内容的提问来源于stack exchange,提问作者Sairam
相关产品推荐
相关产品推荐

