本节摘要:界面与用户对话靠事件,界面的两种高频率结构——"是/否出现的条件渲染"和"一串数据的列表渲染"——构成绝大多数视图的核心。本节把事件委托、条件与列表的思想讲清,define 审查 key 的正确姿势,为第 3、4 章的两套具体语法打底。
本节阅读目标
阅读完本节,你应当能够:
浏览器的事件机制本质是"捕获 → 目标 → 冒泡"的传播链。真正关键的技巧是事件委托:与其给十个按钮各绑一遍处理器,不如在它们的父容器上绑一次,靠事件冒泡统一处理。框架在这一层把绑定和清理都替你收拾好,你只要声明式地写表达式即可:
<button onClick={handleSave}>保存</button>
这不仅减少绑定次数,也在动态增删列表项时免去手动重绑的麻烦。事件做的是"让组件对外报告发生了什么",配合第 2.1 节"靠事件对外通信"的接口思考一起看,理解会更深。
数据驱动下,"某个块出现与否"也是一项数据。三种典型写法:
下面一段小程序把条件渲染与列表渲染一起演示,注意 key 的写法。
const users = [ { id: 1, name: '阿明' }, { id: 2, name: '小雅' }, ]; function UserList({ users }) { if (users.length === 0) { return <p>还没有用户,快去添加一个</p>; // 空态兜底 } return ( <ul> {users.map((u) => ( <li key={u.id}>{u.name}</li> // 稳定唯一的 key ))} </ul> ); }
回到 2.2 节讲过的 diff:列表对比时靠 key 判断每个条目是新增、移动还是重排。用好 key 有三条规则:
声明式绑定虽然把"绑事件"藏起来了,事件委托的价值并没有消失。它真正解决的是"动态列表"场景:列表项一会儿新增一会儿删除,若是给每一项都显式绑定,增删时就要手动挂/卸,极易漏推。与其逐项管理,不如在容器上绑一次、用当前冒泡到的目标来判断。上面那段 UserList 若要给每一行加"删除"按钮,比较稳的写法是:
<ul onClick={(e) => { if (e.target.dataset.action === 'delete') removeUser(e.target.dataset.id); }}> {users.map((u) => ( <li key={u.id} data-id={u.id}> {u.name} <button data-action="delete">删除</button> </li> ))} </ul>
这样列表增删完全不需要重新绑定,父容器一处统一处理。框架的声明式语法并没能消灭委托这种技巧,只是把它的细节封装得更友好——明白这层,排查"为什么动态行点不动"时你就有了方向。

日常视图几乎都是"条件 + 列表"混着用,编排上有一条很值钱的直觉:先判断空不空、再决定渲不渲染列表。也就是先用条件渲染把空态、加载态这类"非列表"分支挡在 v-for / map 之外,再处理里面的每一项。这样既不会出现"空数组还硬渲染一遍空白"的尴尬,也让 read 逻辑更线性。
另一条是"渲染的对象和它该有的 key 别分家":列表条目一旦要随用户操作重排、增删,你就提前想好这个条目的稳定标识,而不是中途才想起来加 key。很多"排序后输入框错乱"的线上问题,最后都收口到"当时没用稳定 key"这一个根因。把这两条并进你的默认习惯,前面 2.2 节讲的 diff 知识才算真正化成了手感。
前面讲了事件怎么绑定、怎么委托,还有个常被一笔带过却天天踩的细节:事件的默认动作与冒泡传播是两条独立的轨道。点击一个链接不"跳转页"要靠 preventDefault 拦默认动作;不想让点击继续往上冒泡传给父容器才用 stopPropagation。两个动作经常被混为一谈,一混,就出现"点删除没反应""点子元素把父层触发两遍"这类现场。
一个顺手的心智模型:先判断"这一步是不是我想要的跳转/提交",需要挡住就 preventDefault;再判断"要不要让父级也知道这次点击",不需要就 stopPropagation。两者各自独立,谁也不替代谁。放到条件+列表的编排里也一样——给"删除"按钮挂委托处理时,你若只在父容器接到事件,就靠 target 的 data-action 分流,一般不会再往上冒泡造成二次处理;但若是按钮自己还套在某个也监听 click 的卡片组件里,那就要想清楚"这一层接完要不要继续往上冒"。
把"冒泡链"和"默认动作"钉进心智,你会少约一半"事件为什么怪怪的"问题。这与前面几条导演性判断一样,都在训练同一件事:先把"发生什么、传到哪里、由谁收口"想清楚,再动手写那两行绑定代码。
第 2 章的地基到此扎稳。从第 3 章开始,我们把这套思想写成 React 代码、再在第 4 章写成 Vue 代码——两套写法会不断印证你在这一章建立的模型。