主题
代码片段与配置(Day1 3/4)
本页对应 React + TypeScript + 医通项目,共 6 段视频,约 48 分钟。
学习目标
- 能够使用
React.createElement创建多层嵌套的 React 元素,并说明其props.children的嵌套结构与最终渲染结果。 - 能够区分 JSX 中浏览器可识别的小写 HTML 标签与首字母大写的组件标签,并修正自定义标签导致的报错。
- 能够判断给定代码是 JS 表达式还是 JS 语句,并依据“是否有值、能否被变量接收或直接输出”说明理由。
- 能够使用
{}插值渲染 number、string、boolean、null、undefined、数组和对象,并准确预测渲染结果或报错信息。 - 能够使用插值表达式完成三元表达式、逻辑与/或运算、函数调用的渲染,并根据表达式规则推导最终输出。
- 能够正确书写 JSX 注释,并解释注释为什么需要放在
{/* ... */}中。
知识点
从 createElement 嵌套理解 React 元素
为什么需要:真实页面布局往往层层嵌套,先用手写 React.createElement 能把“React 元素只是对象、children 里继续放 React 元素”这件事看清。怎么想:先建最外层,再把子元素按参数依次放进去;从第三个参数开始都是 children,没有属性就用 null 占位。怎么做:按课堂推导可整理成:
js
const oDiv = React.createElement(
'div',
{ className: 'wrapper' },
React.createElement(
'div',
{ className: 'spinner' },
React.createElement('h3', null, '我是h3'),
React.createElement('p', null, '我是文字')
),
React.createElement('span', null, '我是span'),
React.createElement('span', null, '我也是span')
);调试时先 console.log(oDiv),再渲染。打印结果是一个带 type、props、ref 等属性的对象;props.children 有 3 个子元素,第一个子元素的 props.children 里又有 h3 和 p。渲染后结构是 root > wrapper > spinner > h3、p,同级还有两个 span。易错点:属性写 className 不是 class;没有属性不能省参数,要用 null 占位;不要把 children 当成真实 DOM,它仍是 React 元素对象。这个练习也说明:createElement 能做,但嵌套一复杂就很费劲,所以需要 JSX。
JSX 标签规则:小写 HTML,大写组件
为什么需要:JSX 最终会变成 React 元素,再转成真实 DOM,浏览器不认识的标签没有意义。怎么想:小写标签按浏览器可识别 HTML 标签处理;首字母大写标签按 React 组件处理。怎么做:<div>、<span> 可以;<userName> 这种自定义小写标签,浏览器会提示 The tag userName is unrecognized in the browser。若写 <User />,JSX 会把它当组件;组件没定义就报 User is not defined。易错点:不要以为 JSX 里能随便写标签;大写不是“普通标签”,而是组件引用。
JSX 注释为什么写成 {/* */}
为什么需要:JSX 里直接写 // 或 /* */ 位置不对,容易被当成文本或解析混乱。怎么想:{} 是进入 JS 表达式的入口,注释本质是 JS 注释。怎么做:写成 {/* 注释内容 */}。易错点:外层 {} 表示插值表达式,里面才按 JS 注释规则写;这也是后面插值表达式的基础。
先分清 JS 表达式和 JS 语句
为什么需要:{} 里只能放“有值”的 JS 表达式,放语句会出问题。怎么想:for、if/else、switch/case、赋值语句没有可直接使用的值;常量、变量、三元表达式、逻辑运算表达式、函数调用都有值。怎么做:把有值的东西放进 {}。易错点:不要直接打印 for 循环或 if 语句;函数调用看返回值,返回值是什么就按什么规则渲染。
插值表达式渲染各数据类型
为什么需要:数据要显示到页面,必须知道哪些类型能渲染、哪些会报错。怎么做:{a} 渲染 number,{str} 渲染字符串;布尔值 true/false、null、undefined 都不渲染任何内容;对象直接渲染会报 Object are not valid as a React child;数组会遍历每个元素渲染,若数组元素是对象,仍会报同样的错。三元、逻辑与/或、函数调用也遵循返回值规则:
jsx
const a = 123;
const str = 'ABC';
const isLoading = true;
<p>{a}</p>
<p>{str}</p>
<p>{isLoading ? '页面加载中' : '页面内容'}</p>
<p>{5 && 8}</p> {/* 8 */}
<p>{0 && 19}</p> {/* 0,不渲染 */}
<p>{5 || 8}</p> {/* 5 */}
<p>{[1, 2, 3, 4]}</p>易错点:对象不能直接塞进 {};数组里不要放对象;函数返回 undefined 时页面不渲染,返回对象时会报错。调试时先注释掉报错对象,再刷新观察页面和控制台报错,这是课堂里反复验证的路径。
为什么需要代码片段
每写一个 React 页面,都要重复敲 <!DOCTYPE html>、引入 react.development.js、react-dom.development.js、babel.min.js、创建 #root、写 createRoot 和 render。这些内容固定、重复、容易漏。转写稿里就出现过“没有引入 babel”导致 JSX 不生效、需要手动补 babel 脚本的调试过程。代码片段的作用是把这套模板固化成唤醒词,输入 !react 就展开,减少重复劳动。
怎么想:先认清四个字段
代码片段本质是一个 JSON 配置:prefix 是唤醒词;scope 是生效文件类型;body 是展开后的代码数组;$1、$2 是展开后光标可停留的位置。本节笔记没有写 scope,所以它默认对多种文件生效;实际使用时可以按需加 "scope": "html"。body 里每一行都是字符串,因此 HTML 中的双引号要写成 \",制表符写成 \t。
怎么做:创建并粘贴片段
按笔记步骤:文件 → 首选项 → 配置用户代码片段 → 新代码片段 → 回车,创建一个代码片段文件。然后把下面内容复制进去:
json
{
"react模板":{
"prefix": "!react",
"body": [
"<!DOCTYPE html>",
"<html lang=\"en\">",
"<head>",
"\t<meta charset=\"UTF-8\">",
"\t<title>Title</title>",
"\t<script src=\"./lib/react.development.js\"></script>",
"\t<script src=\"./lib/react-dom.development.js\"></script>",
"\t<script src=\"./lib/babel.min.js\"></script>",
"</head>",
"<body>",
"\t<div id=\"root\"></div>",
"</body>",
"<script type=\"text/babel\">",
"\tconst root = ReactDOM.createRoot(document.querySelector(\"#root\"));",
"\troot.render((",
"\t\t<div></div>",
"\t))",
"</script>",
"</html>"
],
"description": "快速构建react模板页页面"
},
"react模板2":{
"prefix": "!react2",
"body": [
"<!DOCTYPE html>",
"<html lang=\"en\">",
"<head>",
"\t<meta charset=\"UTF-8\">",
"\t<title>Title</title>",
"\t<script src=\"./lib/react.development.js\"></script>",
"\t<script src=\"./lib/react-dom.development.js\"></script>",
"</head>",
"<body>",
"\t<div id=\"root\"></div>",
"</body>",
"<script>",
"\tconst root = ReactDOM.createRoot(document.querySelector(\"#root\"));",
"\troot.render((",
"\t\t<div></div>",
"\t))",
"</script>",
"</html>"
],
"description": "快速构建react模板页页面"
}
}保存后,在 HTML 文件里输入 !react 或 !react2,按提示展开即可。
两个模板的差别与调试
!react 带 babel.min.js,script 类型是 text/babel,可以直接写 JSX;!react2 不带 babel,script 是普通脚本,只能写 React.createElement 这类 JS。转写稿中“没有引入 babel”后手动补 babel,说明模板 1 适合 JSX 练习,模板 2 适合观察不用 JSX 时的写法。展开后如果页面空白,先查 ./lib/ 路径是否正确,再查是否引入 babel、script type 是否写成 text/babel。
易错点
JSON 中最后一项不能多逗号;body 每行都要用双引号包住,内部双引号必须转义;\t 不要写成真实制表符导致格式混乱;$1、$2 要放在需要光标停留的字符串里,不要漏写;prefix 不要和已有片段冲突。转写稿还提醒:JSX 注释不能直接写 //,要用 {/* */};对象不能直接作为 React 子元素,会报“Objects are not valid as a React child”;小写自定义标签浏览器不认识,首字母大写会被当作组件处理,未定义就报错。用模板生成页面后,先用一个简单 <div> 验证渲染,再逐步加入插值表达式、三元表达式和函数调用,避免一次写太多难以定位错误。
本节目标与适用场景
本节要完成的事情很具体:先获取当前代码片段文件的路径,再按照目录找到该文件并删除。笔记只给了两个操作要点:
- 获取路径:查看 → 外观 → 痕迹导航
- 按照目录找到文件删除即可
为什么需要:删除代码片段文件时,最容易出问题的是“不知道当前文件到底在哪个目录”。如果只凭文件名去目录树里翻,可能遇到同名文件、旧版本文件或缓存文件,删错后很难恢复。先通过“痕迹导航”读出路径,可以把“当前编辑的文件”和“磁盘上的文件”对应起来。
怎么想:不要先删,先确认。确认三件事:当前打开的是不是目标文件、它在项目目录中的完整位置、删除后是否有其他地方引用它。
怎么做:打开目标代码片段文件,在编辑器菜单中依次点击“查看 → 外观 → 痕迹导航”,让界面显示当前文件的路径层级;然后回到项目目录树,按这个层级逐级展开,找到同名文件后删除。
易错点:不同编辑器/IDE 的菜单名称可能略有差异,如果找不到“痕迹导航”,以实际界面为准,不要臆造快捷键;路径中如果有同名目录,要逐级核对,不要只看文件名。
先理解“痕迹导航”的作用
“痕迹导航”可以理解为路径面包屑:它把当前文件所在的目录层级显示出来,例如“项目根目录 / 某目录 / 某文件”。它的价值不是“好看”,而是把当前编辑对象的位置可视化。
为什么需要:代码片段文件可能放在项目目录、用户配置目录或某个子目录中。只看到编辑器标签页上的文件名,无法判断它属于哪个目录。
怎么想:把痕迹导航当成“定位器”。先读最右侧文件名,确认是不是目标文件;再向左读各级目录,形成一条从根目录到文件的路径。
怎么做:执行“查看 → 外观 → 痕迹导航”后,观察编辑器顶部或底部出现的路径层级。如果路径太长被省略,可以逐级点击或结合目录树核对。
易错点:痕迹导航可能只显示部分层级,不能把它当成绝对路径的全部;不要因为文件名相同就直接删除,必须回到目录树确认父级目录。
操作步骤一:通过菜单获取路径
为什么需要:手动回忆路径容易出错,尤其是代码片段文件不在显眼目录时。通过菜单显示痕迹导航,是让编辑器直接告诉你当前文件的位置。
怎么想:先确保当前活动标签页就是目标代码片段文件,再打开痕迹导航。不要在其他文件上取路径。
怎么做:在编辑器菜单中依次选择“查看 → 外观 → 痕迹导航”。显示后,记录或记住路径层级。若界面没有变化,检查是否点在了正确窗口,或该菜单项是否已被勾选/取消。
易错点:不要根据“最近打开文件”列表推断路径;不要在没有确认活动文件的情况下操作。若菜单项是开关状态,重复点击可能关闭显示。
操作步骤二:按目录定位并删除文件
为什么需要:获取路径只是定位,真正删除要在目录树中完成。按目录逐级找到文件,可以避免误删同名文件。
怎么想:把痕迹导航中的层级映射到项目目录树:从根目录开始,一级一级展开,最后定位到目标文件。删除前再看一眼文件名和所在目录。
怎么做:在目录树中按路径逐级展开,找到目标代码片段文件;右键选择删除,或选中后使用删除操作。删除后回到编辑器,关闭已删除文件的标签页,观察是否出现“文件不存在”等提示。
易错点:不要删除整个目录;不要删除同名的配置文件或备份文件;如果项目使用版本控制,删除后要检查变更列表,确认只删除了目标文件;如果该代码片段被其他配置引用,删除文件后还要清理引用,否则可能出现引用失效。
删除后的检查与调试
为什么需要:删除文件不等于问题结束。代码片段文件可能被设置项、插件或其他文件引用,删除后需要验证没有残留影响。
怎么想:按“文件是否存在 → 引用是否残留 → 功能是否正常”的顺序检查。
怎么做:确认目录中目标文件已消失;重新打开相关设置或引用位置,检查是否还有指向该文件的路径;如果出现报错,先根据报错定位引用处,不要盲目恢复或重复删除。
易错点:删除后不要立刻大规模改动其他文件;如果误删,优先使用回收站/撤销/版本控制恢复;不要编造“自动清理引用”的能力,引用清理需要按实际项目检查。
转写稿核对说明
本节笔记未提供图片和代码,因此正文不插入图片或代码块。提供的转写稿主要讨论 React 元素嵌套、JSX 注意事项、JS 表达式与语句、插值表达式等内容,与“删除代码片段文件”这一节不匹配;其中“接操”“GS叉”“差值表达式”等应分别理解为 JSX、JSX、插值表达式,但这些内容不属于本节操作。为避免编造,本节只依据笔记中的“查看 → 外观 → 痕迹导航”和“按照目录找到文件删除”进行扩写,不把无关转写内容硬塞进本节。
本节要解决的问题
这一节不是讲新语法,而是讲 React 根节点使用时的三条“边界”。代码本身很短,但每条都对应一个真实开发中容易踩的坑:根节点和 DOM 容器怎么绑定、render 重复调用是什么行为、为什么不能拿 body 或 html 当根节点。先把本节代码完整放出来:
html
<!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>
</head>
<body>
<div id="root"></div>
</body>
<script>
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render((
// <div></div>
// ))
// 1. dom容器 和 react的根节点需要是一一对应的,虽然可以正常渲染,但是控制台会有警告
// const root1 = ReactDOM.createRoot(document.querySelector("#root"));
// const root2 = ReactDOM.createRoot(document.querySelector("#root"));
// root2.render('root2')
// root1.render('root1')
// 2. render函数可以重复调用,后面的会覆盖前面的
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render('第一次渲染');
// root.render('第二次渲染');
// 3. 根节点不要使用 body 和 html
// const root = ReactDOM.createRoot(document.body); // body
const root = ReactDOM.createRoot(document.documentElement); // html
root.render('第一次渲染');
</script>
</html>注意事项一:DOM 元素和 React 根节点必须一一对应
为什么需要:ReactDOM.createRoot(container) 的本质是“把某个 DOM 容器登记成 React 的渲染入口”。一个入口应该对应一个容器,否则两个 root 同时盯着同一个 #root,职责就重叠了。
怎么想:不要把它理解成“多创建几次也没事”,而要先问:这个 root 到底负责哪块 DOM?如果两个 root 都负责同一个 #root,即使页面还能出内容,控制台也会给出警告。本节笔记明确写了“虽然可以正常渲染,但是控制台会有警告”。
怎么做:一个容器只创建一次 root。需要多个根,就准备多个不同容器,例如 #root1、#root2,而不是反复 querySelector("#root")。
易错点:看到“能渲染”就忽略警告;或者把 root1.render()、root2.render() 的先后顺序当成主要问题。真正的问题是两个 root 绑定了同一个 DOM 容器。
注意事项二:render 可以重复调用,后面的覆盖前面的
为什么需要:React 应用不是只渲染一次,数据变化后要重新渲染。理解“覆盖”才能明白重复 render 不是往页面后面追加内容。
怎么想:同一个 root 上连续调用 render,可以把它看成“用新结果替换这个 root 上一次的渲染结果”。所以第二次调用后,页面看到的是第二次的内容。
怎么做:本节示例中先 root.render('第一次渲染'),再 root.render('第二次渲染'),最终显示第二次的内容。
易错点:误以为两次 render 会拼接成“第一次渲染第二次渲染”。另外,render 的内容要符合 React 可渲染规则;转写稿中专门调试过“对象不能作为 React 子元素”,会报 Objects are not valid as a React child 一类错误。这里注意,转写稿里的“资源”是口误/同音,实际是“子元素”;“案例犯”是 undefined,“波尔值”是“布尔值”。
注意事项三:不要使用 body 和 html 作为 React 根节点
为什么需要:body 和 html 是页面级容器,页面里除了 React 内容,还可能有脚本、其他结构。把 React 根节点直接设成它们,等于让 React 的渲染范围覆盖整个页面骨架,边界不清晰。
怎么想:React 根节点最好是一个“专属容器”。本节 HTML 里已经准备了 <div id="root"></div>,这就是明确边界。不要图省事写 document.body 或 document.documentElement。
怎么做:使用 document.querySelector("#root") 作为容器,而不是 document.body 或 document.documentElement。本节代码最后一行用了 document.documentElement,它正是用来演示“不要这样用”的反例。
易错点:把 document.documentElement 当成普通 div;或者认为“只要不报错就没事”。根节点选择要稳定、专属,避免和页面其他内容混在一起。
调试提醒与练习方法
本节代码在 <script> 里,注释用 //;转写稿里还讲过 JSX 内部注释要写成 {/* ... */},这两种位置不要混。调试时先看控制台:一一对应问题看警告,渲染内容不合法看报错。转写稿最后还强调练习方法:先看标题回忆知识点,再对照代码敲一遍,卡住就重来。这个方法同样适用于本节三条注意事项:先判断“容器是否唯一、render 是否覆盖、根节点是否用了 body/html”,再动手改代码验证。
课堂画面






代码示例
1. VS Code 用户代码片段配置(react.code-snippets)
json
{
"react模板": {
"prefix": "!react",
"body": [
"<!DOCTYPE html>",
"<html lang=\"en\">",
"<head>",
"\t<meta charset=\"UTF-8\">",
"\t<title>Title</title>",
"\t<script src=\"./lib/react.development.js\"></script>",
"\t<script src=\"./lib/react-dom.development.js\"></script>",
"\t<script src=\"./lib/babel.min.js\"></script>",
"</head>",
"<body>",
"\t<div id=\"root\"></div>",
"</body>",
"<script type=\"text/babel\">",
"\tconst root = ReactDOM.createRoot(document.querySelector(\"#root\"));",
"\troot.render((",
"\t\t<div></div>",
"\t))",
"</script>",
"</html>"
],
"description": "快速构建react模板页页面"
},
"react模板2": {
"prefix": "!react2",
"body": [
"<!DOCTYPE html>",
"<html lang=\"en\">",
"<head>",
"\t<meta charset=\"UTF-8\">",
"\t<title>Title</title>",
"\t<script src=\"./lib/react.development.js\"></script>",
"\t<script src=\"./lib/react-dom.development.js\"></script>",
"</head>",
"<body>",
"\t<div id=\"root\"></div>",
"</body>",
"<script>",
"\tconst root = ReactDOM.createRoot(document.querySelector(\"#root\"));",
"\troot.render((",
"\t\t<div></div>",
"\t))",
"</script>",
"</html>"
],
"description": "快速构建react模板页页面"
}
}关键行与易错点
文件 -> 首选项 -> 配置用户代码片段 -> 新代码片段 -> 回车,才会生成片段文件;文件名决定不了唤醒词,prefix才是唤醒词。prefix:输入!react后按 Tab 触发;body:数组每一项是一行代码,\t表示缩进,\"是 JSON 里双引号的转义。- 模板 1 多了
babel.min.js且<script type="text/babel">,所以里面可以写 JSX;模板 2 没引 babel,script里只能写纯 JS(render里不能写<div></div>这种标签)。 - 两个模板都依赖同目录
./lib/下的 react 文件,路径写错页面直接白屏。 - 删除片段文件:
查看 -> 外观 -> 痕迹导航拿到路径,按目录找到文件删除即可。
2. React 根节点注意事项(笔记 4)
html
<!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>
</head>
<body>
<div id="root"></div>
</body>
<script>
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render((
// <div></div>
// ))
// 1. dom容器 和 react的根节点需要是一一对应的,虽然可以正常渲染,但是控制台会有警告
// const root1 = ReactDOM.createRoot(document.querySelector("#root"));
// const root2 = ReactDOM.createRoot(document.querySelector("#root"));
// root2.render('root2')
// root1.render('root1')
// 2. render函数可以重复调用,后面的会覆盖前面的
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render('第一次渲染');
// root.render('第二次渲染');
// 3. 根节点不要使用 body 和 html
// const root = ReactDOM.createRoot(document.body); // body
const root = ReactDOM.createRoot(document.documentElement); // html
root.render('第一次渲染');
</script>
</html>关键行与易错点
createRoot(容器)与render()必须一一对应:同一个#root建两个 root 能渲染,但控制台会警告。render可重复调用,后一次覆盖前一次,不是追加;所以'第一次渲染'会被'第二次渲染'顶掉。- 根节点别用
body/html:用document.body或document.documentElement会破坏页面结构,规范做法是内部放一个#root作为容器。 - 这段是纯 JS 版本,没有引入
babel.min.js,所以render里不能写 JSX 标签,只能写字符串;要写 JSX 必须加babel+type="text/babel"。
3. 插值表达式:各类数据类型 + 表达式渲染
html
<!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">
const root = ReactDOM.createRoot(document.querySelector("#root"));
// 常量 / 变量都是 JS 表达式
const a = 123;
const str = "abc";
const isLoading = true;
// 函数调用也是表达式,值取决于 return
function fn() {}
function f1() {
return [1, 2, 3];
}
function f2() {
return { userName: "爱硅谷", age: 20 };
}
root.render((
<div>
<h3>基本数据类型</h3>
<p>{a}</p>
<p>{str}</p>
<span>{true}</span>
<span>{false}</span>
<h3>null 和 undefined</h3>
<p>{null}</p>
<p>{undefined}</p>
<h3>对象(会报错,注释掉)</h3>
{/* <p>{{ userName: "爱硅谷", age: 20 }}</p> */}
<h3>数组</h3>
<p>{[1, 2, 3, 4]}</p>
<h3>三元表达式</h3>
<p>{isLoading ? "页面加载中" : "页面的内容"}</p>
<h3>逻辑运算</h3>
<p>{5 && 8}</p>
<p>{0 && 19}</p>
<p>{null && 200}</p>
<p>{5 || 8}</p>
<h3>函数调用</h3>
<p>{fn()}</p>
<p>{f1()}</p>
{/* <p>{f2()}</p> */}
</div>
));
</script>
</html>关键行与易错点
- 插值表达式语法就是
{ JS表达式 },里面只能放表达式(有值的):常量、变量、三元、逻辑运算、函数调用;if、for、switch、赋值语句都没值,不能放。 - 渲染规则:
number/string正常显示;true、false、null、undefined什么都不渲染;数组会遍历每个元素逐个渲染,元素是true/false/null/undefined时同样不显示。 - 对象作为子元素直接报错:
Objects are not valid as a React child;数组里放对象同样报错(遍历到元素时又遇到对象)。 - 对象要写成
{{ ... }}:外层花括号是插值边界,内层是对象字面量。 - 逻辑与
5 && 8→8,0 && 19→0,null && 200→null;逻辑或5 || 8→5。规则:看表达式一的布尔值,为真取表达式二(与)/ 表达式一(或),为假取表达式一(与)/ 表达式二(或)。 - 函数调用的值取决于
return:fn()无返回值 →undefined不渲染;f1()返回数组 → 按数组规则遍历渲染;f2()返回对象 → 报错。 - JSX 里注释必须用
{/* ... */},直接写//或/* */会被当成文本,注释不掉会直接报错。
常见错误
1. 输入前缀后候选框根本不出现
- 现象:敲
!react没有提示;或只在.js里能补全,.html里不行。 - 原因:片段文件 JSON 语法错误(漏逗号、字符串里的
"没写成\")会让整个文件失效,不是只坏一条;或scope没写/写错;或当前文件语言模式不是 HTML。 - 修法:看编辑器里的红色波浪线定位 JSON 错误;
scope显式写html;右下角确认语言模式;保存后重新打开文件再试。
2. 模板生成后页面全白,控制台报 ReactDOM is not defined
- 现象:HTML 骨架在,Network 里三个 js 全是 404。
- 原因:片段里写死的
./lib/xxx.js与实际目录不一致(没建 lib、版本号不同)。 - 修法:按真实路径改
src,并把改好的路径同步回片段文件,避免下次再犯。
3. 用无 babel 的模板却写了 JSX
- 现象:
Uncaught SyntaxError: Unexpected token '<',页面全白。 - 原因:script 没有
type="text/babel",也没引入 babel,浏览器把 JSX 当普通 JS 解析。 - 修法:换回带 babel 的模板,或该文件只用
React.createElement写结构。
4. 控制台警告容器已被 createRoot 过,两个 root 互相打架
- 现象:能渲染但有 warning,交替渲染时结果混乱。
- 原因:对同一个
#root调了两次createRoot。 - 修法:一个容器只
createRoot一次并复用,重复渲染直接多次调root.render。
5. 用 document.body / document.documentElement 当容器
- 现象:报错或行为异常。
- 原因:根节点不能是 body / html。
- 修法:在 body 内放
<div id="root">作为唯一容器。
6. 插值里放了对象,整页空白
- 现象:原本能显示的内容一起消失,报
Objects are not valid as a React child。 - 原因:花括号求值结果是一个对象。
- 修法:改成渲染属性值
user.userName或JSON.stringify(user);排查时逐个注释插值定位。
7. 写了 <userName> 或 <User />
- 现象:前者浏览器提示 "unrecognized in the browser" 且无内容;后者报
User is not defined。 - 原因:小写开头按原生标签处理,大写开头按组件(变量)处理。
- 修法:小写只能用浏览器认识的标签;大写必须先定义组件再使用。
8. 期待 true / null 显示成文字,结果什么都没有
- 现象:对应位置空白且不报错。
- 原因:React 对布尔、null、undefined 不渲染任何内容。
- 修法:需要显示就用
String(flag)或三元表达式输出文本。
练习
- 用
createElement手写三层嵌套(div.wrapper内含h3、p和两个span),渲染后展开控制台对象,说出 children 的层级对应关系。 - 自建片段文件,同时提供
!html与!react两条,!react需一键生成#root+ createRoot + render 骨架;再用痕迹导航找到该文件并删除。 - 在同一页面分别用
#root和document.body创建 root,记录控制台输出的差异并解释。 - 用插值依次渲染:数字、字符串、
true、false、null、undefined、数组[1,'a',true],把每次实际输出整理成表。 - 只用三元表达式和逻辑与,实现"加载中 / 加载完成"两种输出的切换,要求全程不出现 if 语句,只改一个布尔变量。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | react元素的嵌套 | 05:23 |
| 2 | jsx注意事项 | 05:51 |
| 3 | js表达式和js语句的差别 | 06:19 |
| 4 | 插值表达式-01 | 14:29 |
| 5 | 插值表达式-02 | 09:48 |
| 6 | 翔哥碎碎念 | 07:04 |
小结
- 代码片段是"配一次、长期用"的工具,出问题的表现往往不是"某条坏了"而是"全都失效",优先怀疑 JSON 转义与
scope。 - 模板的核心是固定骨架,但资源路径和 babel 引入方式必须与本地目录严格对应,否则骨架就是空壳。
createRoot与容器一对一;render可重复调用,后一次覆盖前一次。- 根容器必须是页面内的普通元素,body 与 html 不能用。
- JSX 标签只有两种合法写法:小写是原生标签,大写是组件,二者不可混用。
- 花括号内只接受表达式不接受语句,这是绝大多数插值报错的共同根源。
- 渲染结果按数据类型分档:数字/字符串正常显示,布尔/null/undefined 静默跳过,对象直接报错,数组按上述规则逐项递归。
- 三元、逻辑运算、函数调用的返回值仍受第 7 条约束,返回值类型决定最终能不能显示。