React—06—组件化;组件生命周期;组件通信(父子组件通信、插槽、作用域插槽、context全局);


 

 

零、什么是组件化

组件化思想的应用:

有了组件化的思想,我们在之后的开发中就要充分的利用它。 尽可能的将页面拆分成一个个小的、可复用的组件。 这样让我们的代码更加方便组织和管理,并且扩展性也更强。

 

React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分成很多类组件:

  • 根据组件的定义方式,可以分为:函数组件(Functional Component )和类组件(Class Component);
  • 根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component )和有状态组件(Stateful Component);
  • 根据组件的不同职责,可以分成:展示型组件(Presentational Component)和容器型组件(Container Component);

这些概念有很多重叠,但是他们最主要是关注数据逻辑和UI展示的分离:

  • 函数组件、无状态组件、展示型组件主要关注UI的展示;
  • 类组件、有状态组件、容器型组件主要关注数据逻辑; 当然还有很多组件的其他概念:

比如异步组件、高阶组件等,我们后续再学习。

 

一、类组件

类组件的定义有如下要求:

  1. 组件的名称是大写字符开头(无论类组件还是函数组件)
  2. 类组件需要继承自 React.Component
  3. 类组件必须实现render函数;render() 方法是 class 组件中唯一必须实现的方法;
  4. constructor函数是可选的,但我们通常在constructor中初始化一些数据; this.state中维护的就是我们组件内部的数据; 

 

 

 

(2)render函数的返回值

当this.setState修改后,render函数会被重新调用;并且返回以下类型之一:

  1. React元素即React.createElement(): 就是我们JSX语法创建的东西。 例如,<div /> 会被babel和React 渲染为 DOM 节点,<MyComponent /> 会被 babel和React 渲染为自定义组件; 无论是 <div /> 还是 <MyComponent /> 均为 React 元素。
  2. 数组:react会把数组元素遍历出来按顺序展示。
  3. fragments:使得 render 方法可以返回多个元素。
  4. Portals:可以渲染子节点到不同的 DOM 子树中。
  5. 字符串或数值类型:它们在 DOM 中会被渲染为文本节点
  6. 布尔类型或 null或undefined:什么都不渲染。

 

我们写的jsx本质不就是React.createElement()吗?所以jsx就是React.createElement()的语法糖,本质就是react元素。

 1 class App extends React.Component {
 2   constructor() {
 3     super();
 4     this.state = {
 5       message: 'Hello React Scaffold'
 6     };
 7   }
 8 
 9   render() {
10     const { message } = this.state;
11 
12     //  1.react元素即React.createElement
13     // return (
14     //   <div>
15     //     <h2>{message}</h2>
16     //     <HelloWorld />
17     //   </div>
18     // );
19 
20     // // 2. 数组(数组里面什么类型都可以写,最终会把

数组元素遍历出来按顺序展示)
21 // return [<h1>Hello World</h1>, 111, '星际穿越']; 22 23 // // 3. 字符串 24 // return 'Hello World'; 25 26 // // 4. 数字 27 // return 111; 28 29 // // 5. 布尔值 30 return true; 31 } 32 }

 

 

 

二、函数式组件

函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容。

函数组件有自己的特点(当然,后面我们会讲hooks,就不一样了):

  • 没有生命周期,也会被更新并挂载,但是没有生命周期函数;
  • this关键字不能指向组件实例(因为没有组件实例);
  • 没有内部状态(state);

 

函数式组件,要返回的类型和类组件中render函数返回一样的类型:

 1 function App() {
 2   //  1.react元素即React.createElement
 3   // return (
 4   //   <div>
 5   //     <h2>{message}</h2>
 6   //     <HelloWorld />
 7   //   </div>
 8   // );
 9 
10   // // 2. 数组
11   // return [<h1>Hello World</h1>, 111, '星际穿越'];
12 
13   // // 3. 字符串
14   // return 'Hello World';
15 
16   // // 4. 数字
17   return 111;
18 
19   // // 5. 布尔值
20   // return true;
21 }

 

 三、生命周期

 

export default class App extends React.Component {
  constructor() {
    super();
    console.log('[ 1111constructor ] >');
  }

  render() {
    console.log('[ 2222render ] >');
    return (
      <div>
        <h2>Hello World</h2>
      </div>
    );
  }

  componentDidMount() {
    console.log('[ 3333 componentDidMount ] >');
  }

  componentDidUpdate() {
    console.log('[ 4444 componentDidUpdate ] >');
  }

  componentWillUnmount() {
    console.log('[ 5555 componentWillUnmount ] >');
  }

}

 

一个组件的挂载流程:

先执行constructor里的内容,然后再执行render函数里的内容,最后在渲染完成后,执行componentDidMount钩子函数。

一个组件的更新流程:

先执行render函数,更新完成后,再执行componentDidUpdate钩子函数。(挂载即首次渲染流程,不会执行componentDidUpdate)

一个组件的销毁流程:

在销毁之前,执行componentWillUnmount钩子函数。

 

父子组件的挂载流程:

父constructor --》父render-》子constructor-》子render-》子componentDidMount钩子-》父componentDidMount钩子。

 

四、 生命周期函数

1.Constructor

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

constructor中通常只做两件事情: 通过给 this.state 赋值对象来初始化内部的state; 为事件绑定实例(this);

 

2.componentDidMount

componentDidMount() 会在组件挂载完成后(插入 DOM 树后)立即调用。

componentDidMount中通常进行哪里操作呢?

  • 依赖于DOM的操作可以在这里进行,因为此时dom已经挂载完成。;
  • 在此处发送网络请求就最好的地方;(官方建议)
  • 可以在此处添加一些订阅(会在componentWillUnmount取消订阅);

3.componentDidUpdate

componentDidUpdate() 会在更新完成后会被立即调用,首次渲染不会执行此方法。

当组件更新完成后,可以在此处对 DOM 进行操作;

如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)。

 

4.componentWillUnmount

componentWillUnmount() 会在组件卸载及销毁之前直接调用。

在此方法中执行必要的清理操作; 例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等;

 

 

以下是不常用的生命周期函数:

 

 

 

 

 

 

 

 


 

 


 


在开发过程中,我们会经常遇到需要组件之间相互进行通信:

比如App可能使用了多个Header,每个地方的Header展示的内容不同,那么我们就需要使用者传递给Header一些数据,让其进行展示

; 又比如我们在Main中一次性请求了Banner数据和ProductList数据,那么就需要传递给他们来进行展示;

也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件;

总之,在一个React项目中,组件之间的通信是非常重要的环节; 父组件在展示子组件,可能会传递一些数据给子组件:

父组件通过 属性=值 的形式来传递给子组件数据;

子组件通过 props 参数获取父组件传递过来的数据;

 

一、父传子

在jsx中,直接在组件占位符上写即可,和vue类似;

react:

传递字符串title='title'

传递变量banner={varData}

vue:

传递字符串title='title'

传递变量 v-bind:banner=‘varData’

  render() {
    const { banner } = this.state;
    return (
      <div>
        <BodyBanner title='title' banner={banner} />
      </div>
    );
  }

 或者使用展开运算符...   (注意展开运算符和解构的区别,别搞混了)

  render() {
    const info = {
        color:red,
        size:30
    }
    return (
      <div>
        <BodyBanner {...info} />
      </div>
    );
  }            

 

 

 

子组件接收数据:

1.在construtor里用super(props)然后这个props就绑定到实例上了

2.直接在render函数就可以通过实例的方式引用了。

export class BodyBanner extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    const { banner } = this.props;
    return (
      <div>
        <ul>
          {banner.map(e => (
            <li>{e}</li>
          ))}
          {banner.reduce((pre, cur) => {
            pre.push(<li>{cur}</li>);
            return pre;
          }, [])}
          {banner.reduce((pre, cur) => pre.push(<li>{cur}</li>) && pre, [])}
        </ul>
      </div>
    );
  }
}

 

二、props验证

就像vue一样,可以对props的类型做验证,还有给props一个默认值。

export default {
    props:{
        banner:{
            type:Array,
            default:()=>[]
        }
    }
}

 

那么react怎么做?

当然,如果你项目中默认继承了Flow或者TypeScript,那么直接就可以进行类型验证; 但是,即使我们没有使用Flow或者TypeScript,也可以通过 prop-types 库来进行参数验证

参考官网:https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html

  1. 安装:npm install prop-types
  2. 导入:import PropTypes from 'prop-types';
  3. 验证:

  4.  

    默认值: 

     

    import React, { Component } from 'react';
    import PropTypes from 'prop-types';
    
    export class BodyBanner extends Component {
      constructor(props) {
            ...
      }
      render() {
            ...
      }
    }
    BodyBanner.propTypes = {
      banner: PropTypes.array
    };
    
    BodyBanner.defaultProps = {
      banner: ['默认', '默认222', '默认333']
    };
    
    export default BodyBanner;

     

 

三、子传父通信

这里不需要用vue的$emit('eventName',)和@eventName语法。

react直接用js的回调函数解决。

父组件给子组件传递一个props,值为一个回调函数。

 子组件直接通过this.props.onChangeCount()传递参数par,即可实现子组件给父组件传递数据。

 1 import React, { Component } from 'react';
 2 import PropTypes from 'prop-types';
 3 
 4 export class BodyBanner extends Component {
 5 
 6   render() {
 7     return (
 8       <div>
 9         <button onClick={() => this.props.onChangeCount(1)}>点击加1</button>
10       </div>
11     );
12   }
13 }
14 BodyBanner.propTypes = {
15   onChangeCount: PropTypes.func
16 };
17 
18 BodyBanner.defaultProps = {
19   onChangeCount: () => {}
20 };
21 
22 export default BodyBanner;

 

 

 

 四、插槽

React对于这种需要插槽的情况非常灵活,有两种方案可以实现:

  1. 组件的children子元素;
  2. props属性传递React元素;

方式一:通过this.props.children

父组件直接在react元素里写元素即可,它会在通过babel编译在React.createElement('NavBar‘,{},[ children])的children里,

所以子元素直接通过this.propos.children取即可。

通过children实现的方案虽然可行,但是有一个弊端:通过索引值获取传入的元素很容易出错,不能精准的获取传入的原生;

因为传递2个及以上时,this.propos.children是一个数组,但如果只传递一个元素,那么this.propos.children就变成了本元素,这种不一样的情况实在是麻烦。

 方式二:直接通过props传递

这个jsx的props实在是太牛了,使用{}语法后,除了传递基本的字符串、数字、数组、对象、函数,还可以传递react元素。

直接传递给子组件即可,而且是类似vue的具名插槽,

 1   render() {
 2     return (
 3       <div>
 4         <div className="head-container">
 5           <BodyHead
 6             leftSlot={<button>新款</button>}
 7             centerSlot={<p>流行</p>}
 8             rightSlot={<i>精选</i>}
 9           />
10         </div>
11       </div>
12     );
13   }

 

子组件直接接收使用;

 1   render() {
 2     const { leftSlot, centerSlot, rightSlot } = this.props;
 3     return (
 4       <div>
 5         <p>我是子组件,下面是插槽,用来接收父组件传过来的内容</p>
 6         <div className="lfet">{leftSlot}</div>
 7         <div className="center">{centerSlot}</div>
 8         <div className="right">{rightSlot}</div>
 9       </div>
10     );
11   }

 

 

 五、作用域插槽

一般的,插槽的内容、数据、样式都是由父组件写好传递过来,子组件只负责展示即可;

但是作用域插槽就是,父组件决定子组件某一个块内容用什么标签元素展示,但是数据仍然由子组件提供;

插槽的数据作用域仍然是子组件。

或者说,这一块插槽的内容,是由父子组件共同决定。

其实也简单,和子穿父通信一样,仍然是用回调函数。

父组件:

  render() {
    return (
      <div>
        <div className="head-container">
          <BodyHead
            leftSlot={(par)=><button>{par}</button>}
            centerSlot={(par)=><p>{par}</p>}
            rightSlot={(par)=><i>{par}</i>}
          />
        </div>
      </div>
    );
  }

 

子组件:

  render() {
    const { leftSlot, centerSlot, rightSlot } = this.props;
    return (
      <div>
        <p>我是子组件,下面是插槽,用来接收父组件传过来的内容</p>
        <div className="lfet">{leftSlot('星际穿越')}</div>
        <div className="center">{centerSlot('功夫')}</div>
        <div className="right">{rightSlot('让子弹飞')}</div>
      </div>
    );
  }

 

说白了,就是让父组件从直接传递一个元素,变成了一传递一个返回值为react元素的函数,

然后子组件从直接展示元素,变成了传参调用回调函数。

 

 

 

 

 

 

 

 

 

 

 

 

 

六、context全局上下文

这个主要是爷孙组件传递信息,类似vue的provide和inject。

当然了,有redux尽量不要用这个context,太麻烦了。

 

1.单独找一个文件,建立全局context

 2.爷爷组件引入这个context,并且包裹起来给父组件

          <ThemeContext.Provider value={{color: "red", size: "30"}}>
            <Home/>
          </ThemeContext.Provider>

 

3.孙子组件有两种方式获取爷爷组件的数据

方式一:

先导入全局context,然后将类实例的contextType类型指定为全局context,最后通过this.context引入。

 方式二:先导入类型,然后用标签引入,然后再标签里传递一个回调函数,回调函数的参数就是爷爷传递过来的数据。

 

posted @ 2025-02-09 17:52  Eric-Shen  阅读(14)  评论(0编辑  收藏  举报