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

如何为数组中每个条目显示不同日期?React笔记应用问题排查

笔记应用多问题修复方案

核心问题分析

你的三个问题本质都是笔记数据结构设计不合理导致的连锁反应:

  • 新增两条笔记:addNote里一次性往数组塞了date.toTimeString()和""两个独立字符串,数组长度每次+2,自然渲染两条笔记组件
  • 日期出现在textarea:遍历数组时把日期字符串直接传给了Note组件的text属性,导致输入框显示日期
  • 日期重叠:map的第三个参数是数组本身,不是单条笔记的日期,你调用date.toLocaleString()实际是把整个数组转成字符串,所有笔记共用同一个错误值

修复步骤

1. 重构笔记数据结构

把单一字符串数组改成对象数组,每个笔记独立存储文本和日期:

interface NoteItem {
  text: string;
  date: Date;
}

const [notesList, setNotesList] = React.useState<NoteItem[]>([]);

2. 修正添加笔记逻辑

每次添加一个完整的笔记对象,而非两个零散字符串:

const addNote = (): void => {
  setNotesList([...notesList, { text: "", date: new Date() }]);
};

3. 修正渲染逻辑

遍历数组时正确提取每个笔记的文本和日期,同时优化key值避免组件复用问题:

filteredNotes.map((note: NoteItem, idx: number) => (
  <Note
    onChange={updateNote}
    remove={deleteNote}
    idx={idx}
    text={note.text}
    date={note.date.toLocaleString()}
    key={`${idx}-${note.date.getTime()}`}
  />
))

4. 修正更新/删除逻辑

针对对象数组调整操作逻辑,同时用函数式更新避免依赖问题:

const deleteNote = React.useCallback(
  (idx: number): void => {
    setNotesList(prev => prev.filter((_, i) => i !== idx));
  },
  []
);

const updateNote = React.useCallback(
  (idx: number, text: string): void => {
    setNotesList(prev => prev.map((note, i) => 
      i === idx ? { ...note, text } : note
    ));
  },
  []
);

5. 修正搜索过滤逻辑

搜索时匹配笔记的文本内容,而非整个对象:

const getFilteredNotes = (notes: NoteItem[], query: string,) => {
  if (!query) {
    return notes;
  }
  return notes.filter((note) => note.text.includes(query));
};

6. 修正本地存储逻辑

JSON解析后日期会变成字符串,需要转成Date对象:

React.useEffect(() => {
  const storage_string = sessionStorage.getItem(LOCAL_STORAGE_KEY);
  if (!storage_string) return;
  try {
    const savedNotes = JSON.parse(storage_string) as NoteItem[];
    const parsedNotes = savedNotes.map(n => ({
      ...n,
      date: new Date(n.date)
    }));
    setNotesList(parsedNotes);
  } catch (err) {
    console.log("解析笔记失败");
  }
}, []);

完整修复代码

interface NoteItem {
  text: string;
  date: Date;
}

export const App = () => {
  const [notesList, setNotesList] = React.useState<NoteItem[]>([]);
  const [search, setSearch] = React.useState<string>("");
  const [showSidePanel, setShowSidePanel] = React.useState(true);
  const wrapperRef = React.useRef<HTMLDivElement>(null);

  const addNote = (): void => {
    setNotesList([...notesList, { text: "", date: new Date() }]);
  };

  const deleteNote = React.useCallback(
    (idx: number): void => {
      setNotesList(prev => prev.filter((_, i) => i !== idx));
    },
    []
  );

  const updateNote = React.useCallback(
    (idx: number, text: string): void => {
      setNotesList(prev => prev.map((note, i) => 
        i === idx ? { ...note, text } : note
      ));
    },
    []
  );

  React.useEffect(() => {
    const storage_string = sessionStorage.getItem(LOCAL_STORAGE_KEY);
    if (!storage_string) return;
    try {
      const savedNotes = JSON.parse(storage_string) as NoteItem[];
      const parsedNotes = savedNotes.map(n => ({
        ...n,
        date: new Date(n.date)
      }));
      setNotesList(parsedNotes);
    } catch (err) {
      console.log("解析笔记失败");
    }
  }, []);

  React.useEffect(() => {
    sessionStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(notesList));
  }, [notesList]);

  const getFilteredNotes = (notes: NoteItem[], query: string,) => {
    if (!query) {
      return notes;
    }
    return notes.filter((note) => note.text.includes(query));
  };

  const filteredNotes = getFilteredNotes(notesList, search);
  const updateQuery = (event: React.ChangeEvent<HTMLInputElement>) =>
    setSearch(event.target.value);
  const handleSearch = debounce(updateQuery, 500);

  const toggleSidePanel = () => setShowSidePanel(!showSidePanel);

  return (
    <div ref={wrapperRef}>
      <GlobalStyle />
      <SidePanel showSidePanel={showSidePanel}>
        <HeaderContainer>
          <SearchInput
            placeholder="搜索笔记"
            type="text"
            onChange={handleSearch}
          />
          <Button variant="icon" onClick={addNote}>
            <AddIcon />
          </Button>
        </HeaderContainer>
        {notesList.length > 0 ? (
          filteredNotes.length > 0 ? (
            filteredNotes.map((note: NoteItem, idx: number) => (
              <Note
                onChange={updateNote}
                remove={deleteNote}
                idx={idx}
                text={note.text}
                date={note.date.toLocaleString()}
                key={`${idx}-${note.date.getTime()}`}
              />
            ))
          ) : (
            <Text>未找到匹配结果</Text>
          )
        ) : (
          <Text>暂无笔记</Text>
        )}
      </SidePanel>
      {showSidePanel ? (
        ""
      ) : (
        <ToggleButton variant="primary" onClick={toggleSidePanel}>
          <NoteIcon width="2rem" height="2rem" />
          笔记
        </ToggleButton>
      )}
    </div>
  );
};

额外优化建议

  • 不要用索引作为key,推荐用uuid生成唯一id,避免删除操作后组件复用导致的异常
  • 可以给debounce函数增加类型定义,确保类型安全
  • 本地存储解析失败时可以增加用户提示,提升体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 22:45:11