如何为数组中每个条目显示不同日期?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
相关产品推荐
相关产品推荐

