Skip to content

jsx中的事件与jsx中的注释(Day2 4/4) ​

本页对应 React + TypeScript + 医通项目,共 6 段视频,约 1 小时 1 分。

学习目标 ​

  1. 能够定义一个首字母大写、继承 React.Component 且实现 render 方法并返回 React 元素的类组件,并使用单标签或对标签完成调用渲染。
  2. 能够模拟 ReactDOM.render 处理类组件的过程:识别首字母大写标签、实例化类组件、调用 render,并用返回值替换组件标签位置。
  3. 能够定义一个首字母大写、返回 React 元素的函数组件,并说明其调用时由渲染过程直接执行函数、替换组件标签位置。
  4. 能够模拟函数组件的渲染过程:读取组件调用得到的 React 元素,通过 type、props.children 创建真实 DOM 并挂载到根节点。
  5. 能够在 JSX 中正确绑定事件、编写 JSX 注释,并使用文档碎片组织多个组件调用。
  6. 能够使用 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,那正是本节要解决的嵌套问题。按这个顺序排查,就能把“文档碎片”用在正确的位置。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

课堂画面 6

代码示例 ​

代码一: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 调用 + 严格模式)。

常见错误 ​

  1. <button onClick={click3()}> 页面一加载就打印了参数,点按钮反而没反应

    • 原因:花括号里写的是函数调用表达式,JSX 渲染时立即执行,把返回值(undefined)交给了 onClick。
    • 修法:绑定只传引用 onClick={click3};确需传参就包一层 onClick={() => click3(1, 2)}。
  2. 回调里 this 打印出 undefined,想用 this.state 直接报错

    • 原因:回调由宿主环境调用,React 走严格模式,非方法调用时 this 为 undefined;且类方法默认没绑定实例。
    • 修法:优先用函数组件(本就没有 this 问题);类组件中把回调写成箭头函数类字段,或在 constructor 里 bind(this)。
  3. 既想传参又想 e.preventDefault(),结果 e 是数字 1 或者 undefined

    • 原因:onClick={() => click4(100, 222)} 的箭头函数没接收形参,事件对象在箭头函数这一层被丢掉了。
    • 修法:箭头函数显式收 e 再转发:onClick={(e) => click4(e, 100, 222)},形参顺序与函数定义对齐。
  4. onChange 里期待"失焦才触发",实际每敲一个字符就触发一次,且拿不到原生 change 的时机

    • 原因:React 的 onChange 映射的是原生 input 事件(InputEvent),不是原生 change。
    • 修法:按"输入即触发"来设计逻辑(受控输入、即时校验);若确实要原生行为,用 onBlur 或 e.nativeEvent 判断。
  5. 在 JSX 里写 // 注释 或 /* */ 直接白屏/报错;{// 注释} 单行也报错

    • 原因:JSX 标签区域不是 JS 语句区,// 会把后面的标签全注释掉;单行 {// } 的 } 被吞进注释里。
    • 修法:子节点用 {/* ... */};单行必须换行闭合({ //\n});标签属性位置用 /* */。
  6. 想用 Fragment 去掉多余 div,写成 <Fragment> 却报未定义;或给 <> 加了 key 报错

    • 原因:Fragment 是 React 的具名导出,没解构就直接用;短语法 <> 不支持任何属性。
    • 修法:const { Fragment } = React; 或 React.Fragment;需要 key 时只能用完整写法 <React.Fragment key={id}>。
  7. 类组件写完不显示,控制台报 "render is not a function" / "is not defined"

    • 原因:漏写 render 方法、漏 extends React.Component、组件名首字母小写被当成 HTML 标签、或者只定义未在 JSX 中调用。
    • 修法:按"class 大写名 extends Component + render + return 元素"检查;调用处写 <Header />;确认组件确实被 JSX 引用。
  8. create-react-app 报"不是内部或外部命令",或创建后 npm start 起不来

    • 原因:全局命令未进 PATH(PowerShell 权限/终端类型问题);或项目名含中文、与 react 包重名;或 node_modules 下载不全(超时丢包)。
    • 修法:终端切到 CMD;项目名用英文小写下划线;删掉 node_modules 后 npm i 重装;实在不行走局部安装 npm i create-react-app + npx create-react-app 项目名。
  9. 第二个项目启动失败,提示端口被占用

    • 原因:3000 已被上一个 dev server 占用。
    • 修法:按提示选 Y 换 3001,或先关掉旧进程。

练习 ​

  1. 事件绑定纠错:下面三行分别有什么问题,改成正确写法并说明理由。

    jsx
    &lt;button onClick=&#123;handle(1, 2)&#125;&gt;A&lt;/button&gt;
    <a href="http://baidu.com" onClick=&#123;handle&#125;>B&lt;/a&gt;
    &lt;input onChange=&#123;handle&#125; /&gt;

    (要求:A 能传参;B 点击不跳转且能拿到事件对象;C 说明它实际等价于哪个原生事件。)

  2. 传参 + 事件对象:写一个列表,渲染 3 个按钮,点击任意按钮时在控制台输出"第 n 个"并阻止按钮外层 <a> 的跳转。要求回调函数签名形如 handle(e, index),且不能出现"渲染时就执行"的问题。

  3. 类组件 / 函数组件互转:给定类组件 Header(含 render 返回一个 div),改写为函数组件;再在函数体第一行加 console.log(this) 并解释输出结果,说明为什么函数组件不需要 bind。

  4. 模拟 render:手写一个 myRender(reactElement, container),要求支持函数组件(读 type 判断,调用后拿返回值,创建真实 DOM 并插入容器)。写出思路要点与关键代码片段,并说明它对类组件需要多改哪两步。

  5. 工程化排错:同学 A 全局装完脚手架后命令不可用,同学 B 项目创建成功但 npm start 报模块缺失,同学 C 项目名写成 React-练习。分别给出最短修复路径。

原视频对照 ​

#视频时长
1类组件的定义及调用14:21
2类组件render过程模拟实现12:42
3函数组件的定义及调用06:36
4函数组件调用渲染的过程05:05
5npm-yarn安装并启动项目18:56
6使用npx局部安装并创建启动项目03:53

小结 ​

  1. JSX 事件的三个高频坑集中在"绑定写成了调用"、"this 丢失"、"事件对象在包装层被吞掉",排查顺序就按这三条走。
  2. 传参的本质是"用一层箭头函数把调用推迟到事件发生时",这一层同时也是保留 e 的唯一入口。
  3. React 事件对象是包装过的合成事件,属性够用且已做兼容;需要底层信息时看 nativeEvent,别急着去操作原生 DOM。
  4. 类组件里 render 的 this 恒指向当前实例(由实例调用),但回调函数没有这层保障——这是两个不同的 this 语境,不能混为一谈。
  5. 函数组件没有 this,天然规避绑定问题,加上定义成本低,这是它取代类组件的直接原因之一。
  6. Fragment 解决的是"结构上必须唯一根节点"带来的多余嵌套,短语法最省事,但需要 key 时必须用完整写法。
  7. 脚手架的三条路(npm 全局 / yarn 全局 / npx 局部)不是并列推荐,而是降级备选:前一个不通才用下一个。
  8. 创建项目阶段的失败几乎都能归到两类:命名违规(中文、与包重名)和依赖下载不全(删 node_modules 重装是通用解法)。