主题
jsx与jsx中的插值表达式(Day2 1/4)
本页对应 React + TypeScript + 医通项目,共 7 段视频,约 44 分钟。
学习目标
- 能够使用 JSX 插值表达式和三元表达式实现条件渲染:列表为空时显示“暂无评论”,非空时渲染列表,并正确使用圆括号包裹多行 JSX。
- 能够为列表渲染项设置唯一
key,说明key在虚拟 DOM 比对中的作用,并能根据数据唯一字段(如id、_id)赋值以消除警告。 - 能够使用 JSX 表达式为标签属性赋值,包括
style、className及table的border、width、height,并能使用扩展运算符批量传递属性对象。 - 能够识别并修正 JSX 中常见标签嵌套错误,如
tr必须放在tbody中、div不能直接放在p中。 - 能够通过标签属性绑定事件回调,正确获取事件对象、阻止默认行为,并说明事件回调中
this指向window的原因及通过实参传递this和自定义参数的方法。 - 能够区分
onInput与onChange的触发时机,通过事件对象获取输入框value,并验证“输入即触发”与“内容变化且失焦触发”的差异。
知识点
JSX 是什么:XML 写法 + JS 能力
为什么需要:如果每次都写 React.createElement,结构一复杂就很难读。JSX 用类似 XML 的标签描述界面,能快速创建 React 元素(虚拟 DOM),本质仍是 JS 语法扩展。笔记里说“jsx = xml + js”,XML 曾用自定义标签表示数据,后来多被 JSON 取代,但在 React 里这种标签写法被保留下来。
怎么想:JSX 不是浏览器原生认识的语法,所以要靠 Babel 编译;编译后的结果还是 React.createElement 的方式。理解这一点,就不会把 JSX 当成 HTML 字符串。
怎么做:页面先引入 React、ReactDOM、Babel;写 JSX 的 script 加 type="text/babel";JSX 外层可以加圆括号,方便换行和格式化。
易错点:不引入 Babel、script 类型写错,JSX 就不会被编译;JSX 外层不加括号时,多行写法容易因换行产生歧义。
JSX 的使用步骤与标签规则
为什么需要:JSX 里既能写 HTML 标签,也能写 React 组件,必须让 React 知道“这是普通标签还是组件”。
怎么想:规则只有两类:全部小写的标签,按浏览器可识别的 HTML 标签处理;首字母大写的标签,如 <User></User>,按 React 组件处理。组件没定义就会报错。
怎么做:按笔记三步走:导入 Babel、script 增加 type="text/babel"、JSX 外加 ()。基本代码如下:
jsx
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
<script src="./lib/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/babel">
/**
* jsx: xml + js 的混合语法
* 是react特有的,用来创建react元素[虚拟dom],作用跟 React.createElement一样
*/
const root = ReactDOM.createRoot(document.querySelector("#root"));
const oDiv = (
<div className="wrapper">
<div className="swipper">
<h3>我是轮播图</h3>
<p>我是文字</p>
<span>我是span</span>
<span>我也是span</span>
</div>
</div>
)
console.log(oDiv);
root.render(oDiv);
</script>
</html>易错点:class 在 JSX 中要写 className;小写自定义标签会被当 HTML 标签,大写组件未定义会报错;root.render(oDiv) 才是把虚拟 DOM 渲染到页面。
插值表达式给属性赋值
为什么需要:标签属性经常来自变量,不能把变量名当字符串写死。
怎么想:JSX 的 {} 里可以写 JS 表达式,属性值也能用插值。转写稿用 table 举例:border、width、height 都是它的特殊属性,适合演示。
怎么做:例如定义 width=200、height=300,写 <table width={width} height={height}>。样式可用 style={{ fontSize: "25px" }},类名用 className="f20"。多个属性来自对象时,可用扩展运算符:<table {...props} />。转写稿说扩展运算符像“脱衣服”,把对象属性展开到标签上。
易错点:style 要对象,不是字符串;className 不是 class;table 里不能直接把 tr 放进去,要放 tbody,否则 React 会提示标签嵌套错误;p 里也不要放 div。
事件绑定与回调函数的 this
为什么需要:页面交互要绑定事件。转写稿先对比原生 onclick,再理解 JSX 的 onClick。
怎么想:普通函数里的 this 取决于调用者。转写稿反复强调:原生行内事件回调 FN() 的调用者是 window,所以函数体里的 this 是 window,不是按钮。想拿到按钮,要在调用时把 this 作为实参传进去,再用形参接收。
怎么做:JSX 中写 onClick={fn},传函数引用;需要传参时用箭头函数包一层。原生对比写法是 onclick="fn()"。事件对象通过实参 event 获取,形参可改名,如 e。
易错点:JSX 里不要随手写 onClick={fn()},那会在渲染时执行;事件名要驼峰如 onClick;不要把函数体里的 this 和调用时传入的 this 混为一谈。
事件对象、默认行为与表单事件
为什么需要:很多操作要读输入值、阻止跳转、区分触发时机。
怎么想:转写稿用 a 标签演示:点击后先执行回调,再走默认跳转,所以要用 e.preventDefault() 阻止默认行为。输入框则对比 onInput 和 onChange:onInput 一输入就触发;onChange 是内容变化且失去焦点时触发。
怎么做:事件对象用 e.target.value 取输入值;阻止默认行为写 e.preventDefault();传自定义参数和事件对象时,自己决定实参顺序,如 fn(1, event)。
易错点:原生行内写法里实参常写 event;onInput 触发频繁,onChange 触发时机不同;列表渲染时每个被遍历元素要加唯一 key,一般用 id,MongoDB 数据常用 _id,否则会有 key 相关报错,也不利于 diff 复用。
插值表达式的定位
为什么需要:JSX 里不能像 HTML 那样直接写变量,必须用插值把 JS 数据放进结构。怎么想:JSX 的插值语法就是 {js表达式},花括号里放“有值”的表达式。怎么做:<p>{a}</p>、<p>{'abc'}</p>。易错点:花括号里不是“随便写 JS”,语句不能直接放进去;转写稿里常说的“差值表达式”应纠正为“插值表达式”。
表达式与语句的区别
为什么需要:避免把 if、for、赋值语句塞进 {} 导致报错。怎么想:表达式有值,可被变量接收或 console 打印;语句控制执行顺序,本身没有值。怎么做:表达式包括常量/变量(number、string、null、undefined、boolean、object)、三元、逻辑运算、函数调用;语句包括 var a = 1;、if...else、switch case、循环。易错点:{var a = 1;} 不行;{if (x) {}} 不行;但三元表达式可以。
基本类型插值
为什么需要:不同值在 React 中的渲染结果不同。怎么想:number、string 正常输出;boolean、null、undefined 不输出;对象直接插值报错;数组会逐项展开。怎么做:{123}、{'abc'};{true}{false}{null}{undefined} 页面无内容;{{name:'atguigu'}} 报错;{[1,2,3,4]} 输出每一项。易错点:对象要先取字段或放到属性里;数组元素若仍是对象,也不能直接当文本渲染。笔记代码如下:
jsx
const root = ReactDOM.createRoot(document.querySelector("#root"));
let a = 123;// number
const str = 'abc';
root.render((
<div>
<h3>基本数据类型 number</h3>
<p>{a}</p>
<h3>string</h3>
<p>{str}</p>
<h3>boolean</h3>
<p>true: <span>{true}</span></p>
<p>false: <span>{false}</span></p>
<h3>null</h3>
<p>null: <span>{null}</span></p>
<h3>undefined</h3>
<p>undefined: <span>{undefined}</span></p>
<h3>object- 对象-报错</h3>
{/*
<p>对象:<span>{{username:'atguigu',age:20}}</span></p>
*/}
<h3>object - 数组</h3>
<p>数组: {[1,2,3,4]}</p>
<p>数组: {['a','b','c','d']}</p>
<p>数组: {[true,false,null,undefined]}</p>
<p>数组: {[{name:'atguigu'},{name:'迪丽热巴'}]}</p>
<h3>三元表达式</h3>
{isLoading ? '页面加载中.....' : '页面内容'}
<h3>逻辑 与 运算</h3>
{5 && 8} - {null && 200}
{5 || 8}
<h3>函数调用</h3>
<p>{fn()}</p>
<p>{f1()}</p>
{
/*<p>{f2()}</p>*/
}
</div>
))逻辑运算、三元与函数调用
为什么需要:条件展示、计算和复用逻辑都靠表达式完成。怎么想:&& 看表达式1的布尔值:真则返回表达式2,假则返回表达式1;|| 真则返回表达式1,假则返回表达式2。怎么做:5 && 8 得 8,0 && 19 得 0;5 || 8 得 5。三元写法:
jsx
let isLoading = true;
<div>{isLoading ? '加载中....' : '内容'}</div>函数调用插值看返回值:fn() 无返回值不显示,f1() 返回数组可展开,f2() 返回对象会报错。易错点:逻辑运算返回的是操作数,不一定布尔值;函数调用要加括号,但事件绑定里不能写成 onClick={fn()},否则会立即执行。
属性赋值中的插值
为什么需要:标签属性也常需要动态值。怎么想:属性值同样可用 {} 放表达式。怎么做:转写稿用 table 演示 border、width、height,也演示 style={{...}}、className;对象属性可用扩展运算符 {...props} 一次性铺开。易错点:class 要写 className;style 要对象;扩展运算符像“脱衣服”,把对象属性铺到标签上;tr 不能直接放 table,要放 tbody。
列表渲染与 key
为什么需要:作业要按评论列表渲染,无数据时显示“暂无评论”。怎么想:双分支用三元,列表用 map。怎么做:先判断 list.length === 0 ? <h1>暂无评论</h1> : <ul>...</ul>;遍历时每个 li 给唯一 key。转写稿调试过程:先把长度改成 0,验证显示“暂无评论”;再恢复数据渲染 UL/LI,控制台报 key 错误,给 li 加 key={item.id} 后错误消失。易错点:map 回调用圆括号可省略 return,用花括号必须写 return;key 要唯一,一般用 id,MongoDB 数据常用 _id;key 是 diff 比对依据,不是普通展示属性。
事件回调调试
为什么需要:交互要绑定事件。怎么想:onClick={fn} 传函数引用,不是调用结果。怎么做:转写稿中打印 this,发现普通函数回调的调用者是 window,所以 this 是 window;事件对象通过调用时传 event 实参、形参接收;阻止默认行为用 e.preventDefault();想传自定义参数和事件对象时,自己决定实参位置。易错点:onClick={fn()} 会立即执行;回调里的 this 和调用时传的 this 不是同一个东西。
onInput 与 onChange 的调试对比
为什么需要:输入框取值要选对事件。怎么想:onInput 只要有输入就触发;onChange 在内容变化且失去焦点时触发。怎么做:两个输入框分别绑定 onInput、onChange,通过 e.target.value 取值并打印事件对象。易错点:转写稿演示中 onInput 一输入就输出,onChange 输入时不输出、点外面失焦才输出;两者事件对象和触发时机不同,不能混用。
条件渲染要解决什么问题
条件渲染就是:同一块 JSX 结构里,根据某个条件决定“渲染什么”或“渲染不渲染”。转写稿里的作业很典型:评论列表有数据时渲染 ul/li,没有数据时渲染“暂无评论”。
为什么需要:页面状态不同,展示内容就不同;不能把所有分支都写死。
怎么想:先判断分支数量。单分支用逻辑运算符;双分支用 if...else 或三元;多分支一般封装成函数。
怎么做:在 JSX 插值 {} 中放“表达式结果”,复杂分支先在外部算好变量,或调用返回 JSX 的函数。
易错点:JSX 的 {} 里不能直接写 if...else 语句;逻辑与左值不是布尔值时,可能把 0 渲染出来,条件渲染通常要保证左值是布尔值。
单分支:逻辑运算符与 if 赋值
为什么需要:只有“满足条件才显示”时,没必要写完整双分支。
怎么想:可以用 flag && <div>...</div>;也可以先用 if 给变量赋值,再在 JSX 中插值。
怎么做:笔记里的单分支代码保留了两种写法:
js
let flag = true;
if(flag){xxxx}
flag && 'xxxx'
// 函数调用
function fn(){}
function sum(fn){
fn && fn()
}在 React 中,flag && <div>一起看电影</div> 就是“flag 为真才渲染这个 div”。函数调用里的 fn && fn() 是防止回调为空。
易错点:flag && 'xxxx' 返回的不一定是布尔值;如果 flag 是 0,表达式结果就是 0,放进 JSX 可能显示 0。所以条件最好用布尔值。
双分支:if...else 与三元
为什么需要:加载中显示“页面正在加载中”,加载完显示“我是页面内容”,这是二选一。
怎么想:结构简单用三元;需要多行赋值时用 if...else 先在外部算好变量。转写稿里处理“暂无评论”时也是先判断 list.length === 0,用三元决定渲染 h1 还是 ul。
怎么做:笔记代码里 isLoading 为真时赋值加载提示,否则赋值页面内容:
jsx
let double = null;
if(isLoading){
double = <h3>页面正在加载中....</h3>
}else {
double = <div>我是页面内容</div>
}然后在 JSX 中写 {double},或直接写三元 {isLoading ? <h3>...</h3> : <div>...</div>}。
易错点:不能在 JSX 的 {} 里直接写 if...else 语句;三元嵌套太多会难读,复杂逻辑应抽成函数或变量。
多分支:封装成函数
为什么需要:年龄不同显示不同提示,分支超过两个,直接塞进 JSX 会很乱。
怎么想:把判断逻辑封装成函数,函数返回对应 JSX,再在插值中调用。
怎么做:笔记里的 friends(age) 就是多分支函数:
jsx
function friends(age){
if(age < 18){
return <div>请在父母的陪同下观看</div>
}else if(age > 18 && age < 60){
return <div>敬请欣赏</div>
}else if(age > 60 && age < 80){
return <div>保重身体</div>
}
}调用 {friends(16)}、{friends(24)} 就会得到不同结果。
易错点:函数必须覆盖所有情况并返回 JSX;示例中等于 18、60、80 时没有返回值,实际要补边界。转写稿还提到,同一作业里如果同时做列表渲染,控制台会报 key 错误;列表项要加唯一 key,通常用数据 id,MongoDB 返回数据常用 _id。
完整示例与调试过程
转写稿的调试顺序是:先处理条件分支,用三元判断 list.length === 0,为真显示“暂无评论”,否则显示列表;结构需要换行时用圆括号包住 JSX,方便格式化和阅读;再用 {oDiv} 验证“插值表达式也可以渲染 React 元素”。本节完整代码如下:
jsx
const root = ReactDOM.createRoot(document.querySelector("#root"));
const flag = false;
const isLoading = false;
const oDiv = (
<div>
我是div
</div>
)
function friends(age){
if(age < 18){
return <div>请在父母的陪同下观看</div>
}else if(age > 18 && age < 60){
return <div>敬请欣赏</div>
}else if(age > 60 && age < 80){
return <div>保重身体</div>
}
}
// 单分支 if写法
let single = null;
if(flag){
single = <div>一起看电影</div>
}
// 双分支 if...else
let double = null;
if(isLoading){
double = <h3>页面正在加载中....</h3>
}else {
double = <div>我是页面内容</div>
}
root.render((
<div>
<h3>前提:插值表达式也可以渲染react 元素</h3>
{oDiv}
<h3>单分支</h3>
{flag && <div>一起看电影</div>}
{single}
<h3>双分支</h3>
{isLoading ? <h3>页面正在加载中....</h3> : <div>我是页面内容</div>}
{double}
<h3>多分支</h3>
{friends(16)}
{friends(24)}
</div>
))运行后可以看到:flag、isLoading 为 false 时,单分支不显示“一起看电影”,双分支显示“我是页面内容”;多分支按年龄返回不同提示。最后记住:简单分支用逻辑运算符或三元,复杂分支先算变量或封装函数,列表渲染记得加唯一 key。
先明确:行内样式也是 JSX 属性赋值
为什么需要: 页面里总有一些样式只针对当前这个元素,比如示例中“红色文字、蓝色边框、粉色背景”的 div。用行内样式最直接,不需要额外写 class,也不用到 CSS 文件里找选择器。
怎么想: 转写稿里提到“style 是不是 div 的属性,我们用插值表达式给它赋值”。所以不要把 style 当成普通 HTML 字符串,而要把它当成 JSX 的一个属性来处理:属性名是 style,属性值通过 {} 插入 JavaScript 表达式。
怎么做: 在标签上写 style={...},花括号里放样式对象。示例中第一个 div 直接写对象,第二个 div 把对象抽成变量 style,再用 style={style} 绑定。
易错点: 不要写成 HTML 的 style="color:red"。JSX 里 style 接收的是对象,不是 CSS 字符串;写成字符串虽然看起来像 HTML,但不符合本节规则。
style 的值必须是一个对象
为什么需要: React/JSX 要把样式交给 JavaScript 管理,对象形式方便复用、计算和传递。笔记第一句就是“style 的值必须是一个对象”。
怎么想: 看到 style={{ color: 'red' }} 不要被双花括号吓到:外层 {} 表示“这里要写 JSX 表达式”,内层 {} 才是 JavaScript 对象字面量。等价于先定义 let s = { color: 'red' },再写 style={s}。
怎么做: 单个样式可以写成:
jsx
<div style={{ color: 'red' }}>红色文字</div>多个样式就在对象里用逗号分隔。示例中把 color、border、backgroundColor、width、height、fontSize 都放进同一个对象。
易错点: 少写一层花括号,写成 style={ color:'red' } 不是合法对象;或者把对象写成字符串 style={'color:red'},都不符合“值必须是对象”的要求。
复合属性名要改成小驼峰
为什么需要: CSS 里写 background-color、font-size,但 JavaScript 对象属性名不能直接带连字符。连字符会被当成减号,所以 React 要求用 JavaScript 的属性命名方式。
怎么想: 笔记明确说“如果是复合属性,需要用小驼峰命名法 backgroundColor”。也就是把 background-color 改成 backgroundColor,把 font-size 改成 fontSize。
怎么做: 示例里背景色写 backgroundColor: 'pink',字号写 fontSize: 30;边框因为是复合值,写成字符串 border: '1px solid blue'。转写稿里作业讲解也给用户名设置 fontSize: 25,说明这种写法在实际练习中已经用到。
易错点: 写成 background-color: 'pink' 或 font-size: 30 会报错或无效;border 的值是完整字符串,不要误写成 border: { ... }。
单位是 px 时可以省略,直接写数字
为什么需要: 行内样式里大量宽高、字号都以 px 为单位,每次都写 '200px' 比较啰嗦。React 允许对 px 单位做省略。
怎么想: 笔记说“单位如果是 px,那么可以省略,直接写数字”。所以 height: 500 等价于 height: '500px',fontSize: 30 等价于 fontSize: '30px'。但 width: '200px' 写成字符串也完全合法,示例两种都出现了。
怎么做: 示例中 width: '200px' 保留字符串,height: 500 和 fontSize: 30 直接写数字。刷新后能看到高度和字号生效。转写稿里也反复确认“单位是 PX 是不是可以省略”,并在作业中把 fontSize 写成数字 25、20。
易错点: 只有 px 可省略;如果单位是 %、em、rem 等,不能随便去掉单位。数字和字符串不要混得没有目的:需要明确单位时写字符串,确定是 px 时可写数字。
把样式对象抽成变量,方便复用
为什么需要: 如果多个元素要用同一套样式,或者样式对象很长,直接塞在标签里会很难读。示例中第二个 div 就是复用 style 变量。
怎么想: 先把样式定义成普通对象 let style = {...},再在 JSX 里用 style={style}。这和转写稿里“用对象的方式给 table 的特殊属性赋值”“用扩展运算符一次性把对象属性复制给 table”是同一类思路:JSX 属性值可以来自变量或对象。
怎么做: 示例代码先定义 style,第一个 div 写内联对象,第二个 div 写 style={style}。两个 div 都能渲染出对应样式。
易错点: 变量名和属性名相同容易看混:style={style} 中左边是 JSX 属性名,右边是变量。不要写成 style={ style } 以外的错误形式,比如 style=style。
完整代码与调试过程
先按笔记代码渲染,观察第一个 div 是否红字、蓝框、粉底,宽 200px、高 500px、字号 30px;再观察第二个 div 是否复用同一套样式。若背景色不生效,优先检查是否写成 backgroundColor;若宽高不生效,检查是否把对象写成了字符串。
jsx
const root = ReactDOM.createRoot(document.querySelector("#root"));
let style = {
color: 'red',
border: '1px solid blue',
backgroundColor: 'pink',
width: '200px',
height: 500,
fontSize: 30
}
root.render((
<div>
<h3>行内样式style处理</h3>
<div style={{ color: 'red', border: '1px solid blue', backgroundColor: 'pink', width: '200px', height: 500, fontSize: 30 }}>红色文字,蓝色边框,粉色背景</div>
<div style={style}>我是div</div>
</div>
))易错点总结: style 值必须是对象;复合属性名用小驼峰;px 单位可省略为数字;样式对象可抽变量复用;双花括号要分清“外层插值、内层对象”。
课堂画面






代码示例
示例一:JSX 基本使用 + 插值表达式 + 行内样式
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSX 基本使用 + 插值表达式 + 行内样式</title>
<!-- 与笔记中的 ./lib/react.development.js 等三个文件等价 -->
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
<!-- 必须加 type="text/babel",否则 babel 不会编译 -->
<script type="text/babel">
/**
* jsx: xml + js 的混合语法
* 是react特有的,用来创建react元素[虚拟dom],作用跟 React.createElement 一样
*/
const root = ReactDOM.createRoot(document.querySelector("#root"));
let a = 123; // number
const str = 'abc'; // string
const isLoading = false; // 控制三元表达式
function fn() { }
function f1() { return [1, 2, 3] }
function f2() { return { username: 'atguigu' } } // 对象:插值会报错,注释掉
let style = {
color: 'red',
border: '1px solid blue',
backgroundColor: 'pink',
width: '200px',
height: 500, // 单位是 px 可以省略,直接写数字
fontSize: 30
}
const oDiv = (
<div className="wrapper">
<div className="swipper">
<h3>我是轮播图</h3>
<p>我是文字</p>
</div>
<span>我是span</span>
<span>我也是span</span>
</div>
)
root.render((
<div>
<h3>插值表达式也可以渲染 react 元素</h3>
{oDiv}
<h3>基本数据类型 number</h3>
<p>{a}</p>
<h3>string</h3>
<p>{str}</p>
<h3>boolean</h3>
<p>true: <span>{true}</span></p>
<p>false: <span>{false}</span></p>
<h3>null</h3>
<p>null: <span>{null}</span></p>
<h3>undefined</h3>
<p>undefined: <span>{undefined}</span></p>
<h3>object - 对象 - 报错(注释掉)</h3>
{/* <p>对象:<span>{{username:'atguigu',age:20}}</span></p> */}
<h3>object - 数组</h3>
<p>数组: {[1, 2, 3, 4]}</p>
<p>数组: {['a', 'b', 'c', 'd']}</p>
<p>数组: {[true, false, null, undefined]}</p>
<p>数组: {[{name:'atguigu'},{name:'迪丽热巴'}]}</p>
<h3>三元表达式</h3>
{isLoading ? '页面加载中.....' : '页面内容'}
<h3>逻辑 与 或 运算</h3>
{5 && 8} - {null && 200} - {5 || 8}
<h3>函数调用</h3>
<p>{fn()}</p>
<p>{f1()}</p>
{/* <p>{f2()}</p> */}
<h3>行内样式style处理</h3>
<div style={{ color: 'red', border: '1px solid blue', backgroundColor: 'pink', width: '200px', height: 500, fontSize: 30 }}>红色文字,蓝色边框,粉色背景</div>
<div style={style}>我是div</div>
</div>
))
</script>
</html>要点与易错点:
script必须写type="text/babel",同时引入babel.min.js,否则浏览器不认 JSX。- JSX 里只能用两类标签:全小写的 HTML 标签,和首字母大写的组件标签(
<User></User>未定义会直接报错)。 - 插值语法是
{js表达式},表达式有值,语句(if、for、var a = 1)不能放进去。 {true}、{false}、{null}、{undefined}什么都不渲染;对象{{name:'atguigu'}}会直接报错;数组会遍历每个元素输出。{5 && 8}得8,{null && 200}得null(不渲染),{5 || 8}得5。fn()返回undefined所以不显示,f1()返回数组会被展开渲染,f2()返回对象必须注释掉。class要写成className;style的值必须是对象,且用双花括号style={{...}};复合属性用小驼峰backgroundColor;px 可省略写数字。- JSX 外层加
()只是为了方便换行和格式化,不影响渲染结果。
示例二:条件渲染(单分支 / 双分支 / 多分支)
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSX 条件渲染</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/babel">
const root = ReactDOM.createRoot(document.querySelector("#root"));
const flag = false;
const isLoading = false;
const oDiv = (
<div>
我是div
</div>
)
// 多分支:一般封装成函数
function friends(age) {
if (age < 18) {
return <div>请在父母的陪同下观看</div>
} else if (age > 18 && age < 60) {
return <div>敬请欣赏</div>
} else if (age > 60 && age < 80) {
return <div>保重身体</div>
}
}
// 单分支 if 写法
let single = null;
if (flag) {
single = <div>一起看电影</div>
}
// 双分支 if...else
let double = null;
if (isLoading) {
double = <h3>页面正在加载中....</h3>
} else {
double = <div>我是页面内容</div>
}
root.render((
<div>
<h3>前提:插值表达式也可以渲染react 元素</h3>
{oDiv}
<h3>单分支</h3>
{flag && <div>一起看电影</div>}
{single}
<h3>双分支</h3>
{isLoading ? <h3>页面正在加载中....</h3> : <div>我是页面内容</div>}
{double}
<h3>多分支</h3>
{friends(16)}
{friends(24)}
</div>
))
</script>
</html>要点与易错点:
- 单分支用
flag && 'xxxx':表达式 1 为真取表达式 2 的值,为假就取表达式 1 的值(false不渲染)。 - 双分支用三元
isLoading ? <h3>...</h3> : <div>...</div>,或先if...else算出变量再插值。 - 多分支在 JSX 里写不了
if...else,要封装成函数friends(age),函数返回 react 元素,再用{friends(16)}调用。 friends必须覆盖所有分支(比如 age 正好等于 18 会返回undefined,页面什么都不显示),这是常见坑。- 注意 JSX 中
{flag && <div>...</div>}里不能写if,只能写表达式。
示例三:列表渲染 + key(评论列表作业)
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>列表渲染 + key</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
.f20 {
font-size: 20px;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/babel">
const root = ReactDOM.createRoot(document.querySelector("#root"));
// 把这里清空成 [] 试试,就会渲染“暂无评论”
const list = [
{ id: 1, name: '迪丽热巴', content: '沙发是我的' },
{ id: 2, name: '古力娜扎', content: '板凳也不错' },
{ id: 3, name: '马尔扎哈', content: '地板凉快点' }
];
// 列表:LI > A > H3(用户名) + P(内容)
const oUl = (
<ul>
{list.map(item => (
<li key={item.id}>
<a>
<h3 style={{ fontSize: 25 }}>{item.name}</h3>
<p className="f20">{item.content}</p>
</a>
</li>
))}
</ul>
)
root.render((
<div>
<h1>评论列表</h1>
{/* 双分支:没有评论数据时渲染 h1,否则渲染 ul */}
{list.length === 0 ? <h1>暂无评论</h1> : oUl}
</div>
))
</script>
</html>要点与易错点:
- 双分支用三元:
list.length === 0 ? <h1>暂无评论</h1> : oUl,把list改成[]就能看到“暂无评论”。 - 列表用
list.map(item => (...)),箭头函数返回圆括号包裹的 JSX 时可以省略return;如果写成花括号{},就必须手写return,这是最常见的错误。 - 每个被遍历出来的元素必须加
key,否则控制台报 key 警告:React 做 diff 对比时,同构的<li>无法判断谁对应谁,需要唯一标识作为比对依据。 key的值要唯一,一般用数据里的id;如果是 MongoDB 返回的数据,字段是item._id。- 用户名用行内样式
style={{ fontSize: 25 }}(px 可省略),内容用className="f20"配.f20 { font-size: 20px };JSX 里是className不是class。 - 属性赋值也能用插值表达式,例如
{list.length === 0 ...}里的值、style={style}、{...tableAttrs}(扩展运算符一次性把对象属性铺给标签)。 - 注意 React 对标签嵌套有校验:
<tr>不能直接放在<table>里,要放在<tbody>中;<div>也不能放进<p>里。
常见错误
插值里直接塞对象
- 现象:页面白屏,控制台报
Objects are not valid as a React child。 - 原因:
{}只接受可渲染的值,对象不是合法子节点;数组能渲染是因为会被逐个展开。 - 修法:展示字段就取属性
{user.name};要整体查看数据就JSON.stringify(obj)或放到属性上。
- 现象:页面白屏,控制台报
列表渲染不写 key,或拿 index 当 key
- 现象:控制台黄色警告
Each child in a list should have a unique "key" prop;列表增删后内容错位、输入框串值。 - 原因:同级循环出的元素结构完全相同,diff 缺少比对依据。
- 修法:给遍历出的最外层元素加
key={item.id}(MongoDB 数据用item._id);index 只在列表顺序永不变化时勉强可用。
- 现象:控制台黄色警告
行内样式写成字符串 / 复合属性用中划线
- 现象:样式不生效,或直接抛错。
- 原因:
style的值在 JSX 里必须是对象,且键名是 JS 标识符。 - 修法:写成
style={{...}};复合属性用小驼峰backgroundColor、fontSize;px 单位可省,直接写数字。
onClick={fn()}写成调用- 现象:页面一渲染就执行了一次,点击反而没反应。
- 原因:加括号拿到的是函数执行结果,渲染时立即执行,返回值
undefined成了事件回调。 - 修法:传引用
onClick={fn};需要传参就包一层onClick={() => fn(1, 2)}。
事件回调里 this 拿不到按钮
- 现象:
this是window(严格模式下是undefined),不是当前元素。 - 原因:行内绑定本质是普通函数调用,调用者是 window,不是元素。
- 修法:用
e.target/e.currentTarget拿元素;或绑定处传实参this,形参(如_this)接收。
- 现象:
自定义组件名首字母小写
- 现象:
<user />被当作未知 HTML 标签,不报错也不渲染;<User />未定义则直接抛错。 - 原因:JSX 靠首字母大小写区分「HTML 标签」和「组件」。
- 修法:组件名一律大写开头,文件内先定义再使用。
- 现象:
table 里直接放 tr
- 现象:报标签嵌套错误,表格渲染异常。
- 原因:React 会校验 DOM 嵌套合法性,
tr必须位于tbody中;同理div不能放进p。 - 修法:补上
<tbody>,按标准结构书写。
onInput 和 onChange 混用
- 现象:以为
onChange每敲一个字就触发,结果必须失焦才执行,实时搜索/校验失灵。 - 原因:两者触发时机不同,
onInput每次输入即触发,onChange是值变化且失焦时触发。 - 修法:实时反馈用
onInput,失焦提交用onChange,选之前先明确业务需要哪个时机。
- 现象:以为
练习
评论列表条件渲染 定义
comments数组(字段:id / name / content)。长度为 0 时渲染<h1>暂无评论</h1>;否则渲染ul > li > (a > h3) + p,name字号 25px 用行内样式,content字号 20px 用类名控制。 验收:控制台无 key 警告;把数组清空后页面切换为「暂无评论」。属性赋值三种写法对比 用
table分别实现:① 逐个属性写死;② 每个属性都写成{obj.xxx};③ 用扩展运算符一次性展开对象。三种写法渲染结果必须完全一致。 验收:能说出第三种写法相比前两种省掉了什么。事件参数与默认行为 给按钮绑定点击,同时输出自定义参数和事件对象,观察实参与形参的对应关系;再给
a标签绑定点击,输出后阻止跳转。 验收:控制台先打印自定义参数、再打印事件对象,页面不发生跳转。验证 this 的归属 用行内属性绑定一个普通函数,函数内打印
this;然后在绑定处把this作为实参传入,用形参接收并打印。 验收:能解释这两个this为什么不是同一个,并说明各自是谁。onInput / onChange 触发时机对比 两个
input,分别绑onInput和onChange,在下方实时显示「当前值 + 事件对象类型」。 验收:能通过观察说出两者的触发时机差异,并各举一个适合的使用场景。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 综合练习作业讲解 | 05:46 |
| 2 | 列表渲染的key值问题 | 07:53 |
| 3 | jsx表达式给标签属性赋值 | 08:51 |
| 4 | 表格报错补充说明 | 00:56 |
| 5 | 通过标签属性绑定事件-事件回调函数相关知识 | 12:24 |
| 6 | 函数调用时的实参this说明 | 04:55 |
| 7 | oninput-onchange的区别 | 04:10 |
小结
{}是 JSX 的「取值出口」,只接受有值的表达式;拿不准就把内容先赋给变量或console.log一下,能打印的就是能插值的。- 条件渲染的本质是「让不该出现的那一支返回假值」,而不是在 JSX 里写控制流语句。
- 遍历生成同级元素时,key 是必需品而非性能优化项;key 应选业务上稳定且唯一的字段,而不是循环下标。
- 属性赋值全部走表达式,形态写错通常不会报类型错误,只会静默失效——所以「没效果」时第一反应应该是看控制台。
- 行内绑定的事件回调是普通函数调用,
this不指向元素;需要元素请走事件对象或显式传参。 - 事件对象的传参位置由调用方自己决定,但原生内联写法里那个实参名是固定的,形参名才可随意。
- 报「标签嵌套错误」先查 DOM 结构是否合法,这是 React 的校验,不是框架 bug。
- 选择输入类事件前先明确业务需要的时机:要即时反馈就用 onInput,要确认后提交就用 onChange。