react速通-1

一、JSX语法规则

1.代码

jsx 复制代码
<!-- 1. 容器 -->
<div id="test"></div>

<!-- 2. 引入依赖库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>

<!-- 3. 编写JSX代码 -->
<script type="text/babel">
    // 定义变量
    const myId = 'aTgUiGu';
    const myData = 'HeLlo,rEaCt';

    // 创建虚拟DOM(符合JSX规则)
    const VDOM = (
        <div>
            {/* 小写标签=HTML原生元素,className替代class,{}嵌入JS */}
            <h2 className="title" id={myId.toLowerCase()}>
                {/* 内联样式:双大括号,{}嵌入JS变量 */}
                <span style={{color: 'white', fontSize: '29px'}}>{myData.toLowerCase()}</span>
            </h2>
            <h2 className="title" id={myId.toUpperCase()}>
                <span style={{color: 'white', fontSize: '29px'}}>{myData.toLowerCase()}</span>
            </h2>
            <input type="text"/> {/* 单标签必须闭合 */}
            <Good>123</Good> {/* 大写标签=React组件(需提前定义) */}
        </div>
    );

    // 渲染虚拟DOM到页面
    ReactDOM.render(VDOM, document.getElementById('test'));
</script>

2.知识点

JSX 语法规则

  1. 定义虚拟 DOM 时,不要写引号。

  2. 标签中混入 JS 表达式时要用{}

  3. 样式的类名指定不要用class,要用className

  4. 内联样式,要用style={``{key:value}}的形式去写。

  5. 只有一个根标签

  6. 标签必须闭合

  7. 标签首字母

    (1). 若小写字母开头,则将该标签转为 html 中同名元素,若 html 中无该标签对应的同名元素,则报错。

    (2). 若大写字母开头,react 就去渲染对应的组件,若组件没有定义,则报错。


!CAUTION

补充说明

  • 第 2 条的{}只能嵌入JS 表达式 (变量、运算、函数调用等),不能写语句(如if/for
  • 第 4 条的双大括号:外层{}是 JSX 嵌入表达式的语法,内层{}是样式对象
  • 第 7 条是 React 区分原生 HTML 标签和自定义组件的核心规则,是组件化开发的基础

二、JSX小练习

1.代码

jsx 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title> 1_JSX渲染列表</title>
</head>
<body>
  <!-- 准备好一个"容器" -->
  <div id="test"></div>

  <!-- 引入react核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- 引入react-dom,用于支持react操作DOM -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>

  <script type="text/babel">
    // 模拟一些数据
    const data = ['Angular','React','Vue']

    // 1. 创建虚拟DOM
    const VDOM = (
      <div>
        <h1>前端js框架列表</h1>
        <ul>
          {
            data.map((item,index)=>{
              return <li key={index}>{item}</li>
            })
          }
        </ul>
      </div>
    )
    // 2. 渲染虚拟DOM到页面
    ReactDOM.render(VDOM,document.getElementById('test'))
  </script>
</body>
</html>

2.知识点

  1. 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
  2. 下面这些都是表达式: (1). a (2). a+b (3). demo(1) (4). arr.map() (5). function test () {}
  3. 下面这些都是语句(代码): (1). if(){} (2). for(){} (3). switch(){case:xxxx}

三、函数式组件

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>1_函数式组件</title>
</head>
<body>
  <!-- 准备好一个容器 -->
  <div id="test"></div>

  <!-- 引入react核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- 引入react-dom,用于支持react操作DOM -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>

  <script type="text/babel">
    // 1. 创建函数式组件
    function MyComponent() {
      console.log(this); // 此处的this是undefined,因为babel编译后开启了严格模式
      return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
    }

    // 2. 渲染组件到页面
    ReactDOM.render(<MyComponent/>, document.getElementById('test'))

    /*
      执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
        1.React解析组件标签,找到了MyComponent组件。
        2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中
    */
  </script>
</body>
</html>

!NOTE

执行了ReactDOM.render(...之后,发生了什么?

1.React解析组件标签,找到了MyComponent组件。

2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中

四、复习类相关的知识1

总结:

  1. 类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
  2. 如果 A 类继承了 B 类,且 A 类中写了构造器,那么 A 类构造器中的super是必须要调用的。
  3. 类中所定义的方法,都是放在了类的原型对象上,供实例去使用

五、类式组件

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>2_类式组件</title>
</head>
<body>
  <!-- 组件挂载容器 -->
  <div id="test"></div>

  <!-- 引入react核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- 引入react-dom,用于支持react操作DOM -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>

  <script type="text/babel">
    // 1. 创建类式组件
    class MyComponent extends React.Component {
      render() {
        // render是放在哪里的? --- MyComponent的原型对象上,供实例使用。
        // render中的this是谁? --- MyComponent的实例对象 <=> MyComponent组件实例对象。
        console.log('render中的this:', this);
        return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
      }
    }

    // 2. 渲染组件到页面
    ReactDOM.render(<MyComponent/>, document.getElementById('test'))

    /*
      执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
        1.React解析组件标签,找到了MyComponent组件。
        2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
        3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
    */
  </script>
</body>
</html>

六、初始化state

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Weather 组件示例</title>
</head>
<body>
  <!-- 组件挂载容器 -->
  <div id="test"></div>

  <!-- 引入react核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- 引入react-dom,用于支持react操作DOM -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>

  <script type="text/babel">
    // 1. 创建组件
    class Weather extends React.Component {
      constructor(props) {
        super(props)
        // 初始化状态
        this.state = { isHot: false }
      }

      render() {
        console.log(this)
        return <h1>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
      }
    }

    // 2. 渲染组件到页面
    ReactDOM.render(<Weather/>, document.getElementById('test'))
  </script>
</body>
</html>
jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>React 类组件 - 解构赋值 State</title>
</head>
<body>
    <!-- 容器 -->
    <div id="test"></div>

    <!-- 引入核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>

    <script type="text/babel">
        // 1. 创建类式组件
        class Weather extends React.Component {
            constructor(props) {
                super(props);
                // 初始化状态
                this.state = { isHot: false };
            }

            render() {
                // 🔥 图中的关键写法:从 state 中解构出 isHot
                const { isHot } = this.state;
                
                // 解构后直接使用变量,代码更简洁
                return <h1>今天天气很{isHot ? '炎热' : '凉爽'}</h1>;
            }
        }

        // 2. 渲染组件到页面
        ReactDOM.render(<Weather/>, document.getElementById('test'));
    </script>
</body>
</html>

七、setState的使用

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>React 类组件 State 详解</title>
</head>
<body>
    <!-- 容器 -->
    <div id="test"></div>

    <!-- 引入核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>

    <script type="text/babel">
        // 1. 创建类式组件
        class Weather extends React.Component {
            constructor(props) {
                console.log('constructor'); // 初始化执行
                super(props);
                
                // 初始化状态:包含 isHot 和 wind 两个属性
                this.state = { isHot: false, wind: '微风' };
                
                // 🔥 关键:绑定 this,解决 changeWeather 中的 this 指向问题
                // 因为作为回调函数调用时,this 会是 undefined
                this.changeWeather = this.changeWeather.bind(this);
            }

            // render 调用次数:1 + n 次
            // 1 次是初始化渲染,n 次是 setState 更新状态的次数
            render() {
                console.log('render'); // 每次状态更新都会执行
                
                // 🔥 解构赋值:从 state 中取出 isHot 和 wind
                // 让代码更简洁,不用反复写 this.state
                const { isHot, wind } = this.state;
                
                // 绑定点击事件 this.changeWeather
                // JSX 中必须用 { } 包裹 JS 表达式
                return (
                    <h1 onClick={this.changeWeather}>
                        今天天气很{isHot ? '炎热' : '凉爽'}, {wind}
                    </h1>
                );
            }

            // 自定义方法:切换天气状态
            changeWeather() {
                // 注释知识点:
                // changeWeather 定义在 Weather 的原型对象上,供实例使用
                // 作为 onClick 回调,直接调用时类方法默认严格模式下 this 为 undefined
                // 所以必须在 constructor 中 bind(this)

                console.log('changeWeather');
                
                // 获取原状态
                const isHot = this.state.isHot;
                
                // ⚠️ 严重注意:状态必须通过 setState 更新!
                // 1. 状态更新是合并(merge)操作,不是替换
                // 2. 直接修改 this.state 不会触发视图更新(错误写法)
                this.setState({ isHot: !isHot });

                // ❌ 错误示范:直接修改 state (不会重新渲染)
                // this.state.isHot = !isHot;
            }
        }

        // 2. 渲染组件到页面
        ReactDOM.render(<Weather/>, document.getElementById('test'));
    </script>
</body>
</html>

💡 核心知识点总结(对应图片内容)

1. 构造器与 State 初始化
  • 多状态管理this.state = { isHot: false, wind: '微风' } 可以同时初始化多个状态属性。
  • super(props) :必须调用,继承父类 React.Component,并让组件能通过 this.props 接收参数。
2. this 指向问题(核心难点)
  • 现象 :在 changeWeather 中直接使用 this 会是 undefined
  • 原因onClick={this.changeWeather} 是作为回调函数调用,不是通过实例调用,类方法默认开启严格模式。
  • 解决方案this.changeWeather = this.changeWeather.bind(this) 将方法的 this 强制绑定为组件实例。
3. 渲染次数详解
  • constructor :只执行 1 次(组件初始化时)。

  • render :执行 1 + n 次

    • 1 次:初始化渲染。
    • n 次:每次调用 this.setState 修改状态,render 就会重新执行一次。
4. 状态更新的铁律
  • 必须用 setState :不能直接写 this.state.isHot = true
  • 合并机制setState({ isHot: !isHot }) 只会修改 isHot不会丢失 wind 属性(React 会自动合并)。

八、setState的简化使用

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>React 类组件 State 详解</title>
</head>
<body>
    <!-- 容器 -->
    <div id="test"></div>

    <!-- 引入核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>

    <script type="text/babel">
       class Weather extends React.Component{
            //初始化状态
            state={isHot:false,wind:'微风'}
            reder(){
                const{isHot,wind}=this.state
                return <h1 onClick={this.chaangeWeather}>今天天气很{isHot?'炎热':'凉爽'} ,{wind}</h1>
            }
            //自定义方法--要用赋值语句的形式-箭头函数
            changeWeatherr=()=>{
                const isHot=this.state.isHot
                this.setState({isHot:!isHot})
            }
        }
        ReactDOM.render(<Weather/>,document.getElementById('test'))
    </script>
</body>
</html>

九、props的基本使用

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>React 类组件 - Props 基本使用</title>
    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js -->
    <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
    <!-- 容器1 -->
    <div id="test1"></div>
    <!-- 容器2 -->
    <div id="test2"></div>
    <!-- 容器3 -->
    <div id="test3"></div>

    <script type="text/babel">
        // 创建组件
        class Person extends React.Component {
            render() {
                console.log(this);
                // 🔥 解构赋值:从 props 中取出 name、age、sex
                const { name, age, sex } = this.props;
                
                return (
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age}</li>
                    </ul>
                );
            }
        }

        // 渲染组件到页面
        // 第一个组件
        ReactDOM.render(
            <Person name="jerry" age="19" sex="男"/>, 
            document.getElementById('test1')
        );
        
        // 第二个组件
        ReactDOM.render(
            <Person name="tom" age="18" sex="女"/>, 
            document.getElementById('test2')
        );
        
        // 第三个组件
        ReactDOM.render(
            <Person name="老刘" age="30" sex="女"/>, 
            document.getElementById('test3')
        );
    </script>
</body>
</html>

💡 核心知识点详解(对应图片内容)

1. Props 的基本用法
  • 传递数据 :在组件标签上以属性 的形式传递数据,例如 name="jerry"
  • 接收数据 :在组件类中通过 this.props 接收,this.props 是一个只读对象。
  • 解构赋值 :图片中使用了 const {name, age, sex} = this.props,这是开发中最常用的写法,可以简化代码,避免反复书写 this.props.xxx
2. 多次渲染 API (ReactDOM.render)
  • 图片中连续调用了三次 ReactDOM.render,分别挂载到了 test1test2test3 三个不同的 DOM 容器上。
  • 这说明 React 可以同时向页面的多个不同区域渲染组件,同一个 Person 组件类可以生成多个不同数据的实例。

十、批量传递props

1.对一个数组里面传过来的所有数据进行求和

jsx 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Document</title>
</head>
<body>
  <script type="text/javascript" >
    // 数组展开运算符基础用法
    let arr1 = [1,3,5,7,9]
    let arr2 = [2,4,6,8,10]
    console.log(...arr1); //展开一个数组
    let arr3 = [...arr1,...arr2] //连接数组

    // 剩余参数 + reduce 求和函数
    function sum(...numbers){
      return numbers.reduce((preValue,currentValue)=>{
        return preValue + currentValue
      })
    }

    console.log(sum(1,2,3,4));
  </script>
</body>
</html>

!WARNING

展开运算符不能展开对象

jsx 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Document</title>
</head>
<body>
  <script type="text/javascript" >
    // 数组展开运算符基础用法
    let arr1 = [1,3,5,7,9]
    let arr2 = [2,4,6,8,10]
    console.log(...arr1); //展开一个数组
    let arr3 = [...arr1,...arr2] //连接数组

    //在函数中使用(剩余参数)
    function sum(...numbers){
      return numbers.reduce((preValue,currentValue)=>{
        return preValue + currentValue
      })
    }
    console.log(sum(1,2,3,4));

    //构造字面量对象时使用展开语法
    let person = {name:'tom',age:18}
    let person2 = {...person}
    //console.log(...person); //报错,展开运算符不能展开对象
    person.name = 'jerry'
    console.log(person2);
    console.log(person);

    //合并对象
    let person3 = {...person,name:'jack',address:"地球"}
    console.log(person3);
  </script>
</body>
</html>

代码核心知识点拆解

1. 数组展开运算符

  • console.log(...arr1):将数组 [1,3,5,7,9] 展开为独立参数 1 3 5 7 9 打印
  • let arr3 = [...arr1,...arr2]:合并两个数组,等价于 arr1.concat(arr2),是 ES6 推荐写法

2. 函数剩余参数(...numbers

  • 函数 sum(...numbers) 会把所有传入的参数(1,2,3,4)收集为一个数组 [1,2,3,4]
  • 配合 Array.prototype.reduce 实现累加求和,最终返回 1+2+3+4 = 10

3. 对象展开运算符

  • let person2 = {...person}浅拷贝 对象,修改原对象 person 不会影响拷贝后的 person2
  • 注意:console.log(...person) 会报错,因为展开运算符不能直接展开普通对象(仅支持可迭代对象,数组 / 类数组)
  • let person3 = {...person,name:'jack',address:"地球"}:合并对象,后写的属性会覆盖前面的同名属性(这里 name 被覆盖为 jack,新增 address 属性)

补充说明

  • 对象展开是 ES2018(ES9)新增特性,仅支持对象字面量中使用
  • 展开运算符做的是浅拷贝,如果对象有嵌套属性,嵌套层仍会共享引用
  • 合并对象时,属性顺序很重要:后面的属性会覆盖前面的同名属性

2.批量传递props

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React Props 展开运算符</title>
  <!-- 引入react核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- 引入react-dom,用于支持react操作DOM -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test1"></div>
  <div id="test2"></div>
  <div id="test3"></div>

  <script type="text/babel">
    class Person extends React.Component {
      render() {
        console.log(this);
        const { name, age, sex } = this.props;
        return (
          <ul>
            <li>姓名:{name}</li>
            <li>性别:{sex}</li>
            <li>年龄:{age}</li>
          </ul>
        );
      }
    }
    
    // 用对象 + 展开运算符传递 props
    const p = { name: '老刘', age: 18, sex: '女' };
    console.log('@', ...p);
    // 等价写法:<Person name={p.name} age={p.age} sex={p.sex}/>
    ReactDOM.render(
      <Person {...p}/>,
      document.getElementById('test3')
    );
  </script>
</body>
</html>

核心知识点拆解

1. 展开运算符传递 Props(核心亮点)

  • 传统写法:需要逐个手动传递属性

    jsx 复制代码
    <Person name={p.name} age={p.age} sex={p.sex}/>
  • 简化写法:用 {...p} 一次性把对象 p 的所有属性作为 props 传递给组件

    jsx 复制代码
    <Person {...p}/>
  • 效果完全一致,代码更简洁,适合 props 较多的场景

2. 关键细节

  • console.log('@', ...p) 会报错(对象不能直接展开打印),仅数组 / 可迭代对象支持直接展开
  • {...p}对象展开语法,仅在 JSX 组件标签中生效,用于批量传递 props
  • 展开后 props 是只读的,组件内不能修改 this.props,只能通过父组件重新传递更新

补充说明

  • 这是 React 开发中批量传递 props 的标准写法,在封装通用组件、列表渲染时非常常用

  • 可以在展开后追加 / 覆盖属性,例如:

    jsx 复制代码
    <Person {...p} name="新名字" age={20}/>

    后写的属性会覆盖展开对象中的同名属性

十一、对props进行限制

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>React 组件属性校验与默认值</title>
    <!-- 引入核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    
    <!-- 🔥 必须引入 PropTypes 库 (React 15.5+ 后独立出来) -->
    <script src="https://cdn.jsdelivr.net/npm/prop-types@15.8.1/prop-types.min.js"></script>
</head>
<body>
    <!-- 容器 -->
    <div id="test1"></div>
    <div id="test2"></div>
    <div id="test3"></div>

    <script type="text/babel">
        // 1. 创建类式组件
        class Person extends React.Component {
            render() {
                console.log(this);
                const { name, age, sex, speak } = this.props;
                
                return (
                    <ul>
                        {/* 展示数据 */}
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age + 1}</li> {/* 示例:展示时可以做简单计算 */}
                    </ul>
                );
            }
        }

        // 2. 🔥 对标签属性进行类型、必要性的限制 (PropTypes)
        // 这是开发环境下的校验,生产环境通常会关闭或移除
       Person.propTypes = {
            //name:必须是字符串,且必填(isRequired)
            name:PropTypes.string.isRequired
            
            //sex:限制为字符串
            sex:PropTypes.string
            
            //age:限制为数值
            age:PropTypes.number
            
            //speak:限制为函数
            speak:PropTypes.func
        }

        // 3. 🔥 指定默认标签属性值 (defaultProps)
        // 如果父组件没有传递该属性,就使用这里的默认值
       Person.defaultProps = {
            sex:'男'  //默认性别为男性
            age:18    //默认年龄为18岁
        }

        // 4. 渲染组件到页面
        // test1: 只传递必填项 name,sex 和 age 会走默认值,speak 是函数
        ReactDOM.render(
            <Person name="jerry" speak={() => {console.log('说话')}} />,
            document.getElementById('test1')
        );
        
        // test2: 覆盖默认值
        ReactDOM.render(
            <Person name="tom" age={18} sex="女"/>,
            document.getElementById('test2')
        );

        // 5. 展开运算符传递 props
        const p = { name: '老刘', age: 18, sex: '女' };
        // 等价于 <Person name="老刘" age={18} sex="女"/>
        ReactDOM.render(
            <Person {...p}/>,
            document.getElementById('test3')
        );
    </script>
</body>
</html>

十二、props的简写方式

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>React 类内 static 属性校验</title>
    <!-- 引入核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入 PropTypes 库 -->
    <script src="https://cdn.jsdelivr.net/npm/prop-types@15.8.1/prop-types.min.js"></script>
</head>
<body>
    <div id="test1"></div>
    <div id="test2"></div>
    <div id="test3"></div>

    <script type="text/babel">
        // 创建组件
        class Person extends React.Component {
            // 🔥 类内 static 写法:对标签属性进行类型、必要性的限制
            static propTypes = {
                name: PropTypes.string.isRequired, // 限制name必传,且为字符串
                sex: PropTypes.string,              // 限制sex为字符串
                age: PropTypes.number,              // 限制age为数值
                speak: PropTypes.func               // 限制speak为函数
            }

            // 🔥 类内 static 写法:指定默认标签属性值
            static defaultProps = {
                sex: '男',  // sex默认值为男
                age: 18     // age默认值为18
            }

            render() {
                const { name, age, sex, speak } = this.props;
                return (
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age + 1}</li>
                    </ul>
                );
            }
        }

        // 定义 speak 函数(用于传递给组件)
        const speak = () => {
            console.log('我会说话');
        };

        // 渲染组件到页面
        ReactDOM.render(
            <Person name="jerry" speak={speak}/>,
            document.getElementById('test1')
        );
        ReactDOM.render(
            <Person name="tom" age={18} sex="女"/>,
            document.getElementById('test2')
        );

        const p = { name: '老刘', age: 18, sex: '女' };
        // console.log('@', ...p);
        // ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>, document.getElementById('test3'))
        ReactDOM.render(
            <Person {...p}/>,
            document.getElementById('test3')
        );
    </script>
</body>
</html>

十三、函数式组件使用props

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React 函数式组件 Props 校验</title>
  <!-- 引入核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入 PropTypes 校验库 -->
  <script src="https://cdn.jsdelivr.net/npm/prop-types@15.8.1/prop-types.min.js"></script>
</head>
<body>
  <div id="test1"></div>

  <script type="text/babel">
    // 1. 定义函数式组件
    function Person(props) {
      // 解构 props 中的属性
      const { name, age, sex } = props;
      return (
        <ul>
          <li>姓名:{name}</li>
          <li>性别:{sex}</li>
          <li>年龄:{age}</li>
        </ul>
      );
    }

    // 2. 对 props 进行类型、必要性限制
    Person.propTypes = {
      name: PropTypes.string.isRequired, // 限制 name 必传,且为字符串
      sex: PropTypes.string,              // 限制 sex 为字符串
      age: PropTypes.number               // 限制 age 为数值
    };

    // 3. 指定默认 props 值
    Person.defaultProps = {
      sex: '男',  // sex 默认值为男
      age: 18     // age 默认值为 18
    };

    // 4. 渲染组件到页面
    ReactDOM.render(
      <Person name={100}/>,
      document.getElementById('test1')
    );
  </script>
</body>
</html>

十四、字符串形式的ref

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React 字符串形式 refs</title>
  <!-- 引入核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test"></div>

  <script type="text/babel">
    // 创建组件
    class Demo extends React.Component {
      // 展示左侧输入框的数据
      showData = () => {
        const { input1 } = this.refs
        alert(input1.value)
      }

      // 展示右侧输入框的数据
      showData2 = () => {
        const { input2 } = this.refs
        alert(input2.value)
      }

      render() {
        return (
          <div>
            <input ref="input1" type="text" placeholder="点击按钮提示数据"/>&nbsp;
            <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
            <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
          </div>
        )
      }
    }

    // 渲染组件到页面
    ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test'))
  </script>
</body>
</html>

十五、回调形式的ref

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React 回调形式 refs</title>
  <!-- 引入核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test"></div>

  <script type="text/babel">
    // 创建组件
    class Demo extends React.Component {
      // 展示左侧输入框的数据
      showData = () => {
        const { input1 } = this
        alert(input1.value)
      }

      // 展示右侧输入框的数据
      showData2 = () => {
        const { input2 } = this
        alert(input2.value)
      }

      render() {
        return (
          <div>
            <input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/>&nbsp;
            <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
            <input onBlur={this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点提示数据"/>&nbsp;
          </div>
        )
      }
    }

    // 渲染组件到页面
    ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test'))
  </script>
</body>
</html>

!IMPORTANT

调用次数:初始1次,更新2次

十六、createRef的使用

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React createRef 形式 refs</title>
  <!-- 引入核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test"></div>

  <script type="text/babel">
    // 创建组件
    class Demo extends React.Component {
      /*
        React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是"专人专用"的
      */
      myRef = React.createRef()
      myRef2 = React.createRef()

      // 展示左侧输入框的数据
      showData = () => {
        alert(this.myRef.current.value);
      }

      // 展示右侧输入框的数据
      showData2 = () => {
        alert(this.myRef2.current.value);
      }

      render() {
        return (
          <div>
            <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
            <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
            <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
          </div>
        )
      }
    }

    // 渲染组件到页面
    ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test'))
  </script>
</body>
</html>

十七、react中的事件处理

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React 事件处理</title>
  <!-- 引入核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test"></div>

  <script type="text/babel">
    // 创建组件
    class Demo extends React.Component {
      /*
        (1).通过onXxx属性指定事件处理函数(注意大小写)
            a.React使用的是自定义(合成)事件,而不是使用的原生DOM事件 ------ 为了更好的兼容性
            b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ------为了的高效
        (2).通过event.target得到发生事件的DOM元素对象 ------------------不要过度使用ref
      */

      // 创建ref容器
      myRef = React.createRef()
      myRef2 = React.createRef()

      // 展示左侧输入框的数据
      showData = (event) => {
        console.log(event.target);
        alert(this.myRef.current.value);
      }

      // 展示右侧输入框的数据
      showData2 = (event) => {
        alert(event.target.value);
      }

      render() {
        return (
          <div>
            <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
            <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
            <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
          </div>
        )
      }
    }

    // 渲染组件到页面
    ReactDOM.render(<Demo />, document.getElementById('test'))
  </script>
</body>
</html>

十八、非受控组件

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React 表单登录组件</title>
  <!-- 引入react核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- 引入react-dom,用于支持react操作DOM -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test"></div>

  <script type="text/babel">
    // 创建组件
    class Login extends React.Component {
      handleSubmit = (event) => {
        // 阻止表单默认提交行为(避免页面刷新)
        event.preventDefault()
        const { username, password } = this
        alert(`你输入的用户名是: ${username.value},你输入的密码是: ${password.value}`)
      }

      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            用户名: <input ref={c => this.username = c} type="text" name="username"/>
            密码: <input ref={c => this.password = c} type="password" name="password"/>
            <button>登录</button>
          </form>
        )
      }
    }

    // 渲染组件
    ReactDOM.render(<Login/>, document.getElementById('test'))
  </script>
</body>
</html>

十九、受控组件

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React 受控组件登录</title>
  <!-- 引入核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test"></div>

  <script type="text/babel">
    // 创建组件
    class Login extends React.Component {
      // 初始化状态
      state = {
        username: '', // 用户名
        password: ''  // 密码
      }

      // 保存用户名到状态中
      saveUsername = (event) => {
        this.setState({ username: event.target.value })
      }

      // 保存密码到状态中
      savePassword = (event) => {
        this.setState({ password: event.target.value })
      }

      // 表单提交的回调
      handleSubmit = (event) => {
        event.preventDefault() // 阻止表单提交
        const { username, password } = this.state
        alert(`你输入的用户名是: ${username},你输入的密码是: ${password}`)
      }

      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            用户名: <input onChange={this.saveUsername} type="text" name="username"/>
            密码: <input onChange={this.savePassword} type="password" name="password"/>
            <button>登录</button>
          </form>
        )
      }
    }

    // 渲染组件
    ReactDOM.render(<Login/>, document.getElementById('test'))
  </script>
</body>
</html>

核心知识点拆解

1. 什么是受控组件?

  • 定义 :表单元素的值由 React 的 state 完全控制,输入框的 value 绑定 state,通过 onChange 事件同步更新 state
  • 对比非受控组件 :非受控组件用 ref 直接操作 DOM 获取值;受控组件用 state 管理,更符合 React 数据驱动的思想,方便做校验、联动等复杂逻辑。

2. 代码执行流程

  1. 初始化stateusernamepassword 初始为空字符串。
  2. 输入同步 :用户在输入框输入内容时,触发 onChange 事件,执行 saveUsername/savePassword,将输入值更新到 state
  3. 提交处理 :点击登录按钮,触发表单 onSubmit 事件,先调用 event.preventDefault() 阻止页面刷新,再从 state 中取出用户名和密码,弹窗展示。

二十、高阶函数和函数的柯里化

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React 高阶函数 柯里化 登录表单</title>
  <!-- 引入react核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- 引入react-dom,用于支持react操作DOM -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test"></div>

  <script type="text/babel">
    //#region
    /*
      高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
          1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
          2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
          常见的高阶函数有:Promise、setTimeout、arr.map()等等

      函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
    */
    //#endregion

    // 创建组件
    class Login extends React.Component {
      // 初始化状态
      state = {
        username: '', // 用户名
        password: ''  // 密码
      }

      // 保存表单数据到状态中(柯里化写法)
      saveFormData = (dataType) => {
        return (event) => {
          this.setState({ [dataType]: event.target.value })
        }
      }

      // 表单提交的回调
      handleSubmit = (event) => {
        event.preventDefault() // 阻止表单提交
        const { username, password } = this.state
        alert(`你输入的用户名是: ${username},你输入的密码是: ${password}`)
      }

      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            用户名: <input onChange={this.saveFormData('username')} type="text" name="username"/>
            密码: <input onChange={this.saveFormData('password')} type="password" name="password"/>
            <button>登录</button>
          </form>
        )
      }
    }

    // 渲染组件
    ReactDOM.render(<Login/>, document.getElementById('test'))
  </script>
</body>
</html>

二十一、不用柯里化的写法

jsx 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React 表单 柯里化 vs 非柯里化</title>
  <!-- 引入核心库 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test"></div>

  <script type="text/babel">
    // 创建组件
    class Login extends React.Component {
      // 初始化状态
      state = {
        username: '', // 用户名
        password: ''  // 密码
      }

      // ================== 写法1:非柯里化(箭头函数传参) ==================
      // 保存表单数据到状态中(直接接收两个参数)
      saveFormData = (dataType, event) => {
        this.setState({ [dataType]: event.target.value })
      }

      // ================== 写法2:柯里化(函数返回函数) ==================
      // saveFormData = (dataType) => {
      //   return (event) => {
      //     this.setState({ [dataType]: event.target.value })
      //   }
      // }

      // 表单提交的回调
      handleSubmit = (event) => {
        event.preventDefault() // 阻止表单提交
        const { username, password } = this.state
        alert(`你输入的用户名是: ${username},你输入的密码是: ${password}`)
      }

      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            {/* 写法1:非柯里化,用箭头函数在onChange中传参 */}
            用户名: <input onChange={event => this.saveFormData('username', event)} type="text" name="username"/>
            密码: <input onChange={event => this.saveFormData('password', event)} type="password" name="password"/>

            {/* 写法2:柯里化,直接传字段名,无需额外箭头函数 */}
            {/* 用户名: <input onChange={this.saveFormData('username')} type="text" name="username"/>
            密码: <input onChange={this.saveFormData('password')} type="password" name="password"/> */}

            <button>登录</button>
          </form>
        )
      }
    }

    // 渲染组件
    ReactDOM.render(<Login/>, document.getElementById('test'))
  </script>
</body>
</html>

二十二、生命周期钩子

React 组件生命周期(旧版)

1. 初始化阶段(由 ReactDOM.render () 触发 --- 初次渲染)

  1. constructor()

  2. componentWillMount()

  3. render()

  4. componentDidMount() 常用

    • 一般在这里做初始化操作:开启定时器、发送网络请求、订阅消息

2. 更新阶段(由组件内部 this.setState() 或父组件 render 触发)

  1. shouldComponentUpdate()
  2. componentWillUpdate()
  3. render() 必须使用
  4. componentDidUpdate()

3. 卸载组件(由 ReactDOM.unmountComponentAtNode() 触发)

  1. componentWillUnmount() 常用
    • 一般在这里做收尾工作:关闭定时器、取消订阅消息

!IMPORTANT

三、新旧生命周期核心区别(必背)

1. 被废弃的 3 个钩子(旧版有,新版不能用)

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

2. 新增的 2 个钩子(新版才有)

  • static getDerivedStateFromProps(props, state)
  • getSnapshotBeforeUpdate(prevProps, prevState)

二十三、static getDerivedStateFromProps(props, state)

核心特点(必须记住)

  1. 是静态方法 → 前面必须加 static

  2. 不能用 this → 里面不能写 this.setStatethis.xxx

  3. 必须有返回值

    • 返回一个对象 → 表示要更新 state
    • 返回 null → 表示不更新
  4. 在每次渲染前都会执行

    • 挂载时执行
    • 更新时执行(state 变、props 变都会触发)

二十四、getSnapshotBeforeUpdate(prevProps, prevState)

核心特点(必背)

  1. 执行时机 :在 render() 之后、DOM 真正更新到页面之前执行

    • 此时你能拿到更新前的 props/state/DOM,但 DOM 还没被新数据覆盖
  2. 参数prevProps(更新前的 props)、prevState(更新前的 state)

  3. 必须有返回值

    • 返回任意类型的值(对象、数字、DOM 信息等),这个值会作为第 3 个参数 snapshot 传给 componentDidUpdate(prevProps, prevState, snapshot)
    • 如果不需要快照,返回 null 即可
  4. 不能调用 setState:它是纯函数,只能计算快照,不能触发新的更新

  5. 必须配合 componentDidUpdate 使用 :快照的价值要在 componentDidUpdate 中体现

二十五、新版生命周期钩子

1. 初始化阶段(由 ReactDOM.render() 触发 ------ 初次渲染)

  1. constructor()

  2. static getDerivedStateFromProps(props, state)

  3. render()

  4. componentDidMount() (常用)

    • 一般在这个钩子中做初始化操作,例如:开启定时器、发送网络请求、订阅消息

2. 更新阶段(由组件内部 this.setState() 或父组件重新 render 触发)

  1. static getDerivedStateFromProps(props, state)
  2. shouldComponentUpdate(nextProps, nextState)
  3. render()
  4. getSnapshotBeforeUpdate(prevProps, prevState)
  5. componentDidUpdate(prevProps, prevState, snapshot)

3. 卸载组件(由 ReactDOM.unmountComponentAtNode() 触发)

  1. componentWillUnmount() (常用)

    • 一般在这个钩子中做收尾工作,例如:关闭定时器、取消订阅消息

补充说明

  • getDerivedStateFromProps :静态方法,用于根据最新 props 同步 state,替代了旧版的 componentWillMount / componentWillReceiveProps
  • getSnapshotBeforeUpdate :在 DOM 更新前捕获快照,传递给 componentDidUpdate,替代了旧版的 componentWillUpdate
  • render:唯一必须实现的钩子,用于返回 JSX,生成虚拟 DOM
  • componentDidMount / componentWillUnmount:新旧版本通用,分别负责组件挂载后的初始化和卸载前的清理

二十六、diff算法

1. 虚拟 DOM 中 key 的作用

1.1 简单定义

key 是虚拟 DOM 对象的唯一标识 ,在 React 更新界面时起到核心定位作用。

1.2 详细 Diff 比较规则

当数据变化触发更新时,React 生成新虚拟 DOM ,与旧虚拟 DOM 进行 Diff 对比,规则如下:

  • a. 找到相同 key

    • (1) 若虚拟 DOM 内容未变直接复用之前的真实 DOM(不重新渲染)。
    • (2) 若虚拟 DOM 内容变了 → 生成新的真实 DOM,替换掉旧的。
  • b. 未找到相同 key

    • 根据新数据直接创建新的真实 DOM,渲染到页面。

2. 为什么遍历列表时,key 最好不要用 index

2.1 问题一:性能浪费(逆序操作)

如果进行逆序添加、逆序删除等破坏顺序的操作:

  • 现象 :React 会误以为所有元素的内容都变了,导致产生大量不必要的真实 DOM 更新
  • 结果 :界面显示正常,但执行效率极低
2.2 问题二:界面 Bug(含输入类 DOM)

如果列表结构中包含 <input> 等输入类 DOM:

  • 现象 :利用 index 作为 key,移动 / 增删数据时,输入框的状态会错位(张冠李戴)。
  • 结果 :产生错误的 DOM 更新,导致界面逻辑完全错误。
2.3 特殊情况(例外)

如果不存在逆序操作,且数据仅用于纯展示(没有增删改):

  • 使用 index 作为 key 是完全没问题的。

3. 开发中如何选择 key?(最佳实践)

3.1 首选:唯一标识

必须使用每条数据的唯一身份标识,例如:

  • id(数据库主键)
  • 手机号、身份证号、学号等唯一不重复的值。
3.2 次选:简单展示

如果确定只是静态展示数据(不会进行任何增删改排序操作):

  • 可以使用 index 作为 key。
相关推荐
M ? A2 小时前
Vue slot 插槽转 React:VuReact 怎么处理?
前端·javascript·vue.js·经验分享·react.js·面试·vureact
ZC跨境爬虫2 小时前
3D 地球卫星轨道可视化平台开发 Day10(交互升级与接口溯源)
前端·javascript·3d·自动化·交互
M ? A3 小时前
Vue Transition 组件转 React:VuReact 怎么处理?
前端·javascript·vue.js·经验分享·react.js·面试·vureact
huangql5203 小时前
CSS布局 (三):浮动——从文字环绕到多列布局
前端·javascript·css
倾颜3 小时前
React 19 源码怎么读:JSX 编译后本质上产出的是什么对象?
react.js
遇见你...8 小时前
TypeScript
前端·javascript·typescript
Highcharts.js8 小时前
Highcharts Grid 中文站正式上线:表格数据处理的全新选择
前端·javascript·数据库·表格数据·highcharts·可视化图表·企业级图表
阿正的梦工坊11 小时前
JavaScript 微任务与宏任务完全指南
开发语言·javascript·ecmascript
2301_7990730215 小时前
基于 Next.js + 火山引擎 AI 的电商素材智能生成工具实战——字节跳动前端训练营成果
javascript·人工智能·火山引擎