React(三)-组件化开发_组件的生命周期_组件的嵌套关系_组件父子通信_类型验证propTypes/defaultProps_React插槽_Context的基本使用

目录

一、什么是组件化开发

二、类组件

三、render 函数的返回值

四、函数组件

五、生命周期

5.1  认知生命周期

5.2 生命周期函数

5.3 不常用生命周期函数

六、认识组件的嵌套

6.1 认识组件间的通信

6.1.1 父传子

6.1.2 类型验证propTypes

6.1.3 子传父

6.2 组件通信案例练习

七、组件的插槽实现

7.1 children 实现插槽

7.2 props 实现插槽

7.3 作用域插槽

八、Context 应用场景

8.1 Context相关API

Context.consumer 在组件在组件中需要使用多个 Context 时


一、什么是组件化开发

组件化是一种分而治之的思想:

  • 如果我们将一个页面中所有的处理逻辑全部放在一起处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
  • 但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。

我们需要通过组件化的思想来思考整个应用程序:

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

组件化是 React 的核心思想,也是我们后续课程的重点,前面我们封装的 App 本身就是一个组件:
  • 组件化提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
  • 任何的应用都会被抽象成一颗组件树。
组件化思想的应用:
  • 有了组件化的思想,我们在之后的开发中就要充分的利用它。
  • 尽可能的将页面拆分成一个个小的、可复用的组件。
  • 这样让我们的代码更加方便组织和管理,并且扩展性也更强。
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  的变化并返回以下类型之一:
1.React 元素
  • 通常通过 JSX 创建。
  • 例如,<div /> 会被 React 渲染为 DOM 节点,<MyComponent /> 会被 React 渲染为自定义组件;
  • 无论是 <div /> 还是 <MyComponent /> 均为 React 元素。

2.数组或 fragments:使得 render 方法可以返回多个元素。

3.Portals:可以渲染子节点到不同的 DOM 子树中。

4.字符串或数值类型:它们在 DOM 中会被渲染为文本节点

5.布尔类型或 null:什么都不渲染。

import React from "react";
// 1.类组件 首字母要大写
class App extends React.Component {
  // constructor 维护组件状态
  constructor() {
    super()
    this.state = {
      message: "App component"
    }
  }
  render() {
    // const { message } = this.state
    // 1.react 元素:通过 jsx 编写的代码就会被编译成 React.createElement,所以返回的就是一个React元素
    // return <h2>{message}</h2>

    // 2.组件或者 fragments(后续了解)和数组
    // return ["abc", "cba", "cab"]
    // return [
    //   <h1>h1元素</h1>,
    //   <h2>h2元素</h2>,
    //   <div>div元素</div>
    // ]

    // 3.字符串或数值类型
    // return "你好"

    // 4.布尔类型或 null 不会显示
    return null
  }
}


export default App;

四、函数组件

函数组件是 使用 function 来进行定义的函数 ,只是 这个函数会返回和类组件中 render 函数返回一样的内容
函数组件有自己的特点(当然,后面我们会讲 hooks ,就不一样了):
  • 没有生命周期,也会被更新并挂载,但是没有生命周期函数;
  • this关键字不能指向组件实例(因为没有组件实例);
  • 没有内部状态(state);
我们来定义一个函数组件:
在前面的学习中,我们主要讲解类组件,后面学习 Hooks 时,会针对函数式组件进行更多的学习。

五、生命周期

5.1  认知生命周期

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

5.2 生命周期函数

  • Constructor
  • 如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
  • constructor中通常只做两件事情:
  1. 通过给 this.state 赋值对象来初始化内部的state
  2. 为事件绑定实例(this);
  • componentDidMount
  • componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。
  • componentDidMount中通常进行哪里操作呢?
  1. 依赖于DOM的操作可以在这里进行;
  2. 在此处发送网络请求就最好的地方;(官方建议)
  3. 可以在此处添加一些订阅(会在componentWillUnmount取消订阅);

  • componentDidUpdate
  • componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。
  • 当组件更新后,可以在此处对 DOM 进行操作;
  • 如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)。
  • componentWillUnmount
  • componentWillUnmount() 会在组件卸载及销毁之前直接调用。
  1. 在此方法中执行必要的清理操作;
  2. 例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅

 App.jsx

import React from "react";
import HelloWorld from "./HelloWorld";

class App extends React.Component {
  constructor() {
    super()
    this.state = {
      isShowHW: "true"
    }
  }
  changeShow() {
    // 对 isShowHW 取反
    this.setState({ isShowHW: !this.state.isShowHW })
  }
  render() {
    const { isShowHW } = this.state
    // console.log(isShowHW);
    return (
      <div>
        <h1>App React</h1>
        <button onClick={e => this.changeShow()}>切换</button>
        {isShowHW && <HelloWorld />}
      </div>
    )
  }
}
export default App

HelloWorld.jsx

import React from "react";
class HelloWorld extends React.Component {
  constructor() {
    console.log("HelloWorld Constructor");
    super()
    this.state = {
      message: '程序员的第一个代码 Hello World'
    }
  }
  changeText() {
    this.setState({ message: "message 被修改咯" })
  }
  render() {
    console.log("HelloWorld render");
    const { message } = this.state
    return (
      <div>
        <h1>{message}</h1>
        <button onClick={e => this.changeText()}>修改文本</button>
      </div>
    )
  }

  // 3.组件被渲染到 DOM/ 被挂载到 DOM
  componentDidMount() {
    console.log("componentDidMount");
  }

  // 4.组件被更新完成
  componentDidUpdate() {
    console.log("HelloWorld componentDidUpdate");
  }

  // 5.监听组件被卸载,从 DOM 中移除掉
  componentWillUnmount() {
    console.log("HelloWorld componentWillUnmount");
  }
}


export default HelloWorld

5.3 不常用生命周期函数

除了上面介绍的生命周期函数之外,还有一些不常用的生命周期函数:
  • getDerivedStateFromPropsstate 的值在任何时候都依赖于 props时使用;该方法返回一个对象来更新state
  • getSnapshotBeforeUpdate:在React更新DOM之前回调的一个函数,可以获取DOM更新前的一些信息(比如说滚动位置);
  • shouldComponentUpdate:该生命周期函数很常用,但是我们等待讲性能优化时再来详细讲解;
另外, React 中还提供了一些过期的生命周期函数,这些函数已经不推荐使用。
更详细的生命周期相关的内容,可以参考官网 React.Component – React

 

 // 在界面更新前保存一些数据, 在组件被更新完成 componentDidMount 的第三个参数可以获得返回的数据
  getSnapshotBeforeUpdate() {
    console.log("getSnapshotBeforeUpdate");
    return {
      scrollPosition: 1000
    }
  }


六、认识组件的嵌套

组件当中存在嵌套关系

  • 在之前的案例中,我们只是创建了一个组件App
  • 如果我们一个应用程序将所有的逻辑都放在一个组件中,那么这个组件就会变成非常的臃肿和难以维护;
  • 所以组件化的核心思想应该是对组件进行拆分,拆分成一个个小的组件;
  • 再将这些组件组合嵌套在一起,最终形成我们的应用程序;

 上面的嵌套逻辑如下,它们存在如下关系:

  • App组件是HeaderMainFooter组件的父组件;
  • Main组件是Banner、ProductList组件的父组件;

6.1 认识组件间的通信

在开发过程中,我们会经常遇到需要组件之间相互进行通信:
  • 比如App可能使用了多个Header,每个地方的Header展示的内容不同,那么我们就需要使用者传递给Header一些数据,让其进行展示;
  • 又比如我们在Main中一次性请求了Banner数据和ProductList数据,那么就需要传递给他们来进行展示;
  • 也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件;
总之,在一个 React 项目中,组件之间的通信是非常重要的环节

6.1.1 父传子

父组件在展示子组件,可能会传递一些数据给子组件:

  • 父组件通过 属性= 的形式来传递给子组件数据; <MainBanner banners={banners}>
  • 子组件通过 props 参数获取父组件传递过来的数据;
    class Main extends Component {
        constructor(props){
            super(props)
            this.state={}
        }
        render() {
            this.state
            const {banners} = this.props
        }
    }

 

 

可以换成真实数据进行展示:

 代码如下:

  componentDidMount() {
    axios.get("http://123.207.32.32:8000/home/multidata").then(res => {
      // console.log(res);
      const banners = res.data.data.banner.list
      const recommend = res.data.data.recommend.list
      this.setState({
        banners,
        productList: recommend
      })
    })
  }


6.1.2 类型验证propTypes

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

  • 当然,如果你项目中默认继承了Flow或者TypeScript,那么直接就可以进行类型验证;
  • 但是,即使我们没有使用Flow或者TypeScript,也可以通过 prop-types 库来进行参数验证;
React v15.5 开始,React.PropTypes 已移入另一个包中:prop-types 
更多的验证方式,可以参考官网:使用 PropTypes 进行类型检查 – React
  • 比如验证数组,并且数组中包含哪些元素;
  • 比如验证对象,并且对象中包含哪些key以及value是什么类型;
  • 比如某个原生是必须的,使用 requiredFunc: PropTypes.func.isRequired

如果没有传递,我们希望有默认值呢?
我们使用 defaultProps 就可以了

在上面的案例中添加propTypes的参数如下:

  •  banners: PropTypes.array.isRequired:表示 banners 的类型必须是数组且是必须的
  • title: PropTypes.string:表示 title 的类型必须是 字符串类型的

默认值:defaultProps,当数据中没有东西时,使用默认值


 6.1.3 子传父

某些情况,我们也需要子组件向父组件传递消息:
  • vue中是通过自定义事件来完成的;
  • React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可;
我们这里来完成一个案例:
  • 将计数器案例进行拆解;
  • 将按钮封装到子组件中:CounterButton
  • CounterButton发生点击事件,将内容传递到父组件中,修改counter的值;

 父组件 App.jsx

import React, { Component } from 'react'
import AddCounter from './AddCounter'
import SubCounter from './SubCounter'


export class App extends Component {
  constructor() {
    super()
    this.state = {
      counter: 100
    }
  }
  changeCounter(count) {
    this.setState({
      counter: this.state.counter + count
    })
  }
  render() {
    const { counter } = this.state
    return (
      <div>
        <h1>Counter: {counter}</h1>
        <AddCounter addCount={(count) => this.changeCounter(count)} />
        <SubCounter subCount={(count) => this.changeCounter(count)} />
      </div>
    )
  }
}

export default App

子组件 AddCounter.jsx

import React, { Component } from 'react'

export class AddCounter extends Component {
  addCount(count) {
    this.props.addCount(count)
  }
  render() {
    return (
      <div>
        <button onClick={e => this.addCount(1)}>+1</button>
        <button onClick={e => this.addCount(5)}>+5</button>
        <button onClick={e => this.addCount(10)}>+10</button>
      </div>
    )
  }
}

export default AddCounter

子组件 SubCounter.jsx

import React, { Component } from 'react'

export class SubCounter extends Component {
  subCount(count) {
    this.props.subCount(count)
    console.log(count);
  }
  render() {
    return (
      <div>
        <button onClick={e => this.subCount(-1)}>-1</button>
        <button onClick={e => this.subCount(-5)}>-5</button>
        <button onClick={e => this.subCount(-10)}>-10</button>
      </div>
    )
  }
}

export default SubCounter


6.2 组件通信案例练习

1. 实现鼠标点击哪个标题,标题就添加 active 属性变为红色选中状态:

  • 默认第一个标题是选中状态,所以先自定义一个变量 currentIndex: 0;
  1. 在遍历的时候传入 index,在标题的标签中写入一个判断,className={`item ${index === currentIndex? 'active': ' '}`},判断当前的索引值;
  2. 接下来判断鼠标点击的索引值,完成鼠标点击谁,谁就添加一个 active 属性:在map遍历的地方传入 index,判断当前鼠标点击的标题的索引,然后传入一个 onClick 点击事件,onClick={e=>this.itemClick(index)},这样就能获得鼠标点击的对象的索引值了;
  3. 最后将index的值传给 currentIndex ,这样就完成的点击谁谁就添加 active 属性。

import React, { Component } from 'react'
import './style.css'
export class index extends Component {
  constructor() {
    super()
    this.state = {
      currentIndex: 0
    }
  }
  itemClick(index) {
    this.setState({
      currentIndex: index
    })
  }
  render() {
    const { titles } = this.props
    const { currentIndex } = this.state
    return (
      <div className='tab-control'>
        {
          titles.map((item, index) => {
            return (
              <div className={`item ${index === currentIndex ? 'active' : ''}`} key={item}
                onClick={e => this.itemClick(index)}
              >
                <span className='text'>{item}</span>
              </div>
            )
          })
        }
      </div>
    )
  }
}

export default index

2. 实现点击哪个标题,下面展示相对应的内容

 1、在父组件中创建一个变量 tabIndex,来获取标题的索引

 2、父组件通过创建事件获取子组件的索引值

<TabControl titles={titles} tabClick={(i) => this.tabClick(i)} />

tabClick(index) {
    this.setState({
      tabIndex: index
    })
  }

 3、在组件的事件中通过:this.props.tabClick(index),将索引传给父组件

父组件:

import React, { Component } from 'react'
import TabControl from './TabControl'

export class App extends Component {
  constructor() {
    super()
    this.state = {
      titles: ['流行', '新歌', '精选'],
      tabIndex: 0
    }
  }
  tabClick(index) {
    this.setState({
      tabIndex: index
    })
  }
  render() {
    const { titles, tabIndex } = this.state
    return (
      <div>
        <TabControl titles={titles} tabClick={(i) => this.tabClick(i)} />
        <h1>{titles[tabIndex]}</h1>
      </div>
    )
  }
}

export default App

子组件,将值传递给父组件:this.props.tabClick(index)

itemClick(index) {
    this.setState({
      currentIndex: index
    })
    this.props.tabClick(index)
  }


七、组件的插槽实现

React 对于需要插槽的情况非常灵活,有两种方案可以实现:
  • 组件的children子元素;
  • props属性传递React元素;

7.1 children 实现插槽

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

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

可以通过 propsType 限制children传入的为元素(即只传入一个元素,使children为元素)

7.2 props 实现插槽

通过具体的属性名,可以让我们在传入和获取时更加的精准;
不仅可以传递字符串、数组,还可以传递按钮标签等等

 

 

7.3 作用域插槽

八、Context 应用场景

非父子组件数据的共享:
  • 在开发中,比较常见的数据传递方式是通过props属性自上而下(由父到子)进行传递
  • 但是对于有一些场景:比如一些数据需要在多个组件中进行共享(地区偏好、UI主题、用户登录状态、用户信息等)。
  • 如果我们在顶层的App中定义这些信息,之后一层层传递下去,那么对于一些中间层不需要数据的组件来说,是一种冗余的操作。

我们实现一个一层层传递的案例:
但是,如果层级更多的话,一层层传递是非常麻烦,并且代码是非常冗余的:
  • React提供了一个APIContext
  • Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
  • Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言;

使用 Context 步骤

1. 创建 Context

2.通过 ThemeContext 中的 Provider 中的value属性为后代提供数据

3.设置组件中的 contextType 的类型为某一个 context

4.获取到数据,并且使用数据 

 

 

 

8.1 Context相关API

React.createContext
  • 创建一个需要共享的Context对象:
  • 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值;
  • defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值
// 1. 创建一个 Context
const ThemeContext = React.createContext()
Context.Provider
  • 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化:
  • Provider 接收一个 value 属性,传递给消费组件;
  • 一个 Provider 可以和多个消费组件有对应关系;
  • 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;
  • Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染;
{/* 第二步:通过 ThemeContext 中的 Provider 中的value属性为后代提供数据 */}
        <ThemeContext.Provider value={{ color: 'red', size: '30' }}>
          <Home {...info} />
        </ThemeContext.Provider>
Class.contextType
  • 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:
  • 这能让你使用 this.context 来消费最近 Context 上的那个值;
  • 你可以在任何生命周期中访问到它,包括 render 函数中;

// 第三步.设置组件中的 contextType 的类型为某一个 context
HomeInfo.contextType = ThemeContext


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

function HomeBanner() {
  return <div>
    <span>HomeBanner</span>
    {/* 函数式组件中使用 Context 共享的数据 */}
    <ThemeContext.Consumer>
      {
        value => {
          return <h2>{value.color}</h2>
        }
      }
    </ThemeContext.Consumer>
  </div>
}

export default HomeBanner
什么时候使用默认值 defaultValue 呢?
  • 如果使用Context,该组件不是包裹在 Conntext.Provider中
  • 那么使用的是 defaultValue 里面设置的值
// 1. 创建一个 Context,里面是defaultValue
const ThemeContext = React.createContext({ color: 'red', level: '100' })

什么时候使用 Context.Consumer 呢?
  • 1.当使用value的组件是一个函数式组件时;
  • 2.当组件中需要使用多个Context时;

Context.consumer 在组件在组件中需要使用多个 Context 时

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
React中父组件向子组件传值可以通过props实现,而子组件向父组件传值可以通过回调函数实现。 假设我们有一个父组件Modal,其中包含一个子组件Form,我们希望在Form表单中填写完数据后,将数据传递给Modal组件进行处理。 首先,我们在Modal组件中定义一个state,用来保存Form表单中的数据: ```javascript class Modal extends React.Component { constructor(props) { super(props); this.state = { formData: {} }; } // ... } ``` 然后,在Modal组件中定义一个函数,用来接收Form组件传递的数据,并更新Modal组件的state: ```javascript handleFormData = (data) => { this.setState({ formData: data }); } ``` 接下来,在render函数中,将handleFormData函数传递给Form组件作为props: ```javascript render() { return ( <div> <Form onFormData={this.handleFormData} /> </div> ); } ``` 在Form组件中,我们通过props接收父组件传递过来的onFormData函数,并在表单提交时调用该函数将数据传递给父组件: ```javascript class Form extends React.Component { handleSubmit = (event) => { event.preventDefault(); const data = { name: event.target.name.value, age: event.target.age.value }; this.props.onFormData(data); } render() { return ( <form onSubmit={this.handleSubmit}> <input type="text" name="name" placeholder="姓名" /> <input type="text" name="age" placeholder="年龄" /> <button type="submit">提交</button> </form> ); } } ``` 注意,这里我们使用了event.target来获取表单中的数据,而不是使用refs或者state来获取数据,这是因为React不推荐直接操作DOM元素。 最后,当Form表单提交后,父组件的state中就会保存表单中的数据,我们可以在Modal组件中对数据进行处理或者展示。 这就是父子组件之间传值的基本方法,通过props和回调函数,可以轻松地实现组件之间的数据传递。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值