iT邦幫忙

2023 iThome 鐵人賽

DAY 16
0
自我挑戰組

用ChatGPT詠唱來完成工作與點亮前後端技能樹系列 第 16

【Day16】ChatGPT請教教我:React入門(二)- State(狀態)

  • 分享至 

  • xImage
  •  

目前這個系列已挑選ChatGPT、前端&React相關章節出版成書!
感謝大家的閱讀與支持!
《用 ChatGPT 詠唱來點亮 React & 前端技能樹》
歷經重新整理、校正、編排、與修改後,讓章節內容之間的銜接更平順,除了補充上更多的解說之外,甚至有部分章節為重新撰寫的內容,讓理解變得更輕鬆!
目前已在天瓏書局博客來金石堂等通路都買得到了! 也有電子書的選擇!
歡迎各位點擊連結選購,謝謝大家~
https://ithelp.ithome.com.tw/upload/images/20240419/20135750lQM12by6v1.jpg


上一篇,我們建立了基礎的React專案
學習了JSX、元件、還有傳遞Props的知識

ChatGPT上一篇告訴我們,React的三大核心是:
「元件(Component)」、「屬性(Props)」、「狀態(State)」

接下來要講講React的第三個核心內容:狀態(State)

狀態(State)

https://ithelp.ithome.com.tw/upload/images/20230927/20135750tNGabmZcNL.png

當然,我來為你詳細解釋 React 中的 state,並提供範例。在 React 中,state 是一個非常重要的概念,它用於管理組件(component)的內部數據,當這些數據改變時,React 會自動重新渲染組件,以反映最新的狀態。

什麼是 State?

在 React 中,state 是一個 JavaScript 物件,它包含了組件內部需要跟蹤的數據。這些數據可以是任何你需要在組件中保持同步的值,例如用戶輸入、API 請求的結果、計數器等。state 可以被組件訪問和修改,當它發生變化時,React 會重新渲染組件,以顯示新的數據。

State 的作用是什麼?

State 的主要作用是在 React 應用程序中維護和同步數據,以便根據數據的變化來更新 UI。當組件的 state 發生變化時,React 會自動重新渲染組件,從而實現數據和 UI 的同步更新。這使得我們可以創建動態、互動性強的用戶界面。

如何使用 State?

首先,你需要在 React 組件中引入 useState 鉤子(hook),這是一個用於管理組件狀態的功能。接著,你可以使用 useState 創建一個 state 變數,並給它一個初始值。這個初始值可以是任何你想要的數據類型,例如字符串、數字、物件等。

以下是一個簡單的 React 組件示例,展示如何使用 useState 創建和更新 state:

import React, { useState } from 'react';

function Counter() {
  // 使用 useState 創建一個名為 count 的 state 變數,並初始化為 0
  const [count, setCount] = useState(0);

  // 定義一個函數來增加 count 的值
  const incrementCount = () => {
    setCount(count + 1); // 使用 setCount 更新 count
  };

  return (
    <div>
      <p>目前計數:{count}</p>
      <button onClick={incrementCount}>增加計數</button>
    </div>
  );
}

export default Counter;

在這個例子中,我們使用 useState 創建了一個名為 count 的 state 變數,並初始化為 0。每次點擊按鈕時,incrementCount 函數會被調用,並使用 setCount 更新 count 的值,從而觸發組件重新渲染,顯示最新的計數值。

這就是 React 中 state 的基本使用方式。你可以根據你的需求創建多個 state 變數,並使用它們來管理組件的內部數據。希望這個範例能幫助你更好地理解 React 中的 state 概念。如果你有任何問題或需要進一步的解釋,請隨時告訴我。

按照上面的範例做之後,把檔案引入App.tsx
https://ithelp.ithome.com.tw/upload/images/20230927/20135750WqEeKnhIZE.png

然後看一下成果
Imgur
確實每當點擊按鈕時,就會進行+1

state對初學React來說,是一個完全新的概念

上述有提到一連串狀態與畫面的變化
但似乎說明得還不夠明確,請ChatGPT針對流程進行解釋
另外我想到可以讓ChatGPT繪製示意圖的方式了,使用Mermaid的語法!
https://ithelp.ithome.com.tw/upload/images/20230927/20135750oDArLc4cAM.png

當然,我將用 Mermaid 來繪製示意圖,以顯示 state 變化與組件渲染的流程。

graph TD;
A[開始] -->|初始化| B[渲染初始組件]
B -->|按下按鈕| C[觸發增加計數事件]
C -->|更新state| D[重新渲染組件]
D -->|顯示新數據| B

以上是一個簡單的示意圖,說明了在 React 中當按下按鈕時,如何觸發增加計數事件、更新 state,以及重新渲染組件的過程。

  • 開始:代表程式的起點。
  • 渲染初始組件:當 React 應用程序啟動時,首先會渲染初始的組件,這裡是指 Counter 組件,並初始化計數器的狀態為 0。
  • 按下按鈕:當用戶按下增加計數的按鈕時,會觸發相應的事件。
  • 觸發增加計數事件:按鈕的點擊事件會觸發增加計數事件,這裡指的是 incrementCount 函數。
  • 更新 state:在增加計數事件中,我們使用 setCount 更新了 count 的值。
  • 重新渲染組件:當 state 更新時,React 會重新渲染組件,以顯示最新的計數值。

這個流程圖可以幫助你更好地理解 React 中 state 變化與組件渲染的過程。如果你有任何進一步的問題或需要額外的說明,請隨時告訴我。

將Mermaid 的語法丟到:https://mermaid.live/edit
https://ithelp.ithome.com.tw/upload/images/20230927/201357505EQ1YGBpzi.png

就可以看到ChatGPT為我們繪製的流程示意圖
因此可以理解,使用const [count, setCount] = useState(0);
這個語法之後,count就是變數值,setCount 可以更改count的值
而且畫面會自動根據count 的變化,更改元件的顯示

請ChatGPT為我們詳細解釋useState 的用法
https://ithelp.ithome.com.tw/upload/images/20230927/20135750pmzvFbIunp.png

當然,讓我進一步深入介紹 useState

什麼是 useState

useState 是一個 React 的 hook,用於在 functional component 中添加狀態(state)。它允許你在不轉換成 class component 的情況下,使用狀態管理功能。

如何使用 useState

要使用 useState,首先需要在你的 functional component 中引入 React 和 useState

import React, { useState } from 'react';

然後,你可以在 component 內部使用 useState hook 來創建狀態變數。語法如下:

const [state, setState] = useState(initialState);

這裡的 state 是一個變數,它包含了當前的狀態值,setState 是一個函數,用於更新 state 的值,initialState 是狀態的初始值。

以下是一個簡單的例子:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>目前計數:{count}</p>
      <button onClick={incrementCount}>增加計數</button>
    </div>
  );
}

export default Counter;

在這個例子中,我們使用 useState(0) 創建了一個名為 count 的狀態變數,並將其初始化為 0。setCount 函數用於更新 count 的值。

useState 的返回值

useState 返回一個包含兩個值的陣列,第一個值是當前的狀態值,第二個值是一個函數,用於更新該狀態的值。

多個狀態變數

你可以在同一個 component 中多次使用 useState 來創建多個狀態變數。

const [state1, setState1] = useState(initialState1);
const [state2, setState2] = useState(initialState2);
// ...

函數更新

setState 也可以接受一個函數作為參數,這個函數將接收前一個狀態值作為參數,並返回新的狀態值。

setCount(prevCount => prevCount + 1);

這樣做的好處是可以避免因為非同步更新而導致的問題。

使用 useState 的注意事項

  • 不要在條件判斷或迴圈中調用 useState,應該保證它在每次 render 中都被調用,並且總是以相同的順序被調用。
  • useState 的初始值可以是一個值,也可以是一個返回值的函數,這樣可以避免不必要的計算。

希望這份深入介紹能幫助你更好地理解和使用 useState。如果你有任何進一步的問題,請隨時告訴我。

ChatGPT提到了「函數更新」,內部傳送的東西看起來是一個箭頭函式,是一個callback函數
和前面直接放數值或是函數回傳值的方式不太一樣
請ChatGPT為我們深入介紹
https://ithelp.ithome.com.tw/upload/images/20230927/20135750NlMNUkBAKH.png

當使用 setState 函數時,你可以選擇使用函數式更新,即將一個函數作為參數傳遞給 setState,而不是直接傳遞一個新的狀態值。這種方式是為了更可靠地處理狀態更新,特別是在多個連續的狀態更新操作中。以下是對函數式更新的解釋和目的:

函數式更新的語法

函數式更新的語法如下:

setState((prevState) => {
  // 在這裡根據 prevState 計算新的狀態值 newState
  return newState;
});
  • prevState:這是前一個狀態的快照,它是 setState 函數的參數。
  • newState:這是一個函數,用於計算新的狀態值。

目的和好處

函數式更新的主要目的是確保在狀態更新期間,你能夠基於前一個狀態值來計算新的狀態值,而不受非同步操作的干擾。這樣做的好處包括:

  1. 避免競爭條件:當多個狀態更新操作在同一時間內執行時,React 可能會將它們合併為一個更新。如果你使用了函數式更新,React 可以確保每次更新都是基於前一個狀態值進行計算,從而避免競爭條件和錯誤的發生。
  2. 更可讀的代碼:函數式更新允許你在函數內部使用清晰的邏輯來計算新的狀態值,這可以使你的代碼更容易閱讀和維護。
  3. 高性能:React 可以根據需要優化函數式更新的執行,以提高性能。例如,React 可能會延遲執行多個函數式更新,以減少渲染的次數,從而節省效能。

何時使用函數式更新

通常情況下,如果你的狀態更新操作是基於前一個狀態值進行計算的,建議使用函數式更新。這尤其適用於計數器、計數器、或任何需要累積狀態的場景。

以下是一個示例,展示了如何使用函數式更新來計數按鈕點擊的次數:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const incrementCount = () => {
    setCount((prevCount) => prevCount + 1); // 使用函數式更新
  };

  return (
    <div>
      <p>目前計數:{count}</p>
      <button onClick={incrementCount}>增加計數</button>
    </div>
  );
}

export default Counter;

在這個例子中,我們將 setCount 中的更新邏輯包裹在一個函數中,以確保它是基於前一個計數值進行計算的。

總之,函數式更新是一個在 React 中管理狀態時非常有用的技巧,它確保了可靠性和性能。當你需要基於前一個狀態值計算新的狀態時,不妨考慮使用函數式更新。如果有任何進一步的問題,請隨時提問。

使用函數式更新時,提到目的是為了「避免競爭條件」
什麼是「競爭條件」?
https://ithelp.ithome.com.tw/upload/images/20230927/20135750FOJXsCa620.png

函數式更新和非函數式更新的主要差別在於它們處理狀態更新的方式以及如何處理競爭條件的能力。

1. 差別:狀態更新方式

  • 函數式更新:使用函數式更新時,你將一個函數作為 setState 的參數,該函數接收前一個狀態值作為參數,並返回新的狀態值。這樣可以確保每次更新都是基於前一個狀態值計算的,從而確保了狀態的正確性。
  • 非函數式更新:如果你直接傳遞一個新的狀態值給 setState,則狀態更新是基於當前的狀態值,而不考慮之前的狀態。這可能會導致不可預測的結果,特別是在多個連續的狀態更新操作中。

2. 問題:競爭條件

競爭條件是指在多個操作同時競爭修改同一數據的情況下可能出現的問題。在 React 中,當你執行多個狀態更新操作時,React 可能會將它們合併為一個更新,這樣可以提高性能。然而,如果你不小心使用了非函數式更新,可能會導致競爭條件。

例如,考慮以下情景:

// 非函數式更新
setCount(count + 1);
setCount(count + 1);

在這種情況下,React 可能將這兩個 setCount 調用合併為一個更新,導致 count 只增加了一次,而不是預期的兩次。這是因為 React 不知道在第一個 setCount 調用之後,count 的值已經改變。

但是,如果你使用函數式更新,情況就會不同:

// 函數式更新
setCount((prevCount) => prevCount + 1);
setCount((prevCount) => prevCount + 1);

在這種情況下,每次 setCount 調用都是基於前一個 prevCount 的值計算的,因此 count 會正確地增加兩次。

總結來說,使用函數式更新可以確保你在處理狀態更新時能夠基於前一個狀態值計算新的狀態,從而避免競爭條件和不可預測的結果。因此,當你處理需要連續更新的狀態時,建議使用函數式更新。

https://ithelp.ithome.com.tw/upload/images/20230927/20135750rNRk4E4PBL.png

當然,以下是一份示例程式碼,可以清晰地展示有發生競爭條件和沒有的差別:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  // 有競爭條件的情況
  const incrementWithRaceCondition = () => {
    setCount(count + 1);
    setCount(count + 1);
  };

  // 沒有競爭條件的情況
  const incrementWithoutRaceCondition = () => {
    setCount((prevCount) => prevCount + 1);
    setCount((prevCount) => prevCount + 1);
  };

  return (
    <div>
      <p>目前計數:{count}</p>
      <button onClick={incrementWithRaceCondition}>增加計數(有競爭條件)</button>
      <button onClick={incrementWithoutRaceCondition}>增加計數(無競爭條件)</button>
    </div>
  );
}

export default Counter;

在這個範例中,我們創建了一個簡單的計數器組件,並提供了兩個按鈕:一個用於觸發有競爭條件的計數器增加,另一個用於觸發沒有競爭條件的計數器增加。

  • incrementWithRaceCondition 函數中,我們連續兩次呼叫 setCount(count + 1),這可能會導致競爭條件,因為 React 可能會將這兩次調用合併為一次,導致計數器只增加了一次。
  • incrementWithoutRaceCondition 函數中,我們使用了函數式更新,確保了每次都是基於前一個狀態值計算的,因此計數器會正確地增加兩次。

你可以在你的 React 應用中嘗試運行這段程式碼,以查看兩者之間的差異。

既然拿到了範例程式碼,就來測試一下
開一個新檔案,把ChatGPT給的組件貼上去
然後在App.tsx 中引入
https://ithelp.ithome.com.tw/upload/images/20230927/20135750VMZrkxX95u.png

來看看效果
img

我對於合併執行的理解是:
兩個按鈕都是執行兩次setCount

有兢爭條件的那個,執行 setCount(count + 1);
第一次執行時,初始Count = 0,按下去後雖然執行了setCount(0+1)
但因為setCount是異步的,當執行第二次時
count 被增加的效果可能還沒觸發,導致執行第二次時,仍然是執行setCount(0+1)
最終兩次的setCount(count + 1) ,合併成一次的效果執行

事件綁定

上面有個這樣的語法 : <button onClick={incrementCount}>Increment Count</button>
我們先前一直忽視事件綁定的語法,只專注在useState的使用

現在來詢問一下React中,事件綁定的語法和說明
https://ithelp.ithome.com.tw/upload/images/20230927/20135750Hy1weXUyFO.png

當然,讓我為你解釋 React 中的事件綁定。

在 React 中,你可以通過綁定事件來處理用戶與應用程序的互動,這是構建動態用戶界面的重要一環。

1. 基本事件綁定

React 使用 onClickonBluronChange 等屬性來綁定事件,這些屬性接收一個函數作為值,這個函數會在事件發生時被調用。

以下是一個簡單的例子,展示了如何綁定一個點擊事件:

import React from 'react';

function ButtonComponent() {
  function handleClick() {
    alert('按鈕被點擊了!');
  }

  return (
    <button onClick={handleClick}>
      點擊我
    </button>
  );
}

export default ButtonComponent;

2. 傳遞參數

如果你需要在事件處理函數中傳遞參數,可以使用箭頭函數或者使用 bind 方法。以下是使用箭頭函數的例子:

import React from 'react';

function ButtonComponent() {
  function handleClick(parameter) {
    alert(`你傳遞了參數:${parameter}`);
  }

  return (
    <button onClick={() => handleClick('這是參數')}>
      點擊我
    </button>
  );
}

export default ButtonComponent;

3. 阻止默認行為

有時你可能需要阻止某些元素的默認行為,例如阻止表單的提交。你可以在事件處理函數中調用 preventDefault 方法。

import React from 'react';

function FormComponent() {
  function handleSubmit(event) {
    event.preventDefault();
    alert('表單已提交!');
  }

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">提交表單</button>
    </form>
  );
}

export default FormComponent;

4. 事件對象

React 會自動傳遞一個事件對象給事件處理函數。你可以使用這個事件對象來獲取相關的信息,例如鼠標位置、按鈕狀態等。

import React from 'react';

function MouseComponent() {
  function handleMouseMove(event) {
    console.log(`鼠標位置:${event.clientX}, ${event.clientY}`);
  }

  return (
    <div onMouseMove={handleMouseMove}>
      移動鼠標以獲取位置信息
    </div>
  );
}

export default MouseComponent;

這些是 React 中基本的事件綁定方式,你可以根據需要使用不同的事件來處理用戶的操作。希望這些說明對你有所幫助!如果你有任何進一步的問題,請隨時告訴我。

最後,比較一下React綁定事件的方式和jQuery綁定事件的方式
https://ithelp.ithome.com.tw/upload/images/20230927/20135750XdSqtihF1I.png

當然,我會提供用 jQuery 和 React 分別實現點擊按鈕後計數+1 的範例。

用 jQuery 撰寫的範例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>計數器範例(jQuery)</title>
  <script src="<https://code.jquery.com/jquery-3.6.0.min.js>"></script>
</head>
<body>

  <button id="countButton">點擊我增加計數</button>
  <p>目前計數:<span id="count">0</span></p>

  <script>
    $(document).ready(function() {
      let count = 0;

      $('#countButton').click(function() {
        count++;
        $('#count').text(count);
      });
    });
  </script>

</body>
</html>

用 React 撰寫的範例:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <button onClick={incrementCount}>點擊我增加計數</button>
      <p>目前計數:{count}</p>
    </div>
  );
}

export default Counter;

React 寫法與 jQuery 寫法的比較:

1. jQuery 寫法:

  • 在 jQuery 中,我們使用了 $(document).ready() 函數,確保當文檔完全加載後再執行腳本。
  • 我們使用了一個變數 count 來跟蹤計數器的值。
  • 點擊按鈕時,我們使用了 $('#countButton').click() 來綁定點擊事件,並通過選擇器 $('#count') 更新計數器的值。

2. React 寫法:

  • 在 React 中,我們使用了 useState 鉤子來創建狀態變數 count 並設置初始值為 0。
  • 我們定義了一個函數 incrementCount 來更新 count 的值,並將其綁定到按鈕的 onClick 事件中。
  • 在 JSX 中,我們使用 {count} 來顯示計數器的值。

總的來說,React 提供了一種更結構化和組件化的方式來處理 UI,並將狀態和 UI 邏輯進行了更好的隔離。而 jQuery 則是一個基於 DOM 操作的函數庫,它可以用於直接修改 HTML 元素的屬性和內容。

在現代的前端開發中,React 或其他框架通常被更廣泛地使用,因為它們提供了更清晰、可維護且可擴展的開發模型。

React將「計數」的變數,管理在元件內部本身,用「狀態」的概念來維護
改變了「狀態」,會自動重新渲染使用到該變數的畫面

而jQuery的話,變數的存取可能沒有限制或規範,任何一處的程式碼都可以存取更動
更動了變數,還需要再特別將變數寫回畫面中

結語

本次的對話在這邊:
https://chat.openai.com/share/857b03f9-9255-443a-a7c3-d3ea85384264

state的處理在React中相當重要
注意state是一種異步的處理,並根據情況選擇使用函數式更新
可以避免一些機率性的錯誤以及踩坑

React將狀態、事件都和元件(畫面)寫在一起,避免了全域變數的存取
且讓程式碼易於重複使用

今天的state學習就到這邊

我是和React熱戀中的前端工程師一宵三筵,我們明天見~


上一篇
【Day15】ChatGPT請教教我:React入門(一)- 起始安裝、JSX元素、元件!
下一篇
【Day17】ChatGPT請教教我:React入門(三)- 運用JS高階函數,進階JSX組合元件!
系列文
用ChatGPT詠唱來完成工作與點亮前後端技能樹56
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言