• React笔记(四)类组件(2)


    一、类组件的props属性

    组件中的数据,除了组件内部的状态使用state之外,状态也可以来自组件的外部,外部的状态使用类组件实例上另外一个属性来表示props

    1、基本的使用

    在components下创建UserInfo组件

    1. import React, { Component } from 'react'
    2. import '../assets/css/userinfo.scss'
    3. export default class UserInfo extends Component {
    4.  render() {
    5.    return (
    6.      <div className='info-box'>
    7.          <h3>{this.props.title}h3>
    8.          <div>姓名:{this.props.name}div>
    9.          <div>年龄:{this.props.age}div>
    10.          <div>性别:{this.props.gender===1?'男':'女'}div>
    11.          <div>爱好:{this.props.hobby.join("、")}div>
    12.      div>
    13.   )
    14. }
    15. }

    在App.jsx中引用组件,并且传递数据到组件的内部

    1. import React, { Component } from 'react'
    2. import UserInfo from './components/UserInfo'
    3. export default class App extends Component {
    4.  constructor(){
    5.    super()
    6.    this.state={
    7.      users:[
    8.       {
    9.          title:'教师信息',
    10.          name:'翟吉喆',
    11.          age:38,
    12.          gender:1,
    13.          hobby:['爱打游戏','爱读书']
    14.       },
    15.       {
    16.          title:'学生信息',
    17.          name:'张资源',
    18.          age:24,
    19.          gender:1,
    20.          hobby:['爱写js','爱UI设计']
    21.       },
    22.       {
    23.          title:'班主任信息',
    24.          name:'王玉倩',
    25.          age:28,
    26.          gender:0,
    27.          hobby:['爱唱歌','爱学习']
    28.       }
    29.     ]
    30.   }
    31. }
    32.  render() {
    33.    const {users}=this.state
    34.    return (
    35.      <>
    36.         {
    37.            users.map((item,index)=><UserInfo key={index} {...item}/>)
    38.         }
    39.      
    40.   )
    41. }
    42. }
    2、props的默认值

    React的props的默认值设置有两种方法

    • 类名.defaultProps={key:value}

    1. export default class UserInfo extends React.Component{}
    2. UserInfo.defaultProps={
    3.  title:'暂无标题',
    4.  name:'暂无名称',
    5.  age:0,
    6.  gender:1,
    7.  hobby:['暂无兴趣'],
    8.  birthday:new Date(),
    9.  avatar:'http://old.woniuxy.com/page/img/head_picture/0.jpg'
    10. }
    • static defaultProps={key:value}(推荐)

    1. export default class UserInfo extends React.Component{
    2.    static defaultProps={
    3.        title:'暂无标题',
    4.        name:'暂无名称',
    5.        age:0,
    6.        gender:1,
    7.        hobby:['暂无兴趣'],
    8.        birthday:new Date(),
    9.        avatar:'http://old.woniuxy.com/page/img/head_picture/0.jpg'
    10.     }
    11. }
    3、props验证器[了解]

    配置React验证器的步骤

    • 下载prop-types依赖包

    yarn add prop-types
    • 引入prop-types

    import PropTypes from 'prop-types'
    • 配置验证器

      配置验证器也有两种方法

      • 类名.propTypes={key:value}

      1. export default class UserInfo extends React.Component{}
      2. UserInfo.propTypes={
      3.    age:PropTypes.number.isRequired
      4. }
      • static propTypes={key:value}

      1. export default class UserInfo extends React.Component{
      2.   static propTypes={
      3.    age:PropTypes.number.isRequired
      4.   }
      5. }
      4、props的只读性

      总结:

      • 通过props传递进来的基本类型,组件内部不能直接更改,如果更改就会报如下错误

      • 通过props传递进来的引用数据,不能直接更改,如果更改会报如下错误

      • 通过props传递进来的引用数据类型,可以更改引用数据类型的属性

    二、React插槽

    在React中实现插槽需要我们自己来实现 主要用到props.children

    三、组件的生命周期

    生命周期指的就是从组件的加载初始化-数据的改变-组件的卸载阶段。描述的就是组件从创建到死亡的阶段,react的生命周期中提供了很多个生命周期钩子函数来方便我们操作。

    react的生命周期主要分为以下的几个步骤:

    1. 挂载阶段:组件数据的初始化,及组件数据的渲染

    2. 运行阶段:这个节点是最长的阶段,主要用户对组件的数据进行修改,状态改变以及重绘

    3. 卸载阶段:这个阶段也是销毁阶段,组件运行完成后,或者从页面中移除,那么组件就应该被销毁。这个阶段我们可以执行一些资源的回收,性能优化的代码可以在这里设计。

    组件的生命周期流程图

    React lifecycle methods diagram

    1、组件挂载阶段

    组件的挂载阶段也是组件创建-数据初始化-数据渲染的过程。接下来通过代码来演示执行的流程。

    1.1 constructor构造器执行的阶段

    构造执行那就意味着组件正在被创建,并且数据也可以在这里初始化。

    通常,在 React 中,构造函数仅用于以下两种情况:

    • 通过给 this.state 赋值对象来初始化内部 state

    • 为事件处理函数绑定实例

    如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

    1. import React, { Component } from 'react'
    2. export default class LifeCycle extends Component {
    3. constructor(props){
    4. super(props)
    5. this.state={count:0}
    6. this.increment=this.increment.bind(this)
    7. console.log('====执行constructor组件正在创建和初始化');
    8. }
    9. increment(){
    10. this.setState({count:this.state.count+1})
    11. }
    12. render() {
    13. console.log("====调用render方法开始执行数据渲染");
    14. return (
    15. <div>
    16. <h1>React生命周期全过程h1>
    17. {this.state.count}
    18. <button onClick={this.increment}>+button>
    19. div>
    20. )
    21. }
    22. }

    输出的结果为:

    ====执行constructor组件正在创建和初始化
    ====调用render方法开始执行数据渲染
    

    先执行constructor构造器将数据初始化,其中props和state的数据就会被加载。接着执行render函数来渲染。

    1.2 componentDidMount函数执行

    界面渲染完毕(DOM挂载完毕)的一个通知,类似于vue组件中的created,我们可以在这个生命周期做

    很多事情。

    • 获取DOM节点

    • 发送请求获取后台数据

    • 设置定时器、延时器等等

    • 绑定全局事件,例如document的点击事件等等

    1. async componentDidMount(){
    2. console.log("===componentDidMount==");
    3. const {data:{movies}}=await
    4. axios.get('https://www.fastmock.site/mock/4441a79ad5f884f83b784cf4c588bdb6/movies/getHotMovies')
    5. }
    2、组件更新阶段

    每当组件的props或者state变化之后,都会导致组件的更新,我们可以使用钩子函数在组件更新之前或者之后做一些逻辑操作。

    2.1、创建shouldComponentUpdate钩子函数来拦截数据修改

    1. shouldComponentUpdate(){
    2. return false;
    3. }

    定义在重新render之前被调用,可以返回一个布尔值来决定一个组件是否更新, 如果返回false,那么前面的流程都不会被触发。这个方法默认的返回值都是true。

    这里我们再来完成一个功能就是将外部传递进来的数据赋给内部变量data,当点击按钮的时候每次增加1

    1. import React, { Component } from 'react'
    2. import axios from 'axios'
    3. export default class LifeCycle extends Component {
    4. constructor(props){
    5. super(props)
    6. this.state={title:props.title}
    7. }
    8. changeTitle=()=>{
    9. this.setState({
    10. title:"计数器演示"
    11. })
    12. }
    13. render() {
    14. console.log("====调用render方法开始执行数据渲染");
    15. return (
    16. <div>
    17. <h1>{this.state.title}h1>
    18. <button onClick={this.changeTitle}>更改标题button>
    19. div>
    20. )
    21. }
    22. shouldComponentUpdate(nextProps,nextState){
    23. console.log(this.state.title+"---------->"+nextState.title);
    24. return true
    25. }
    26. }

    但是从控制台的显示结果来看,每次点击之后都是上次的数据,如果要想得到更改后的数据我们可以使用如下参数来解决这个问题

    参数:nextProps代表更改后的props值;nextState代表更改过后的state值。我们可以获取到这个数据进行判断

    1. shouldComponentUpdate(nextProps,nextState){
    2. console.log(this.state.data+"---->"+nextState.data);
    3. return true;
    4. }
    5. 控制台显
    示效果如下所示

    2.3 componentDidUpdate钩子函数来执行通知。

    1. componentDidUpdate(){
    2. console.log("数据修改成功");
    3. }
    3、组件卸载阶段

    componentWillUnmount,在组件被卸载和销毁之前调用的方法,可以在这里做一些清理的工作。我们要完成组件的卸载,那需要创建两个组件,在父组件中引入子组件,当条件为true的时候就加载组件,条件为false的时候就卸载组件。

    1. export default class ParentCom extends Component {
    2. state = {
    3. isShow:true
    4. }
    5. changePanel = ()=>{
    6. this.setState({
    7. isShow:false
    8. })
    9. }
    10. render() {
    11. return (
    12. <div>
    13. <h1>ParentComponenth1>
    14. {this.state.isShow && <ChildCom/>}
    15. {/* {this.state.isShow && } */}
    16. <button type="button" onClick={this.changePanel}>点击切换button>
    17. div>
    18. )
    19. }
    20. }

    花括号中嵌入逻辑与 (&&) 运算符。它可以很方便地进行元素的条件渲染,&&是我们的短路运算符,一旦this.state.boo的值为false,那后面的组件就不会渲染。

    当点击按钮我们动态修改isShow的值,然后判断条件false组件就卸载。

    1. export default class ChildCom extends Component {
    2. render() {
    3. return (
    4. <div>
    5. <h2>ChildComponenth2>
    6. div>
    7. )
    8. }
    9. componentWillUnmount(){
    10. console.log("ChildCom组件正在卸载");
    11. }
    12. }

    一旦组件卸载那就执行componentWillUnmount钩子函数完成调用,那我们可以完成清理工作。

    执行结果为:

    ChildCom组件正在卸载
    4、资源清理

    在componentWillUnmount钩子函数中我们可以执行一些资源清理工作。比如事件解绑,定时器清除等等工作。

    先在ChildCom子组件里面定义事件和定时器等等代码。

    1. componentDidMount(){
    2. this.timer = setInterval(function(){
    3. console.log("定时器执行");
    4. },1000);
    5. }

    在componentDidMount组件里面设置一个定时器,我们在父组件里面将组件卸载。但是定时器依然在执行所以,所以在组件卸载的时候我们需要清楚定时器。

    1. componentWillUnmount(){
    2. console.log("ChildCom组件正在卸载");
    3. clearInterval(this.timer);
    4. }

    执行完组件的卸载,那定时器就被清除。

    四、兄弟组件通信

    • 状态提升

    • 事件总线

    • 发布订阅模式

    1、状态提升

    React中的状态提升概括来讲,就是将多个组件需要共享的状态提升到他们最近的父组件上,再在父组件上改变这个状态,然后通过props分发给子组件

    1)新建Child.jsx组件

    1. import React, { Component } from 'react'
    2. import './son.css'
    3. export default class Son extends Component {
    4. render() {
    5. return (
    6. <div className='sonbox'>
    7. <h2>儿子h2>
    8. <button onClick={()=>{
    9. this.props.callback('通过父亲给妹妹1万元')
    10. }}>发送button>
    11. div>
    12. )
    13. }
    14. }

    2)新建Parent.jsx组件

    1. import React, { Component } from 'react'
    2. import './parent.css'
    3. import Son from './Son'
    4. import Daughter from './Daughter'
    5. export default class Parent extends Component {
    6. state={
    7. fromChildMsg:''
    8. }
    9. render() {
    10. return (
    11. <div className='box'>
    12. <Son className="c1" callback={(msg)=>{
    13. //接收儿子给的信息
    14. console.log('接受儿子的信息',msg);
    15. //然后将这个信息更新到状态中
    16. this.setState(()=>{return {fromChildMsg:msg}},()=>{console.log(this.state.fromChildMsg)})
    17. }}>Son>
    18. <Daughter className="c2" cmsg={this.state.fromChildMsg}>Daughter>
    19. div>
    20. )
    21. }
    22. }

    3)新建Daughter.jsx

    1. import React, { Component } from 'react'
    2. import './daughter.css'
    3. export default class Daughter extends Component {
    4. render() {
    5. return (
    6. <div className='daughterbox'>
    7. <h2>女儿h2>
    8. <div>{this.props.cmsg}div>
    9. div>
    10. )
    11. }
    12. }
    2、事件总线方式

    兄弟之间传递参数我们有多种方案,本节中我们就给大家带来。基于事件总线的方式来设计

    EventBus 又称为事件总线。在Vue中可以使用 EventBus 来作为沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件,但也就是太方便所以若使用不慎,就会造成难以维护的灾难,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次。

    但在React中没有EventBus的概念,可以通过 node events模块进行模拟,在React中可以依赖一个使用较多的库 events 来完成对应的操作。

    1)安装events

    yarn add events

    2)创建事件中心

    新建一个文件MyEventListener.js文件用于作为事件中心。

    1. import { EventEmitter } from 'events';
    2. export default new EventEmitter();

    我们只需要引入EventEmitter对象,将这个对象实例化过后返回出去,调用的时候执行对应的API就可以完成事件的绑定和通知

    events常用的API:

    • 创建EventEmitter对象:eventBus对象;

    • 发出事件:eventBus.emit("事件名称", 参数列表);

    • 监听事件:eventBus.addListener("事件名称", 监听函数);

    • 移除事件:eventBus.removeListener("事件名称", 监听函数);

    3)监听事件

    1. import React, { Component } from 'react'
    2. import MyEventListener from './MyEventListener'
    3. export default class Borther1 extends Component {
    4. state={
    5. className:'web05'
    6. }
    7. //在组件完毕后就监听dataChange事件
    8. componentDidMount(){
    9. MyEventListener.addListener("dataChange",this.changeData);
    10. }
    11. //当组件卸载的时候就移除事件监听
    12. componentWillUnmount(){
    13. MyEventListener.removeListener('dataChange',this.changeData);
    14. }
    15. changeData=(params)=>{
    16. this.setState({
    17. className:params
    18. });
    19. }
    20. render() {
    21. return (
    22. <div>
    23. <h2>兄弟1h2>
    24. <p>数据为:{this.state.className}p>
    25. div>
    26. )
    27. }
    28. }

    在生命周期函数里面绑定事件监听和移除事件监听,当事件中心里面加入了dataChange事件,当前组件就能监听到,接着执行我们绑定的changeData。

    4)发送事件

    在Brother2组件里面我们添加一个按钮,往事件中心发出一个dataChange事件,并将值传递给调用者

    1. import React, { Component } from 'react'
    2. import MyEventListener from './MyEventListener'
    3. export default class Borther2 extends Component {
    4. changeData=()=>{
    5. MyEventListener.emit("dataChange","web08");
    6. }
    7. render() {
    8. return (
    9. <div>
    10. <h2>兄弟2h2>
    11. <button onClick={this.changeData}>修改数据button>
    12. div>
    13. )
    14. }
    15. }

    其中emit这个API就可以完成事件发送。这样就可以完成兄弟组件之间的参数传递,当然事件总线这种设计模式可以应用在任何组件之间。实现跨组件通信。

    3、发布与订阅模式

    1)定义订阅者

    1. //创建一个observer.js文件
    2. const observer={
    3. list:[],
    4. subscribe(callback){
    5. this.list.push(callback)
    6. },
    7. dispatch(data){
    8. this.list.forEach(item => {
    9. item(data)
    10. });
    11. }
    12. }
    13. export default observer

    2)定义子组件用来发送数据

    1. import React, { Component } from 'react'
    2. import './son.css'
    3. import observer from '../observer'
    4. export default class Son extends Component {
    5. render() {
    6. return (
    7. <div className='sonbox'>
    8. <h2>儿子h2>
    9. <button onClick={()=>{
    10. observer.dispatch('兄弟姐妹们,我给每个人200元')
    11. }}>发送button>
    12. div>
    13. )
    14. }
    15. }

    3)定义子组件用来接受数据

    1. import React, { Component } from 'react'
    2. import './daughter.css'
    3. import observer from '../observer'
    4. export default class Daughter extends Component {
    5. state={
    6. fromMsg:''
    7. }
    8. componentDidMount(){
    9. observer.subscribe(data=>{
    10. this.setState(()=>{
    11. return{
    12. fromMsg:data
    13. }
    14. })
    15. })
    16. }
    17. render() {
    18. return (
    19. <div className='daughterbox'>
    20. <h2>女儿h2>
    21. <div>{this.state.fromMsg}div>
    22. div>
    23. )
    24. }
    25. }

    五、表单处理

    表单的处理:从表单中获取内容

    vue中如果获取表单的内容:v-model:是一个双向绑定的指令,react不是一个双向绑定的

    1、受控表单
    1.1、什么是受控组件

    在 HTML 中,表单元素(如