函数/类组件-组件生命周期-嵌套-通信-插槽

160 阅读10分钟

1.React的组件化开发

将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,之后整个页面的管理和维护就变得非常容易

  • 我们将一个完整的页面分成很多个组件;
  • 每个组件都用于实现页面的一个功能块;
  • 而每一个组件又可以进行细分;
  • 而组件本身又可以在多个地方进行复用

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

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

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

  • 函数组件、无状态组件、展示型组件主要关注UI的展示;
  • 类组件、有状态组件、容器型组件主要关注数据逻辑;

组件的其他概念:比如异步组件、高阶组件等

类组件★★★

  • 类组件的定义有如下要求:
    • 组件的名称是大写字符开头(无论类组件还是函数组件)
    • 组件需要继承自 React.Component
    • 类组件必须实现render函数
  • 在ES6之前,可以通过create-react-class 模块来定义类组件,但是目前官网建议使用ES6的class类定义。
  • 使用class定义一个组件:
    • constructor是可选的,我们通常在constructor中初始化一些数据;
    • this.state中维护的就是我们组件内部的数据;
    • render() 方法是 class 组件中唯一必须实现的方法;

render函数的返回值★★★

  • 当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:
    • React 元素:
      • 通常通过 JSX 创建。
      • 例如,
         会被 React 渲染为 DOM 节点, 会被 React 渲染为自定义组件;
      • 无论是 
         还是  均为 React 元素。
    • 数组或 fragments:使得 render 方法可以返回多个元素。
    • Portals:可以渲染子节点到不同的 DOM 子树中。
    • 字符串或数值类型:它们在 DOM 中会被渲染为文本节点
    • 布尔类型或 null:什么都不渲染。
import React from "react"

class App extends React.Component{
  render(){
    // return(
    //   <div>
    //     <h2>12312</h2>
    //   </div>
    // )
    // 返回数组
    // return [1,23,4,435 ]
    return[
      <h1>数组1</h1>,
      <h2>数组2</h2>,
      <h3>数组3</h3>
    ]
  }
}
export default App

函数组件★★★

  • 函数组件是使用function来进行定义的函数这个函数会返回和类组件中render函数返回一样的内容。
  • 函数组件有特点:
    • 没有生命周期,也会被更新并挂载,但是没有生命周期函数;
    • this关键字不能指向组件实例(因为没有组件实例);
    • 没有内部状态(state);

function App(){
  // 返回值render函数返回值一致
  return (
    <div>
      <h1>测试fun函数组件</h1>
    </div>
  )
} 

export default App

2.React组件生命周期 生命周期图片来源★★★

  • React组件也有自己的生命周期,了解组件的生命周期可以让我们在最合适的地方完成自己想要的功能;
  • 生命周期和生命周期函数的关系:
    • 生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多个阶段;
    • 比如装载阶段(Mount),组件第一次在DOM树中被渲染的过程;
    • 比如更新过程(Update),组件状态发生变化,重新更新渲染的过程;
    • 比如卸载过程(Unmount),组件从DOM树中被移除的过程;
  • React内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的某些函数进行回调,这些函数就是生命周期函数:
    • 实现componentDidMount函数:组件已经挂载到DOM上时,就会回调;
    • 实现componentDidUpdate函数:组件已经发生了更新时,就会回调;
    • 实现componentWillUnmount函数:组件即将被移除时,就会回调;
    • 我们可以在这些回调函数中编写自己的逻辑代码,来完成自己的需求功能; 谈React生命周期时,主要谈的类的生命周期,因为函数式组件是没有生命周期函数的;(可以通过hooks来模拟一些生命周期的回调) image.png
import React from "react"

class Unmount extends React.Component {
  render(){
    return <h1>被卸载了</h1>
  }
  componentWillUnmount(){
    console.log("测试组件被卸载了");
  }
  componentDidMount() {
    console.log("测试组件开始挂载");
  }
}



class App extends React.Component {
  constructor() {
    super()
    this.state = {
      name: "我是父组件",
      show: true,
    }
  }
  onClick() {
    this.setState((pros) => {
      return {
        name: "我是父组件修改了",
        show: !pros.show,
      }
    })
  }
  render() {
    const { name, show } = this.state;
    let element = null
    if (show) {
      element = <div>
        <h2>{name}</h2>
        <button onClick={() => this.onClick()}>修改操作</button>
      </div>
    } else {
      element = <div>
        <Unmount />
        <button onClick={() => this.onClick()}>修改操作</button>
      </div>
    }
    return (
    <div>
      {element}
    </div>
    )
  }
  componentDidMount() {
    console.log("父组件开始挂载");
  }
  componentDidUpdate() {
    console.log("父组件修改了");
  }
}
export default App
  • Constructor

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

  • constructor中通常只做两件事情:

    • 通过给 this.state 赋值对象来初始化内部的state;
    • 为事件绑定实例(this);
  • componentDidMount

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

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

    • 依赖于DOM的操作可以在这里进行;
    • 在此处发送网络请求就最好的地方;(官方建议)
    • 可以在此处添加一些订阅(会在componentWillUnmount取消订阅);
  • componentDidUpdate

    • componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。
    • 当组件更新后,可以在此处对 DOM 进行操作;
    • 如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)。
  • componentWillUnmount

    • componentWillUnmount() 会在组件卸载及销毁之前直接调用。
    • 在此方法中执行必要的清理操作;
    • 例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等;
  • 除了上面生命周期函数之外,还有一些不常用的生命周期函数:

    • getDerivedStateFromProps:state 的值在任何时候都依赖于 props时使用;该方法返回一个对象来更新state;
    • getSnapshotBeforeUpdate:在React更新DOM之前回调的一个函数,可以获取DOM更新前的一些信息(比如说滚动位置);
    • shouldComponentUpdate:该生命周期函数很常用:可以用于性能优化,可以决定render函数要不要重新渲染

image.png

  • 另外,React中还提供了一些过期的生命周期函数,这些函数已经不推荐使用。 更详细的生命周期相关的内容,可以参考官网:; image.png

3.React组件间的通信★★★

组件嵌套顾名思义,多个小功能组件进行拆分再复用组合形成一个完整界面就存在嵌套关系; 组件之间的嵌套数据传递就需要进行组件间通信

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

父传子★★★

image.png

参数验证 PropTypes★★★

  • 对于传递给子组件的数据,有时候我们可能希望进行验证,特别是对于大型项目来说:

    • 当然,如果你项目中默认继承了Flow或者TypeScript,那么直接就可以进行类型验证;
    • 但是,即使我们没有使用Flow或者TypeScript,也可以通过 prop-types 库来进行参数验证;
  • 从 React v15.5 开始,React.PropTypes 已移入另一个包中:prop-types 库

  • 更多的验证方式,可以参考官网中文网

  • 验证数组,并且数组中包含哪些元素;

  • 验证对象,并且对象中包含哪些key以及value是什么类型;

  • 某个原生是必须的,使用 requiredFunc: PropTypes.func.isRequired

如果没有传递,我们使用defaultProps就可以了

// 子组件
import React, { Component } from 'react'
import PropTypes from 'prop-types';

export class Header extends Component {
  // 方式一类型校验
  // static propTypes = {
  //   message: PropTypes.string.isRequired,//必传属性
  // };
  // static defaultProps = {
  //   message: "我是默认值",
  // };
  // 如果没有用到state可以不用添加
  // constructor(props) {
  //   console.log(props);
  //   super(props)
  // }
  render() {
    const { message,message1 } = this.props
    return (
      <div>
        <h1>{message}</h1>
        <h2>{message1}</h2>
        Header
      </div>
    )
  }
}
// 方式二类型校验
// Header.propTypes = {
//   message: PropTypes.string.isRequired,//必传属性
// };
// Header.defaultProps={
//   message: "我是默认值",
// }
export default Header

函数组件用法

import PropTypes from 'prop-types'

function HelloWorldComponent({ name }) {
  return (
    <div>Hello, {name}</div>
  )
}

HelloWorldComponent.propTypes = {
  name: PropTypes.string
}

export default HelloWorldComponent

这里还有一个 属性展开用法 如果你已经有了一个 props 对象,你可以使用展开运算符 ... 来在 JSX 中传递整个 props 对象。以下两个组件是等价的:

function App1() {
  return <Greeting firstName="Ben" lastName="Hector" />;
}

function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <Greeting {...props} />;}

子传父★★★

通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可

image.png 子组件传递父组件本质上就是使用原生函数进行传递,将函数作为参数传递给子组件,子组件进行调用。

4.React组件插槽用法★★★

  • 组件的children子元素;
  • props属性传递React元素

children实现插槽

  • 每个组件都可以获取到 props.children:它包含组件的开始标签和结束标签之间的内容

image.png

props实现插槽

通过children实现的方案有一个弊端(对顺序要求过高):通过索引值获取传入的元素很容易出错,不能精准的获取传入的原生; 使用 props 实现: 通过具体的属性名,可以让我们在传入和获取时更加的精准;

image.png

5.React非父子的通信★★★

  • React提供了一个API:Context;
  • Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props;
  • Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言

Context相关API

  • React.createContext

    • 创建一个需要共享的Context对象:
    • 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值;
    • defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值
  • Context.Provider

    • 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化:
    • Provider 接收一个 value 属性,传递给消费组件;
    • 一个 Provider 可以和多个消费组件有对应关系;
    • 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;
    • 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染;
  • Class.contextType

    • 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:
    • 这能让你使用 this.context 来消费最近 Context 上的那个值;
    • 你可以在任何生命周期中访问到它,包括 render 函数中;
  • Context.Consumer

    • 这里,React 组件也可以订阅到 context 变更。这能让你在 函数式组件 中完成订阅 context。
    • 这里需要 函数作为子元素(function as child)这种做法;
    • 这个函数接收当前的 context 值,返回一个 React 节点;

Class组件 image.png 函数组件的使用Context.Consumer同时也可以在class组件中使用 image.png

image.png

使用默认值defaultValue

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。此默认值有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。

什么时候使用Context.Consumer呢?

1.当使用value的组件是一个函数式组件时;

2.当组件中需要使用多个Context时;

image.png