React基础使用教程

news2024/10/26 12:50:39

初识JSX

  • React主要使用的就是jsx语法来编写dom,所以我们要先认识jsx,然后再开始学习两者相结合
  • jsx其实就是在JS中编写HTML的一种写法
  • 编写jsx要注意以下几个规则:
    1. 定义虚拟DOM时,不要写引号
    2. 标签中混入JS表达式时要用=={}==
    3. 样式的类名指定不要用class,要用className
    4. 内联样式,要用==style={{key:value}}==的形式去写
    5. 只有一个根标签
    6. 标签必须闭合
    7. 标签首字母
      1. 若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错
      2. 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。

初学React

1、小案例

  • 我们使用React编写一个Hello World的小案例,这里演示使用的是React16.8.1版本

  • 注意:react和react-dom的版本一定要对应!

  • 案例中使用js文件引入依赖,也可以使用CDN引入:BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务

  • CDN中,要选umd下的,不要选cjs下的

    在这里插入图片描述

  • babel.min.js文件地址(可使用script标签,或下载到本地):https://unpkg.com/@babel/standalone/babel.min.js

1.1、文件结构

在这里插入图片描述

1.2、代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <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>


  <!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
  <script type="text/babel">
    // 这里一定不要带引号,不是js里面的字符串
    const VNODE = <h1>Hello World</h1>
    // 使用ReactDOM.render渲染
    // 第一个参数:要渲染的虚拟dom
    // 第二个参数:要渲染的dom节点
    // 注意:react没有提供选择器参数,只能自己获取节点,然后才能挂载上去
    ReactDOM.render(VNODE, document.getElementById('test'))
  </script>
</body>
</html>

1.3、效果

在这里插入图片描述

2、渲染DOM的两种写法

2.1、JSX(常用)

  • 第一种就是上面写的JSX方式,写法简单,易懂
  • 其实他实际会被转换为下面的写法,不过不需要咱们写,引用的babel会自动转换
const VNODE = <h1>Hello World</h1>

2.2、h函数

  • 第二种就是react提供的另一个渲染方式React.createElement
  • 这就是vue中也提供的h函数
  • 一个节点的话看起来还好,如果有多个元素,并且嵌套子元素,写起来非常麻烦,读起来也难懂,所以基本不用!
const VNODE = React.createElement('h1', {id: 'title'}, 'Hello World')

3、虚拟DOM与真实DOM

  • 虚拟DOM本质是Object类型的对象
  • 虚拟DOM比较**“轻”真实DOM比较"重"**,因为虚拟DOMReact内部在用,无需真实DOM上那么多的属性
  • 虚拟DOM最终会被React转化为真实DOM,呈现在页面上

在这里插入图片描述

4、JSX小练习

  • 模拟一些集合数据,然后使用JSX渲染ul、li标签到页面上
  • 注意:[]
    • jSX{}中只能写js表达式,
    • 被遍历的jsx标签最外层元素必须要有一个唯一值key
  • js语句(代码)js表达式
    • 表达式:
      • 一个表达式会产生一个值,可以放在任何一个需要值的地方,下面这些都是表达式
        1. a
        2. a+b
        3. demo(1)
        4. arr.map()
        5. function test() {}
    • 语句(代码):
      1. if(){}
      2. for(){}
      3. switch(){}
  • 如果代码要换行的话,最好把这一整段代码,用()包起来(<div>xxxxx</div>)
  • 另外,如果要在jsx中写注释,要这么写才会被正确解析为注释{ /* SDFSDF */ },外层用{},里面写js的注释

4.1、写死

  • 写死肯定是不行的,因为这些遍历的数据都是通过查询获得的,你并不知道数据是什么,下面只进行演示
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <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>


  <!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
  <script type="text/babel">
    // 模拟假数据
    let dataArr = ['Vue', 'React', 'Angular', 'Java', 'Python']

    const VNODE = (
      <ul>
        <li>Vue</li>
        <li>React</li>
        <li>Angular</li>
        <li>Java</li>
        <li>Python</li>
      </ul>
    )
    ReactDOM.render(VNODE, document.getElementById('test'))

  </script>
</body>
</html>
效果

在这里插入图片描述

4.2、中间穿插JS

  • 首先在JSX中使用JS,外面要包一层{}
  • 其次数据要遍历,肯定要使用循环,但是上面说了==for循环是语句,不是表达式==,所以我们可以使用语句里的arr.map()方法
  • 然后使用map方法遍历数据,封装标签。标签里面依旧使用{}来使用item(遍历的每一项)
  • 最后要给遍历封装的标签每个都添加唯一值的key属性(原理和Vue一样,后面会讲),不加的话虽然不影响渲染,但是控制台会报错
    • 在这里插入图片描述
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <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>


  <!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
  <script type="text/babel">
    // 模拟假数据
    let dataArr = ['Vue', 'React', 'Angular', 'Java', 'Python']
    
    const VNODE = (
      <ul>
        {
          dataArr.map((item, index) => <li key={index}>{item}</li>)
        }
      </ul>
    )
    ReactDOM.render(VNODE, document.getElementById('test'))

  </script>
</body>
</html>
效果

在这里插入图片描述

5、模块与组件、模块化与组件化

  • 模块就是把公共的或者复杂的==JS代码==提取出来单独的文件
  • 组件就是把公共的或者复杂的==HTML代码(包含html、css、js)==提取出来单独的文件

5.1、模块

  • 理解:向外提供特定功能的js程序,一般就是一个js文件
  • 随着业务逻辑增加,代码越来越多并且复杂,如果都集中在一个文件,那么后期必定很难维护、写起来也费劲
  • 作用:复用js,简化js的编写,提高js运行效率
  • 典型的引入方式有三种
    • 使用原生<script type="text/javascript" src="xxx/xxx.js"></script>
    • 使用ES6提供的import xxx from 'xxx/xxx.js'(常用)
    • 使用CommonJS提供的const a = require(xxx/xxx.js)

5.2、组件

  • 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image)等这个模块需要的资源
  • 随着页面的增加以及各种各样的样式呈现,一个html肯定有无数个dom,以及对应的功能,如果都集中在一个文件,那么后期必定很难维护、写起来也费劲,并且其他页面也不能复用
  • 作用:实现代码复用,精简页面代码复杂度
  • React中编写组件可以使用两种方式,后面会逐一讲解演示
    • 类式定义组件(功能多,写法稍微复杂)
    • 函数式定义组件(功能少,写法简单)

5.3、模块化

当一个项目都是使用JS模块来编写的,那么这个应用就是模块化的一个应用

5.4、组件化

当一个项目都是使用组件进行编写的,那么这个应用就是一个组件化的应用

6、开发者工具

  • Vue有自己的开发者工具([Vue.js devtools](Vue.js devtools - Microsoft Edge Addons)),帮助开发者观察响应式数据变化,以及组件代码树的可视化
  • React也有自己的开发者工具([React Developer Tools](React Developer Tools - Microsoft Edge Addons))
  • 以上地址皆是Edge插件下载地址,需要Chrome插件地址的话,自行百度
  • 下载好后,在咱们页面右上角查看插件可以看到这样一个红色的图标,这不是一个正常的图标因为咱们现在还在开发者模式,没有打包,没有进行工程化处理,所以它是这么显示的
    • 在这里插入图片描述
  • 后面我们会用到工程化搭建React项目,到时候图标就正常了

7、函数式组件(功能少,简单)

  • 使用函数定义组件,组件名称(函数名)首字母必须为大写!!,如果首字母为小写的化,那么会将其看作普通标签对待
  • 放到render函数中的时候,要以标签的形式(单/双)都可以
  • 函数必须返回JSX代码作为渲染内容!
  • React解析组件标签,找到了Demo组件,但是发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中
  • 其他功能后面会讲到,这里只演示渲染

7.1、代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <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>


  <!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
  <script type="text/babel">

    // 首字母必须为大写
    function Demo() {
      return <h2>函数定义的组件,适用于简单组件的定义!</h2>
    }

    // 要用标签的形式
    ReactDOM.render(<Demo></Demo>, document.getElementById('test'))

  </script>
</body>
</html>

7.2、效果

在这里插入图片描述

8、类式组件(功能多,常用)

  • 类的相关知识这里就不讲解了,可以参考网上视频多研究下JS中的概念
  • 上面讲解了简单功能少的函数式组件,这里我们讲解常用的类式组件,就是相当于,一个类就是一个组件!
  • 类写法有以下必须条件:
    1. 类首字母也必须大写
    2. 必须要继承React.Component
    3. 类中必须要有render函数,并且必须返回JSX代码作为渲染内容
    4. 放到render函数中的时候,要以标签的形式(单/双)都可以
  • React解析组件标签,找到了Demo组件,然后发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法,最后将render返回的虚拟DOM转为真实DOM,随后成呈现在页面中。
  • render方法中的this,其实是类Demo的的实例对象

8.1、代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <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>


  <!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
  <script type="text/babel">

    // 首字母必须为大写,必须继承 React.Component
    class Demo extends React.Component {
      // 放在原型对象上,供实例使用 new Demo().render()。虽然你没有创建实例,但是React会帮你创建并调用render方法
      render() {
        // render中的this,就是react帮你创造出来的实例对象
        console.log("render中的this", this);
        return (
          <div>
            <h1>类定义的组件,适用于 复杂组件 的定义</h1>
          </div>
        )
      }
    }

    // 要用标签的形式
    ReactDOM.render(<Demo/>, document.getElementById('test'))

  </script>
</body>
</html>

8.2、效果

在这里插入图片描述

9、组件三大核心之一:state

  • 理解:
    • state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
    • 组件被称为"状态机",通过更新组件的state来更新对应的页面提示(重新渲染组件)
  • 注意:
    • 组件中render方法中的this为组件实例对象
    • 组件自定义的方法中thisundefined,如何解决?
      1. 强制绑定this:通过函数对象的bind()
      2. 箭头函数
    • 状态数据,不能直接修改或更新
  • 下面使用一个案例来给大家演示下
    • 页面展示今天很幸运,点击文字后,切换为今天很倒霉

9.1、初始化state

代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <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>


  <!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
  <script type="text/babel">

    // 首字母必须为大写,必须继承 React.Component
    class Demo extends React.Component {
      
      constructor(props) {
        // 在使用this时,要先调用super()方法
        super(props);

        // 建议最好定义为对象,因为一般不会只有一个动态值
        this.state = {isLucky: true};

        // 解决change方法中的this,方法一:使用bind()
        this.change = this.change.bind(this);
      }

      // 这里面的this 指向的是当前组件实例,里面有咱们定义好的state对象
      render() {
        // 下面使用state的时候可以优化为如下解构取值
          const {isLucky } = this.state;

        return (
          // 这里不要使用this.change(),否则会直接调用该方法,拿该方法的返回值`undefined`去当作onClick执行的方法
          // 注意,这里使用的不是原生的onclick,而是React封装的onClick方法!,其他原生方法也一致,比如:onChange,onSubmit,onBlur等等
          <div onClick={this.change}>
            <h1>今天很 { this.state.isLucky ? '幸运' : '不幸运'}</h1>
          </div>
        )
      }

      // 点击修改值的函数,注意:这里访问不到this,因为不是由实例对象调用的!!!
      change() {
        // 这是继承的类 React.Component 中提供的方法,用来修改state的值,并且会自动重新渲染,其余修改值的方法不会造成页面重新渲染
        // 方法里面传入对象,对象里面可以写多个值,会自动合并!!!,并不会覆盖整个对象
        this.setState({
          isLucky: !this.state.isLucky
        })
      }
    }

    // 要用标签的形式
    ReactDOM.render(<Demo/>, document.getElementById('test'))

  </script>
</body>
</html>
效果

在这里插入图片描述

9.2、优化代码

  • 上面的state定义可以优化
  • change方法中使用this,有两种方法,上面只写了一种
  • 优化到最后可以发现,基本情况下constructor构造器可以省略掉
9.2.1、优化state
  • 熟悉JS中的Class的会知道,类里面可以直接定义属性以及初始值,无需在构造器中定义赋值
  • 那么我们就可以将自己定义的state对象直接当作类属性编写,即可在构造器中移除这段代码,代码如下,注意state定义的位置
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <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>


  <!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
  <script type="text/babel">

    // 首字母必须为大写,必须继承 React.Component
    class Demo extends React.Component {
      constructor(props) {
        // 在使用this时,要先调用super()方法
        super(props);

        // 解决change方法中的this,方法一:使用bind()
        this.change = this.change.bind(this);
      }

      // 简化state,将state直接写入属性中,并赋初始值
      state = {
        isLucky: true
      }

      // 这里面的this 指向的是当前组件实例,里面有咱们定义好的state对象
      render() {
        // 下面使用state的时候可以优化为如下解构取值
          const {isLucky } = this.state;

        return (
          // 这里不要使用this.change(),否则会直接调用该方法,拿该方法的返回值`undefined`去当作onClick执行的方法
          // 注意,这里使用的不是原生的onclick,而是React封装的onClick方法!,其他原生方法也一致,比如:onChange,onSubmit,onBlur等等
          <div onClick={this.change}>
            <h1>今天很 { this.state.isLucky ? '幸运' : '不幸运'}</h1>
          </div>
        )
      }

      // 点击修改值的函数,注意:这里访问不到this,因为不是由实例对象调用的!!!
      change() {
        // 这是继承的类 React.Component 中提供的方法,用来修改state的值,并且会自动重新渲染
        // 方法里面传入对象,对象里面可以写多个值,会自动合并!!!,并不会覆盖整个对象
        this.setState({
          isLucky: !this.state.isLucky
        })
      }
    }

    // 要用标签的形式
    ReactDOM.render(<Demo/>, document.getElementById('test'))

  </script>
</body>
</html>
9.2.2、优化change中的this
  • 当使用普通函数change(){}的时候,这时候change方法是在对象的原型身上Demo.prototype.change()
    • 在这里插入图片描述
  • 当使用赋值函数change = function(){}的时候,这时候change方法就是在自身上面,不在原型上了,但是函数依旧是由window调用的,由于使用了babel,开启了严格模式,所以里面的this,依旧指向(undefined)
    • 在这里插入图片描述
  • 方法使用箭头函数change = () => {}的时候,这时候的this就会找函数外层的this了,也就是当前类了,也就可以正常获取state属性了
    • 箭头函数this普通函数this指向可以自学下ES6语法
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <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>


  <!-- 注意:这里类型一定要写 text/babel,这样才会被babel编译 -->
  <script type="text/babel">

    // 首字母必须为大写,必须继承 React.Component
    class Demo extends React.Component {
      constructor(props) {
        // 在使用this时,要先调用super()方法
        super(props);
      }

      // 简化state,将state直接写入属性中,并赋初始值
      state = {
        isLucky: true
      }

      // 这里面的this 指向的是当前组件实例,里面有咱们定义好的state对象
      render() {
        // 下面使用state的时候可以优化为如下解构取值
          const {isLucky } = this.state;

        return (
          // 这里不要使用this.change(),否则会直接调用该方法,拿该方法的返回值`undefined`去当作onClick执行的方法
          // 注意,这里使用的不是原生的onclick,而是React封装的onClick方法!,其他原生方法也一致,比如:onChange,onSubmit,onBlur等等
          <div onClick={this.change}>
            <h1>今天很 { this.state.isLucky ? '幸运' : '不幸运'}</h1>
          </div>
        )
      }

      // 当使用箭头函数时,this指向的就不再是调用者(window)了,就是当前函数外层的this了,即指向当前组件实例
      change = () => {
        this.setState({
          isLucky: !this.state.isLucky
        })
      }
    }

    // 要用标签的形式
    ReactDOM.render(<Demo/>, document.getElementById('test'))

    const demo = new Demo()
    console.log("tcc", demo);
    

  </script>
</body>
</html>
9.2.3、优化constructor

现在回头看下constructor里面就只剩下super函数的调用了,咱们直接删除构造器即可

10、组件三大核心之二:props

  • 使用过vue的话,我们都知道,props是用来给组件传递参数的,React支持组件化,那么也是由如此配置的,下面咱们学习如何使用

10.1、基础使用

  • 上面我们在打印this的时候,可以看到里面有一个props的空对象,这个对象里面用来存放我们给组件传递的值
  • 给组件传值,我们只需要在组件标签上,通过标签属性的形式传值即可,React会自动将上面绑定的值,给放入props对象中,供组件内部使用
  • 下面我们封装一个展示用户信息的一个组件,姓名、性别、年龄均由组件传递参数读取显示
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <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>
  <br>
  <div id="test2"></div>
  <br>
  <div id="test3"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      render() {
        // 这里我们之前打印过,有一个属性教props的对象
        console.log("tcc", this.props);
        
        const {name, age, sex} = this.props;
        return (
          <div>
            <h2>姓名:{ name }</h2>
            <h2>年龄:{ age }</h2>
            <h2>性别:{ sex }</h2>
          </div>
        )
      }
    }

    // 在标签上写的所有属性,都会放到props对象中
    ReactDOM.render(<Demo name="张三" age="18" sex="男"/>, document.getElementById('test1'))
    ReactDOM.render(<Demo name="李四" age="19" sex="女"/>, document.getElementById('test2'))
    ReactDOM.render(<Demo name="王五" age="21" sex="男"/>, document.getElementById('test3'))
  </script>
</body>
</html>
效果

在这里插入图片描述

10.2、传参类型

  • 上面通过打印props可以发现,里面属性都是字符串类型
  • 如果我们想传递其他类型,可以使用{},里面就可以写js表达式了,同理,函数也一样,不过不要在函数后面添加()即可
  • 以下案例我们要给所有年龄+1显示,如果像原来一样直接在age+1就是"18"+1="181"
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <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>
  <br>
  <div id="test2"></div>
  <br>
  <div id="test3"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      render() {
        // 这里我们之前打印过,有一个属性教props的对象
        console.log("tcc", this.props);
        
        const {name, age, sex} = this.props;
        return (
          <div>
            <h2>姓名:{ name }</h2>
            { /* 展示的时候,性别+1 */ }
            <h2>年龄:{ age+1 }</h2>
            <h2>性别:{ sex }</h2>
          </div>
        )
      }
    }

    // 在标签上写的所有属性,都会放到props对象中,传入除字符串以外其他类型的参数的话,外层需要使用{}包裹起来
    ReactDOM.render(<Demo name="张三" age={18} sex="男"/>, document.getElementById('test1'))
    ReactDOM.render(<Demo name="李四" age={19} sex="女"/>, document.getElementById('test2'))
    ReactDOM.render(<Demo name="王五" age={21} sex="男"/>, document.getElementById('test3'))
  </script>
</body>
</html>
效果

在这里插入图片描述

10.3、注意事项

10.3.1、props中的属性不能在组件内部修改值
  • vue、React中,都是不可以通过子组件修改父组件的值的!!!
代码
render() {
    // 这里我们之前打印过,有一个属性教props的对象
    console.log("tcc", this.props);

    const {name, age, sex} = this.props;

    // 修改组件传过来的值,会报错!!!!
    this.props.age++;
    return (
        <div>
            <h2>姓名:{ name }</h2>
            { /* 展示的时候,性别+1 */ }
            <h2>年龄:{ age+1 }</h2>
            <h2>性别:{ sex }</h2>
        </div>
    )
}
效果

在这里插入图片描述

10.3、配置

  • 这里我们自己写的组件,知道里面的age会进行+1显示,其他人用的话,如果不进入组件内部看,一般不会知道的。
  • 那么每个用的人都进组件内部看,肯定不现实,那么我们就要对传入的数据进行一些限制
  • 如果你的数据没有通过校验,那么就报错到控制台,你一看便知
  • 假设,我们要给姓名设置字符串、必填,年龄设置数字类型,性别设置默认值 男,那么请看如下代码
10.3.1、配置数据类型校验
  • 我们通过给组件身上添加propTypes属性,配置对props的限制
  • 15.5版本我们直接使用React自带的React.PropTypes即可,但是这个版本以后,React把他单独拆分了出来了一个prop-types的js依赖
  • 注意:如果限制类型为函数,那么并不是PropTypes.function,而是PropTypes.func,因为function是关键字
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>
  <br>
  <div id="test2"></div>
  <br>
  <div id="test3"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      render() {
        // 这里我们之前打印过,有一个属性教props的对象
        console.log("tcc", this.props);
        const {name, age, sex} = this.props;
        return (
          <div>
            <h2>姓名:{ name }</h2>
            { /* 展示的时候,性别+1 */ }
            <h2>年龄:{ age+1 }</h2>
            <h2>性别:{ sex }</h2>
          </div>
        )
      }
    }

    // 验证属性的类型以及必填
    Demo.propTypes = {
      // React15.xxx版本的话,类型是直接使用React.PropTypes里面定义好的类型即可,但是React16.xxx版本,需要使用prop-types库
      // name: React.PropTypes.string, 
      name: PropTypes.string.isRequired,
      age: PropTypes.number,
    }

    // 在标签上写的所有属性,都会放到props对象中,传入除字符串以外其他类型的参数的话,外层需要使用{}包裹起来
    ReactDOM.render(<Demo name="张三" age={18} sex="男"/>, document.getElementById('test1'))
    ReactDOM.render(<Demo name={1} age={19} sex="女"/>, document.getElementById('test2'))
    ReactDOM.render(<Demo age="21" sex="男"/>, document.getElementById('test3'))
  </script>
</body>
</html>
效果

在这里插入图片描述

10.3.2、配置默认值
  • 我们通过给组件身上添加propTypes属性,配置对props的限制
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>
  <br>
  <div id="test2"></div>
  <br>
  <div id="test3"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      render() {
        // 这里我们之前打印过,有一个属性教props的对象
        console.log("tcc", this.props);
        const {name, age, sex} = this.props;
        return (
          <div>
            <h2>姓名:{ name }</h2>
            { /* 展示的时候,性别+1 */ }
            <h2>年龄:{ age+1 }</h2>
            <h2>性别:{ sex }</h2>
          </div>
        )
      }
    }

    // 验证属性的类型以及必填
    Demo.propTypes = {
      // React15.xxx版本的话,类型是直接使用React.PropTypes里面定义好的类型即可,但是React16.xxx版本,需要使用prop-types库
      // name: React.PropTypes.string, 
      name: PropTypes.string.isRequired,
      age: PropTypes.number,
    }

    // 给props中的属性指定默认值
    Demo.defaultProps = {
      sex: '男'
    }

    // 在标签上写的所有属性,都会放到props对象中,传入除字符串以外其他类型的参数的话,外层需要使用{}包裹起来
    ReactDOM.render(<Demo name="张三" age={18}/>, document.getElementById('test1'))
    ReactDOM.render(<Demo name={1} age={19} sex="女"/>, document.getElementById('test2'))
    ReactDOM.render(<Demo age="21"/>, document.getElementById('test3'))
  </script>
</body>
</html>
效果

在这里插入图片描述

10.4、简化

10.4.1、简化props传递
  • 上面组件案例,我们只列举了用户信息中的三个字段,如果要展示多个字段,那么我们要写很多属性,如果再多次使用组件,那么写起来会特别繁重
  • babel配合React使我们可以在jsx中使用...展开运算符展开一个对象(只有在标签中使用才有用,其他地方使用打印为空或报错)
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>
  <br>
  <div id="test2"></div>
  <br>
  <div id="test3"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      render() {
        // 这里我们之前打印过,有一个属性教props的对象
        console.log("tcc", this.props);
        const {name, age, sex} = this.props;
        return (
          <div>
            <h2>姓名:{ name }</h2>
            { /* 展示的时候,性别+1 */ }
            <h2>年龄:{ age+1 }</h2>
            <h2>性别:{ sex }</h2>
          </div>
        )
      }
    }

    // 验证属性的类型以及必填
    Demo.propTypes = {
      // React15.xxx版本的话,类型是直接使用React.PropTypes里面定义好的类型即可,但是React16.xxx版本,需要使用prop-types库
      // name: React.PropTypes.string, 
      name: PropTypes.string.isRequired,
      age: PropTypes.number,
    }

    // 给props中的属性指定默认值
    Demo.defaultProps = {
      sex: '男'
    }
    
    const u1 = {name: '张三', age: 18, sex: '男'}
    const u2 = {name: '李四', age: 19, sex: '女'}
    const u3 = {name: '王五', age: 21, sex: '男'}

    // 这里的{...u1},并不是把u1对象解构出来放到的新创建的对象中,()在jsx中代表里面内容是js表达式,并不是对象的{}
    ReactDOM.render(<Demo {...u1}/>, document.getElementById('test1'))
    ReactDOM.render(<Demo {...u2}/>, document.getElementById('test2'))
    ReactDOM.render(<Demo {...u3}/>, document.getElementById('test3'))
  </script>
</body>
</html>
效果

在这里插入图片描述

10.4.2、简化配置写法
  • 我们写的Demo.propTypes、Demo.defaultProps就是想直接把属性添加到Demo对象上,其实在类内部我们也可以实现
  • 通过在类中使用static关键字,即可实现把属性添加到类上
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>
  <br>
  <div id="test2"></div>
  <br>
  <div id="test3"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      // 通过static关键字,将propTypes和defaultPorps两个属性放入Demo对象上
      // 验证属性的类型以及必填
      static propTypes = {
        // React15.xxx版本的话,类型是直接使用React.PropTypes里面定义好的类型即可,但是React16.xxx版本,需要使用prop-types库
        // name: React.PropTypes.string, 
        name: PropTypes.string.isRequired,
        age: PropTypes.number,
      }

      // 给props中的属性指定默认值
      static defaultProps = {
        sex: '男'
      }

      render() {
        // 这里我们之前打印过,有一个属性教props的对象
        console.log("tcc", this.props);
        const {name, age, sex} = this.props;
        return (
          <div>
            <h2>姓名:{ name }</h2>
            { /* 展示的时候,性别+1 */ }
            <h2>年龄:{ age+1 }</h2>
            <h2>性别:{ sex }</h2>
          </div>
        )
      }
    }

    
    const u1 = {name: '张三', age: 18}
    const u2 = {name: 1, age: "19", sex: '女'}
    const u3 = {age: 21}

    // 这里的{...u1},并不是把u1对象解构出来放到的新创建的对象中,()在jsx中代表里面内容是js表达式,并不是对象的{}
    ReactDOM.render(<Demo {...u1}/>, document.getElementById('test1'))
    ReactDOM.render(<Demo {...u2}/>, document.getElementById('test2'))
    ReactDOM.render(<Demo {...u3}/>, document.getElementById('test3'))
  </script>
</body>
</html>

10.5、props与构造器的关系

  • 我们打开官网,搜索constructor可以看到这么一段话
    • 在这里插入图片描述
  • 就是如果在constructor构造器中访问this.props,那么必须要先调用super(props),否则可能获取到的为undefined,具体要看React的版本,我这里是直接控制台报错
  • 一般不会出现以上需求,因为你直接使用构造器参数中的props即可,没有必要访问this.props
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>
  <br>
  <div id="test2"></div>
  <br>
  <div id="test3"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      constructor(props) {
        // 如果在调用 super(props); 前 使用this.props,那么this.props就是undefined
        console.log("tccc1", this.props);
        super(props);
        console.log("tcc2", this.props);
        
      }

      render() {
        const {name, age, sex} = this.props;
        return (
          <div>
            <h2>姓名:{ name }</h2>
            { /* 展示的时候,性别+1 */ }
            <h2>年龄:{ age+1 }</h2>
            <h2>性别:{ sex }</h2>
          </div>
        )
      }
    }

    
    const u1 = {name: '张三', age: 18, sex: '男'}
    const u2 = {name: '李四', age: 19, sex: '女'}
    const u3 = {name: '王五', age: 21, sex: '男'}

    // 这里的{...u1},并不是把u1对象解构出来放到的新创建的对象中,()在jsx中代表里面内容是js表达式,并不是对象的{}
    ReactDOM.render(<Demo {...u1}/>, document.getElementById('test1'))
    ReactDOM.render(<Demo {...u2}/>, document.getElementById('test2'))
    ReactDOM.render(<Demo {...u3}/>, document.getElementById('test3'))
  </script>
</body>
</html>
效果

在这里插入图片描述

10.6、函数式组件的props

  • 虽然函数不能玩refs、state两个属性,但是可以使用props,因为函数可以接收参数
  • 所有传入函数式组件的props,都会被封装成一个对象传入组件函数的参数中
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">
    function Demo(props) {
      console.log("tcc", props);
      
      const {name, age, sex} = props;
      return (
          <div>
            <h2>姓名:{ name }</h2>
            { /* 展示的时候,性别+1 */ }
            <h2>年龄:{ age+1 }</h2>
            <h2>性别:{ sex }</h2>
          </div>
        )
    }

    
    const u1 = {name: '张三', age: 18, sex: '男'}

    // 这里的{...u1},并不是把u1对象解构出来放到的新创建的对象中,()在jsx中代表里面内容是js表达式,并不是对象的{}
    ReactDOM.render(<Demo {...u1}/>, document.getElementById('test1'))
  </script>
</body>
</html>
效果

在这里插入图片描述

11、组件三大核心之三:refs

11.1、使用

  • 通过在dom上定义ref属性,React即会把标签上含有ref属性的元素放入类自身的refs对象中,在其他地方编写this.refs.xxxx即可获取dom标签元素
  • 以下案例:输入框1输入内容后,点击右侧按钮,弹出输入的内容;输入框2输入内容失去焦点后,弹出输入的内容
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      render() {
        return (
          <div>
            {/* 通过在标签上定义 ref属性,这个dom就会被放到this.refs.input1中 */ }
            <input ref="input1" type="text" placeholder="请输入" />
            <button onClick={this.handleAlertVal}>点击弹出输入内容</button>
            <input ref="input2" type="text" placeholder="请输入" onBlur={this.handleAlertVal2} />
          </div>
        )
      }

      handleAlertVal = () => {
        // 读取react放入到refs对象中的input1元素
        console.log("tcc", this.refs);
        
        alert(this.refs.input1.value)
      }

      handleAlertVal2 = (event) => {
        // 这里由于是在input标签上帮的方法,所以event.target就是input元素,当然也可以使用this.refs.input2获取
        alert(event.target.value)
      }
    }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>
效果

在这里插入图片描述

11.2、字符串形式的ref

  • 上面我们使用的就是字符串形式的ref="xxxx",但是官网说使用这种ref有性能的问题
    • 在这里插入图片描述
  • 下面我们将会使用到其他形式的ref

11.3、回调形式的ref

  • 通过给标签上ref的属性值上绑定函数,即是回调形式的ref
  • 使用回调形式的ref,会在执行render方法的时候,发现标签上有ref属性,并且值是一个函数就会立即帮你执行一次函数并且把当前这个标签的dom节点作为参数传递给这个函数
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      render() {
        return (
          <div>
            {/* 使用回调形式的ref,会在执行render方法的时候,发现标签上有ref属性,并且值是一个函数,
                就会立即帮你执行一次函数
                并且把当前这个标签的dom节点作为参数传递给这个函数 */ }
            <input ref={(currentNode) => this.input1 = currentNode} type="text" placeholder="请输入" />
            <button onClick={this.handleAlertVal}>点击弹出输入内容</button>
            <input ref={(currentNode) => this.input2 = currentNode} type="text" placeholder="请输入" onBlur={this.handleAlertVal2} />
          </div>
        )
      }

      handleAlertVal = () => {
        console.log(this);
        
        alert(this.input1.value)
      }

      handleAlertVal2 = () => {
        alert(this.input2.value)
      }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>
效果

在这里插入图片描述

11.3.1、关于回调 refs 的说明
  • 如果ref的值是内联函数,那么在每次重新渲染(首次渲染没问题)的时候,都会调用两次这个函数,一次参数是null,第二次参数才是真正的dom。如果定义成class的绑定函数的方式可以避免上述问题
  • 因为在执行redner的时候,React不知道你绑定的函数是不是之前的函数了,所以会先给你一个null,清空之前的数据,再把真实的dom给你
  • 如果定义成class的绑定函数方式,那么React就知道你这次绑定的函数肯定和上次绑定的函数一样,所以就没有任何问题了,更新的话一次也不会帮你调用了
    • 在这里插入图片描述
  • 其实一般情况下没有什么问题,所以想怎么写怎么写
  • 下面演示一下
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      state = {
        isLucky: false
      }
      render() {
        return (
          <div>
            <h2>{this.state.isLucky ? "恭喜你,中奖了" : "很遗憾,未中奖"}</h2>
            <button onClick={() => {
              this.setState({
                isLucky: !this.state.isLucky
              })
            }}>点我改变运气</button>

            {/* class绑定函数,只有在首次渲染时帮你调用一次 */ }
            <input ref={this.bindInput1} type="text" placeholder="请输入" />
            <button onClick={this.handleAlertVal}>点击弹出输入内容</button>
            {/* 内联函数,再重新渲染时,react会帮你调用两次  */}
            <input ref={(currentNode) => {this.input2 = currentNode;console.log("tcc2", currentNode)} } type="text" placeholder="请输入" onBlur={this.handleAlertVal2} />
          </div>
        )
      }

      // class绑定函数,依旧可以接收到currentNode
      bindInput1 = (currentNode) => {
        console.log("tcc1", currentNode);
        
        this.input1 = currentNode
      }

      handleAlertVal = () => {
        alert(this.input1.value)
      }

      handleAlertVal2 = () => {
        alert(this.input2.value)
      }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>
效果

在这里插入图片描述

11.4、createRef API

  • 最后一种方式时使用React提供的一个React.createRef API,来创建一个容器,然后在ref中把这个容器绑定上,React就能自动把标签放入容器的current属性中
  • 需要先使用React.creatRef定义容器,然后编写this.xxx.current来获取容器
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      input1 = React.createRef();
      input2 = React.createRef();

      render() {
        return (
          <div>
            {/* 把容器交给ref */ }
            <input ref={this.input1} type="text" placeholder="请输入" />
            <button onClick={this.handleAlertVal}>点击弹出输入内容</button>
            <input ref={this.input2 } type="text" placeholder="请输入" onBlur={this.handleAlertVal2} />
          </div>
        )
      }


      handleAlertVal = () => {
        // 在使用的时候,要使用 .current 才能获取到容器
        console.log("tcc1", this.input1);
        alert(this.input1.current.value)
      }

      handleAlertVal2 = () => {
        console.log("tcc1", this.input2);
        alert(this.input2.current.value)
      }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>
效果

在这里插入图片描述

12、数据动态绑定

  • 页面上需要表单数据的地方有很多,当我们点登录的时候,要把用户填写的所有信息都提交到后台,难道这时候我们要一个表单一个表单,通过ref获取表单数据吗,不可能
  • 我们可以在用户修改数据的时候,把用户改完的数据绑定到state中,这样我们点击提交的时候,就可以直接从state中获取数据即可

12.1、使用

代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      
      state = {
        username: null,
        password: null
      }

      render() {
        return (
          <div>
            账号:<input onChange={this.usernameChange} type="text" placeholder="请输入" />
            密码:<input onChange={this.passwordChange} type="text" placeholder="请输入" />
            <button onClick={this.login}>登录</button>
          </div>
        )
      }

      usernameChange = (e) => {
        this.setState({
          username: e.target.value
        })
      }

      passwordChange = (e) => {
        this.setState({
          password: e.target.value
        })
      }

      login = () => {
        console.log("登录信息:", this.state);
      }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>
效果

我们观察之前下载的react插件也可观察出来

在这里插入图片描述

12.2、优化

  • 上面每个表单,我们都要写一个方法,去修改对应字段,其实我们可以使用如下方法即可解决以上问题
    • 使用高阶函数(函数参数是一个对象,或者,函数返回一个对象),比如:柯里化
写法一:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      
      state = {
        username: null,
        password: null
      }

      render() {
        return (
          <div>
            { /* 这里直接调用 handleFormChange 函数,并把要修改的字段告诉他 */ }
            账号:<input onChange={this.handleFormChange('username')} type="text" placeholder="请输入" />
            密码:<input onChange={this.handleFormChange('password')} type="text" placeholder="请输入" />
            <button onClick={this.login}>登录</button>
          </div>
        )
      }

      // 这里返回值是一个函数,供 onChange 使用,并且在调用的时候,依旧会传入参数 e
      handleFormChange = (field) => {
        return (e) => {
          this.setState({
            [field]: e.target.value
          })
        }
        
      }

      login = () => {
        console.log("登录信息:", this.state);
      }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>
写法二:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      
      state = {
        username: null,
        password: null
      }

      render() {
        return (
          <div>
            { /* 这里直接传入一个函数,函数中再调用handleFormChange,并且参数在传入中定义即可 */ }
            账号:<input onChange={(e) => this.handleFormChange(e, 'username')} type="text" placeholder="请输入" />
            密码:<input onChange={e => this.handleFormChange(e, 'password')} type="text" placeholder="请输入" />
            <button onClick={this.login}>登录</button>
          </div>
        )
      }

      // 这里直接使用两个参数即可
      handleFormChange = (e, field) => {
        this.setState({
            [field]: e.target.value
          })
      }

      login = () => {
        console.log("登录信息:", this.state);
      }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>

13、生命周期旧(v16重要)

  • 理解

    • 组件从创建到死亡它会经经历一些特定的阶段
    • React组件中包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用
    • 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作
    • 钩子函数执行顺序与编写顺序无关!!!!
  • 生命周期指v16版本以前的生命周期

  • 生命周期流程图

    • 在这里插入图片描述
  • 虽然看上面钩子函数有很多,但其实常用的只有几个

    1. render:每次state中数据修改时,React会帮咱调用,用于重新渲染页面
    2. componentDidMount:组件挂载好后会自动调用,在这里面可以写页面初始化的一些数据获取等逻辑
    3. componentWillUnmount:组件将要卸载时自动调用,在这里可以清空一些定时器等操作
  • 总结所有钩子函数调用时机,由于时机不同,以下不按照调用顺序解释

    • constructor:挂载时第一个调用,初始化state等使用,一般无需使用

    • componentWillMount,组件准备挂载时调用,一般不用

    • *render:当数据修改时,React会自动调用渲染页面

    • *componentDidMount:组件挂载完毕,一般获取页面初始化数据等操作在这里执行

    • componentWillReceiveProps:父组件调用render后,子组件会自动调用,并且会把props传入其中

    • shouldComponentUpdate:调用setState()方法后会自动调用

      • 如果返回true,那么正常修改值,并且render()
      • 如果返回false,那么反之不会修改值,并且不调用render()
      • 如果不写该函数,那么React会自动补全这个函数,并且返回true
    • componentWillUpdate:组件初始化调用render后,再次调用render前会调用的钩子函数

    • componentDidUpdate:组件更新完毕后的钩子函数

    • *componentWillUnmount:组件卸载前的钩子函数,可以清空定时器等结束操作

13.1、挂载时流程

  • 挂载时,组件会以此自动调用如下方法constructor、componentWillMount、render、componentDidMount
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      constructor() {
        super();
        console.log("constructor:构造器函数");
      }

      componentWillMount() {
        console.log("componentWillMount:组件将要挂载");
      }

      componentDidMount() {
        console.log("componentDidMount:组件挂载完毕");
      }

      render() {
        console.log("render:渲染组件");
        return (
          <div>
          </div>
        )
      }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>
效果

在这里插入图片描述

13.2、父组件render流程

  • 这里我们涉及到父组件,不过父子组件编写并不难,仔细观察如下案例即可
  • 父组件调用render后,子组件会按照如下流程依次执行:componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">
    // 父组件
    class Demo extends React.Component {
      state = {
        isLucky: true
      }

      render() {
        console.log("render:渲染组件");
        return (
          <div>
            {/* 直接在这里使用标签形式的子组件即可,传入isLucky属性,子组件通过props来获取 */}
            <Demo2 isLucky={this.state.isLucky} />
            <button onClick={() => {
              this.setState({
                isLucky: !this.state.isLucky
              })
            }}>抽奖</button>
          </div>
        )
      }
   }

   // 子组件
   class Demo2 extends React.Component {
    componentWillReceiveProps(nextProps) {
      console.log("componentWillReceiveProps:组件收到新的属性", nextProps);
    }

    constructor() {
        super();
        console.log("constructor:构造器函数");
      }

      componentWillMount() {
        console.log("componentWillMount:组件将要挂载");
      }

      componentDidMount() {
        console.log("componentDidMount:组件挂载完毕");
      }

      shouldComponentUpdate() {
        console.log("shouldComponentUpdate:组件是否需要更新");
        return true;
      }

      componentWillUpdate() {
        console.log("componentWillUpdate:组件将要更新");
      }

      componentDidUpdate() {
        console.log("componentDidUpdate:组件更新完毕");
      }

      render() {
        console.log("render:渲染组件");
        return (
          <div>
            <div>{this.props.isLucky ? '恭喜你,中奖了' : '很遗憾,未中奖'}</div>
          </div>
        )
      }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>
效果

在这里插入图片描述

13.3、setState()方法调用后流程

  • shouldComponentUpdate()方法,如果返回true,那么就会正常修改值,调用render方法。如果返回false,那么旧不会修改值,也就不会引起页面修改。如果不写的话,React会自动补全这个方法,并且返回true
  • 当组件调用setState()修改值以后,组件会按照如下流程依次执行:shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">

    class Demo extends React.Component {
      // 返回true,正常修改。false,停止修改
      shouldComponentUpdate() {
        console.log("shouldComponentUpdate:组件是否需要更新");
        return true;
      }

      componentWillUpdate() {
        console.log("componentWillUpdate:组件将要更新");
      }

      componentDidUpdate() {
        console.log("componentDidUpdate:组件更新完毕");
      }
      
      state = {
        isLucky: true
      }

      render() {
        console.log("render:渲染组件");
        return (
          <div>
            <div>{this.state.isLucky ? '恭喜你,中奖了' : '很遗憾,未中奖'}</div>
            <button onClick={() => {
              this.setState({
                isLucky: !this.state.isLucky
              })
            }}>抽奖</button>
          </div>
        )
      }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>
效果

在这里插入图片描述

13.4、forceUpdate()方法调用后流程

  • 这个函数用于强制使页面刷新,在继承类React.Component.Prototype中,本身直接由于继承,直接使用this.forceUpdate()调用即可,一般没必要调用!!
  • 调用这个方法后,会依次执行如下函数:componentWillUpdate、render、componentDidUpdate
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">

    class Demo extends React.Component {

      componentWillUpdate() {
        console.log("componentWillUpdate:组件将要更新");
      }

      componentDidUpdate() {
        console.log("componentDidUpdate:组件更新完毕");
      }

      render() {
        console.log("render:渲染组件", new React.Component());
        return (
          <div>
            <button onClick={() => {
              this.forceUpdate()
            }}>强制刷新</button>
          </div>
        )
      }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>
效果

在这里插入图片描述

13.5、卸载时流程

  • 卸载时,React只会调用一个方法componentWillUnmount
  • 我们调用ReactDOM.unmountComponentAtNode(),方法,参数里面传入咱们挂载时的节点,即可卸载挂载到上面的组件,对应的,节点内的所有内容消失
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 引入prop-types,用于校验属性的类型, 引入他,就有了全局的PropTypes对象 -->
   <script type="text/javascript" src="../js/prop-types.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">

    class Demo extends React.Component {

      componentWillUnmount() {
        console.log("componentWillUnmount:组件将要卸载");
      }

      render() {
        console.log("render:渲染组件");
        return (
          <div>
            <button onClick={() => {
              ReactDOM.unmountComponentAtNode(document.getElementById('test1'))
            }}>卸载组件</button>
          </div>
        )
      }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>
效果

在这里插入图片描述

14、生命周期新(v17)

  • 以下生命周期钩子函数适用于v17以上的版本,虽然使用以前的钩子函数依旧可以正常执行,但是某几个钩子执行控制台会有警告
  • 以下案例,我会修改上面使用的React版本为v17.0.1
  • 生命周期钩子流程图
    • 在这里插入图片描述
  • 对比旧图,可以发现少了componentWillMount、componentWillReceiveProps、componentWillUpdate,多了两个getDerivedStateFromProps、getSnapshotBeforeUpdate两个钩子,React更新DOM和refs这个其实旧版本也有这个操作,只不过图上没有表示出来,自己也无法控制,所以暂且不提。
  • 第一条说有几个钩子会有警告,其实是3条,就是上面图上“少了”的3个钩子(其实也不是少了,改名了),下面会详细讲解原因

14.1、改名的三个钩子

  • 改名的是上面图中缺少的三个钩子componentWillMount -> UNSAFE_componentWillMount、componentWillReceiveProps -> UNSAFE_componentWillReceiveProps、componentWillUpdate -> UNSAFE_componentWillUpdate
  • 为此,我特意写了如下组件验证原来钩子,可见控制台有如下三个警告,并且修改方法也写明了
以前写法
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/v17/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/v17/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">
    // 父组件
    class Demo extends React.Component {
      state = {
        parentLuckeyNum: 0
      }

      render() {
        return (
          <div>
            <Demo2 parentLuckeyNum={this.state.parentLuckeyNum} />
            <button onClick={() => {
            this.setState({
              parentLuckeyNum: parseInt(Math.random() * 10)
            })
          }}>点击更改父亲的幸运数</button>
          </div>
        )
      }
   }

   // 子组件
   class Demo2 extends React.Component {
    constructor() {
      super();
      console.log("constructor:构造器函数");
    }

    componentWillMount() {
      console.log("componentWillMount:组件将要挂载");
    }

    componentDidMount() {
      console.log("componentDidMount:组件挂载完毕");
    }

    shouldComponentUpdate() {
      console.log("shouldComponentUpdate:组件是否需要更新");
      return true;
    }

    componentWillUpdate() {
      console.log("componentWillUpdate:组件将要更新");
    }

    componentDidUpdate() {
      console.log("componentDidUpdate:组件更新完毕");
    }

    componentWillUnmount() {
      console.log("componentWillUnmount:组件将要卸载");
    }

    componentWillReceiveProps(nextProps) {
      console.log("componentWillReceiveProps:组件将要接受新的属性");
    }

    state = {
      myLuckeyNum: 0
    }

    render() {
      const { parentLuckeyNum } = this.props
      const { myLuckeyNum } = this.state
      return (
        <div>
          <h2>父亲的幸运数:{parentLuckeyNum}</h2>
          <h2>我的幸运数:{myLuckeyNum}</h2>
          <button onClick={() => {
            this.setState({
              myLuckeyNum: parseInt(Math.random() * 10)
            })
          }}>点击更改我的幸运数</button>
          <button onClick={() => {
            ReactDOM.unmountComponentAtNode(document.getElementById('test1'))
          }}>点击卸载组件</button>
        </div>
      )
    }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>
效果
  1. 查看钩子是否正常执行

    在这里插入图片描述

  2. 查看警告控制台

    在这里插入图片描述

新写法
  • 我们只要按照上面提示,修改这三个钩子方法的名字即可
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/v17/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/v17/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">
    // 父组件
    class Demo extends React.Component {
      state = {
        parentLuckeyNum: 0
      }

      render() {
        return (
          <div>
            <Demo2 parentLuckeyNum={this.state.parentLuckeyNum} />
            <button onClick={() => {
            this.setState({
              parentLuckeyNum: parseInt(Math.random() * 10)
            })
          }}>点击更改父亲的幸运数</button>
          </div>
        )
      }
   }

   // 子组件
   class Demo2 extends React.Component {
    constructor() {
      super();
      console.log("constructor:构造器函数");
    }

    UNSAFE_componentWillMount() {
      console.log("componentWillMount:组件将要挂载");
    }

    componentDidMount() {
      console.log("componentDidMount:组件挂载完毕");
    }

    shouldComponentUpdate() {
      console.log("shouldComponentUpdate:组件是否需要更新");
      return true;
    }

    UNSAFE_componentWillUpdate() {
      console.log("componentWillUpdate:组件将要更新");
    }

    componentDidUpdate() {
      console.log("componentDidUpdate:组件更新完毕");
    }

    componentWillUnmount() {
      console.log("componentWillUnmount:组件将要卸载");
    }

    UNSAFE_componentWillReceiveProps(nextProps) {
      console.log("componentWillReceiveProps:组件将要接受新的属性");
    }

    state = {
      myLuckeyNum: 0
    }

    render() {
      const { parentLuckeyNum } = this.props
      const { myLuckeyNum } = this.state
      return (
        <div>
          <h2>父亲的幸运数:{parentLuckeyNum}</h2>
          <h2>我的幸运数:{myLuckeyNum}</h2>
          <button onClick={() => {
            this.setState({
              myLuckeyNum: parseInt(Math.random() * 10)
            })
          }}>点击更改我的幸运数</button>
          <button onClick={() => {
            ReactDOM.unmountComponentAtNode(document.getElementById('test1'))
          }}>点击卸载组件</button>
        </div>
      )
    }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>

14.2、为什么改名

  • 其实这是React在为未来谋划,未来使用异步渲染的时候,他觉得这几个钩子可能会引发问题,觉得我们经常会滥用它们,就是在不合适的时候使用,并且在未来版本有可能直接不兼容

在这里插入图片描述

14.3、getDerivedStateFromProps

  • 该钩子函数和其他方法使用不同,是一个静态方法
  • 该方法必须返回一个对象,或者null
    • 返回对象的时候,他会和组件本身state进行合并操作,并且被合并的return对象中的数据无法被修改
    • 返回null的时候,他则无任何修改state操作
  • 使用这个钩子函数的时候,不会调用以上三个可能会有问题的钩子了
    • 在这里插入图片描述
  • 该方法会接收两个参数props、state,每当props或者state修改后,都会执行这个钩子函数
  • 这个钩子函数主要用于组件state里面的值全部由props来决定,并且不可更改
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/v17/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/v17/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">
    // 父组件
    class Demo extends React.Component {
      state = {
        parentLuckeyNum: 0
      }

      render() {
        return (
          <div>
            <Demo2 parentLuckeyNum={this.state.parentLuckeyNum} myLuckeyNum={666} />
            <button onClick={() => {
            this.setState({
              parentLuckeyNum: parseInt(Math.random() * 10)
            })
          }}>点击更改父亲的幸运数</button>
          </div>
        )
      }
   }

   // 子组件
   class Demo2 extends React.Component {
    constructor() {
      super();
      console.log("constructor:构造器函数");
    }

     // 直接将父组件的 props,作为子组件本身的state使用
    static getDerivedStateFromProps(props, state) {
      console.log("getDerivedStateFromProps:获取最新的属性", props, state);
      return props;
    }

    componentDidMount() {
      console.log("componentDidMount:组件挂载完毕");
    }

    shouldComponentUpdate() {
      console.log("shouldComponentUpdate:组件是否需要更新");
      return true;
    }

    componentDidUpdate() {
      console.log("componentDidUpdate:组件更新完毕");
    }

    componentWillUnmount() {
      console.log("componentWillUnmount:组件将要卸载");
    }

    state = {
      myLuckeyNum: 0
    }

    render() {
      const { parentLuckeyNum } = this.props
      const { myLuckeyNum } = this.state
      return (
        <div>
          <h2>父亲的幸运数:{parentLuckeyNum}</h2>
          <h2>我的幸运数:{myLuckeyNum}</h2>
          <button onClick={() => {
            this.setState({
              myLuckeyNum: parseInt(Math.random() * 10)
            })
          }}>点击更改我的幸运数</button>
          <button onClick={() => {
            ReactDOM.unmountComponentAtNode(document.getElementById('test1'))
          }}>点击卸载组件</button>
        </div>
      )
    }
   }


    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>
</body>
</html>
效果

在这里插入图片描述

14.4、getSnapshotBeforeUpdate

  • 该生命周期会接收两个参数:prevProps、prevState,更新前的props和state
  • 并且可以返回一个值,将作为componentDidUpdate钩子函数的第三个此参数传入
  • 此生命周期通常不需要,但在重新渲染期间手动保留滚动位置等情况下可能很有用
  • 下面我编写一个滚动的案例,滚动后定格在当前滚动的高度
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/v17/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/v17/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">
    // 父组件
    class Demo extends React.Component {
      getSnapshotBeforeUpdate(prevProps, prevState) {
        // 这里返回每次新增用户前,ul的滚动条离顶部的距离
        return document.querySelector(".userList").scrollHeight
        
      }

      componentDidUpdate(prevProps, prevState, snapshot) {
        // 这里snapshot能获取到上面return的结果,然后指定ul滚动条距离顶部的距离为 当前dom滚动条的高度 += dom新增完后ul的高度 - dom新增前ul的高度 就可以保持滚动条一直处于不动状态
        const dom = document.querySelector(".userList")
        dom.scrollTop += dom.scrollHeight - snapshot
      }

      componentDidMount() {
        // 定时新增用户
        setInterval(() => {
          // 注意,是unshift,让元素从上面新增
          this.state.userList.unshift(`用户${this.state.index}`)

          this.setState({
            userList: this.state.userList,
            index: this.state.index + 1
          })
        }, 1000);
      }

      state = {
        userList: [],
        index: 0
      }

      render() {
        // 这里渲染的元素必须由key属性,这里暂且使用index代替
        return (
          <div>
            <ul className="userList">
              {this.state.userList.map((item, index) => {
                return <li key={index} className="userList-item">{item}</li>
              })}
            </ul>
          </div>
        )
      }
   }

    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>

  <style>
    .userList {
      width: 100px;
      height: 130px;
      overflow: auto;
      background-color: cadetblue;
    }

    .userList-item {
      height: 30px;
    }
  </style>
</body>
</html>
效果

在这里插入图片描述

15、key

  • 上面我们在遍历数据然后使用map返回jsx的时候,都会在返回的jsx上面添加一个属性key,不然控制台就会有报错,那么为什么要加这个呢
  • React在每次执行render方法的时候,不是简单的把return里面的东西,直接放到上面,然后等待babel渲染,而是会先变成虚拟DOM,然后再经过一个diff算法,最后把算法结果放到上面
  • diff算法中,会获取到原来的虚拟DOM树,然后和新的虚拟DOM树比较,比较的时候,就会先拿着相同key的元素比较,如果一致,那么则原来的dom不动;如果不一致,那么就重新渲染这一块的dom
  • 一般key会使用如下两种方式赋值
    1. 使用index:这种方式用于遍历的dom不会改变的时候,即被遍历的集合不会被修改了,这样的话,使用index是没问题的,可以暂用。
    2. 使用数据唯一标识符:比如遍历一个对象集合,一般都会使用对象的id属性,如果没有,那么其他唯一值也是可以的,因为这些数据是经历后台查询出来的,有增删改的,会改变集合的,那么再使用index的话,之前元素上的index和之后的index对不上,实际上内容并没有被改变,但是因为参照物上的index变了,所以完全对不上了,就会重新被渲染,,就会导致性能变差,或渲染错乱!!

15.1、虚拟DOM

虚拟 DOM(Virtual DOM)在现代前端开发中具有重要的用处,主要体现在以下几个方面:

一、提高性能

  1. 减少直接操作真实 DOM 的开销
    • 在浏览器中,操作真实 DOM 是相对昂贵的操作,因为它会触发浏览器的重排(reflow)和重绘(repaint)。虚拟 DOM 则是在内存中以 JavaScript 对象的形式存在,对其进行操作不会直接引起浏览器的重排和重绘,从而大大减少了性能开销。
    • 例如,当你需要频繁更新页面上的一部分内容时,如果直接操作真实 DOM,可能会导致整个页面的重绘和重排,影响性能。而使用虚拟 DOM,可以先在内存中对虚拟 DOM 进行修改,然后一次性地将变化更新到真实 DOM 上,减少了不必要的重绘和重排次数。
  2. 高效的 DOM diff 算法
    • 虚拟 DOM 可以通过高效的 DOM diff 算法来计算出真实 DOM 中需要更新的最小部分。这个算法会比较新旧两个虚拟 DOM 树的差异,只对发生变化的部分进行实际的 DOM 操作。
    • 比如,当一个列表中的某一项数据发生变化时,DOM diff 算法可以精确地找到这个变化的项,并只更新对应的真实 DOM 元素,而不是重新渲染整个列表。

二、提升开发效率

  1. 跨平台渲染
    • 虚拟 DOM 可以实现跨平台渲染,使得同一份代码可以在不同的环境中运行,如浏览器、移动端、服务器端等。这是因为虚拟 DOM 是一个抽象的概念,不依赖于特定的平台。
    • 例如,使用 React Native 可以利用虚拟 DOM 的原理在移动端进行开发,实现一次编写,多平台运行,提高了开发效率。
  2. 组件化开发
    • 在组件化开发中,虚拟 DOM 使得组件的更新更加高效和可控。每个组件可以维护自己的虚拟 DOM 树,当组件的状态发生变化时,只需要更新组件对应的虚拟 DOM 部分,而不会影响到整个页面。
    • 例如,在一个大型的单页应用中,有很多个组件,每个组件都可以独立地进行开发和维护。当某个组件的状态发生变化时,虚拟 DOM 可以确保只更新该组件在页面上的部分,而不会影响其他组件的显示。

三、增强可维护性

  1. 清晰的代码结构
    • 使用虚拟 DOM 可以使代码结构更加清晰,易于理解和维护。通过将页面表示为虚拟 DOM 树,开发者可以更直观地看到页面的结构和组成部分。
    • 例如,在使用 React 开发时,组件的结构和状态都通过虚拟 DOM 来表示,开发者可以很容易地理解组件的工作原理和更新机制。
  2. 方便的调试和测试
    • 虚拟 DOM 使得调试和测试更加方便。由于虚拟 DOM 是在内存中存在的,开发者可以在不影响真实 DOM 的情况下进行调试和测试,更容易定位问题。
    • 例如,在进行单元测试时,可以使用虚拟 DOM 来模拟真实 DOM 的行为,从而更方便地测试组件的功能和性能。

15.2、diff算法

Diff 算法,即差异比较算法,在前端开发中尤其是在使用虚拟 DOM 的框架(如 React、Vue 等)中起着至关重要的作用。

一、Diff 算法的作用

  1. 高效更新真实 DOM
    • 在前端应用中,当数据发生变化时,需要更新页面以反映这些变化。如果直接对真实 DOM 进行操作,每次更新都可能导致浏览器进行重排和重绘,这是非常耗时的操作。Diff 算法通过比较新旧虚拟 DOM 树的差异,只对发生变化的部分进行实际的 DOM 操作,从而大大提高了更新效率。
    • 例如,当一个列表中的某一项数据发生变化时,Diff 算法可以精确地找到这个变化的项,并只更新对应的真实 DOM 元素,而不是重新渲染整个列表。
  2. 最小化 DOM 操作次数
    • Diff 算法的目标是找到最小的操作集合,以将旧的虚拟 DOM 转换为新的虚拟 DOM。这样可以减少对真实 DOM 的操作次数,提高性能。
    • 比如,在一个复杂的页面中,有多个组件同时发生变化,Diff 算法会分析这些变化,并确定最有效的方式来更新真实 DOM,避免不必要的操作。

二、Diff 算法的工作原理

  1. 树的比较
    • Diff 算法首先会对新旧两棵虚拟 DOM 树进行比较。它会从树的根节点开始,逐个比较节点的类型、属性和子节点。
    • 如果节点类型不同,那么旧节点将被替换为新节点。如果节点类型相同,那么继续比较节点的属性和子节点。
  2. 节点的比较
    • 对于相同类型的节点,Diff 算法会比较它们的属性。如果属性发生了变化,那么对应的真实 DOM 元素的属性将被更新。
    • 例如,如果一个按钮的文本从 “点击我” 变为 “按下我”,Diff 算法会检测到这个变化,并更新真实 DOM 中按钮的文本内容。
  3. 子节点的比较
    • Diff 算法会比较节点的子节点列表。它会采用一些优化策略来提高比较效率,比如同层比较和 key 属性的使用。
    • 同层比较是指只比较同一层级的子节点,而不会跨层级比较。这样可以减少比较的次数,提高性能。
    • key 属性是一种给子节点添加唯一标识的方式。当子节点具有 key 属性时,Diff 算法可以更准确地识别哪些子节点发生了变化,哪些子节点是新添加的或被删除的。

三、Diff 算法的优势

  1. 性能优化
    • 通过最小化 DOM 操作次数,Diff 算法可以显著提高前端应用的性能。特别是在处理大型应用和频繁更新的场景下,Diff 算法的优势更加明显。
    • 例如,在一个数据密集型的应用中,Diff 算法可以快速地更新页面,而不会导致页面卡顿或响应缓慢。
  2. 可维护性
    • Diff 算法使得前端代码更加易于维护。由于它可以自动处理页面的更新,开发者只需要关注数据的变化和虚拟 DOM 的构建,而不需要手动操作真实 DOM。
    • 例如,在一个复杂的单页应用中,有很多个组件和状态需要管理。Diff 算法可以帮助开发者更轻松地处理这些变化,提高代码的可维护性。

总之,Diff 算法是前端开发中非常重要的一种技术,它通过高效地比较新旧虚拟 DOM 树的差异,实现了对真实 DOM 的最小化更新,提高了前端应用的性能和可维护性。

15.3、使用index和key

  • 注意观察左侧使用index会导致什么问题
代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <!-- react 核心库,引入他,就有了全局的React对象 -->
  <script type="text/javascript" src="../js/v17/react.development.js"></script>
  <!-- react-dom,用于支持react此操作DOM,引入他,就有了全局的ReactDOM对象 -->
  <script type="text/javascript" src="../js/v17/react-dom.development.js"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
</head>
<body>
  <div id="test1"></div>


  <script type="text/babel">
    // 父组件
    class Demo extends React.Component {

      state = {
        list1: [
          {
            id: 1,
            name: '张三'
          },
          {
            id: 2,
            name: '李四'
          },
          {
            id: 3,
            name: '王五'
          }
        ]
      }

      render() {
        // 这里渲染的元素必须由key属性,这里暂且使用index代替
        return (
          <div style={{display: 'flex', gap: '40px'}}>
            <button onClick={this.addUser}>添加用户</button>
            <div>
              <h3>使用index</h3>
              <ul>
                {this.state.list1.map((item, index) => {
                  return <li key={index} className="userList-item">{item.name + '几岁了:'} <input /></li>
                })}
              </ul>
            </div>

            <div>
              <h3>使用id</h3>
              <ul>
                {this.state.list1.map((item, index) => {
                  return <li key={item.id} className="userList-item">{item.name + '几岁了:'} <input /></li>
                })}
              </ul>
            </div>
            
          </div>
        )
      }

      addUser = () => {
        this.setState({
          list1: [
            {
              id: Math.random(),
              name: '赵六'
            },
            ...this.state.list1,
          ]
        })
      }
   }

    ReactDOM.render(<Demo />, document.getElementById('test1'))
  </script>

  <style>
    .userList {
      width: 100px;
      height: 130px;
      overflow: auto;
      background-color: cadetblue;
    }

    .userList-item {
      height: 30px;
    }
  </style>
</body>
</html>
效果

在这里插入图片描述

使用脚手架

  • 以上使用原生html编写过于麻烦,还要自己引入react、react-dom、babel等第三方库,并且没有规范化,可以随意写。并且渲染效率极低,现用现编译。
  • 有时候项目大的话,启动的时候,编译就会很慢,导致页面白屏现象发生
  • 脚手架就像盖楼外面的铁架子一样,帮我们把基本框架搭好,用到的第三方依赖库先下好,打包配置等都先把基本的给我们完成,我们只需在上面改改,加加我们自己的东西即可,也是一个规范化约束的工具,也可以在启动的时候先编译好我们的代码,提高效率!
  • vue的脚手架有vue-cli、vite等,而React的脚手架有create-react-app
  • 征募整体技术架构为react + webpack + es6 + eslint

1、安装并启动

  • 安装一般使用npm来安装,如果还不懂npm,那么先查看:Node-包管理工具整套下载使用讲解(nvm、npm、yarn、cnpm、pnpm、nrm)
  • 命令如下
npm install -g create-react-app

// 或

yarn global add create-react-app

2、使用脚手架创建项目

  • 进入到项目存放地址,然后在地址栏输入cmd,最后执行如下命令即可,第一次肯定会很慢
  • 如果创建失败,可以试试更换镜像地址npm config set registry https://registry.npmmirror.com
create-react-app my-react

在这里插入图片描述

3、进入项目文件夹

  • 进入到项目根文件夹中
cd xxx/xxx/xxx

在这里插入图片描述

4、启动项目

npm start

在这里插入图片描述

在这里插入图片描述

5、项目目录讲解

  • 项目目录大概分为如下结构
    • noede_modules:项目所需第三方依赖
    • public:静态资源文件夹
      • favicon.icon:网站页签图标
      • index.html:主页面
      • logo192.png:logo图
      • logo512.png:logo图
      • manifest.json:应用加壳的配置文件
      • robots.txt:爬虫协议文件
    • src:源码文件夹
      • App.css:App组件的样式
      • App.js:App组件
      • App.test.js:用于给App做测试
      • index.css:全局样式
      • index.js:入口文件
      • logo.svg:logo图
      • reportWebVitals.js:页面性能分析文件(需要web-vitals库的支持)
      • setupTests.js:组件单元测试的文件(需要jest-dom库的支持)
    • .gitignore:git忽略文件
    • package-lock.json:npm缓存依赖文件
    • package.json:项目配置文件以及依赖版本文件
    • README.md:项目简介

5.1、主页面public->index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <!-- %PUBLIC_URL%代表public文件夹的路径 -->
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <!-- 开启理想视口,用于做移动端网页的适配 -->
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!-- 用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器) -->
    <meta name="theme-color" content="#000000" />
    <!-- 网页描述 -->
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <!-- 应用加壳时的配置文件 -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <!-- 若浏览器不支持js则展示标签中的内容 -->
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <!-- 根元素,用用于把组件放到这里来 -->
    <div id="root"></div>
  </body>
</html>

5.2、入口文件src->index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
// 全局样式
import './index.css';
// APP组件
import App from './App';
// import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // 一下标签是校验react写法的,可以删除,如果写法不正确,会在控制台提示警告等信息
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// 页面性能分析,可以删除
// reportWebVitals();

5.3、APP文件src->App.js

// 引入图片
import logo from './logo.svg';
// 引入组件样式
import './App.css';

function App() {
  return (
    <div className="App">
      {/* 页面所有内容区域,可任意编辑 */}
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

6、编写组件测试

  • 我们编写两个测试组件来进行测试,是否没问题

6.1、代码目录

在这里插入图片描述

6.2、组件代码

在这里插入图片描述

6.3、App.js

import "./App.css";
import Header from "./views/header/index.jsx";

import Content from "./views/content/index";

function App() {
  return (
    <div className="App">
      <Header />
      <Content />
    </div>
  );
}

export default App;

6.4、效果

在这里插入图片描述

7、样式冲突问题

  • header.js和content.js相同的类名,并且在该类名上有相同的样式,那么渲染就会出现问题
  • 会以后引入的组件样式优先,把上面组件相同类名的样式覆盖掉!

7.1、样式复现

7.1、组件代码

在这里插入图片描述

7.2、效果

在这里插入图片描述

7.2、问题解决

7.2.1、方法一
  • 项目使用less 或 sass,在最外层包裹一个唯一的样式类名,其余子元素样式在这个样式里面写,这样编译出来的时候,每个样式外面都会有那个唯一的样式,就不会重复了
  • 但是由于项目是由多个人开发,这个唯一的名字还是有可能重复的

在这里插入图片描述

7.2.2、方法二
  • 使用==module形式的css,修改header.css文件名,文件名中间添加.module即可,修改为header.module.css==
  • 改为如上名称后,在header组件中,即可使用import headerCss from './header.module.css',引入样式模块
  • 最后直接在使用到该类名的地方,使用headerCss.xxx即可
代码
import headerCss from './header.module.css'
import React, { Component } from 'react'

export default class header extends Component {
  render() {
    console.log("tcc", headerCss);

    
    return (
      <div className={ headerCss.header }>
        <span className={ headerCss.title }>header</span>
      </div>
    )
  }
}
效果

在这里插入图片描述

在这里插入图片描述

8、插件推荐

  • 该插件集成了很多内置代码简写生成方式,比如编写rcc(快速创建类组件)、fcc(快速创建函数式组件)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

编写TODO组件

  • 上面我们讲解了能加速我们开发的方式脚手架、插件等,下面我们编写一个案例来更加深入体会开发步骤

1、效果

在这里插入图片描述

2、分组件

  • 由于是练习,所以组件会分的比较多,其实实际开发,只需要把能被多次复用的地方提取为组件即可
  • 这里共分为四个组件Todo、TodoHeader、TodoItem、TodoFooter

在这里插入图片描述

3、组件结构

在这里插入图片描述

4、组件代码

记住,先把App.css里面所有样式删除,否则影响给咱们编写的代码一个默认样式

4.1、Todo

代码
import React, { Component } from 'react'
import './todo.css'
import TodoHeader from '../TodoHeader'
import TodoItem from '../TodoItem'
import TodoFooter from '../TodoFooter'

export default class Todo extends Component {

  state = {
    todos: []
  }

  render() {
    return (
      <div className='todo'>
        <TodoHeader addTodo={this.addTodo}/>
        <TodoItem todos={this.state.todos} handleItemChecked={this.handleItemChecked} handleDelete={this.handleDelete} />
        <TodoFooter todos={this.state.todos} handleCheckAll={this.handleCheckAll} />
      </div>
    )
  }

  handleCheckAll = (isChecked) => {
    this.setState({
      todos: this.state.todos.map(item => {
        return {
          ...item,
          isChecked
        }
      })
    })
  }

  /**
   * 添加todo
   * @param {Object} todo标题
   */
  addTodo = (todo) => {
    // 校验标题是否重复
    if (this.state.todos.some(item => item.title === todo)) {
      alert('任务名称重复!')
      return
    }
    
    this.setState({
      todos: [...this.state.todos, {title: todo, id: Date.now(), isChecked: false}]
    })
  }

  /**
   * 处理item中勾选框事件
   * @param {Boolean} checked 
   * @param {Number} id 
   */
  handleItemChecked = (checked, id) => {
    this.setState({
      todos: this.state.todos.map(item => item.id === id ? {...item, isChecked: checked} : item)
    })
  }

  /**
   * 删除Todo
   * @param {Number} id 
   */
  handleDelete = (id) => {
    this.setState({
      todos: this.state.todos.filter(item => item.id !== id)
    })
  }
}
样式
.todo {
  padding: 10px;
  border: 1px solid gray;
  margin: 10vh auto;
  width: 40vw;
}

4.2、TodoHeader

代码
import React, { Component } from 'react'
import './todoHeader.css'

export default class TodoHeader extends Component {
  render() {
    return (
      <div className='todo-header'>
        <input placeholder='请输入任务名称,按回车键确认' onKeyUp={this.handleInputKeyUp}></input>
      </div>
    )
  }

  handleInputKeyUp = (e) => {
    if (!e.target.value || e.key !== 'Enter') {
      return;
    }

    // 调用父组件的添加 todo 的方法,并且将输入框中的值传入方法中
    this.props.addTodo(e.target.value);
    // 清空输入框
    e.target.value = '';
  }
}
样式
.todo-header {
  width: 100%;
  height: 40px;
}

.todo-header input {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
}

4.3、TodoItem

代码
import React, { Component } from 'react'
import './todoItem.css'

export default class TodoItem extends Component {
  render() {
    const { todos, handleItemChecked, handleDelete } = this.props
    return (
      <div className='todo-item'>
        {
          todos.map(item => (
            <div className='todo-item-list' key={item.id} onMouseOver={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
              <input type="checkbox" checked={item.isChecked} onChange={e => handleItemChecked(e.target.checked, item.id)} />
              <span>{item.title}</span>

              <button className='deleteBtn' onClick={() => handleDelete(item.id)}>删除</button>
            </div>
          ))
        }
      </div>
    )
  }

  handleMouseOver = (e) => {
    e.target.classList.add("showDeleteBtn")
  }

  handleMouseOut = (e) => {
    e.target.classList.remove("showDeleteBtn")
  }
}
样式
.todo-item {
  max-height: 300px;
  overflow-y: auto;
  margin-top: 15px;
}

.todo-item .todo-item-list {
  padding: 10px;
  border: 1px solid #d9d9d9;
}

.todo-item input{
  margin-left: unset;
  cursor: pointer;
}

.todo-item .deleteBtn {
  opacity: 0;
  vertical-align: middle;
  float: right;
  background-color: #ff5959;
  border: unset;
  border-radius: 4px;
  padding: 3px 10px;
  transition: all 0.3s ease-in-out;
  cursor: pointer;
}

.todo-item .showDeleteBtn .deleteBtn {
  opacity: 1;
}

4.4、TodoFooter

代码
import React, { Component } from 'react'
import './todoFooter.css'

export default class TodoFooter extends Component {
  state = {
  }

  render() {
    const checkedCount = this.props.todos.filter(item => item.isChecked).length

    const totalCount = this.props.todos.length

    return (
      <div className='todo-footer'>
        <input type='checkbox' checked={!this.props.todos.some(item => !item.isChecked)} onChange={e => this.props.handleCheckAll(e.target.checked)} />
        <span className='count'>已完成 {checkedCount} / 全部 {totalCount}</span>
      </div>
    )
  }
}
样式
.todo-footer {
  padding: 20px 0 10px;
}

.todo-footer .count {
  margin-left: 10px;
}

代理/发送请求(Axios)

  • 原生自带发送ajax请求有两种方式:
    • fetchAPI
    • new XMLHttpRequest()
  • Axios就是基于XMLHttpRequest二次封装的工具类,可以很好的配置请求的发送以及请求和响应的拦截,并且返回的类型也是Promise对象

1、Axios

1.1、安装

npm i axios

1.2、使用

import React, { Component } from "react";
// 引入 axios
import axios from "axios";

export default class App extends Component {
  render() {
    return (
      <div>
        <button onClick={this.sendAxios}>发送请求</button>
      </div>
    );
  }

  sendAxios = () => {
    // 使用 axios.get 发送GET请求,返回值为Promise对象,可以使用.then()和.catch()处理
    axios
      .get("http://localhost:8888/getUser")
      .then((res) => {
        console.log(res);
      })
      .catch((err) => {
        console.log(err);
      });
  };
}

1.3、效果

在这里插入图片描述

在这里插入图片描述

1.4、跨域

  • 上面报错其实是正常的,咱们接口调用写的没有问题
  • 只不过前端会进行跨域拦截,当检测到你前台的端口为3000,而后台的端口不为3000时,后台是可以正常被调用的,只不过调用结果就会被前台拦截掉
  • 我这里前台端口为3000,后台端口为8888,以你自己前台端口为准
    • 在这里插入图片描述

在这里插入图片描述

2、配置代理

  • 解决跨域在前台或者后台都可以解决,这里主要讲解前台解决
  • 前台解决主要使用代理来解决,React有两种方式配置代理
    • package.json中配置
      • 好处
        • 配置简单
      • 坏处
        • 只能配置单一后端地址,如果要请求两个端口的接口则无法配置
    • 新建setupProxy.js配置
      • 好处
        • 能进行更复杂的配置,多功能化
      • 坏处
        • 配置复杂一些

2.1、package.json配置

  • 通过在这个文件中添加"proxy": "http://localhost:8888",这样所有接口调用http://localhost:3000/xxx时,代理都会转为http://localhost:8888/xxx去访问接口,并且响应结果不会被拦截
package.json文件

在这里插入图片描述

接口调用文件

在这里插入图片描述

结果

在这里插入图片描述

2.2、setupProxy.js配置

  • 使用这个方式,要先删除上面在package.json中的配置
  • src目录下新建setupProxy.js文件,并写入如下配置
setupProxy.js文件
const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function (app) {
  /*
    如果请求是/api/xxx ,则代理到http://localhost:8888/xxx
    如果请求是http://localhost:3000/api/xxx,则代理到http://localhost:8888/xxx
  */
  app.use(
    createProxyMiddleware("/api", {
      target: "http://localhost:8888",
      // 控制服务器收到的请求头中的Host的值
      changeOrigin: true,
      // 重写请求路径
      pathRewrite: {
        "^/api": "",
      },
    })
  );
};
App.js文件
import React, { Component } from "react";
// 引入 axios
import axios from "axios";

export default class App extends Component {
  render() {
    return (
      <div>
        <button onClick={this.sendAxios}>发送请求</button>
      </div>
    );
  }

  sendAxios = () => {
    // 使用 axios.get 发送GET请求,返回值为Promise对象,可以使用.then()和.catch()处理
    // http://localhost:3000/api/getUser 也可以
    axios
      .get("/api/getUser")
      .then((res) => {
        console.log(res);
      })
      .catch((err) => {
        console.log(err);
      });
  };
}

兄弟组件交互

  • 这里会先写一个demo,然后再讲兄弟组件交互优化

1、Demo

  • 这次做一个输入框查询数据的案例,很简单,涉及到兄弟组件之间的交互
  • 后台代码很简单,自行编写即可

1.1、效果

在这里插入图片描述

1.2、App.js

import React, { Component } from "react";
import Search from "./components/search";
import List from "./components/list";

export default class App extends Component {
  state = {
    loading: false,
    list: [],
    isNoneData: false,
  };

  render() {
    return (
      <div>
        <Search
          changeLoading={this.changeLoading}
          changeList={this.changeList}
        />
        <List
          list={this.state.list}
          loading={this.state.loading}
          isNoneData={this.state.isNoneData}
        />
      </div>
    );
  }

  // 修改 loading
  changeLoading = (loading) => {
    this.setState({ loading });
  };

  // 修改list数据
  changeList = (list) => {
    this.setState({ list });
    // 判断是否有数据
    this.setState({ isNoneData: !list || !list.length });
  };
}

1.3、search组件

import React, { Component } from 'react'
// 引入 axios
import axios from "axios";
import './search.css'

export default class search extends Component {
  render() {
    return (
      <div className='search'>
        <input type="text" placeholder="请输入后按回车查询" onKeyUp={this.handleKeyUp} />
      </div>
    )
  }

  handleKeyUp = (e) => {
    if (e.key !== 'Enter') {
      return;
    }

    // 通知副组件。loading开启
    this.props.changeLoading(true);

    // 使用 ?userName=xxx 的方式传递参数
    axios
      .get(`/api/getUser?userName=${e.target.value}`)
      .then((res) => {
        this.props.changeList(res.data);
      })
      .catch((err) => {
        console.log(err);
      })
      .finally(() => {
        // 通知副组件。loading关闭
        this.props.changeLoading(false);
      });
  }
}

在这里插入图片描述

1.4、list组件

import React, { Component } from 'react'

export default class List extends Component {
  render() {
    const { list, loading, isNoneData } = this.props
    return (
      <div>
        {
          loading ? <div>加载中...</div> :
            isNoneData ? <div>暂无数据</div> :
              <ul>
                {
                  list.map((item) => {
                    return (
                      <li key={item.id}>姓名:{item.name},年龄:{item.age}</li>
                    )
                  })
                }
              </ul>
        }
        
      </div>
    )
  }
}

2、分析优化

  • 通过上面观察,在Search中获取到数据以后,必须要==调用App.js中的changeList==方法,List组件才能获取数据,并且loading也是同样的做法,都要放到App.js中,再通过props进行父子传递
  • 有没有一种东西,让父子之间可以直接进行数据传递,而不用中间组件App.js
  • 消息订阅与发布机制:类似对讲机一样,定义一个频道,连接到这个频道的人可以随时发送消息,并且其他人可以立即收到消息,这种机制不仅适用于兄弟组件中沟通,适用于所有组件的沟通
  • 下面我们使用主流的[pubsub-js](GitHub - mroderick/PubSubJS: Dependency free publish/subscribe for JavaScript)库
    • List组件订阅消息
    • Search组件发布消息

2.1、安装

npm i pubsub-js

2.2、订阅消息(List组件)

import React, { Component } from 'react'
import PubSub from 'pubsub-js';

export default class List extends Component {
  state = {
    loading: false,
    list: [],
    isNoneData: false,
    pubsub: null
  };

  // 组件挂载完成 订阅消息
  componentDidMount() {
    // 把返回值放到state中,方便取消订阅
    this.state.pubsub = PubSub.subscribe('MY TOPIC', (msg, data) => {
      // 这里利用 setState 方法只会合并,并不会覆盖机制
      this.setState({
        ...data
      })
    });
  }

  // 取消订阅
  componentWillUnmount() {
    // 取消订阅
    PubSub.unsubscribe(this.state.pubsub);
  }

  render() {
    const { list, loading, isNoneData } = this.state
    return (
      <div>
        {
          loading ? <div>加载中...</div> :
            isNoneData ? <div>暂无数据</div> :
              <ul>
                {
                  list.map((item) => {
                    return (
                      <li key={item.id}>姓名:{item.name},年龄:{item.age}</li>
                    )
                  })
                }
              </ul>
        }
        
      </div>
    )
  }
}

2.3、发布消息(Search组件)

import React, { Component } from 'react'
// 引入 axios
import axios from "axios";
import './search.css'
import PubSub from 'pubsub-js';

// 这里定义为常量,防止在代码中随意修改,或写错
const pubSubName = "MY TOPIC"

export default class search extends Component {
  render() {
    return (
      <div className='search'>
        <input type="text" placeholder="请输入后按回车查询" onKeyUp={this.handleKeyUp} />
      </div>
    )
  }

  handleKeyUp = (e) => {
    if (e.key !== 'Enter') {
      return;
    }


    // 发布消息。通知订阅相同消息的List组件loading开启
    PubSub.publish(pubSubName, {loading: true});

    // 使用 ?userName=xxx 的方式传递参数
    axios
      .get(`/api/getUser?userName=${e.target.value}`)
      .then((res) => {
        // 将响应结果传递给List组件
        PubSub.publish(pubSubName, {list: res.data, isNoneData: !res.data || !res.data.length});
      })
      .catch((err) => {
        console.log(err);
      })
      .finally(() => {
        // 通知List组件。loading关闭
        PubSub.publish(pubSubName, {loading: false});
      });
  }
}

2.4、App.jsx

import React, { Component } from "react";
import Search from "./components/search";
import List from "./components/list";

export default class App extends Component {
  render() {
    return (
      <div className="app">
        <Search />
        <List />
      </div>
    );
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2223975.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯模块(四)数码管动态显示

一、数码管电路图 二、电路分析 1.数码管电路分析 端口分公共端和段码&#xff0c;先用公共端控制一个数码管&#xff0c;再用段码实现显示数字。共阳数码管公共端输入高电平&#xff0c;段码输入低电平实现点亮 2.锁存器 Y7控制段码&#xff0c;Y6控制公共端 3.数码管编码基…

2024.7最新子比主题zibll7.9.2开心版源码+授权教程

授权教程&#xff1a; 1.进入宝塔搭建一个站点 绑定 api.zibll.com 域名 并上传 index.php 文件 2.设置伪静态 3.开启SSL证书&#xff0c;找一个能用的域名证书&#xff0c;将密钥(KEY)和证书(PEM格式)复制进去即可 4.在宝塔文件地址栏中输入 /etc 找到 hosts文件并打开&a…

初探Vue前端框架

文章目录 简介什么是Vue概述优势MVVM框架 Vue的特性数据驱动视图双向数据绑定指令插件 Vue的版本版本概述新版本Vue 3Vue 3新特性UI组件库UI组件库概述常用UI组件库 安装Vue安装Vue查看Vue版本 实例利用Vue命令创建Vue项目切换工作目录安装vue-cli脚手架创建Vue项目启动Vue项目…

从零开始:Python与Jupyter Notebook中的数据可视化之旅

目录 **理解数据与数据可视化的基本流程****了解Python与其他可视化工具****掌握Anaconda、Jupyter Notebook的常用操作方法****原理** 环境配置1. **安装Anaconda软件&#xff0c;创建实验环境**2. **安装Jupyter Notebook**3. **创建第一个Jupyter Notebook文本**&#xff08…

双指针法应用超级大总结

前言 前面很多题目都有采用双指针的思想解题&#xff0c;有的是最基本的双指针、有的用快慢指针、有的是滑动窗口&#xff0c;有的是降低时间复杂度&#xff0c;有的是必须采用这种思想&#xff0c;整的人头都大了&#x1f62d;&#x1f62d;&#x1f62d;。现在系统整理总结一…

Spring Boot技术中小企业设备管理系统设计与实践

6系统测试 6.1概念和意义 测试的定义&#xff1a;程序测试是为了发现错误而执行程序的过程。测试(Testing)的任务与目的可以描述为&#xff1a; 目的&#xff1a;发现程序的错误&#xff1b; 任务&#xff1a;通过在计算机上执行程序&#xff0c;暴露程序中潜在的错误。 另一个…

Could not retrieve mirrorlist http://mirrorlist.centos.org错误解决方法

文章目录 背景解决方法 背景 今天在一台新服务器上安装nginx&#xff0c;在这个过程中需要安装相关依赖&#xff0c;在使用yum install命令时&#xff0c;发生了以下报错内容&#xff1a; Could not retrieve mirrorlist http://mirrorlist.centos.org/?release7&archx8…

SpringMVC实战:构建高效表述层框架

文章目录 1. SpringMVC简介和体验1.1 介绍1.2 主要作用1.3 核心组件和调用流程1.4 快速体验 2. SpringMVC接收数据2.1 访问路径设置2.2 接收参数2.2.1 param和json参数比较2.2.2 param参数接收2.2.3 路径参数接收2.2.4 json参数接收 2.3 接收cookie数据2.4 接收请求头数据2.5 原…

10340 文本编辑器(vim)

经验值&#xff1a;1600 时间限制&#xff1a;1000毫秒 内存限制&#xff1a;512MB 经开区2023年信息学竞赛试题 不许抄袭&#xff0c;一旦发现&#xff0c;直接清空经验&#xff01; 题目描述 Description 李明正在学习使用文本编辑器软件 Vim。与 Word、VSCode 等常用的…

用扣子模板,走AI捷径,这个双11,大模型要发威了?

AI离我们越来越近&#xff0c;还是越来越远&#xff1f; 一年前&#xff0c;ChatGPT刚出现那会儿&#xff0c;AI极其火热&#xff0c;很多国内企业奋不顾身的杀进去&#xff0c;或自研或投资或结盟&#xff0c;那时&#xff0c;感觉AI已经离我们很近了&#xff0c;一场商业革命…

单链表OJ题:移除链表元素(力扣)

目录 解法一&#xff1a;带头节点的新链表 解法二&#xff1a;不带头节点的新指向关系链表 总结 这是一道简单的力扣题目&#xff0c;关于解法的话&#xff0c;这里提供了二种思路&#xff0c;重点解释前两种&#xff0c;还有一种思路好想&#xff0c;但是时间复杂度为O(n^2…

使用Prometheus对微服务性能自定义指标监控

背景 随着云计算和容器化技术的不断发展&#xff0c;微服务架构逐渐成为现代软件开发的主流趋势。微服务架构将大型应用程序拆分成多个小型、独立的服务&#xff0c;每个服务都可以独立开发、部署和扩展。这种架构模式提高了系统的可伸缩性、灵活性和可靠性&#xff0c;但同时…

Java.9--集合

一、Collection接口 -->单列集合&#xff08;共享给大家&#xff09; .add();把给定的对象添加到当前集合中 clear();清空集合中所有的元素 remove();把给定的对象在当前集合中删除 contains();判断当前集合中是否包含给定的对象 isEmpty();判断当前集合是否为空 siz…

SQL注入之sqlilabs靶场21-30题

重点插入&#xff1a;html表 第二十一题 分析过程&#xff1a;&#xff08;没有正确的账号密码是否能拿到Cookie&#xff1f;最后注释好像只能使用#&#xff0c;--好像无法注释&#xff09; 查看源码 这里输入账号密码处被过滤了 但Cookie被base64编码了 可以从Cookie入手 …

智联招聘×Milvus:向量召回技术提升招聘匹配效率

01. 业务背景 在智联招聘平台&#xff0c;求职者和招聘者之间的高效匹配至关重要。招聘者可以发布职位寻找合适的人才&#xff0c;求职者则通过上传简历寻找合适的工作。在这种复杂的场景中&#xff0c;我们的核心目标是为双方提供精准的匹配结果。在搜索推荐场景下&#xff0c…

Ollama+Open WebUI,windows部署一个本地AI

在Ollama官网下载&#xff0c;点击DownLoad 下载完之后进行安装&#xff0c;配置环境变量&#xff0c;完成后打开CMD命令行工具测试 运行并下载模型 之后选择Open WebUI作为图形化界面 &#x1f680; Getting Started | Open WebUI 运行Docker命令 docker run -d -p 3000:80…

【Sublime Text】设置中文 最新最详细

在编程的艺术世界里&#xff0c;代码和灵感需要寻找到最佳的交融点&#xff0c;才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里&#xff0c;我们将共同追寻这种完美结合&#xff0c;为未来的世界留下属于我们的独特印记。 【Sublime Text】设置中文 最新最详细 开…

万字图文实战:从0到1构建 UniApp + Vue3 + TypeScript 移动端跨平台开源脚手架

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f343; vue-uniapp-template &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f…

团结引擎内置 AI 助手团结 Muse Chat 测试版上线!新功能怎么用?能做什么?

在开发过程中&#xff0c;快速获得精准的技术支持能够有效提高开发效率。生成式 AI 的出现为实现实时技术支持提供了新的机会。Unity 中国积极探索 AI 在开发中的应用&#xff0c;并在团结引擎 1.3.0 版本中上线了新功能&#xff1a;团结 Muse Chat。 团结 Muse Chat 是 Unity…

【linux】服务器Ubuntu20.04安装cuda11.8教程

【linux】服务器Ubuntu20.04安装cuda11.8教程 文章目录 【linux】服务器Ubuntu20.04安装cuda11.8教程到官网找到对应版本下载链接终端操作cudnn安装到官网下载下载后解压进入解压后的目录&#xff1a;将头文件复制到 /usr/local/cuda/include/ 目录&#xff1a;将库文件复制到 …