iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 13
2
Modern Web

給初入JS框架新手的React.js入門系列 第 13

【React.js入門 - 13】 useState - 在function component用state

  • 分享至 

  • xImage
  •  

(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問


我也想要在function component中使用state,可是function不能夠用extend繼承,怎麼辦?

在以前,你就只能乖乖的用class component。在React Ver.16.8後,React hook出現了,它提供一系列讓你能在function component中使用「在class component常用的功能」的方法,useState就是其中之一。

useState是什麼?

你可以把useState當成是在function component中state和setState的集合體。它的使用方式是:

變數型態 [state變數名稱, setState函式名稱] = useState(state變數初始值)

/* 舉例來說 */

const [percent, changePercent] = useState("20%");

在上面的例子,我們宣告了一個變數和一個函式,第一個percent是變數,變成了等同於在class component中的this.state.percent;而第二個changePercent變成了等同於(值)=>{this.setState({percent:值})}的函式(這裡的函式在語法上沒有限定命名原則,但一般會以set做為開頭,這篇是為了配合前一篇的程式碼才用change當開頭)。當我們在function component中使用:

changePercent("70%");

這時percent就會變成70%

我們用useState做了什麼?

實際上useState只是一個函式,它會接收一作為初始值的參數並回傳一個包含兩個值的array,第一個值是state、第二個值是用來對剛那個state做setState的函式。

console.log( useState("初始值"))   

而我們所做的const [percent, changePercent]=,是利用javascript的解構賦值,也就是我們一次宣告兩個變數,percent會變成等號後面賦予的array的第一個值,changePercent會變成等號後面賦予的array的第二個值。

把class component變成function component吧!

在之前,我們有一個App.js:

import React, { Component } from 'react';
class App extends Component{
  constructor(props) {
    super(props);
    this.state={
      percent:"30%"
    }
    this.changePercent=this.changePercent.bind(this);
  }

  changePercent(){ 
    this.setState({percent:"70%"})
  }


    render(){
        return(
          <div>
            <div className="progress-back" style={{backgroundColor:"rgba(0,0,0,0.2)",width:"200px",height:"7px",borderRadius:"10px"}}>
              <div className="progress-bar" style={{backgroundColor:"#fe5196",width:this.state.percent,height:"100%",borderRadius:"10px"}}></div>
            </div>
            <button onClick={this.changePercent}>轉換百分比 </button>
          </div>
        );
    }
}
export default App;

現在,我們來新增一個Progress.js,讓他有跟剛剛App.js一模一樣的功能,但是是用function component實現:

  1. 引入React以及useState

    import React, { useState } from 'react';
    
  2. 建立Progress function,並把剛剛App.jsrender()部份貼過來

    const Progress=()=>{
         return(
           <div>
             <div className="progress-back" style={{backgroundColor:"rgba(0,0,0,0.2)",width:"200px",height:"7px",borderRadius:"10px"}}>
               <div className="progress-bar" style={{backgroundColor:"#fe5196",width:this.state.percent,height:"100%",borderRadius:"10px"}}></div>
             </div>
             <button onClick={this.changePercent}>轉換百分比 </button>
           </div>
         );
     }
    
  3. 用useState在function中建立我們要的state變數以及改變它的方法

     const Progress=()=>{
         const [percent, changePercent] = useState("30%");
         return(
             (略)
         )
     }
    
  4. 把在return()中使用原本class的state以及function的地方,改成使用剛剛建立的percent以及函數

     <div className="progress-bar" style={{backgroundColor:"#fe5196",width:percent,height:"100%",borderRadius:"10px"}}></div>
    
    <button onClick={()=>{changePercent("70%")}}>轉換百分比 </button>
    
  5. 最後,記得export後在index.js中使用

    import React, { useState } from 'react';
    
    const Progress=()=>{
        const [percent, changePercent] = useState("20%");
        console.log( useState("初始值"))    
        return(
          <div>
            <div className="progress-back" style={{backgroundColor:"rgba(0,0,0,0.2)",width:"200px",height:"7px",borderRadius:"10px"}}>
                <div className="progress-bar" style={{backgroundColor:"#fe5196",width:percent,height:"100%",borderRadius:"10px"}}></div>
                </div>
            <button onClick={()=>{changePercent("70%")}}> 轉換百分比 </button>
          </div>
        );
    }
    export default Progress;
    

    index.js

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import Progress from './Progress'
    import * as serviceWorker from './serviceWorker';
    
    ReactDOM.render(
        <div>
            <Progress/>
        </div>,
        document.getElementById('root')
    );
    
    serviceWorker.unregister();
    

這樣就以function完成了一個和上一篇一模一樣的component。

useState(和其他的React hook)不能在function component中的迴圈、if-else、nest function(在function scope中宣告的function)被定義使用。

精確的說法是你不能在這些地方去定義產生React hook。(例如,宣告由變數和函式並從useState取得)

為什麼會這樣呢? 原因是和React hook的運作原理有關。

剛剛提到,我們只是用變數去接useState提供給我們的state和setState函式。對useState而言,它是依照順序去分辨每一個hook,而不是用我們的定義內容/來接的變數的名字去分辨
這是第一次render發生的事:

// ------------
// 你做的事情
// ------------

useState(0)           
useState('哈哈哈')        
useState(true) 

// -------------
// 對React hook而言
// -------------

我產生了一個hook,它是第1個,製造一個state並把其值指定為0
我產生了一個hook,它是第2個,製造一個state並把其值指定為'哈哈哈'
我產生了一個hook,它是第3個,製造一個state並把其值指定為true

// ...

re-render時:

// ------------
// 你做的事情
// ------------

useState(0)           
useState('哈哈哈')        
useState(true) 

// -------------
// 對React hook而言
// -------------

去找第1個hook -> 找到useState(0) 
去找第2個hook -> 找到useState('哈哈哈')   
去找第3個hook -> 找到useState(true) 

// ...

而迴圈、if-else、nest function有可能在某次re-render時不會執行。,以剛剛為例,如果今天沒有任何防護措施,假設今天把第2個React hook放在某個if,當某次re-render時這個if的執行條件為false,就會發生:
這是第一次render發生的事:

// ------------
// 你做的事情
// ------------

useState(0)           
useState('哈哈哈')        
useState(true) 

// -------------
// 對React hook而言
// -------------

我產生了一個hook,它是第1個,製造一個state並把其值指定為0
我產生了一個hook,它是第2個,製造一個state並把其值指定為'哈哈哈'
我產生了一個hook,它是第3個,製造一個state並把其值指定為true

// ...

re-render時(當沒有執行useState('哈哈哈')):

// ------------
// 你做的事情(這一次 useState('哈哈哈') 並沒有被執行)
// ------------

useState(0)
useState(true) 

// -------------
// 對React hook而言
// -------------

去找第1個hook -> 找到useState(0)
去找第2個hook -> 找到useState('哈哈哈') 

// ...

這樣會取得錯誤的hook。因此,React所選擇的防護措施是不讓你產生在這些scope中定義的React hook。以確保每次render時的順序一模一樣。

小結: 為什麼不用class component就好?

其中一個原因是元件結構會變簡單。

以前,當我們的專案到一定的規模後,component越來越多,JSX夾雜各處。有的時候即使只是很簡單的元件,卻因為要follow class component的語法而變得看起來雜亂、一大堆巢狀結構。於是,工程師開始思考能不能在結構相對陽春的function component中,使用class component常用的功能。

於是,React hook就出現了。

現在,請你比較一下App.jsProgress.js。功能完全一模一樣的元件,前者用了二十幾行,後者用了十幾行。

從這次鐵人賽不少以React hook為主體的系列文就可以知道,目前像這樣更簡易的元件設計方式正在被React社群大力推行。你就知道工程師看code看到多爆氣了

總之,多用React hook吧!


上一篇
【React.js入門 - 12】 state 與 詳解setState語法
下一篇
【React.js入門 - 14】 Debug利器 : React-Developer-Tools
系列文
給初入JS框架新手的React.js入門31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言