likes
comments
collection
share

React类组件实战知识点

作者站长头像
站长
· 阅读数 20

React02-项目搭建

一、React介绍

React这个项目是facebook开源出来的一个优秀前端库。

官网将React称为库(jquery库),React默认提供的只是前端的页面更新方案。并没有提供完整的前端所有核心内容。比如路由、状态机。这些都需要我们自己搭建。

在2013年5月开源出来。

提出了很多概念在当时都是比较前卫的思想。

组件化开发、虚拟dom加载。

React开发有两个分支:

  1. React-DOM:这是一个包。这个分支解决我们React开发WEB业务
  2. React-Native:专门用于开发移动端。只能开发android和ios

学习:react.nodejs.cn/

优势:

  1. 组件化开发、虚拟dom设计,对应项目来说性能有保证。
  2. 技术成熟、社区配置完善。适用于中大型web项目
  3. facebook团队维护,版本更新、担心后续维护
  4. 目前来说React在公司里面,适用于中大型项目。大公司用的比较多

二、搭建项目

目前React官方基于webpack封装了脚手架。

你们可以基于脚手架工具。快速搭建一个项目。

(1)创建项目

npx create-react-app 项目名字

脚手架工具的文档:

cra.nodejs.cn/docs/gettin…

这种方式来创建项目,本地无需安装脚手架。就可以直接创建项目

临时在本地安装一个脚手架工具,创建项目,创建完成后,删除脚手架

在本地安装好脚手架,直接用本地脚手架搭建项目。这种方式我们不推荐。

npm i create-react-app -g
create-react-app 项目名字

创建项目,项目名字不能出现大写字母,不能中文

(2)启动项目

安装yarn包管理器。默认提示你采用yarn启动项目

yarn start

npm来启动项目

npm run start

(3)改造项目

src目录下面只留

  1. index.js:这个文件项目启动文件。类似于Vue中main.js以后需要全局挂载内容都可以在里面写
  2. App.js:后缀是js,其中这个是React组件。里面代码不是原生js代码。会在index默认加载App.js文件

剩下的文件夹需要大家自己创建

component:存放组件

utils:存放开发自己封装工具

assets:静态资源目录

libs:存放第三方包 sdk

apis:存放异步请求

三、组件的介绍

(1)概念介绍

组件化思想:针对页面UI部分,进行了页面的拆分。将公共的页面模板拆分,最后在自己组合在一起。

模块化思想:针对JS脚本设计,将代码进行模块拆分。减少当前脚本体积,提高复用性。比如很多的工具提取出来。

<html>
<head>
    <script src="./a.js"></script>
    <script src="./b.js"></script>
</head>
</html>

a.js

var m = 10

b.js

var m = 20

最早解决思路

采用IIFE来解决问题。

a.js

(function(){
    var m = 10
})()

b.js

(function(){
    var m = 20
})()

requirejs seajs等等。第三方开发的模块中代码。

ES6这个版本,官方也推出了模块化思想

import xxx from "./"
export {}
export default {}

工程化思想:以前我们项目在没有脚手架搭建的时候,都是自己下载包。自己来进行代码压缩混淆。

基于webpack或者vite这种打包工具。你们直接依赖这些工具完成前端项目构建,打包、部署

(2)组件创建

React中组件有两种

  1. 类组件开发:采用的面向对象开发思想。
  2. 函数组件开发:基于函数来设计组件。所有业务都是基于函数来开展

在目前项目开发过程中,两种组件都会接触到。

在React中我们组件后缀有两种格式,js在里面写jsx代码,jsx后缀支持的一种格式

安装插件

React类组件实战知识点

React类组件实战知识点

创建组件的时候

rcc:创建一个类组件的模板

rfc:创建一个函数组件的的模块

类组件的代码

import React, { Component } from 'react'

/**
 * 在React中当写一个类,继承Component,当前这个类是一个组件
 * Component:React提供的组件
 */
class Header extends Component {
    /**
     * render渲染函数,我们当前这个组件要显示模板,在render里面
     * JSX代码
     * @returns 
     */
    render() {
        return (
            <div>Header</div>
        )
    }
}


export default Header

React设计思想:将JS+HTML模板统一放在一个文件中,写完了代码后。JS 和HTML分开解析。最后html+css+js

原生JS代码

const array = [1,2,3]
let temp = ""
for(var i=0;i<array.length;i++){
    temp += `<li>${array[i]}</li>`
}
obody.innerHTML = temp

函数组件的代码

import React from 'react'

function Content() {
    return (
        <div>Content</div>
    )
}


export default Content

我们先学习类组件,后面再学习函数组件

(3)JSX概念

组件本身设计的一种模块,可以将代码抽取出来。再React中就是采用JSX来作为组件的语法

JSX = JavaScript + XML

JavaScript就是基于ES5\ES6引入过来规范。

XML:标签语言,表示组件中所有标签模板。代码解析的时候。XML标签就是转化HTML标记了。

接下来你再组件中写的所有代码都是JSX代码。浏览器无法直接解析。

在启动项目的时候

yarn start

webpack就会开始打包项目。babel这个插件,将jsx代码转化为浏览器能是被的js代码。最后才浏览器运行

四、组件的页面

(1)组件中类名

<div className="">

每个组件都要求,必须提供根标签。否则报错

<React.Fragment></React.Fragment>

使用这个来作为根标签,最终页面不会渲染出来。解决报错问题

<></>

也是代表虚拟标签,最终并不会解析出来。

(2)组件中引入图片

import React, { Component } from 'react'
import logo from "../assets/images/logo-250px.645f24b5.png"
const logo2 = require("../assets/images/logo-250px.645f24b5.png")

/**
 * 在React中当写一个类,继承Component,当前这个类是一个组件
 * Component:React提供的组件
 */
class Header extends Component {
    /**
     * render渲染函数,我们当前这个组件要显示模板,在render里面
     * JSX代码
     * render函数:方法重写,调用render优先执行子类render
     * @returns 
     */
    render() {
        return (
            <React.Fragment>

                <div className='container'>
                    <div className='logo'>
                        <img src={logo2} alt="" />
                    </div>
                    <div>
                        <span>欢迎【xx】登录</span>
                    </div>
                </div>
                <p></p>

            </React.Fragment>
        )
    }
}


export default Header

总结:

  1. 服务器网络图片,地址直接写死,动态加载都可以。
  2. 图片本地图片,采用import 或者 require的方式引入进来,动态设置src属性

在JSX中动态绑定一个属性,我们需要src={logo}

(3)样式设计

在JSX中支持两种样式设计

  1. 内联样式:直接在标签上面style属性。
  2. 外部样式:将样式文件提取到外部css文件中,引入进来使用

内联样式:JSX中的样式已经经过封装过了。

<span style={{color:"red",fontSize:"20px"}}>欢迎【xx】登录</span>

因为style对象可以接受多个css样式,所以我们提供的是一个对象,定义很多样式

外部样式:

在styles文件夹下面创建css文件header.css

.container{
    display: flex;
    justify-content: space-between;
    height: 80px;
    width: 100%;
    box-shadow: 0px 0px 10px rgba(0,0,0,.3);
    padding: 0px 15px;
    box-sizing: border-box;
}
.container .logo{
    width: 180px;
    height: 60px;
    border: 1px solid red;
    margin-top: 10px;
}
.container .logo img{
    width: 100%;
}

JSX代码

render() {
        return (
            <React.Fragment>
                <div className='container'>
                    <div className='logo'>
                        <img src={logo2} alt="" />
                    </div>
                    <div>
                        <span>欢迎【xx】登录</span>
                    </div>
                </div>
                <p></p>

            </React.Fragment>
        )
    }

(4)样式模块化

在组件中样式导入过后默认是全局的样式,

不管在哪个组件中,一旦名字一致,相互影响

为了解决这个问题,React提出了一个概念。模块化样式。

开发步骤 styles文件夹下面创建文件名字 tabA.module.css

.op{
    color: pink;
}
.box span{
    color:blue
}

组件中使用

import styles from "../assets/tabA.module.css"

<div clasName={styles.box}></div>

(5) 组件的内部数据

Vue中组件内部数据使用data来定义。只要放在data中,这个数据响应式变化。一旦发生数据更新,页面也会更新

在React中组件内部数据采用state来定义

语法一:在构造器中定义state变量

constructor(){
        //只要有继承,构造器第一句话必须写super
        //super代表调用父类构造器,先创建父类
        //当你不写构造器的时候,编辑器默认添加constructor
        super()
        //state对象里面存放的数据,类似于Vuedata定义的数据
        this.state = {
            msg:"小王"
        }
    }

页面上使用这个变量的时候

<div>{this.state.msg}</div>

为了更加优化代码,一般会在render函数里面解构,在去使用

render() {
        const {msg} = this.state
        return (
            <React.Fragment>
                <div className='menu'>
                    <ul>
                        <li className='active'>拼团管理</li>
                        <li>拼团数据</li>
                        <li>扩展内容</li>
                    </ul>
                </div>
                <div className='main'>
                    <p>{msg}</p>
                    <TabA></TabA>
                    <TabB></TabB>
                </div>
            </React.Fragment>
        )
    }

语法二:无需再构造器中定义

import React, { Component } from 'react'
import "../assets/styles/content.css"
import TabA from './TabA'
import TabB from './TabB'
import TabC from './TabC'

export default class Content extends Component {
    state = {
        msg:"xiaofeifei"
    }
    render() {
        const {msg} = this.state
        return (
            <React.Fragment>
                <div className='menu'>
                    <ul>
                        <li className='active'>拼团管理</li>
                        <li>拼团数据</li>
                        <li>扩展内容</li>
                    </ul>
                </div>
                <div className='main'>
                    <p>{msg}</p>
                    <TabA></TabA>
                    <TabB></TabB>
                </div>
            </React.Fragment>
        )
    }
}

(6)组件事件绑定

基本语法

React标签上面事件都被封装了一次。JSX的规则

命名规范采用驼峰命名方式

Vue

<div @click=“check”>
    
</div>
check(){
    
}
<div onClick={this.check}>
<div onChange={this.check}>

事件参数传递
 <li onClick={this.check} className='active'>拼团管理</li>

绑定事件,提供事件函数,如果这个函数没有加括号。将函数地址放在这个模板中。

点击li元素,找到这个函数,并执行

 <li onClick={this.check(123)} className='active'>拼团管理</li>

对于JSX来说,立即调用check这个函数。

解决这个问题方案:

 <li onClick={()=>this.check("TabA")} className='active'>拼团管理</li>

给onClick这个事件绑定一个箭头函数,再这个箭头函数里面调用check函数。

只有事件被触发,箭头函数调用,check才会执行。传递参数过去

事件绑定中this指向问题

如果你函数是普通函数,默认拿到this结果为undefined

页面中绑定事件,节点来触发事件。React底层处理过,并不会直接返回节点。

import React, { Component } from 'react'
import styles from "../assets/styles/tabA.module.css"

export default class TabA extends Component {
  state = {
    count: 10
  }

  changeCount() {
    //React底层处理过这个事件
    console.log(this); //TabA
    this.setState({
      count:20
    })

  }
  render() {
    const { count } = this.state
    return (
      <div>
        <p>state的数据:{count}</p>
        <button onClick={this.changeCount.bind(this)}>点击触发</button>
      </div>
    )
  }
}

如果函数是普通函数,绑定事件bind来改变this的指向,函数里面this指向当前组件。

import React, { Component } from 'react'
import styles from "../assets/styles/tabA.module.css"

export default class TabA extends Component {
  state = {
    count: 10
  }
  changeCount = ()=>{
    console.log(this); //TabA
  }
  
  render() {
    const { count } = this.state
    return (
      <div>
        <p>state的数据:{count}</p>
        <button onClick={this.changeCount}>点击触发</button>
      </div>
    )
  }
}

总结:以后再React开发过程中,能用箭头函数就直接用箭头函数。

事件对象

只要有事件绑定,那一定会有event事件对象产生。

默认绑定不传递参数的时候,事件函数第一个参数就是event对象

changeCount = (event)=>{
    console.log(event.target);
}
<button onClick = {this.changeCount}>

如果你的事件函数要传递参数

changeCount = (event,val)=>{
    console.log(event,val);
  }
<button onClick={(event)=>this.changeCount(event,123)}>点击触发</button>

遇到要传递参数的时候,外层箭头函数才是事件绑定的函数。传递给内部的函数使用

(7)组件数据更新

组件内部数据state来定义。直接修改state的值,页面并不会检测更新

check(val){
        this.setState({
            active:val
        })
    }

当React检测到调用这个函数,将原来值进行替换。render函数重新调用一次

当我们进行数据修改的时候,setState里面传递的新的对象,修改的值和会原来state的值进行合并。不会覆盖

更新规则

setState在更新数据的时候。合并操作。并不是覆盖操作

state = {
    count: 10,
    msg:"xiaowang"
  }
  changeCount = (event,val)=>{
    this.setState({
      count:val
    })
  }

修改的时候,指定了修改的变量。state原来的值没有变化,并不会被覆盖。

异步更新

setState这个函数,可以更新state的数据,并且调用render实现页面刷新。

但是本身是一个异步任务。无法立即获取修改的结果

changeCount = (event,val)=>{
    //异步代码。
    this.setState({
      count:val
    })
    
    console.log(this.state.count); //原来的数据
  }

为了性能考虑,设计的方案,不管是Vue还是React,我们都是异步更新

为了能获取更新过后的结果,我们提供回调函数

changeCount = (event, val) => {
    //异步代码。
    this.setState({
      count: val
    }, () => {
      console.log(this.state.count); //得到更新过后结果
    })
  }
合并更新

setState一旦调用,数据发生更新,render就会被执行

changeCount = (event, val) => {
    //异步代码。
    this.setState({
      count: val
    })
    this.setState({
      count: 1000
    })
  }

一次性执行多个二setState,每个更新任务,都会放在队列中。

队列中任务会取出来进行合并,当发现多个任务的时候。后进入队列中将内容和前面任务合并了。

合并过后,只需要更新页面一次。不要频繁的触发

五、组件通信

父子组件通信

  1. 父传子:在父组件中定义值,通过动态参数传递给子组件,子组件接受结果
  2. 子传父:子组件调用父组件传递过来函数,将内容传递回去

父传子

组件的数据来源于两部分:

  1. 组件内部自身状态,state来定义
  2. 组件外部的状态,props接受

父组件

state = {
    currentPage:1,
    pageSize:3
}

const {currentPage,pageSize} = this.state
<Pageing currentPage={currentPage} pageSize={pageSize}>

子组件

如果是类组件,默认在this对象身上会有一个props的对象,这个对象默认代表外部数据。如果你传递。空对象

import React, { Component } from 'react'
import style from "../../assets/styles/fenye.module.css"

export default class Pageing extends Component {
    //mounted 挂载完毕
    componentDidMount(){
        console.log(this.props); -->{currentPage:1,pageSize:3}
    }
    render() {
        return (
            <div className={style.fenye}>
            	
            </div>
        )
    }
}

将props的值取出来,页面渲染。父组件中变量产生数据变化,子组件跟着变化

子组件如果要针对props的数据进行处理,一般是在子组件计算属性。

不要出现将props的值赋值给state,然后再去用state

验证

父组件调用子组件的时候,如果遇到子组件针对参数有验证规则。我们严格按照子组件要求来传递参数

子组件可以添加验证规则,一旦验证规则生效。使用这个组件,可以根据规则来传递参数

数据类型校验

在React老版本中,需要我们自己下载验证包,但是在新版本里面无需下载,直接使用

import React, { Component } from 'react'
import style from "../../assets/styles/fenye.module.css"
import PropTypes from "prop-types"

class Pageing extends Component {
    //mounted 挂载完毕
    get totalPage() {
        const { pageSize, total } = this.props
        return Math.ceil(total / pageSize)
    }
    render() {
        const { currentPage, pageSize, total } = this.props
        return (
            <div className={style.fenye}>
               ....
            </div>
        )
    }
}

//在暴露给外部调用之前,我们内部先进行props的验证
Pageing.propTypes = {
    currentPage:PropTypes.number.isRequired,
    pageSize:PropTypes.number.isRequired
}

export default Pageing

指定接受的参数要进行验证。

也可以设置默认值

import React, { Component } from 'react'
import style from "../../assets/styles/fenye.module.css"
import PropTypes from "prop-types"

class Pageing extends Component {
    //mounted 挂载完毕
    get totalPage() {
        const { pageSize, total } = this.props
        return Math.ceil(total / pageSize)
    }
    render() {
        const { currentPage, pageSize, total } = this.props
        return (
            <div className={style.fenye}>
                ....
            </div>
        )
    }
}

Pageing.defaultProps = {
    currentPage:3
}

export default Pageing

如果你没有传递对应属性,采用默认props值。父组件传递了属性。以父组件的数据为主

子传父

父组件传递一个函数给子组件,子组件调用这个函数。就可以将参数传递过去

父组件

getCurrentPage = (page)=>{
    
}

<Pageing getCurrentPage={this.getCurrentPage}>

子组件调用

fenye = (event)=>{
    const page = event.target.getAttribute("index")
    console.log(typeof page) //string
    this.props.getCurrentPage(Number(page))
}
<span index={1} onClick = {this.fenye}>1</span>

六、组件封装插槽

在React中子组件需要接受父组件定义模板。可以用插槽来接受数据

import React, { Component } from 'react'
import style from "../../assets/styles/model.module.css"

export default class Model extends Component {
  render() {
    return (
      <div className={style.container}>
        <div className={style.modelContent}>
            <h3>添加优惠券</h3>
            {this.props.children}
            {/* 存放表单 */}
            <button>关闭</button>
        </div>
      </div>
    )
  }
}

this.props.children代表接受父组件传递过来模板

七、表单组件

在React中没有提供Vue双向绑定语法,v-model

如果要操作表单,我们有两种模式。受控组件,非受控组件。

受控组件

含义:每个表单都会涉及到value属性或者checked属性,这些属性你需要获取到结果。

当我们定义state数据,和表单value或者checked属性建立绑定关系。将这个组件受控组件

受控组件开发步骤:

  1. 需要在state中定义属性

    state = {
        pname:"新人券"
    }
    
  2. 在表单元素上面,使用value属性显示state里面的结果

    <input value={this.state.pname}>
    

    input元素就变成受控组件。

  3. 必须给受控组件,提供一个onChange事件

    如果只是单向将state的数据,绑定文本框的value属性。可以实现数据显示。

    无法修改数据

    控制台默认抛出警告

    
    ynameChange = (event)=>{
    	this.setState({
            yname:event.target.value
        })
    }
    <input onChange={this.ynameChange} value={this.state.pname}>
    

受控组件就已经实现了。

这个过程就是v-model实现过程。Vue将双向绑定过程封装v-model语法糖

完整的表单通过受控组件获取参数

import React, { Component } from 'react'
import styles from "../assets/styles/tabA.module.css"
import Pageing from './UIComponent/Pageing'
import Model from './UIComponent/Model'

export default class TabA extends Component {
  state = {
    tableData: [
      {
        time: [
          "2021-01-13 17:39:42",
          "2023-01-13 17:39:42"
        ],
        useTime: [
          "2023-01-13 17:39:42",
          "2023-02-13 17:39:42"
        ],
        _id: "63919f95024a7602dc78082a",
        title: "测试优惠券",
        receiveType: "2",
        price: 30,
        state: false,
        show: false,
        system_data: true,
        create_time: "2022-12-08T08:25:57.209Z",
        imgSrc: "https://pic.616pic.com/ys_bnew_img/00/06/12/6QLoLGyq3C.jpg"
      },
      {
        time: [
          "2021-01-13 17:39:42",
          "2023-01-13 17:39:42"
        ],
        useTime: [
          "2022-01-13 17:39:42",
          "2024-01-13 17:39:42"
        ],
        _id: "63bd14c40d58511b7c6b6b18",
        title: "跨店满减",
        receiveType: "1",
        price: 200,
        state: true,
        show: true,
        create_time: "2023-01-10T07:33:24.011Z",
        imgSrc: "https://pic2.zhimg.com/v2-12943651b8b7fb48144151e638c8ed81_1440w.jpg?source=172ae18b"
      },
      {
        time: [
          "2021-01-13 17:39:42",
          "2023-01-13 17:39:42"
        ],
        useTime: [
          "2022-01-13 17:39:42",
          "2024-01-13 17:39:42"
        ],
        _id: "63bd14c40d58511b7c6b6b19",
        title: "新人券",
        receiveType: "2",
        price: 400,
        state: true,
        show: true,
        create_time: "2023-01-10T07:33:24.011Z",
        imgSrc: "https://pic2.zhimg.com/v2-12943651b8b7fb48144151e638c8ed81_1440w.jpg?source=172ae18b"
      },
      {
        time: [
          "2021-01-13 17:39:42",
          "2023-01-13 17:39:42"
        ],
        useTime: [
          "2022-01-13 17:39:42",
          "2024-01-13 17:39:42"
        ],
        _id: "63bd14c40d58511b7c6b6b20",
        title: "满100减20",
        receiveType: "3",
        price: 80,
        state: true,
        show: true,
        create_time: "2023-01-10T07:33:24.011Z",
        imgSrc: "https://pic2.zhimg.com/v2-12943651b8b7fb48144151e638c8ed81_1440w.jpg?source=172ae18b"
      },
      {
        time: [
          "2021-01-13 17:39:42",
          "2023-01-13 17:39:42"
        ],
        useTime: [
          "2022-01-13 17:39:42",
          "2022-01-15 17:39:42"
        ],
        _id: "63bd14c40d58511b7c6b6b21",
        title: "数码产品券",
        receiveType: "1",
        price: 60,
        state: true,
        show: true,
        create_time: "2023-01-10T07:33:24.011Z",
        imgSrc: "https://pic2.zhimg.com/v2-12943651b8b7fb48144151e638c8ed81_1440w.jpg?source=172ae18b"
      },
      {
        time: [
          "2021-01-13 17:39:42",
          "2023-01-13 17:39:42"
        ],
        useTime: [
          "2022-01-13 17:39:42",
          "2022-02-13 17:39:42"
        ],
        _id: "63bd14c40d58511b7c6b6b22",
        title: "跨店满减",
        receiveType: "2",
        price: 90,
        state: true,
        show: true,
        create_time: "2023-01-10T07:33:24.011Z",
        imgSrc: "https://pic2.zhimg.com/v2-12943651b8b7fb48144151e638c8ed81_1440w.jpg?source=172ae18b"
      },
      {
        time: [
          "2021-01-13 17:39:42",
          "2023-01-13 17:39:42"
        ],
        useTime: [
          "2022-01-13 17:39:42",
          "2022-01-13 17:39:42"
        ],
        _id: "63bd14c40d58511b7c6b6b23",
        title: "中秋现礼券",
        receiveType: "3",
        price: 10,
        state: true,
        show: true,
        create_time: "2023-01-10T07:33:24.011Z",
        imgSrc: "https://pic2.zhimg.com/v2-12943651b8b7fb48144151e638c8ed81_1440w.jpg?source=172ae18b"
      }
    ],
    searchType: "",
    searchData: "",
    status: 0,
    orderBy: 0,
    currentPage: 1,
    pageSize: 3,
    visible1: false,
    visible2: false,
    yname: "",
    yprice: "",
    ytype: "",
    ytime: [],
    ystate: ""
  }
 
  /**
   * 优惠券添加
   * @returns 
   */
  ynameChange = (event) => {
    const value = event.target.value;
    this.setState({
      yname: value
    })
  }

  ytypeChange = (event) => {
    // "true"
    // Boolean("false") false 
    const value = event.target.value;
    this.setState({
      ystate: JSON.parse(value)
    })
  }

  ytimeChange = (event) => {
    const value = event.target.value
    const { ytime } = this.state
    if (event.target.checked) {
      ytime.push(value)
    }else {
      const index = ytime.findIndex(item=>item.ytime == value)
      ytime.splice(index,1)
    }
    this.setState({
      ytime
    })
  }


  render() {
    return (
      <React.Fragment>
       
        {/* 添加优惠券的模态框 */}
        <Model title="添加优惠券" close={this.modelClose1} visible={visible1}>
          {/* 插槽的概念 */}
          <div className='item'>
            <label htmlFor="">优惠券名字:</label>
            <input type="text" onChange={this.ynameChange} value={this.state.yname} placeholder='请输入优惠券名字' />
          </div>
          <div className='item'>
            <label htmlFor="">优惠券价格:</label>
            <input type="text" onChange={(e) => this.setState({ yprice: e.target.value })} value={this.state.yprice} />
          </div>
          <div className='item'>
            <label htmlFor="">优惠券类型:</label>
            <select name="" id="" onChange={(e) => this.setState({ ytype: e.target.value })} value={this.state.ytype}>
              <option value="">请选择</option>
              <option value="1">新人券</option>
              <option value="2">门槛券</option>
              <option value="3">通用券</option>
            </select>
          </div>
          <div className='item'>
            <label htmlFor="">优惠券状态:</label>
            <span>开启</span>
            <input type="radio" onChange={this.ytypeChange} value={true} name='state' />
            <span>关闭</span>
            <input type="radio" onChange={this.ytypeChange} value={false} name='state' />
          </div>
          <div>
            <label htmlFor="">日期:</label>
            <span>2023-01-02</span>
            <input type="checkbox" onChange={this.ytimeChange} value="2023-01-02" />
            <span>2023-04-02</span>
            <input type="checkbox" onChange={this.ytimeChange} value="2023-04-02" />
          </div>
          <div className='item'>
            <button>添加</button>
          </div>
        </Model>
        {/* 修改优惠券模态框 */}
        <Model title="修改优惠券" close={this.modelClose2} visible={visible2}>
          <input type="text" />
        </Model>
      </React.Fragment>
    )
  }
}

非受控组件

概念

回顾:表单元素value属性或者checked属性跟state的状态绑定在一起。表单元素内部的状态被state控制了。如果要进行修改提供change事件获取值,更新state达到state的值更新,页面表单值或者状态更新

非受控组件:表单的值和state没有直接绑定在一起。表单是独立了。值也可以动态修改。可以获取表单的值,保存到state中。

介绍两个运算符

?.

{/* ?.代表可选链运算符,这是ES2020新增的特性。当useTime undefined或者null 不会继续往后执行 */}
{this.state.product.useTime?.indexOf("2023-01-13 17:39:42")}

当检测到?.前面结果是undefined或者null的时候,结束继续往后面寻找

 this.state.product.useTime && this.state.product.useTime.indexOf()

??

let m = a ?? "xiaowang"

当检测到a这个变量是undefined或者null的时候。默认返回??后面结果

回显

在表单组件中,如果要使用非受控组件来进行数据回显。

可以使用defaultValuedefaultChecked

<div className='item'>
    <label htmlFor="">优惠券名字:</label>
    <input disabled defaultValue={this.state.product.title} type="text" placeholder='请输入优惠券名字' />
</div>
<div className='item'>
    <label htmlFor="">优惠券类型:</label>
    <select name="" id="" defaultValue={this.state.product.receiveType}>
        <option value="">请选择</option>
        <option value="1">新人券</option>
        <option value="2">门槛券</option>
        <option value="3">通用券</option>
    </select>
</div>
<div className='item'>
    <label htmlFor="">优惠券状态:</label>
    <span>开启</span>
    <input type="radio" defaultChecked={this.state.product.state==true} value={true} name='state' />
    <span>关闭</span>
    <input type="radio" defaultChecked={this.state.product.state==false} value={false} name='state' />
</div>
<div>
    <label htmlFor="">日期:</label>
    <span>2023-01-02</span>
    <input type="checkbox" defaultChecked={this.state.product.useTime?.includes("2023-01-13 17:39:42")} value="2023-01-13 17:39:42" />
    <span>2023-04-02</span>
    <input type="checkbox" defaultChecked={this.state.product.useTime?.includes("2023-02-13 17:39:42")} value="2023-02-13 17:39:42" />
</div>
获取表单的数据

非受控组件采用获取节点,在获取数据。更新到state中

针对表单

<input ref={callback}/>

callback回调函数,传递ref,调用函数并将节点返回给函数。保存起来

<input ref={element=>this.titleElement=element}/>

将得到节点默认挂载this全局。以后再任何一个函数中,都可以通过this来获取

再函数中获取到表单的值

update = ()=>{
    const element = this.titleElement //获取input文本框
    const value = element.value
}

完整绑定

<div className='item'>
    <label htmlFor="">优惠券名字:</label>
    <input ref={element => this.titleElement = element} defaultValue={this.state.product.title} type="text" placeholder='请输入优惠券名字' />
</div>
<div className='item'>
    <label htmlFor="">优惠券类型:</label>
    <select ref={element => this.typeElement = element} name="" id="" defaultValue={this.state.product.receiveType}>
        <option value="">请选择</option>
        <option value="1">新人券</option>
        <option value="2">门槛券</option>
        <option value="3">通用券</option>
    </select>
</div>
<div className='item'>
    <label htmlFor="">优惠券状态:</label>
    <span>开启</span>
    <input type="radio" ref={element => this.stateElement1 = element} defaultChecked={this.state.product.state == true} value={true} name='state' />
    <span>关闭</span>
    <input type="radio" ref={element => this.stateElement2 = element} defaultChecked={this.state.product.state == false} value={false} name='state' />
</div>
<div>
    <label htmlFor="">日期:</label>
    <span>2023-01-02</span>
    <input type="checkbox" ref={element => this.timeElement1 = element} defaultChecked={this.state.product.useTime?.includes("2023-01-13 17:39:42")} value="2023-01-13 17:39:42" />
    <span>2023-04-02</span>
    <input type="checkbox" ref={element => this.timeElement2 = element} defaultChecked={this.state.product.useTime?.includes("2023-02-13 17:39:42")} value="2023-02-13 17:39:42" />
</div>
<div>
    <button onClick={this.updateMsg}>修改</button>
</div>

对应的js代码如下

updateMsg = () => {
    const title = this.titleElement.value;
    const receiveType = this.typeElement.value;
    if (this.stateElement1.checked) {
      console.log(this.stateElement1.value);
    } else {
      console.log(this.stateElement2.value);
    }

    if (this.timeElement1.checked) {
      console.log(this.timeElement1.value);
    }
    if (this.timeElement2.checked) {
      console.log(this.timeElement2.value);
    }

    //将获取结果,用于覆盖product对象
    const {product,tableData} = this.state

    let newObj = {...product,title,receiveType}

    //替换tableData中原来的这个对象
    const index = tableData.findIndex(item=>item._id == newObj._id)
    tableData[index] = newObj

    this.setState({
      tableData
    })

  }

事件委托获取表单值

采用事件委托的方式,我们可以动态构造一个对象,获取值存放对象中

每个表单元素都有change事件,事件委托在父级添加一个change

每个表单元素新增name属性,这个属性就是你想要的对象key

import React, { Component } from 'react'

export default class TabB extends Component {
  state = {
    obj: {
      useTime: []
    }
  }
  contenChange = (event) => {
    const { obj } = this.state
    const element = event.target
    const value = element.value
    //获取element这个表单组件的name属性。对象key,value对象值
    const key = element.name
    if (key == "useTime") {
      if (element.checked) {
        obj.useTime.push(value)
      } else {
        const index = obj.useTime.findIndex(item => item == value)
        obj.useTime.splice(index, 1)
      }
    } else {
      obj[key] = value
    }
    this.setState({
      obj
    })
  }
  render() {
    return (
      <div onChange={this.contenChange}>
        <div className='item'>
          <label htmlFor="">优惠券名字:</label>
          <input type="text" name='title' placeholder='请输入优惠券名字' />
        </div>
        <div className='item'>
          <label htmlFor="">优惠券价格:</label>
          <input type="text" name='price' />
        </div>
        <div className='item'>
          <label htmlFor="">优惠券类型:</label>
          <select name="receiveType" id="">
            <option value="">请选择</option>
            <option value="1">新人券</option>
            <option value="2">门槛券</option>
            <option value="3">通用券</option>
          </select>
        </div>
        <div className='item'>
          <label htmlFor="">优惠券状态:</label>
          <span>开启</span>
          <input type="radio" value={true} name='state' />
          <span>关闭</span>
          <input type="radio" value={false} name='state' />
        </div>
        <div>
          <label htmlFor="">日期:</label>
          <span>2023-01-02</span>
          <input type="checkbox" name='useTime' value="2023-01-02" />
          <span>2023-04-02</span>
          <input type="checkbox" name='useTime' value="2023-04-02" />
        </div>
        <div className='item'>
          <button>添加</button>
        </div>
      </div>
    )
  }
}

八、组件生命周期

Vue应用生命周期

main.js

import Vue from "vue"
import App from ""

const app = new Vue({
    router,
    store,
    created(){
        
    },
    mounted(){
        
    }
}).$mount("#app")

单页面开发,整个应用程序,只需要new一次Vue

关闭项目,Vue才会销毁。

组件生命周期

因为涉及到单页面开发,引入路由来链接你们组件。路由切换组件创建或者销毁

组件生命周期:

  1. 挂载阶段:组件创建完成后,被加载到应用中。
  2. 更新阶段:组件的state(内部数据),组件props(外部数据)发生变化。完成更新阶段
  3. 销毁阶段:离开这个组件的时候,默认销毁组件

挂载阶段

进入组件中

  1. 执行构造器,创建当前组件实例
  2. 执行render函数,返回要挂载的模板。React加载模板并渲染
  3. 执行componentDidMount,实际上表示模板已经加载完毕
// mounted
componentDidMount(){
    console.log("03-componentDidMount");
}

可以在这个生命周期函数中,发送异步请求。获取页面DOM节点

更新阶段

组件state或者props发生数据变化,都要进入第二步

更新之前

// 更新阶段,获取更新props,更新state
shouldComponentUpdate(newProps,newState){
    const {count} = this.state
    if(count == newState.count){
        return false
    }

    return true
}

这个生命周期函数,需要提供boolean值,

true:允许页面更新,接下来调用render函数

false: 阻止更新

props发生变化,这个生命也执行

componentDidUpdate(){
    console.log("componentDidUpdate");
  }

先执行render,完成渲染。调用更新完毕的函数

销毁阶段

销毁的时候,可以在生命周期函数中,执行资源清理

 componentWillUnmount(){
    console.log("componentWillUnmount");
  }

比如关闭定时器、解除元素的事件绑定、比如state数据清空等等