主题
jsx中的事件与jsx中的注释(Day2 4/4)
本页对应 React + TypeScript + 医通项目,共 6 段视频,约 1 小时 1 分。
学习目标
- 能够定义一个首字母大写、继承
React.Component且实现render方法并返回 React 元素的类组件,并使用单标签或对标签完成调用渲染。 - 能够模拟
ReactDOM.render处理类组件的过程:识别首字母大写标签、实例化类组件、调用render,并用返回值替换组件标签位置。 - 能够定义一个首字母大写、返回 React 元素的函数组件,并说明其调用时由渲染过程直接执行函数、替换组件标签位置。
- 能够模拟函数组件的渲染过程:读取组件调用得到的 React 元素,通过
type、props.children创建真实 DOM 并挂载到根节点。 - 能够在 JSX 中正确绑定事件、编写 JSX 注释,并使用文档碎片组织多个组件调用。
- 能够使用 npm/yarn 全局安装
create-react-app创建并启动项目,并能使用npx局部安装创建并启动项目,处理端口占用等常见启动问题。
知识点
绑定事件的基本语法
为什么需要: 在 React/JSX 中,事件不是直接写原生 onclick 字符串,而是把事件处理函数作为标签属性传进去。这样事件逻辑和组件状态、渲染逻辑放在一起,便于维护。
怎么想: 先记住 JSX 事件名采用驼峰写法,例如 onClick;属性值要的是“函数本身”,不是函数执行结果。也就是传“函数引用地址”。
怎么做:
jsx
<button onClick={函数名}>click</button>例如:
jsx
function click1() {
console.log('click1');
console.log('this: ', this);
}
<button onClick={click1}>click1</button>易错点: 不要写成 onClick={click1()}。加括号会在渲染阶段立即调用,把返回值绑定给事件;如果函数没有返回值,实际绑定的就是 undefined。调试时如果打开页面就打印了 click1,说明你写成了调用而不是引用。
事件回调函数中的 this
为什么需要: 初学事件时容易以为回调里的 this 指向组件实例。实际要先看“谁调用了这个函数”。
怎么想: React 的事件回调不是我们手动 obj.click1() 这样调用,而是事件触发时由 React 的事件系统调用。普通函数调用在严格模式下 this 是 undefined;笔记中明确:React 使用严格模式,所以这里打印 this 为 undefined,而不是 window。
怎么做: 在回调里直接打印验证:
jsx
function click1() {
console.log('this: ', this); // undefined
}易错点: 不要套用“DOM 事件里 this 指向元素”的旧经验。这里的普通事件回调不是组件实例方法绑定,不能想当然认为 this 指向组件。需要组件数据时,函数组件里优先用闭包/参数,而不是依赖 this。
事件对象 e
为什么需要: 阻止默认行为、读取输入值、获取触发元素,都离不开事件对象。
怎么想: React 会把事件对象作为第一个实参传给回调;这个对象是 React 处理后的合成事件,常用属性基本都有,并做了兼容性处理。若要原生对象,用 e.nativeEvent。
怎么做:
jsx
function click2(e) {
console.log(e);
e.preventDefault();
}
<a href="http://baidu.com" onClick={click2}>百度</a>点击链接时调用 e.preventDefault() 可阻止跳转。调试时可同时打印 e 和 e.nativeEvent,观察合成事件与原生事件的区别。
易错点: 回调没有形参却写 e.preventDefault() 会报错;要接收就写 function click2(e)。另外不要以为 e 就是原生事件对象,需要原生能力时用 nativeEvent。
事件回调函数传递参数
为什么需要: 同一个处理函数可能要给不同按钮传不同参数,例如 click3(1, 2)。
怎么想: 直接写 onClick={click3(1, 2)} 会立即执行;正确做法是“包一层函数”,让点击时再执行内部调用。这个包裹函数可以是箭头函数,也可以是普通匿名函数。
怎么做:
jsx
<p><button onClick={() => click3(2, 3)}>包裹箭头函数传参</button></p>既传参数又传事件对象时,要让包裹函数显式接收 e:
jsx
<p>
<button onClick={(e) => click4(e, 100, 222)}>
即传递参数,又同时传递事件对象
</button>
</p>易错点: onClick={outFn} 这种写法中,outFn 内部再调用 click3(1, 2) 也可以,但要注意事件对象不会自动进入 click3;需要 e 时必须在包裹函数形参里接住并继续传下去。
JSX 中的 onChange 事件
为什么需要: 表单输入需要实时拿到用户输入值。笔记指出:JSX 中的 onChange 实际对应原生 oninput 事件。
怎么想: 它的触发时机是键盘输入就触发,事件对象是 InputEvent。所以读取值通常看 e.target.value。
怎么做:
jsx
function change(e) {
console.log('change e: ', e);
console.log('value: ', e.target.value);
}
<input type="text" onChange={change} />易错点: 不要按原生 change 的“失焦才触发”来理解;这里输入过程中就会触发。调试时先打印 e.target.value,确认拿到的是当前输入框的值。
完整示例代码
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">
/**
* 在标签属性上通过 on事件名=事件回调函数 进行绑定
* 1. 事件名首字母大写
* 2. 事件回调函数填的是函数名[函数的定义、函数的引用地址],而不是函数的调用
*
* -----------
* this指向问题:undefined 说明 react中的事件回调的调用者也是window,严格模式所以是undefined
*
* 事件对象e:
* 1. 一个经过react包装后的事件对象,原生的事件对象常用属性,基本都有。这个事件对象更好用,已经做过兼容性处理了
* 2. 如果想获取原生的事件对象:e.nativeEvent
* 3. 事件的回调函数是window帮我们调用的,会默认的将事件对象作为第一个实参传递给我
*
* 事件回调函数参数的传递
* 包裹一个箭头函数,在箭头函数内部调用函数并传递参数
*/
const root = ReactDOM.createRoot(document.querySelector("#root"));
function click1() {
console.log('click1');
console.log('this: ', this);//undefined
}
function click2(e) {
console.log(e);
e.preventDefault();
}
function click3(a, b) {
console.log('click3');
console.log('a: ', a);
console.log('b: ', b);
}
function outFn() {
click3(1, 2);
}
function click4(e, a, b) {
console.log('e: ', e);
console.log('a: ', a);
console.log('b: ', b);
e.preventDefault();
}
/**
* react中的 onChange事件,实际是原生的 oninput事件
*/
function change(e){
console.log('change e: ', e);
console.log('value: ', e.target.value);
}
root.render((
<div>
<p><button onClick={click1}>click1</button></p>
<p><a href="http://baidu.com" onClick={click2}>百度</a></p>
<h3>事件回调函数传参</h3>
<p><button onClick={click3()}>click3</button></p>
<p><button onClick={outFn}>传递参数</button></p>
<p><button onClick={function () {
click3(2, 3);
}}>包裹匿名函数传递参数</button></p>
<p><button onClick={() => click3(2, 3)}>包裹箭头函数传参</button></p>
<h3>即传递参数,又同时传递事件对象</h3>
<p><button onClick={(e) => {
console.log('e: ', e);
click3(1, e);
}}>即传递参数,又同时传递事件对象</button></p>
<p><a href="http://baidu.com" onClick={(e) => click4(e, 100, 222)}>百度</a></p>
<hr/>
<input type="text" onChange={change} name="" id=""/>
</div>
))
</script>
</html>先分清:JSX 注释不是 HTML 注释
为什么需要:JSX 虽然看起来像 HTML,但它最终是 JavaScript 表达式的一部分,不是普通 HTML 字符串。直接写 <!-- 注释 --> 并不能按 HTML 注释的方式工作,容易造成页面出现多余文本或语法错误。
怎么想:先判断注释写在哪里。写在 JSX 子节点位置时,要用 {} 进入 JavaScript 表达式上下文;写在标签属性列表里时,直接用 JavaScript 块注释 /* */。
怎么做:子节点位置常用 {/* */},单行可用 { // ... } 但必须换行;属性位置用 /* */。
易错点:不要用 <!-- -->;注释只给开发者看,不会渲染到 DOM;{/* */} 不要漏掉花括号。
单行注释:{ // ... } 为什么要“最少两行”
为什么需要:有时只想临时注释一行说明,或快速屏蔽一小段内容。
怎么想:{ 后面进入的是 JavaScript 表达式上下文,// 会注释到当前行行尾。如果写成 {//注释},后面的 } 也会被 // 注释掉,导致 JSX 结构不完整。
怎么做:把右花括号放到下一行:
jsx
{ //单行注释,最少得两行
}易错点:{//注释} 写在同一行容易报错;// 后必须有换行;这种写法适合短注释,复杂注释优先用 {/* */}。
多行注释:{/* ... */} 最常用
为什么需要:多行说明、临时屏蔽一段 JSX,或注释掉多个子节点时,单行注释不够清晰。
怎么想:用 {} 包住 JavaScript 块注释 /* */,块注释可以跨行,也不会被渲染。
怎么做:
jsx
{/*多行注释*/}
{/*
多行注释
123123
123123
123123
*/}易错点:不要写成 /* */ 直接裸放在 JSX 子节点位置;块注释不能嵌套;注释内容里不要提前出现 */。
行内属性注释:属性列表里用 /* */
为什么需要:调试时经常想临时注释掉某个属性,比如先去掉 className 看样式影响。
怎么想:标签内部是属性列表,不是 JSX 子节点,所以不能再用 {/* */} 包一层,而应直接使用 JavaScript 块注释。
怎么做:
jsx
<div id='box' /* className='wrapper' */>我是div</div>易错点:不要写成 <div {/* ... */} id='box'>;被注释的属性不会进入 props;注释后要检查标签结构是否仍然完整。
完整代码
jsx
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render((
<div>
{ //单行注释,最少得两行
}
{/*多行注释*/}
{/*
多行注释
123123
123123
123123
*/}
<div id='box' /* className='wrapper' */>我是div</div>
</div>
))调试时的判断顺序与易错点
写注释前先问自己:当前位置是 JSX 子节点,还是标签属性列表?如果是子节点,就用 {} 进入表达式上下文,再写 // 或 /* */;如果是属性列表,就直接写 /* */。调试时如果报语法错误,优先检查单行注释是否把 } 注释掉、{/* */} 是否漏了花括号、属性注释是否误用了 {/* */}。记住:注释不是页面结构,不会出现在最终 DOM 中。
为什么需要文档碎片
本节笔记的核心是 React.Fragment。笔记第一句已经点明:React 中“必须有唯一根节点”,如果为了满足这个要求而在外层多包一个 div,就会让最终标签结构多一层嵌套。为什么需要 Fragment?因为组件经常要一次返回多个并列结构,例如头部、内容、底部,或者两个并列的 div;但 root.render() 接收的是一个 React 元素,不能直接并列写多个顶层元素。怎么想?不要用无意义的 div 去“凑”根节点,而是用 Fragment 做逻辑包裹:它把多个子元素组织成一个整体,但不额外生成真实 DOM 包裹层。怎么做?在 root.render() 中把多个并列标签放进 React.Fragment、解构后的 Fragment,或者短语法 <>...</> 中。易错点:Fragment 不是普通 DOM 容器,不要指望它变成页面里的真实标签;它主要解决“唯一根节点”与“多余嵌套”的矛盾。
三种写法要会读也会选
笔记给出的代码要保留,先看原样:
jsx
const root = ReactDOM.createRoot(document.querySelector("#root"));
const { Fragment } = React;
root.render((
// 用法一
/*
<React.Fragment>
<div>1111</div>
<div>2222</div>
</React.Fragment>
*/
// 用法二
/*
<Fragment>
<div>1111</div>
<div>2222</div>
</Fragment>
*/
// 用法三
<>
<div>1111</div>
<div>2222</div>
</>
))为什么有三种?因为 React.Fragment 是 React 对象上的属性,所以可以直接写 <React.Fragment>;也可以用 const { Fragment } = React; 解构出来,写 <Fragment>;还可以用短语法 <>...</>。怎么想?初学时先记“它们表达的是同一件事:包裹多个并列子元素,不产生额外 DOM 层”。怎么做?如果只是包裹,短语法最简洁;如果后面需要给 Fragment 加 key,就不能用短语法,要用 <React.Fragment key={...}> 或解构后的 <Fragment key={...}>。易错点:短语法不能写属性;不要把它和浏览器原生的 DocumentFragment 混为一谈,这里讲的是 React 元素层面的 React.Fragment;注释代码切换用法时,要保证 root.render() 括号内最终只有一个顶层 React 元素。
结合 render 与组件调用理解
转写稿里反复讲 root.render() 会解析 JSX:发现首字母大写的标签,就当作组件处理,查找组件定义;类组件会实例化并调用 render 方法,函数组件会加括号执行,最后把返回值替换到组件标签调用的位置。把这段过程迁移到本节:root.render() 外面接收的是一个 React 元素,所以当你要放多个组件或多个标签时,先用 Fragment 把它们包成一个整体。转写稿中有一句“咱是不是用一下文档碎片?……把 header 组件放进去”,可以理解为:在入口渲染时,用 Fragment 包住多个组件调用,避免额外 div。怎么做?例如逻辑上可以写成 <> <Header /> <Main /> <Footer /> </> 再交给 root.render();本节笔记代码则用两个 div 演示同一机制。易错点:组件名首字母要大写,否则 JSX 会当普通 HTML 标签处理;render 方法/函数组件必须返回 React 元素;不要在一个 return 后并列写多个顶层标签而不包裹。调试时先看是否只有一个顶层节点,再看 Fragment 写法是否合法,最后检查组件名和返回值。
易错点与调试顺序
为什么单独说易错?因为 Fragment 太简单,反而容易在“要不要包”“能不能加属性”“短语法能不能带 key”上出错。怎么想?先判断问题是不是“多个顶层元素”引起的;如果是,优先用 Fragment,而不是加无意义 div。怎么做?按顺序检查:第一,root.render() 括号内是否只有一个顶层 React 元素;第二,多个并列标签是否被 <>...</> 或 <React.Fragment> 包住;第三,若需要 key,是否改用了可写属性的 Fragment 写法;第四,组件标签首字母是否大写。易错点:<>...</> 不能加 key;Fragment 不生成真实 DOM,所以不要给它写 className、style 等普通 DOM 属性;不要为了“看起来能跑”而层层套 div,那正是本节要解决的嵌套问题。按这个顺序排查,就能把“文档碎片”用在正确的位置。
课堂画面






代码示例
代码一:JSX 中的事件绑定、事件对象与传参
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>9-2 jsx中的事件</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">
/**
* 在标签属性上通过 on事件名=事件回调函数 进行绑定
* 1. 事件名首字母大写
* 2. 事件回调函数填的是函数名[函数的定义、函数的引用地址],而不是函数的调用
*
* this指向问题:undefined 说明 react中的事件回调的调用者也是window,严格模式所以是undefined
*
* 事件对象e:
* 1. 一个经过react包装后的事件对象,原生的事件对象常用属性,基本都有
* 2. 如果想获取原生的事件对象:e.nativeEvent
* 3. 事件的回调函数是window帮我们调用的,会默认的将事件对象作为第一个实参传递给我
*
* 事件回调函数参数的传递:包裹一个箭头函数,在箭头函数内部调用函数并传递参数
*/
const root = ReactDOM.createRoot(document.querySelector("#root"));
function click1() {
console.log('click1');
console.log('this: ', this); // undefined
}
function click2(e) {
console.log(e);
console.log('nativeEvent: ', e.nativeEvent);
e.preventDefault();
}
function click3(a, b) {
console.log('click3');
console.log('a: ', a);
console.log('b: ', b);
}
function outFn() {
click3(1, 2);
}
function click4(e, a, b) {
console.log('e: ', e);
console.log('a: ', a);
console.log('b: ', b);
e.preventDefault();
}
/**
* react中的 onChange事件,实际是原生的 oninput事件
*/
function change(e) {
console.log('change e: ', e);
console.log('value: ', e.target.value);
}
root.render((
<div>
<p><button onClick={click1}>click1</button></p>
<p><a href="http://baidu.com" onClick={click2}>百度</a></p>
<h3>事件回调函数传参</h3>
<p><button onClick={click3()}>click3</button></p>
<p><button onClick={outFn}>传递参数</button></p>
<p><button onClick={function () {
click3(2, 3);
}}>包裹匿名函数传递参数</button></p>
<p><button onClick={() => click3(2, 3)}>包裹箭头函数传参</button></p>
<h3>即传递参数,又同时传递事件对象</h3>
<p><button onClick={(e) => {
console.log('e: ', e);
click3(1, e);
}}>即传递参数,又同时传递事件对象</button></p>
<p><a href="http://baidu.com" onClick={(e) => click4(e, 100, 222)}>百度</a></p>
<hr/>
<input type="text" onChange={change} name="" id=""/>
</div>
))
</script>
</html>关键行与易错点
onClick={click1}传的是函数名(引用地址),不是click1();onClick={click3()}会在渲染时立刻执行并返回undefined,点击时就会报错——这正是笔记里故意保留的反例。this打印为undefined:React 的事件回调由window调用,而 React 走严格模式,所以不是window而是undefined。e是 React 包装过的事件对象(合成事件),原生属性基本都在;要拿原生对象用e.nativeEvent。- 阻止默认行为统一用
e.preventDefault(),不要在 React 里用return false。 - 既要传参又要事件对象:
onClick={(e) => click4(e, 100, 222)},把e作为第一个形参透传。 onChange在 React 里实际对应原生oninput,键盘每输入一次就触发,事件对象是InputEvent,取值为e.target.value。
代码二:JSX 中的注释与文档碎片(Fragment)
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>10-11 jsx注释 & 文档碎片</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"));
const { Fragment } = React;
root.render((
<>
{ //单行注释,最少得两行
}
{/*多行注释*/}
{/*
多行注释
123123
123123
123123
*/}
<div id='box' /* className='wrapper' */>我是div</div>
{/* 文档碎片:解决“必须有唯一根节点”导致的多一层嵌套 */}
<Fragment>
<div>1111</div>
<div>2222</div>
</Fragment>
</>
))
</script>
</html>关键行与易错点
- JSX 里
{ }内是表达式容器,所以单行注释必须写成{ //注释换行},一行写不进去。 - 多行注释用
{/* ... */},注意{和/*之间不能有空格歧义,写成{ /* */ }在某些位置会出问题。 - 标签属性的注释只能写
/* */(如id='box' /* className='wrapper' */),属性位置不能用{/* */}。 - 三种 Fragment 写法等价:
<React.Fragment>、解构后的<Fragment>、简写<>...</>;简写不能带key等属性。 - Fragment 渲染后不会产生真实 DOM 节点,只是为了满足“唯一根节点”的语法要求,不会多套一层
div。
代码三:类组件的定义、调用,以及模拟 root.render 的渲染过程
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>12 类组件 & 模拟 render 过程</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 { Component } = React;
// 1. 定义类组件:class + 首字母大写 + 继承 React.Component + 必须有 render + render 返回 React 元素
class Header extends Component {
render() {
// render 方法永远由“当前类组件的实例对象”调用,所以 this 指向实例
console.log('Header render 里的 this: ', this);
return <div>我是Header</div>;
}
}
// 2. 模拟 root.render 做的事情
function render(reactComponent, root) {
console.log('reactComponent: ', reactComponent); // React 元素(虚拟 DOM)
console.log('type: ', reactComponent.type); // 类组件时 type 就是那个类
// 第一步:实例化该组件类
const instance = new reactComponent.type();
// 第二步:用实例对象调用 render,拿到返回值(React 元素)
const vDom = instance.render();
console.log('vDom: ', vDom);
// 第三步:把虚拟 DOM 变成真实 DOM,并替换掉组件标签所在位置
const realDom = document.createElement(vDom.type);
realDom.innerHTML = vDom.props.children;
root.appendChild(realDom);
}
const oRoot = document.querySelector('#root');
render(<Header />, oRoot);
</script>
</html>关键行与易错点
- 类组件五个要点缺一不可:
class定义、首字母大写、extends React.Component、必须有render、render必须return一个 React 元素。 <Header />经 Babel 转译后是一个 React 元素对象,它的type属性就是Header这个类;而普通 HTML 标签的type是字符串标签名(如'div')。new reactComponent.type()就是“render 帮我们实例化该类”,所以render里的this永远指向当前类组件的实例对象。vDom.type拿到真实标签名,vDom.props.children拿到内容,这才是“虚拟 DOM → 真实 DOM”的最小闭环;真实项目里这一步由ReactDOM完成,不需要手写。- 换成函数组件时流程只差一步:不 new,直接
reactComponent.type()加括号调用,所以函数组件里this是undefined(window 调用 + 严格模式)。
常见错误
<button onClick={click3()}>页面一加载就打印了参数,点按钮反而没反应- 原因:花括号里写的是函数调用表达式,JSX 渲染时立即执行,把返回值(undefined)交给了 onClick。
- 修法:绑定只传引用
onClick={click3};确需传参就包一层onClick={() => click3(1, 2)}。
回调里
this打印出 undefined,想用this.state直接报错- 原因:回调由宿主环境调用,React 走严格模式,非方法调用时 this 为 undefined;且类方法默认没绑定实例。
- 修法:优先用函数组件(本就没有 this 问题);类组件中把回调写成箭头函数类字段,或在 constructor 里
bind(this)。
既想传参又想
e.preventDefault(),结果 e 是数字 1 或者 undefined- 原因:
onClick={() => click4(100, 222)}的箭头函数没接收形参,事件对象在箭头函数这一层被丢掉了。 - 修法:箭头函数显式收 e 再转发:
onClick={(e) => click4(e, 100, 222)},形参顺序与函数定义对齐。
- 原因:
onChange里期待"失焦才触发",实际每敲一个字符就触发一次,且拿不到原生 change 的时机- 原因:React 的 onChange 映射的是原生 input 事件(InputEvent),不是原生 change。
- 修法:按"输入即触发"来设计逻辑(受控输入、即时校验);若确实要原生行为,用
onBlur或e.nativeEvent判断。
在 JSX 里写
// 注释或/* */直接白屏/报错;{// 注释}单行也报错- 原因:JSX 标签区域不是 JS 语句区,
//会把后面的标签全注释掉;单行{// }的}被吞进注释里。 - 修法:子节点用
{/* ... */};单行必须换行闭合({ //\n});标签属性位置用/* */。
- 原因:JSX 标签区域不是 JS 语句区,
想用 Fragment 去掉多余 div,写成
<Fragment>却报未定义;或给<>加了 key 报错- 原因:Fragment 是 React 的具名导出,没解构就直接用;短语法
<>不支持任何属性。 - 修法:
const { Fragment } = React;或React.Fragment;需要 key 时只能用完整写法<React.Fragment key={id}>。
- 原因:Fragment 是 React 的具名导出,没解构就直接用;短语法
类组件写完不显示,控制台报 "render is not a function" / "is not defined"
- 原因:漏写 render 方法、漏
extends React.Component、组件名首字母小写被当成 HTML 标签、或者只定义未在 JSX 中调用。 - 修法:按"class 大写名 extends Component + render + return 元素"检查;调用处写
<Header />;确认组件确实被 JSX 引用。
- 原因:漏写 render 方法、漏
create-react-app报"不是内部或外部命令",或创建后npm start起不来- 原因:全局命令未进 PATH(PowerShell 权限/终端类型问题);或项目名含中文、与
react包重名;或 node_modules 下载不全(超时丢包)。 - 修法:终端切到 CMD;项目名用英文小写下划线;删掉 node_modules 后
npm i重装;实在不行走局部安装npm i create-react-app+npx create-react-app 项目名。
- 原因:全局命令未进 PATH(PowerShell 权限/终端类型问题);或项目名含中文、与
第二个项目启动失败,提示端口被占用
- 原因:3000 已被上一个 dev server 占用。
- 修法:按提示选
Y换 3001,或先关掉旧进程。
练习
事件绑定纠错:下面三行分别有什么问题,改成正确写法并说明理由。
jsx<button onClick={handle(1, 2)}>A</button> <a href="http://baidu.com" onClick={handle}>B</a> <input onChange={handle} />(要求:A 能传参;B 点击不跳转且能拿到事件对象;C 说明它实际等价于哪个原生事件。)
传参 + 事件对象:写一个列表,渲染 3 个按钮,点击任意按钮时在控制台输出"第 n 个"并阻止按钮外层
<a>的跳转。要求回调函数签名形如handle(e, index),且不能出现"渲染时就执行"的问题。类组件 / 函数组件互转:给定类组件
Header(含 render 返回一个 div),改写为函数组件;再在函数体第一行加console.log(this)并解释输出结果,说明为什么函数组件不需要 bind。模拟 render:手写一个
myRender(reactElement, container),要求支持函数组件(读type判断,调用后拿返回值,创建真实 DOM 并插入容器)。写出思路要点与关键代码片段,并说明它对类组件需要多改哪两步。工程化排错:同学 A 全局装完脚手架后命令不可用,同学 B 项目创建成功但
npm start报模块缺失,同学 C 项目名写成React-练习。分别给出最短修复路径。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 类组件的定义及调用 | 14:21 |
| 2 | 类组件render过程模拟实现 | 12:42 |
| 3 | 函数组件的定义及调用 | 06:36 |
| 4 | 函数组件调用渲染的过程 | 05:05 |
| 5 | npm-yarn安装并启动项目 | 18:56 |
| 6 | 使用npx局部安装并创建启动项目 | 03:53 |
小结
- JSX 事件的三个高频坑集中在"绑定写成了调用"、"this 丢失"、"事件对象在包装层被吞掉",排查顺序就按这三条走。
- 传参的本质是"用一层箭头函数把调用推迟到事件发生时",这一层同时也是保留 e 的唯一入口。
- React 事件对象是包装过的合成事件,属性够用且已做兼容;需要底层信息时看
nativeEvent,别急着去操作原生 DOM。 - 类组件里
render的 this 恒指向当前实例(由实例调用),但回调函数没有这层保障——这是两个不同的 this 语境,不能混为一谈。 - 函数组件没有 this,天然规避绑定问题,加上定义成本低,这是它取代类组件的直接原因之一。
- Fragment 解决的是"结构上必须唯一根节点"带来的多余嵌套,短语法最省事,但需要 key 时必须用完整写法。
- 脚手架的三条路(npm 全局 / yarn 全局 / npx 局部)不是并列推荐,而是降级备选:前一个不通才用下一个。
- 创建项目阶段的失败几乎都能归到两类:命名违规(中文、与包重名)和依赖下载不全(删 node_modules 重装是通用解法)。