You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React自定义Hook问题:useTags返回undefined及代码性能咨询

问题解决与代码性能评估

一、点击按钮返回undefined的原因及解决方法

核心问题

  1. Hook状态完全隔离:你在App组件和TagList组件中分别调用了useTags,这是两个独立的Hook实例,它们的tags状态互不共享,App里的tags和TagList中操作的tags根本不是同一个值。
  2. 参数缺失导致初始值异常: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>
    </>
  );
});

二、代码性能评估与优化点

原代码的性能问题

  1. 冗余的JSON.stringify计算:useTags中用JSON.stringify(data)作为useEffect依赖,每次渲染都会执行序列化操作,对大数组来说会增加不必要的计算开销。
  2. 状态冗余导致数据不同步:App和TagList各自维护tags状态,既浪费内存,又引发了数据不一致的问题。
  3. 未使用memo减少重渲染:TagList未包裹memo,当App组件重渲染时(比如setData触发),TagList会被强制重渲染,即使传入的props没有变化。
  4. key值不唯一:Tag组件用tag作为key,若存在重复tag会导致React渲染异常,无法正确识别元素更新。
  5. 函数式更新使用不当:tagRemoveHandler中依赖当前tags变量执行过滤,可能因闭包问题拿到过期的状态值。

优化后的性能提升

  1. 移除JSON.stringify依赖:直接用initialData作为useEffect依赖,React会对比数组引用,仅当data实际更新时才触发状态同步。
  2. 状态提升消除冗余:共享同一tags状态,避免重复维护,同时确保数据同步。
  3. memo优化重渲染:TagList和Tag都添加了memo,仅当props变化时才重渲染,减少不必要的渲染次数。
  4. 唯一key值避免渲染异常:用${tag}-${index}作为key,确保即使tag重复,key也唯一,保证React渲染的正确性。
  5. 正确使用函数式更新:updateTags支持函数式更新,确保每次更新都基于最新状态,规避闭包带来的stale值问题。

内容的提问来源于stack exchange,提问作者Sairam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 19:22:48