Skip to content

代码片段与配置(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> 验证渲染,再逐步加入插值表达式、三元表达式和函数调用,避免一次写太多难以定位错误。

本节目标与适用场景 ​

本节要完成的事情很具体:先获取当前代码片段文件的路径,再按照目录找到该文件并删除。笔记只给了两个操作要点:

  1. 获取路径:查看 → 外观 → 痕迹导航
  2. 按照目录找到文件删除即可

为什么需要:删除代码片段文件时,最容易出问题的是“不知道当前文件到底在哪个目录”。如果只凭文件名去目录树里翻,可能遇到同名文件、旧版本文件或缓存文件,删错后很难恢复。先通过“痕迹导航”读出路径,可以把“当前编辑的文件”和“磁盘上的文件”对应起来。

怎么想:不要先删,先确认。确认三件事:当前打开的是不是目标文件、它在项目目录中的完整位置、删除后是否有其他地方引用它。

怎么做:打开目标代码片段文件,在编辑器菜单中依次点击“查看 → 外观 → 痕迹导航”,让界面显示当前文件的路径层级;然后回到项目目录树,按这个层级逐级展开,找到同名文件后删除。

易错点:不同编辑器/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

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

课堂画面 6

代码示例 ​

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) 或三元表达式输出文本。

练习 ​

  1. 用 createElement 手写三层嵌套(div.wrapper 内含 h3、p 和两个 span),渲染后展开控制台对象,说出 children 的层级对应关系。
  2. 自建片段文件,同时提供 !html 与 !react 两条,!react 需一键生成 #root + createRoot + render 骨架;再用痕迹导航找到该文件并删除。
  3. 在同一页面分别用 #root 和 document.body 创建 root,记录控制台输出的差异并解释。
  4. 用插值依次渲染:数字、字符串、true、false、null、undefined、数组 [1,'a',true],把每次实际输出整理成表。
  5. 只用三元表达式和逻辑与,实现"加载中 / 加载完成"两种输出的切换,要求全程不出现 if 语句,只改一个布尔变量。

原视频对照 ​

#视频时长
1react元素的嵌套05:23
2jsx注意事项05:51
3js表达式和js语句的差别06:19
4插值表达式-0114:29
5插值表达式-0209:48
6翔哥碎碎念07:04

小结 ​

  1. 代码片段是"配一次、长期用"的工具,出问题的表现往往不是"某条坏了"而是"全都失效",优先怀疑 JSON 转义与 scope。
  2. 模板的核心是固定骨架,但资源路径和 babel 引入方式必须与本地目录严格对应,否则骨架就是空壳。
  3. createRoot 与容器一对一;render 可重复调用,后一次覆盖前一次。
  4. 根容器必须是页面内的普通元素,body 与 html 不能用。
  5. JSX 标签只有两种合法写法:小写是原生标签,大写是组件,二者不可混用。
  6. 花括号内只接受表达式不接受语句,这是绝大多数插值报错的共同根源。
  7. 渲染结果按数据类型分档:数字/字符串正常显示,布尔/null/undefined 静默跳过,对象直接报错,数组按上述规则逐项递归。
  8. 三元、逻辑运算、函数调用的返回值仍受第 7 条约束,返回值类型决定最终能不能显示。