首頁 > 軟體

React生命週期與父子元件間通訊知識點詳細講解

2022-11-28 22:00:17

宣告週期

很多的事物都有從建立到銷燬的整個過程,這個過程稱之為是生命週期;

React元件也有自己的生命週期,瞭解元件的生命週期可以讓我們在最合適的地方完成自己想要的功能;

生命週期和生命週期函數的關係:

生命週期是一個抽象的概念,在生命週期的整個過程,分成了很多個階段;

比如裝載階段(Mount),元件第一次在DOM樹中被渲染的過程;

比如更新過程(Update),元件狀態發生變化,重新更新渲染的過程;

比如解除安裝過程(Unmount),元件從DOM樹中被移除的過程;

React內部為了告訴我們當前處於哪些階段,會對我們元件內部實現的某些函數進行回撥,這些函數就是生命週期函數:

比如實現componentDidMount函數:元件已經掛載到DOM上時,就會回撥;

比如實現componentDidUpdate函數:元件已經發生了更新時,就會回撥;

比如實現componentWillUnmount函數:元件即將被移除時,就會回撥;

我們可以在這些回撥函數中編寫自己的邏輯程式碼,來完成自己的需求功能;

我們談React生命週期時,主要談的類的生命週期,因為函數式元件是沒有生命週期函數的

宣告週期解析

我們先來了解一下最基礎、最常用的生命週期函數:

生命週期函數

class HelloWorld extends React.Component {
  // 1.構造方法: constructor
  constructor() {
    console.log("HelloWorld constructor")
    super()
    this.state = {
      message: "Hello World"
    }
  }
  changeText() {
    this.setState({ message: "你好啊, 李銀河" })
  }
  // 2.執行render函數
  render() {
    console.log("HelloWorld render")
    const { message } = this.state
    return (
      <div>
        <h2>{message}</h2>
        <p>{message}是程式設計師的第一個程式碼!</p>
        <button onClick={e => this.changeText()}>修改文字</button>
      </div>
    )
  }
  // 3.元件被渲染到DOM: 被掛載到DOM
  componentDidMount() {
    console.log("HelloWorld componentDidMount")
  }
  // 4.元件的DOM被更新完成: DOM發生更新
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("HelloWorld componentDidUpdate:", prevProps, prevState, snapshot)
  }
  // 5.元件從DOM中解除安裝掉: 從DOM移除掉
  componentWillUnmount() {
    console.log("HelloWorld componentWillUnmount")
  }
  // 不常用的生命週期補充
  shouldComponentUpdate() {
    return true
  }
  getSnapshotBeforeUpdate() {
    console.log("getSnapshotBeforeUpdate")
    return {
      scrollPosition: 1000
    }
  }
}

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:該生命週期函數很常用,但是我們等待講效能優化時再來詳細講解;

另外,React中還提供了一些過期的生命週期函數,這些函數已經不推薦使用。

認識元件間的通訊

在開發過程中,我們會經常遇到需要元件之間相互進行通訊:

比如App可能使用了多個Header,每個地方的Header展示的內容不同,那麼我們就需要使用者傳遞給Header一些資料,讓其進行展示;

又比如我們在Main中一次性請求了Banner資料和ProductList資料,那麼就需要傳遞給他們來進行展示;

也可能是子元件中發生了事件,需要由父元件來完成某些操作,那就需要子元件向父元件傳遞事件;

總之,在一個React專案中,元件之間的通訊是非常重要的環節;

父元件在展示子元件,可能會傳遞一些資料給子元件:

父元件通過 屬性=值 的形式來傳遞給子元件資料;

子元件通過 props 引數獲取父元件傳遞過來的資料;

引數propTypes

對於傳遞給子元件的資料,有時候我們可能希望進行驗證,特別是對於大型專案來說:

當然,如果你專案中預設繼承了Flow或者TypeScript,那麼直接就可以進行型別驗證;

但是,即使我們沒有使用Flow或者TypeScript,也可以通過 prop-types 庫來進行引數驗證;

從 React v15.5 開始,React.PropTypes 已移入另一個包中:prop-types 庫

import PropTypes from 'prop-types';
MyComponent.propTypes = {
  // 你可以將屬性宣告為 JS 原生型別,預設情況下
  // 這些屬性都是可選的。
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,
  // 任何可被渲染的元素(包括數位、字串、元素或陣列)
  // (或 Fragment) 也包含這些型別。
  optionalNode: PropTypes.node,
  // 一個 React 元素。
  optionalElement: PropTypes.element,
  // 一個 React 元素型別(即,MyComponent)。
  optionalElementType: PropTypes.elementType,
  // 你也可以宣告 prop 為類的範例,這裡使用
  // JS 的 instanceof 操作符。
  optionalMessage: PropTypes.instanceOf(Message),
  // 你可以讓你的 prop 只能是特定的值,指定它為
  // 列舉型別。
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),
  // 一個物件可以是幾種型別中的任意一個型別
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),
  // 可以指定一個陣列由某一型別的元素組成
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
  // 可以指定一個物件由某一型別的值組成
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),
  // 可以指定一個物件由特定的型別值組成
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),
  // An object with warnings on extra properties
  optionalObjectWithStrictShape: PropTypes.exact({
    name: PropTypes.string,
    quantity: PropTypes.number
  }),
  // 你可以在任何 PropTypes 屬性後面加上 `isRequired` ,確保
  // 這個 prop 沒有被提供時,會列印警告資訊。
  requiredFunc: PropTypes.func.isRequired,
  // 任意型別的必需資料
  requiredAny: PropTypes.any.isRequired,
  // 你可以指定一個自定義驗證器。它在驗證失敗時應返回一個 Error 物件。
  // 請不要使用 `console.warn` 或丟擲異常,因為這在 `oneOfType` 中不會起作用。
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },
  // 你也可以提供一個自定義的 `arrayOf` 或 `objectOf` 驗證器。
  // 它應該在驗證失敗時返回一個 Error 物件。
  // 驗證器將驗證陣列或物件中的每個值。驗證器的前兩個引數
  // 第一個是陣列或物件本身
  // 第二個是他們當前的鍵。
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

限制單個元素

你可以通過 PropTypes.element 來確保傳遞給元件的 children 中只包含一個元素。

import PropTypes from 'prop-types';
class MyComponent extends React.Component {
  render() {
    // 這必須只有一個元素,否則控制檯會列印警告。
    const children = this.props.children;
    return (
      <div>
        {children}
      </div>
    );
  }
}
MyComponent.propTypes = {
  children: PropTypes.element.isRequired
};

預設 Prop 值

您可以通過設定特定的 defaultProps 屬性來定義 props 的預設值:

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}
// 指定 props 的預設值:
Greeting.defaultProps = {
  name: 'Stranger'
};
// 渲染出 "Hello, Stranger":
const root = ReactDOM.createRoot(document.getElementById('example')); 
root.render(<Greeting />);

從 ES2022 開始,你也可以在 React 類元件中將 defaultProps 宣告為靜態屬性。這種現代語法需要新增額外的編譯步驟才能在老版瀏覽器中工作。

class Greeting extends React.Component {
  static defaultProps = {
    name: 'stranger'
  }
  render() {
    return (
      <div>Hello, {this.props.name}</div>
    )
  }
}

defaultProps 用於確保 this.props.name 在父元件沒有指定其值時,有一個預設值。propTypes 型別檢查發生在 defaultProps 賦值後,所以型別檢查也適用於 defaultProps

對於函數式元件

如果你在常規開發中使用函陣列件,那你可能需要做一些適當的改動,以保證 PropsTypes 應用正常。

假設你有如下元件:

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

如果要新增 PropTypes,你可能需要在匯出之前以單獨宣告的一個函數的形式,宣告該元件,具體程式碼如下:

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

接著,可以直接在 HelloWorldComponent 上新增 PropTypes:

import PropTypes from 'prop-types'
function HelloWorldComponent({ name }) {
  return (
    <div>Hello, {name}</div>
  )
}
HelloWorldComponent.propTypes = {
  name: PropTypes.string
}
export default HelloWorldComponent

子元件傳遞父元件

某些情況,我們也需要子元件向父元件傳遞訊息:

在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>
        <h2>當前計數: {counter}</h2>
        <AddCounter addClick={(count) => this.changeCounter(count)}/>
        <SubCounter subClick={(count) => this.changeCounter(count)}/>
      </div>
    )
  }
}
export default App

subCounter.jsx

import React, { Component } from 'react'
export class SubCounter extends Component {
  subCount(count) {
    this.props.subClick(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

addCounter.jsx

import React, { Component } from 'react'
// import PropTypes from "prop-types"
export class AddCounter extends Component {
  addCount(count) {
    this.props.addClick(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>
    )
  }
}
// AddCounter.propTypes = {
//   addClick: PropTypes.func
// }
export default AddCounter

到此這篇關於React生命週期與父子元件間通訊知識點詳細講解的文章就介紹到這了,更多相關React生命週期內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com