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。
相关推荐
gogoing1 分钟前
ESLint 配置字段说明
前端·javascript
Lkstar12 分钟前
面试官让我手写 Promise.all / Promise.race / Promise.allSettled,我直接水灵灵地写出来了
javascript·面试
gogoing13 分钟前
webpack 的性能优化
前端·javascript
gogoing16 分钟前
Node.js 模块查找策略(require 完整流程)
javascript·node.js
gogoing20 分钟前
await fetch() 的两阶段设计
前端·javascript
gogoing26 分钟前
前端首屏加载优化
前端·javascript
gogoing30 分钟前
重排与重绘
前端·javascript
光影少年2 小时前
useEffect 完整理解:依赖数组、副作用清理、模拟生命周期
前端·react.js·程序员
之歆2 小时前
DAY_18深度解析:数据类型转换与运算符全攻略(上)
前端·javascript
大家的林语冰3 小时前
pnpm 11 发布,弃用 JSON 和 npm CLI,进化为纯 ES6 模块,新增 pnpm pack-app 等命令,供应链保护默认启用,要求 Node
前端·javascript·node.js