Skip to content

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) {&#125;&#125; 不行;但三元表达式可以。

基本类型插值 ​

为什么需要:不同值在 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 单位可省略为数字;样式对象可抽变量复用;双花括号要分清“外层插值、内层对象”。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

课堂画面 6

代码示例 ​

示例一: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> 里。

常见错误 ​

  1. 插值里直接塞对象

    • 现象:页面白屏,控制台报 Objects are not valid as a React child。
    • 原因:{} 只接受可渲染的值,对象不是合法子节点;数组能渲染是因为会被逐个展开。
    • 修法:展示字段就取属性 {user.name};要整体查看数据就 JSON.stringify(obj) 或放到属性上。
  2. 列表渲染不写 key,或拿 index 当 key

    • 现象:控制台黄色警告 Each child in a list should have a unique "key" prop;列表增删后内容错位、输入框串值。
    • 原因:同级循环出的元素结构完全相同,diff 缺少比对依据。
    • 修法:给遍历出的最外层元素加 key={item.id}(MongoDB 数据用 item._id);index 只在列表顺序永不变化时勉强可用。
  3. 行内样式写成字符串 / 复合属性用中划线

    • 现象:样式不生效,或直接抛错。
    • 原因:style 的值在 JSX 里必须是对象,且键名是 JS 标识符。
    • 修法:写成 style={{...}};复合属性用小驼峰 backgroundColor、fontSize;px 单位可省,直接写数字。
  4. onClick={fn()} 写成调用

    • 现象:页面一渲染就执行了一次,点击反而没反应。
    • 原因:加括号拿到的是函数执行结果,渲染时立即执行,返回值 undefined 成了事件回调。
    • 修法:传引用 onClick={fn};需要传参就包一层 onClick={() => fn(1, 2)}。
  5. 事件回调里 this 拿不到按钮

    • 现象:this 是 window(严格模式下是 undefined),不是当前元素。
    • 原因:行内绑定本质是普通函数调用,调用者是 window,不是元素。
    • 修法:用 e.target / e.currentTarget 拿元素;或绑定处传实参 this,形参(如 _this)接收。
  6. 自定义组件名首字母小写

    • 现象:<user /> 被当作未知 HTML 标签,不报错也不渲染;<User /> 未定义则直接抛错。
    • 原因:JSX 靠首字母大小写区分「HTML 标签」和「组件」。
    • 修法:组件名一律大写开头,文件内先定义再使用。
  7. table 里直接放 tr

    • 现象:报标签嵌套错误,表格渲染异常。
    • 原因:React 会校验 DOM 嵌套合法性,tr 必须位于 tbody 中;同理 div 不能放进 p。
    • 修法:补上 <tbody>,按标准结构书写。
  8. onInput 和 onChange 混用

    • 现象:以为 onChange 每敲一个字就触发,结果必须失焦才执行,实时搜索/校验失灵。
    • 原因:两者触发时机不同,onInput 每次输入即触发,onChange 是值变化且失焦时触发。
    • 修法:实时反馈用 onInput,失焦提交用 onChange,选之前先明确业务需要哪个时机。

练习 ​

  1. 评论列表条件渲染 定义 comments 数组(字段:id / name / content)。长度为 0 时渲染 <h1>暂无评论</h1>;否则渲染 ul > li > (a > h3) + p,name 字号 25px 用行内样式,content 字号 20px 用类名控制。 验收:控制台无 key 警告;把数组清空后页面切换为「暂无评论」。

  2. 属性赋值三种写法对比 用 table 分别实现:① 逐个属性写死;② 每个属性都写成 {obj.xxx};③ 用扩展运算符一次性展开对象。三种写法渲染结果必须完全一致。 验收:能说出第三种写法相比前两种省掉了什么。

  3. 事件参数与默认行为 给按钮绑定点击,同时输出自定义参数和事件对象,观察实参与形参的对应关系;再给 a 标签绑定点击,输出后阻止跳转。 验收:控制台先打印自定义参数、再打印事件对象,页面不发生跳转。

  4. 验证 this 的归属 用行内属性绑定一个普通函数,函数内打印 this;然后在绑定处把 this 作为实参传入,用形参接收并打印。 验收:能解释这两个 this 为什么不是同一个,并说明各自是谁。

  5. onInput / onChange 触发时机对比 两个 input,分别绑 onInput 和 onChange,在下方实时显示「当前值 + 事件对象类型」。 验收:能通过观察说出两者的触发时机差异,并各举一个适合的使用场景。

原视频对照 ​

#视频时长
1综合练习作业讲解05:46
2列表渲染的key值问题07:53
3jsx表达式给标签属性赋值08:51
4表格报错补充说明00:56
5通过标签属性绑定事件-事件回调函数相关知识12:24
6函数调用时的实参this说明04:55
7oninput-onchange的区别04:10

小结 ​

  1. {} 是 JSX 的「取值出口」,只接受有值的表达式;拿不准就把内容先赋给变量或 console.log 一下,能打印的就是能插值的。
  2. 条件渲染的本质是「让不该出现的那一支返回假值」,而不是在 JSX 里写控制流语句。
  3. 遍历生成同级元素时,key 是必需品而非性能优化项;key 应选业务上稳定且唯一的字段,而不是循环下标。
  4. 属性赋值全部走表达式,形态写错通常不会报类型错误,只会静默失效——所以「没效果」时第一反应应该是看控制台。
  5. 行内绑定的事件回调是普通函数调用,this 不指向元素;需要元素请走事件对象或显式传参。
  6. 事件对象的传参位置由调用方自己决定,但原生内联写法里那个实参名是固定的,形参名才可随意。
  7. 报「标签嵌套错误」先查 DOM 结构是否合法,这是 React 的校验,不是框架 bug。
  8. 选择输入类事件前先明确业务需要的时机:要即时反馈就用 onInput,要确认后提交就用 onChange。