React 函數組件和類組件的區別


函數組件和類組件有什么不同,在編碼過程中應該如何選擇呢?

一、什么是函數組件

定義一個組件最簡單的方式就是使用 JavaScript 函數:

import React from 'react'
const Welcome = (props) => {
  return <h1>welcome, {props.name}</h1>
}
export default Welcome

這個函數接收一個 props 對象並返回一個 react 元素

二、什么是類組件

React 可以使用 ES6 class 語法去寫一個組件:

import React from 'react'
class Welcome extends React.Component {
  constructor(props) {
    super(props)
  }
  render() {
    return <h1>welcome, {this.props.name}</h1>
  }
}

export default Welcome

這兩個版本是等價的,它們具有相同的輸出。那么我們應該去選擇哪一種實現方式呢?

三、函數組件與類組件的區別

1、語法上

兩者最明顯的不同就是在語法上:
函數組件是一個純函數,它接收一個 props 對象返回一個 react 元素;
類組件需要去繼承 React.Component 並且創建 render 函數返回 react 元素,雖然實現的效果相同,但需要更多的代碼。

2、狀態管理

因為函數組件是一個純函數,所以不能在組件中使用 setState(),這也是為什么把函數組件稱作為無狀態組件。
如果要在組件中使用 state,可以選擇創建一個類組件或者將 state 提升到你的父組件中,然后通過 props 對象傳遞到子組件。

3、生命周期鈎子

函數組件中不能使用生命周期鈎子,原因和不能使用 state 一樣,所有的生命周期鈎子都來自於繼承的 React.Component 中。
因此,如果要使用生命周期鈎子,就需要使用類組件。

注意:在 react16.8 版本中添加了 hooks,使得我們可以在函數組件中使用 useState 鈎子去管理 state,使用 useEffect 鈎子去使用生命周期函數。
因此,2、3 兩點就不是它們的區別點。
而從這個改版中我們也可以看出 React 團隊更看重函數組件,而且曾提及到在 react 之后的版本將會對函數組件的性能方面進行提升。

4、調用方式

如果 SayHi 是一個函數,React 需要調用它:

// 你的代碼 
function SayHi() { 
    return <p>Hello, React</p> 
} 
// React 內部 
const result = SayHi(props) // » <p>Hello, React</p>

如果 SayHi 是一個類,React 需要先用 new 操作符將其實例化,然后調用剛才生成實例的 render 方法:

// 你的代碼 
class SayHi extends React.Component { 
    render() { 
        return <p>Hello, React</p> 
    } 
} 
// React 內部 
const instance = new SayHi(props) // » SayHi {} 
const result = instance.render() // » <p>Hello, React</p>

可想而知,函數組件重新渲染將重新調用組件方法返回新的 react 元素,類組件重新渲染將 new 一個新的組件實例,然后調用 render 類方法返回 react 元素,這也說明為什么類組件中 this 是可變的。

5、獲取渲染時的值

這一點是他們最大差異,但又常常被人們忽略。

考慮以下組件:

function ProfilePage(props) {
  const showMessage = () => {
    alert('Followed ' + props.user);
  }

  const handleClick = () => {
    setTimeout(showMessage, 3000);
  }

  return (
    <button onClick={handleClick}>Follow</button>
  )
}

UserProfile 組件很簡單,就一個 Follow 按鈕,該按鈕使用了 setTimeout 模擬網絡請求。用戶點擊這個按鈕之后會彈出一個警告框。如果 props.user'Dan',它將在三秒鍾后顯示 'Followed Dan'

我們如何將其編寫為類?天真的翻譯可能像這樣:

class ProfilePage extends React.Component {
  showMessage() {
    alert('Followed ' + this.props.user);
  }

  handleClick() {
    setTimeout(this.showMessage.bind(this), 3000);
  }

  render() {
    return <button onClick={this.handleClick.bind(this)}>Follow</button>
  }
}

通常認為這兩個代碼段是等效的。人們經常在這些模式之間自由重構,而沒有注意到它們的含義

但是,這兩個代碼段是完全不同的。

分別按下面的順序來操作 Follow 按鈕:

  1. 先點擊 Follow 按鈕
  2. 3s 之前更改下拉選擇項的選項
  3. 閱讀彈出的警告框內容

這就發現函數組件和類組件是有區別的:

函數組件:按上面所列的三個步驟操作時,當用戶在 3s 前更改下拉選擇框的選項時,h1 的用戶名會立馬改變,而 3s 后彈出的警告框中的用戶名並不會改變
類組件:按上面所列的三個步驟操作時,當用戶在 3s 前更改下拉選擇框的選項時,h1 中的用戶名會立馬改變,而 3s 后彈出的警告框中的用戶名也會改變


那么,為什么我們的類示例會這樣表現呢?

讓我們仔細看一下 showMessage 類中的方法:

showMessage() {
    alert('Followed ' + this.props.user);
  }

showMessage 方法中讀取了 this.props.user(也是我們要輸出的用戶名稱)。而 React 中的 props 是不可變的,但是 this 是可變的,而且是一直是可變的。這也是類組件中 this 的目的。React 自身會隨着時間的推移對 this 進行修改,以便在 render 函數或生命周期中讀取新的版本。

因此,如果組件在請求重新渲染時,this.props 將會改變。showMessage 方法會從新的 props 中讀取 user。所看到的效果也正是因為這個原因。

React 中的組件,UI 在概念上可以理解是程序當前狀態的函數,那么事件處理就是讓 UI 的渲染結果一部分一部分可視化輸出。我們的事件處理程序屬於具有特定 propsstate 的特定渲染。但是,當回調超時的話,this.props 就會打破這種聯系。示例中的 showMessage 方法在回調時沒有綁定到任何特定的渲染,因此它會丟失真正的 props

那么有沒有一種較好的方式可以使用正確的 props 來修復 rendershowMessage 回調之間的聯系呢?
我們可以在事件發生的早期,將 this.props 傳遞給超時完成的處理程序來嘗試着解決這個問題。這種解決方式屬於閉包的范疇。

class ProfilePage extends React.Component {
  showMessage(user) {
    alert('Followed ' + user);
  }

  handleClick() {
    cosnt {user} = this.props
    setTimeout(this.showMessage.bind(this, user), 3000);
  }

  render() {
    return <button onClick={this.handleClick.bind(this)}>Follow</button>
  }
}

我們使用閉包機制將上一狀態的值保存下來待 showMessage 方法調用。即使 this.props 發生變化,但並不改變 user

這種方法雖然解決我們前面所提到的問題,但是這種方法代碼會隨着 props 的個數增加,代碼也會變得更加冗余也易於出錯。
如果我們也需要訪問 state。如果 showMessage 調用另一個方法,該方法會讀取 this.props.somethingthis.state.something
我們又會碰到同樣的問題。所以我們必須通過 this.props 作為 showMessage 的參數來修復它們之間存在的問題。

但這么做會破壞類提供的特性,也令人難於記住或執行。

另外,在 handleClick 中內聯 alert 中的代碼並不能解決更大的問題。
我們希望以一種允許代碼分解成更多方法的方式來構造代碼,同時還可以讀取與其相關的 render 所對應的 propsstate

或許,我們可以在類的構造函數中綁定這些方法:

class ProfilePage extends React.Component {
  render() {
    // 獲取 props
    cosnt props = this.props
    
    // 它們不是類方法
    const showMessage = () => {
        alert('Followed ' + props.user);
    }
    
    const handleClick = () => {
        setTimeout(showMessage, 3000)
    }
    
    return <button onClick={handleClick}>Follow</button>
  }
}

這樣一來,函數組件和類組件所達到的效果都一樣了。在類組件中可以捕獲渲染時的 props。效果上看上去是一樣了,但看起來怪怪的。如果在類組件中的 render 中定義函數而不是使用類方法,那么還有使用類的必要性?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM