news 2026/7/22 11:53:19

从零开始学前端 | 第三十五章:记账板筛选切换、统计卡片与组件通信

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零开始学前端 | 第三十五章:记账板筛选切换、统计卡片与组件通信

本章定位

上一章,我们已经把记账板第一版最关键的录入闭环打通了。

你已经真正做出了这些东西:

  1. 表单可以录入账单信息。
  2. 提交前可以做基础校验。
  3. 提交成功后可以新增一条记录。
  4. 列表可以根据状态渲染出来。
  5. 没有数据时会显示空状态提示。

也就是说,到现在为止,你已经不只是有一个“页面骨架”,而是已经拥有了一个:

能把用户输入转换成真实记录,并显示到界面上的 React 小应用雏形。

但如果你继续往下用,很快就会遇到几个新的现实问题:

  1. 记录一多,怎么只看收入或只看支出?
  2. 顶部统计卡片里的数字,怎么接上真实数据?
  3. 筛选按钮点下去后,为什么会影响别的组件?
  4. 这些变化到底是谁控制的,谁又只是负责显示?

你会发现,这三个问题虽然看起来分别属于:

  • 筛选功能
  • 统计功能
  • 组件通信

但它们背后其实都在围绕同一件事:

页面里的多个组件,如何围绕同一份状态协作。

所以这一章,我们会正式把记账板从“表单新增 + 列表渲染”继续推进到:

筛选切换、统计卡片和组件通信真正接入项目。

本章学习目标

学完这一章后,你应该能做到:

  1. 理解为什么筛选、统计和组件通信会在同一阶段出现。
  2. 知道当前筛选类型为什么适合作为页面层状态。
  3. 学会为记账板补充FilterType和统计结果类型。
  4. 理解什么是派生数据。
  5. 知道为什么筛选结果通常不一定需要单独做成状态。
  6. 学会根据recordListfilterType计算筛选结果。
  7. 学会根据账单列表计算收入、支出和结余。
  8. 理解SummaryCardsFilterTabs为什么更适合做展示型组件。
  9. 掌握父传子、子通过回调影响父的基础通信方式。
  10. 理解筛选变化后,列表和提示文案为什么会一起更新。
  11. 建立“共享状态放在父层,派生结果按需计算”的项目意识。
  12. 为下一篇继续实现编辑、删除或持久化能力做好准备。

一、这一篇要把项目推进到哪一步

上一章我们完成的是:

录入 -> 新增 -> 列表渲染

这一章要继续补齐的是:

筛选 -> 统计 -> 组件联动

具体来说,这一篇希望你真正做出来这些能力:

  1. 可以在“全部 / 收入 / 支出”之间切换
  2. 列表会根据当前筛选结果变化
  3. 顶部统计卡片会显示真实数字
  4. 筛选按钮、统计卡片、列表组件之间能围绕同一份页面状态协作

这一步非常重要。

因为从这里开始,你会第一次非常明确地看到:

React 项目不是“一个组件一个功能”这么简单,而是多个组件一起围绕数据流工作。

二、为什么筛选、统计和组件通信总是会一起出现

这一点特别值得先讲清楚。

很多初学者会觉得:

  • 筛选是按钮逻辑
  • 统计是计算逻辑
  • 组件通信是 React 概念

好像是三件互不相干的事情。

但在真实页面里,它们经常会同时出现。

例如在记账板里:

  1. 你点击“只看收入”
  2. 当前筛选状态变化
  3. 列表显示的内容变了
  4. 空状态文案可能也变了
  5. 顶部某些数字可能也要重新计算

这说明什么?

说明它们并不是三条平行线,而是:

某个共享状态一变,多个组件会一起响应。

而组件通信,本质上就是在解决:

当多个组件都和同一份页面状态有关时,它们怎么协作。

三、先补两个这一篇会用到的重要类型

这一篇继续用 React + TypeScript,所以正式开始前,我们先把两个很关键的类型补上。

exporttypeFilterType="all"|"income"|"expense";exportinterfaceSummaryData{incomeTotal:number;expenseTotal:number;balance:number;}

1.FilterType在表达什么

它表达的是:

当前页面正在使用哪一种筛选方式。

第一版我们先只保留三种:

  • all
  • income
  • expense

这已经足够支撑当前阶段的筛选交互。

2.SummaryData在表达什么

它表达的是统计卡片真正需要的结果:

  • 总收入
  • 总支出
  • 当前结余

这一点很重要,因为统计卡片真正关心的不是整条记录数组,而是:

从数组里算出来的几个结果值。

四、为什么当前筛选类型适合放在App

这一步非常关键。

很多初学者第一次写筛选时,会自然地想:

既然按钮在FilterTabs组件里,那筛选状态是不是就放在FilterTabs里?

看起来好像挺合理,但当前阶段更稳的思路是:

哪个状态会影响多个组件,哪个状态就更适合放在它们共同的父层。

filterType明显会影响:

  • FilterTabs自己的高亮显示
  • RecordList的列表结果
  • 空状态提示文案

所以它更适合放在App层。

1. 这体现了 React 哪条非常核心的思路

就是:

共享状态上移到共同父组件。

2. 当前阶段怎么理解最够用

你可以先记住一句话:

谁掌握了会影响全局展示的状态,谁就更像这个页面的数据协调者。

在这个项目里,App就在扮演这个角色。

五、第一版先把filterType状态接进来

当前阶段我们先加一个最基础的筛选状态就够了。

const [filterType, setFilterType] = useState<FilterType>("all");

1. 为什么默认值先用all

因为页面初次打开时,最自然的体验通常是:

先看到全部记录。

这样用户对整个页面的数据范围会更容易建立认知。

2. 这一条状态后面会影响什么

至少会影响这几件事:

  1. 哪个筛选按钮高亮
  2. 列表该显示哪些记录
  3. 空状态要显示什么文案

所以别看它只是一条短短的状态,它其实是这一章非常关键的一条主线。

六、什么是派生数据,为什么这一章一定要理解它

这一节非常重要。

因为从这一章开始,你会开始频繁遇到一种很常见的数据:

它不是用户直接输入的,也不是你直接手动保存的,而是根据已有状态算出来的。

这类数据,就很适合先理解为:

派生数据

例如在这一章里:

  • 当前筛选后的记录列表
  • 当前统计卡片显示的数据

都属于这种感觉。

1. 为什么这类数据特别值得单独讲

因为初学者很容易犯一个常见问题:

只要页面上要显示一个结果,就下意识想再开一个状态。

但实际上,有些结果完全可以:

根据已有状态直接算出来。

这会让你的状态更少、结构更清楚。

2. 当前阶段最值得你先记住什么

如果一个结果只是根据已有状态计算出来的,而且没有必要独立保存,那么你可以先想一想:

它是不是更适合做成派生数据,而不是新的useState

七、为什么filteredRecordList通常不一定需要单独做状态

这是本章最关键的认知之一。

很多人第一次做筛选时,最容易走到这样的想法:

  1. 先有recordList
  2. 再开一个filteredRecordList
  3. 每次切换筛选时手动去改它

这不是绝对不行,但当前阶段更稳的思路通常是:

只保留最原始的状态,再根据它们算出筛选结果。

在这个项目里:

  • 原始数据是recordList
  • 筛选条件是filterType

那么筛选后的结果完全可以理解成:

recordList + filterType的计算结果

1. 这样做有什么好处

  1. 原始状态更少
  2. 不容易出现两份列表不同步
  3. 数据流更清楚

2. 当前阶段最怕什么

最怕的是:

你一边维护原列表,一边维护筛选列表,最后两边更新时机越来越乱。

所以这一章很值得建立这个意识:

能算出来的结果,先尽量别重复存。

八、先写一个获取筛选结果的函数

这一节我们就把“筛选结果是派生数据”这件事真正写出来。

importtype{FilterType,RecordItem}from"../types/record";exportfunctiongetFilteredRecordList(recordList:RecordItem[],filterType:FilterType):RecordItem[]{if(filterType==="all"){returnrecordList;}returnrecordList.filter(function(record){returnrecord.type===filterType;});}

1. 这个函数到底在做什么

它的逻辑其实很直白:

  1. 如果当前筛选是all,直接返回全部记录
  2. 否则,只保留类型匹配的记录

2. 为什么这个函数值得单独提出来

因为它已经不是 JSX 本身的一部分了。

它更像是:

一段和页面逻辑相关、但和具体界面结构无关的纯计算逻辑

这种函数单独放出来,后面会更容易读,也更容易复用。

九、现在回头看,filteredRecordList应该怎么得到

当你已经有了:

  • recordList
  • filterType
  • getFilteredRecordList

那当前筛选结果就可以很自然地写成:

const filteredRecordList = getFilteredRecordList(recordList, filterType);

1. 为什么这个写法特别值得你建立感觉

因为它非常清楚地表达了:

当前要显示的列表,不是另外保存的一份状态,而是根据已有数据推导出来的。

2. 这对后面做更复杂功能有什么好处

比如后面你想继续加:

  • 关键词搜索
  • 日期筛选
  • 排序逻辑

你会更容易把它们都组织成:

若干条件共同推导出最终显示结果

这比维护很多平行状态稳得多。

十、统计卡片为什么也适合走“派生结果”这条路

当我们讲清楚筛选结果之后,再看统计卡片就会更容易。

很多初学者一看到统计数字,也会下意识想:

那我是不是还要开三个状态:incomeTotalexpenseTotalbalance

当前阶段通常也先不用。

因为它们本质上也是:

根据账单记录数组计算出来的结果。

例如:

  • 总收入 = 所有收入记录金额之和
  • 总支出 = 所有支出记录金额之和
  • 结余 = 总收入 - 总支出

所以它们也非常适合先走:

原始列表状态 -> 统计结果

这条路。

十一、先写一个统计函数,把卡片数据算出来

下面我们先写一个当前阶段很够用的统计函数。

importtype{RecordItem,SummaryData}from"../types/record";exportfunctioncalculateSummaryData(recordList:RecordItem[]):SummaryData{constincomeTotal=recordList.filter(function(record){returnrecord.type==="income";}).reduce(function(total,record){returntotal+record.amount;},0);constexpenseTotal=recordList.filter(function(record){returnrecord.type==="expense";}).reduce(function(total,record){returntotal+record.amount;},0);return{incomeTotal,expenseTotal,balance:incomeTotal-expenseTotal};}

1. 这个函数在做什么

它做了三件事:

  1. 先把收入金额加总
  2. 再把支出金额加总
  3. 最后用收入减去支出得到结余

2. 为什么这里先用全部记录,而不是筛选结果

因为对记账板来说,统计卡片最常见的一种展示方式是:

显示整个账单板当前的整体收支情况

也就是说:

  • 筛选主要影响列表
  • 统计先看全局结果

这是一种非常自然、也比较适合当前阶段理解的做法。

3. 如果以后你想让统计卡片跟着筛选变怎么办

那也很简单。

你只需要把:

calculateSummaryData(recordList)

换成:

calculateSummaryData(filteredRecordList)

就能切换成“当前筛选结果统计”。

这也进一步说明:

派生函数一旦设计清楚,后面改展示策略会轻松很多。

十二、统计结果怎样真正接到SummaryCards

现在我们已经有了统计函数,就可以把真实数据接到统计组件里了。

先看在App层里怎么得到它:

const summaryData = calculateSummaryData(recordList);

然后SummaryCards的 props 可以先这样设计:

import type { SummaryData } from "../types/record"; interface SummaryCardsProps { summaryData: SummaryData; }

1. 为什么这里传summaryData对象也很合适

因为统计卡片本来就是一组相关的结果:

  • 收入
  • 支出
  • 结余

把它们打包成一个对象传进去,会更像一组完整数据。

2. 这也在练哪种意识

这也在帮你慢慢建立:

组件接收的 props,不一定永远是单个简单值,也可以是一组有意义的结果对象。

十三、SummaryCards第一版更适合做展示型组件

这一点也非常重要。

当前阶段更稳的做法是让SummaryCards只负责:

接收数据并展示

例如:

export function SummaryCards({ summaryData }: SummaryCardsProps) { return ( <section className="summary-grid"> <article className="panel summary-card"> <span className="summary-label">总收入</span> <strong className="summary-number">¥{summaryData.incomeTotal}</strong> </article> <article className="panel summary-card"> <span className="summary-label">总支出</span> <strong className="summary-number">¥{summaryData.expenseTotal}</strong> </article> <article className="panel summary-card"> <span className="summary-label">当前结余</span> <strong className="summary-number">¥{summaryData.balance}</strong> </article> </section> ); }

1. 为什么不把统计逻辑直接写在SummaryCards

因为这样会把:

  • 页面数据组织
  • 统计计算
  • 组件展示

三件事混在一起。

当前阶段更稳的分工是:

父层先算好结果,子组件专心展示结果。

2. 这种分工对后面有什么好处

后面如果你要换:

  • 样式布局
  • 数字格式
  • 卡片文案

SummaryCards会更容易改,因为它更专注。

十四、FilterTabs第一版应该怎样设计

现在轮到筛选组件。

当前阶段FilterTabs最核心的职责有两个:

  1. 展示当前有哪些筛选选项
  2. 把用户点击的筛选值告诉父组件

也就是说,它不是页面最终数据的拥有者,而是:

页面筛选动作的触发者

先看 props 设计:

import type { FilterType } from "../types/record"; interface FilterTabsProps { activeFilter: FilterType; onFilterChange: (nextFilter: FilterType) => void; }

1.activeFilter在做什么

它告诉组件:

当前哪个选项处于激活状态。

这样按钮才能正确高亮。

2.onFilterChange在做什么

它告诉组件:

当用户点击某个选项时,应该把新的筛选值往父层传回去。

这就是一个很典型的 React 回调传递场景。

十五、这就是一次最典型的“父传子 + 子回调父”

这一节非常关键。

因为这其实就是“组件通信”在真实项目里的样子。

先看父组件给子组件传什么:

<FilterTabs activeFilter={filterType} onFilterChange={setFilterType} />

1. 父组件传下去了什么

传下去了两样东西:

  1. 当前筛选状态filterType
  2. 修改筛选状态的方法setFilterType

2. 子组件做了什么

子组件并不自己决定页面最终显示什么。

它只是:

  1. 收到当前激活值
  2. 把点击产生的新值通过回调传回去

3. 为什么这正是组件通信的核心例子

因为它非常完整地体现了:

  • 父组件掌握共享状态
  • 子组件通过props拿到状态
  • 子组件通过回调影响父组件状态
  • 父组件状态变化后,再反过来影响其他子组件

这就是 React 里非常经典的一条通信链路。

十六、先把FilterTabs真正写出来

下面看一个当前阶段很适合作为第一版的写法。

const filterOptions = [ { label: "全部", value: "all" }, { label: "收入", value: "income" }, { label: "支出", value: "expense" } ] as const; export function FilterTabs({ activeFilter, onFilterChange }: FilterTabsProps) { return ( <section className="panel filter-tabs"> {filterOptions.map(function (option) { const isActive = option.value === activeFilter; return ( <button key={option.value} type="button" className={isActive ? "tab-button active" : "tab-button"} onClick={function () { onFilterChange(option.value); }} > {option.label} </button> ); })} </section> ); }

1. 这个组件当前阶段最值得你观察什么

最值得观察的是:

它没有自己的筛选状态,但它能根据父层给的数据正确高亮,并把用户动作反馈回去。

2. 这对理解组件职责有什么帮助

它会帮助你慢慢建立一种非常关键的感觉:

某个组件能很有用,不一定是因为它管理了很多状态,也可能只是因为它把展示和交互入口组织得很清楚。

十七、筛选一变,为什么列表会跟着变

现在我们把前面的链路真正串起来看。

当用户点击FilterTabs里的“收入”按钮时:

  1. FilterTabs调用onFilterChange("income")
  2. 父组件里的filterType更新成"income"
  3. filteredRecordList被重新计算
  4. RecordList收到新的recordList
  5. 界面重新渲染出“只有收入”的列表

这整个过程非常重要。

因为它让你真正看到:

子组件自己没有直接改列表,但它通过改变父层共享状态,间接影响了列表结果。

这就是组件通信和数据流协作的实际样子。

十八、现在让RecordList改成接收筛选结果

上一章里,RecordList还是直接接recordList

到了这一章,更自然的做法是:

<RecordList recordList={filteredRecordList} />

1. 这一步最关键的变化是什么

变化不在组件内部,而在于:

RecordList不再负责决定“显示哪些数据”,它只负责“把收到的数据展示出来”。

2. 为什么这种分工特别稳

因为这样你会越来越清楚:

  • 数据怎么筛,是父层决定
  • 数据怎么显示,是列表组件决定

这就是非常典型的“数据决策在上层,展示逻辑在下层”。

十九、筛选后空状态文案,为什么也可以跟着一起变

这一点很适合拿来练“页面联动感”。

记账板里会有两种很常见的空状态:

1. 一种是页面根本还没有任何记录

这时更适合提示:

先录入第一条账单记录。

2. 另一种是页面本来有记录,但当前筛选下没有匹配结果

这时更适合提示:

当前筛选下还没有对应类型的记录。

3. 为什么这一步很值得做

因为它会让你很直观地感受到:

同一个列表组件,收到的数据和上下文一变,展示文案也可以更贴近真实场景。

这不是为了“花哨”,而是为了让页面反馈更准确。

二十、怎么给RecordList补上更合适的空状态提示

当前阶段可以先在App层推导一个空状态配置,再传给列表组件。

例如:

const emptyStateConfig = recordList.length === 0 ? { title: "还没有账单记录", description: "先在上方录入第一条收入或支出记录。" } : { title: "当前筛选下没有记录", description: "可以切换筛选条件,或者继续新增新的账单记录。" };

然后再传给RecordList

<RecordList recordList={filteredRecordList} emptyTitle={emptyStateConfig.title} emptyDescription={emptyStateConfig.description} />

1. 这一步又在练什么

这一步其实又在练两件事:

  1. 派生结果
  2. 父组件统一组织页面上下文

2. 为什么这比把判断塞进RecordList更清楚

因为RecordList只需要知道:

如果空了,要显示什么

而“为什么空”“当前属于哪种空”,父层更容易判断清楚。

二十一、把这一章的App主体真正串起来

到这里,我们已经有了:

  • recordList
  • formValue
  • message
  • filterType
  • filteredRecordList
  • summaryData

所以App的主体会变得更像一个真正的页面协调中心。

例如:

const filteredRecordList = getFilteredRecordList(recordList, filterType); const summaryData = calculateSummaryData(recordList); return ( <main className="app-shell"> <header className="hero"> <p className="eyebrow">第四阶段综合实战</p> <h1>记账板</h1> <p className="hero-desc">让筛选、统计和列表围绕同一份状态协作起来。</p> </header> <SummaryCards summaryData={summaryData} /> <FilterTabs activeFilter={filterType} onFilterChange={setFilterType} /> <RecordForm formValue={formValue} message={message} onFormChange={handleFormValueChange} onSubmit={handleSubmitRecord} /> <RecordList recordList={filteredRecordList} /> </main> );

1. 为什么这时的App看起来更像“总控”

因为它已经开始掌握:

  • 原始数据
  • 共享状态
  • 派生结果
  • 子组件之间的衔接关系

2. 这其实就是哪种能力在成长

这其实就是:

页面级状态组织能力

它是做 React 小项目时非常关键的一步。

二十二、把这一条数据流翻译成人话

这一节非常重要。

因为只要你能把这条链路用自己的话讲清楚,说明你已经真的开始理解项目,而不是只是在照着写。

1. 页面真正保存的原始状态有哪些

例如:

  • 所有账单记录recordList
  • 当前表单值formValue
  • 当前筛选类型filterType

2. 页面不是直接保存、而是算出来的内容有哪些

例如:

  • 当前筛选结果filteredRecordList
  • 当前统计卡片数据summaryData
  • 当前空状态文案

3. 用户点筛选按钮时发生了什么

发生的是:

子组件通过回调改了父组件状态,父组件状态变化后,列表和按钮高亮一起更新。

4. 用户新增记录时发生了什么

发生的是:

原始列表状态更新了,所以统计卡片、列表结果和空状态判断也都跟着重新计算。

5. 这一章最关键的一句话是什么

就是:

页面真正保存的是原始状态,很多展示结果其实是围绕原始状态推导出来的。

这句话非常关键。

二十三、这一章最容易踩的几个坑

这一节建议你认真看。

因为这一章开始,页面里的联动明显变多了,初学者很容易被“功能都不难,但怎么老是绕”这种感觉卡住。

1. 坑一:把筛选结果也单独存成状态

如果你已经有:

  • recordList
  • filterType

那很多时候filteredRecordList完全可以直接算出来。

2. 坑二:把统计数字也一项项开成状态

如果统计结果只是来源于当前列表,就先别急着单独保存。

3. 坑三:把筛选状态放进FilterTabs自己内部

这样一来,别的组件就很难共享这条状态。

4. 坑四:父组件和子组件都在做同一份判断

例如父组件已经决定了当前筛选结果,子组件里又再筛一次。

这样会越来越乱。

5. 坑五:组件既想算数据,又想管状态,又想管展示

这通常会让组件越来越胖。

当前阶段更稳的方向是:

谁负责组织数据,谁负责展示结果,要慢慢分开。

6. 坑六:只管列表变化,不管空状态变化

筛选一加进来后,空状态就不再只有“完全没数据”这一种情况了。

这一点非常容易漏掉。

二十四、本章实践练习

这一章的练习重点,是把“共享状态 + 派生结果 + 父子通信”真正练熟。

1. 练习 1:给页面加 3 条不同类型的模拟账单

请你先准备:

  • 至少 1 条收入
  • 至少 2 条支出

然后手动切换筛选按钮,观察:

  1. 列表结果是否变化
  2. 按钮高亮是否变化
  3. 统计卡片是否显示正确

这个练习会帮助你:

真正看到筛选和统计是怎么接上真实数据的。

2. 练习 2:把统计卡片改成显示两位小数

例如把:

  • 88

显示成:

  • 88.00

这个练习会帮助你继续思考:

统计逻辑和展示格式是不是应该放在同一层处理。

3. 练习 3:让筛选后的空状态文案更具体

例如:

  • 当前是收入筛选但没有收入记录
  • 当前是支出筛选但没有支出记录

这个练习会帮助你把:

页面上下文和展示反馈联系得更紧。

4. 练习 4:尝试把筛选函数和统计函数都放进utils/

如果你现在还把它们写在App.tsx里,可以尝试继续做一次小步整理。

这个练习会帮助你继续建立:

页面、组件、工具函数慢慢分层的手感。

二十五、学习重点提示

这一章请你重点记住下面这些话:

  1. 筛选、统计和组件通信在项目里经常会一起出现,因为它们本质上都围绕共享状态展开。
  2. 会影响多个组件的状态,通常更适合放在共同父组件中。
  3. 当前筛选结果和统计卡片数据,很多时候都属于派生数据。
  4. 能根据已有状态算出来的结果,先不要急着重复保存成新状态。
  5. 父组件更适合组织共享状态和派生结果,子组件更适合负责展示和触发动作。
  6. FilterTabs是非常典型的“父传子 + 子回调父”通信场景。
  7. SummaryCards更适合做展示型组件,而不是自己持有统计逻辑。
  8. 列表数据一变,统计、筛选结果和空状态都可能跟着联动,这正是 React 项目数据流的价值。
  9. 先把原始状态想清楚,再去组织派生结果,页面会轻松很多。

如果你只记一句话,请记住:

这一章真正要建立的,不只是“会做筛选和统计”,而是“会围绕原始状态组织多个组件一起协作”。

二十六、本章小结

这一章,我们正式把记账板从“新增记录与列表渲染”推进到了“筛选、统计和组件联动”阶段。

你已经理解了:

  • 为什么当前筛选类型适合放在App
  • 什么是派生数据
  • 为什么filteredRecordListsummaryData往往不一定需要单独做状态
  • 如何根据原始列表和筛选条件得到真正要展示的结果
  • SummaryCardsFilterTabs如何通过props接入真实项目
  • 为什么这正是一次很典型的 React 组件通信场景
  • 为什么筛选切换后,列表、按钮高亮和空状态会一起更新

更重要的是,你开始真正建立一种很关键的页面级理解:

一个 React 页面真正强大的地方,不是某个按钮会不会变色,而是多个组件能不能围绕同一份状态清楚地协作。

这一步非常关键。

因为从这里开始,你已经不只是在给项目加功能,而是在开始真正理解:

React 小项目的数据流组织方式。

二十七、课后思考题

请你认真思考下面这些问题:

  1. 为什么说筛选、统计和组件通信在项目里经常会一起出现?
  2. 为什么filterType更适合放在App层,而不是放在FilterTabs自己内部?
  3. 什么样的数据更适合理解成“派生数据”?
  4. 为什么很多时候filteredRecordList不一定需要单独开一个状态?
  5. 为什么SummaryCards更适合接收结果并展示,而不是自己去算所有数据?
  6. 为什么说FilterTabs是一个典型的“子通过回调影响父”的通信例子?
  7. 如果以后要继续加关键词搜索或日期筛选,这一章建立的“原始状态 + 派生结果”思路为什么会特别有用?

建议你把这些问题用自己的话写下来。

只要你能把这些问题讲清楚,说明你已经真正开始进入 React 综合项目的数据流组织主线了。

二十八、下一篇预告

下一篇我们会继续推进记账板综合实战,进入:

从零开始学前端 | 第三十六章:记账板编辑记录、删除交互与本地存储

到那时,你会继续把这个项目往前推进:

  • 如何修改已有记录
  • 如何删除一条账单
  • 为什么本地存储值得接进项目
  • 页面刷新后数据怎样保留下来

也就是说,下一篇开始,我们会从“筛选、统计与组件通信”继续走到:

记账板更接近真实可用状态的完整交互体验。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/22 11:53:12

深入解析TI DSP视频端口驱动:分层架构、EDMA与FVID接口实战

1. 项目概述与核心价值在嵌入式视频处理领域&#xff0c;尤其是基于德州仪器&#xff08;TI&#xff09;TMS320C6000系列DSP的开发中&#xff0c;视频端口&#xff08;Video Port&#xff09;的驱动设计往往是项目成败的关键一环。它直接决定了视频数据能否被稳定、高效地“搬”…

作者头像 李华
网站建设 2026/7/22 11:52:50

GTC泽汇:“太空概念估值持续降温”

雅虎财经报道&#xff0c;SpaceX相关交易品种在上市后高位回落&#xff0c;最新价格较峰值跌约45%&#xff0c;并低于135美元发行价&#xff0c;GTC泽汇认为&#xff0c;这反映市场正在重新审视高成长叙事与实际盈利能力之间的距离。Cathie Wood旗下基金近期仍买入约5100万美元…

作者头像 李华
网站建设 2026/7/22 11:52:27

从 0 到生产级:2026 年 6 大 AI Agent 框架横评,附架构对比与落地避坑

在构建智能应用的过程中&#xff0c;很多开发者都经历过从“写死逻辑”到“引入大模型”的阵痛期。 对于正在寻找生产级解决方案的团队来说&#xff0c;选择合适的框架直接决定了项目的迭代速度和最终稳定性。市面上涌现出的众多方案各有千秋&#xff0c;有的擅长处理长链路任务…

作者头像 李华
网站建设 2026/7/22 11:51:52

定制珠宝展示柜材质都有哪些?开珠宝店必看!

在珠宝终端门店建设中&#xff0c;展示柜的材质选择常被低估&#xff0c;但它直接影响珠宝的视觉呈现、柜体寿命和后期维护成本。作为在展柜行业深耕27年的源头工厂&#xff0c;凯尔达在服务超百家珠宝品牌实践中&#xff0c;积累了一些关于材质选型的经验&#xff0c;在此与大…

作者头像 李华
网站建设 2026/7/22 11:49:32

能科科技AI+工业场景化应用【第二期】:PLM AI智能助手

当前&#xff0c;PLM&#xff08;例如Teamcenter&#xff09;作为一款专业的产品生命周期管理系统&#xff0c;广泛应用于制造企业&#xff0c;帮助企业实现产品数据的统一管理与协同共享。但是在实际应用中存在两大难题&#xff1a;一是隐性人力成本高&#xff0c;新员工培训耗…

作者头像 李华