iT邦幫忙

2023 iThome 鐵人賽

DAY 17
0
自我挑戰組

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

【Day17】ChatGPT請教教我:React入門(三)- 運用JS高階函數,進階JSX組合元件!

  • 分享至 

  • xImage
  •  

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


我們在前兩篇中,學會了JSX的語法、元件Component的撰寫
也會傳Props和使用State了,一開始ChatGPT說的三大核心的學會了

挽起袖子開始要撰寫自己的Component、撰寫自己的程式碼
把程式碼分成很多小小的Component,然後組裝起來

但這時候就會遇到一個問題 —— 大量重複的Component,要怎麼組裝起來?

舉個最簡單的例子
今天如果需要製作一個select 下拉選單,裡面有1 ~ 10的選項 可供選擇
如果硬刻的話,大概是這樣的狀況

function MySelect() {
 // 用useState 紀錄目前選擇的選項
	const [selectedNumber, setSelectedNumber] = useState(1);

	return (
		<select 
			value={selectedNumber}
			onChange={(event)=>setSelectedNumber(parseInt(event.target.value));}
		>
			<option value={1}>1</option>
			<option value={2}>2</option>
			<option value={3}>3</option>
			<option value={4}>4</option>
			<option value={5}>5</option>
			<option value={6}>6</option>
			<option value={7}>7</option>
			<option value={8}>8</option>
			<option value={9}>9</option>
			<option value={10}>10</option>
		</select>
	);
}

這個是10個選項的狀況
複製貼上再修改的過程,會不免讓人懷疑自己是不是該請工讀生來幫忙處理...?
但那如果需求需要50個呢? 100個呢?

除了會貼到死、造成很多冗長的程式之外,還很有可能不小心改錯
而且我們是工程師!怎麼可以做這種冗贅的事情!

所以來問問看ChatGPT吧
https://ithelp.ithome.com.tw/upload/images/20230928/20135750DEafPVSnOZ.png

在 React 中,若你需要處理大量重複的內容,你可以考慮使用迴圈或生成元素的方式來簡化程式碼,這樣可以減少手動輸入的工作量。

以你提供的 <select> 元素為例,如果你想要生成 1 到 100 的選項,可以使用迴圈來動態生成這些選項,如下:

<select
  value={selectedNumber}
  onChange={(event) => setSelectedNumber(parseInt(event.target.value))}
>
  {Array.from({ length: 100 }, (_, index) => (
    <option key={index + 1} value={index + 1}>
      {index + 1}
    </option>
  ))}
</select>

這段程式碼會生成 1 到 100 的選項,並且在 <option> 元素中使用迴圈的 index 來當作選項的值和內容。這樣的做法可以節省大量的手動輸入。

在這段程式碼中,Array.from({ length: 100 }, (_, index) => ...) 會產生一個長度為 100 的陣列,然後使用 index 來生成選項的值和內容。

記得在迴圈中每個元素都要有獨一無二的 key 屬性,這樣 React 才能正確地辨識元素。

希望這樣的寫法能對你有所幫助,如果你有任何問題或需要進一步的說明,請隨時告訴我!

Array.from 是原本JS就存在的語法
ChatGPT提供的寫法,用簡單幾行程式,就生成了下面這樣的陣列

[
	<option key={1} value={1} >1</option>,
	<option key={2} value={2}>2</option>,
	<option key={3} value={3}>3</option>,
	...,
	<option key={10} value={10}>10</option>
]

在JSX語法中,可以直接吃一個JSX元素陣列作為內容

但這邊要注意一點,上面提到「迴圈中每個元素都要有獨一無二的 key 屬性」
我們來進一步詢問這個KEY是什麼,作用是什麼
https://ithelp.ithome.com.tw/upload/images/20230928/201357500crLKyuwL9.png

key 在 React 中是一個特殊的屬性,用來幫助 React 依靠元素的獨一無二的標識來辨識、跟蹤和管理元素。

具體來說,key 的作用和目的如下:

  1. 唯一標識元素key 的主要目的是讓 React 可以區別不同的元素,這樣 React 就可以準確地確定哪些元素需要被修改、更新或刪除,而不會誤判。
  2. 提高重渲染效率:當 React 進行列表更新時,使用 key 可以幫助它識別哪些元素是新的、哪些元素是已經存在的。這樣 React 可以最小化對 DOM 的操作,提高重渲染的效率。
  3. 保持元素的穩定性key 可以確保元素在重新排列時保持穩定,即使列表順序發生變化,React 也能正確地辨識元素。
  4. 提供穩定的元素狀態:當元素在列表中添加或刪除時,key 可以保證元素的狀態保持穩定,這對於某些需要維持狀態的場景非常重要。

總的來說,key 在 React 中是一個非常重要的屬性,特別是在處理動態生成的列表時。它可以幫助 React 正確地辨識、跟蹤和管理元素,保證應用程式的正確運作。

如果你不提供 key,React 會在開發者工具中發出警告,提醒你加上 key 屬性以確保元素的唯一性。

希望這能幫到你理解 key 的重要性和作用!如果你還有其他問題,歡迎隨時提出。

他說了key是唯一的標識,讓React區別不同元素,幫助React哪些元素是新的

但上面的例子,我們使用了index作為key值
index只和順序有關係,也就是改變了順序的話,不同子元件的key值就會被變動

這樣是可以的嗎?
https://ithelp.ithome.com.tw/upload/images/20230928/201357504lrdbiNVu5.png

雖然可以使用索引(index)作為 key,但這並不是一個推薦的做法,特別是在動態生成的列表中。

以下是一些需要考慮的因素:

  1. 穩定性問題:使用索引作為 key 可能會導致列表項目在重新排列時出現穩定性問題。如果列表中的項目會被重新排序,索引可能不再唯一,這可能會導致 React 遇到困難。
  2. 效能問題:當項目被插入或刪除時,使用索引作為 key 可能會導致 React 需要重新調整整個列表,這可能會降低效能。
  3. 重複性問題:如果列表中的項目具有唯一標識(例如 ID 或其他獨特的值),最好使用這些值作為 key,而不是依賴於索引。

總的來說,最好的做法是盡量避免使用索引作為 key,而是優先選擇具有唯一標識的值作為 key。這樣可以確保 React 正確地跟蹤和管理元素,並且在列表項目發生變化時保持穩定性。

如果你的列表項目沒有明顯的唯一標識,你可以考慮使用一些其他唯一的值,或者在需要時生成一個唯一的標識。

希望這能幫到你理解為什麼使用索引作為 key 可能不是最佳做法。如果你還有其他問題,歡迎隨時提出。

https://ithelp.ithome.com.tw/upload/images/20230928/20135750Zs7gzcWhE8.png

當然,我可以提供一個範例來展示使用索引作為 key 可能會遇到的問題。

假設我們有一個簡單的 React 組件,它動態生成一個列表,並將索引作為 key

import React, { useState } from 'react';

const ExampleComponent = () => {
  const [items, setItems] = useState(['apple', 'banana', 'cherry']);

  const handleRemove = (index) => {
    const newItems = [...items];
    newItems.splice(index, 1);
    setItems(newItems);
  };

  return (
    <div>
      <ul>
        {items.map((item, index) => (
          <li key={index}>
            {item}
            <button onClick={() => handleRemove(index)}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default ExampleComponent;

在這個例子中,我們有一個簡單的列表,每個列表項目都有一個 "Remove" 按鈕,當按下這個按鈕時,該項目會被從列表中移除。

然而,這個例子存在一個潛在的問題。假設我們移除了列表中的第一個項目(蘋果),那麼狀態陣列將變為 ['banana', 'cherry']

如果我們使用索引作為 key,React 會將新的項目陣列視為全新的列表,這將導致 React 需要重新渲染整個列表,而不僅僅是更新第一個項目。這可能會導致效能問題,尤其在列表項目數量很大的情況下。

為了解決這個問題,最好的做法是使用具有唯一標識的值作為 key,而不是依賴於索引。這樣可以確保 React 正確地跟蹤和管理元素,並在列表項目發生變化時保持穩定性。

希望這個範例能幫到你理解使用索引作為 key 可能會出現的問題。如果你還有其他問題,歡迎隨時提出。

好的,把上面的程式碼運行看看
結果就會得到這樣

PxN2ttVfcg.gif

每按一下,列表裡面的所有內容都重新渲染了一下
為了得到對照,請ChatGPT生成一份「沒有渲染效能問題」的
https://ithelp.ithome.com.tw/upload/images/20230928/20135750QH3Hh1vuMk.png

要修正上面的程式碼中的 key 問題,你可以使用具有唯一標識的值作為 key,而不是依賴於索引。

在這個例子中,可以使用列表中的元素(例如水果名稱)作為 key,因為這些名稱是唯一的。這樣可以確保 React 正確地跟蹤和管理元素。

以下是修正後的程式碼:

import React, { useState } from 'react';

const ExampleComponent = () => {
  const [items, setItems] = useState(['apple', 'banana', 'cherry']);

  const handleRemove = (item) => {
    const newItems = items.filter((i) => i !== item);
    setItems(newItems);
  };

  return (
    <div>
      <ul>
        {items.map((item) => (
          <li key={item}>
            {item}
            <button onClick={() => handleRemove(item)}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default ExampleComponent;

在這個修正後的程式碼中,我們將水果名稱作為每個列表項目的 key。這樣當我們移除列表中的項目時,React 可以正確地辨識和更新相應的元素,而不會出現效能問題。

希望這能幫到你理解如何修正 key 的問題。如果你還有其他問題,歡迎隨時提出。

oZS1MLXe3Q.gif

可以和先前的對比一下
這次的程式碼,在點擊移除的時候
非移除的其他子項目,內容(水果名稱)的部分是不會重新渲染的

如果內容複雜(例如是圖片、更複雜大型的Component)
其渲染效能就會有顯著差異

其他JSX寫法

既然可以用Array.from 或是.map來生成JSX
是不是可以善用JS的靈活性,動態生成或是決定我們要渲染的元件呢?

請ChatGPT來提供一些範例看看
(然後我打錯字了,但ChatGPT仍然看得懂)
https://ithelp.ithome.com.tw/upload/images/20230928/20135750OlLnnsL8DO.png

除了使用 .map 以外,React 提供了一些其他方法來處理 JSX 元素的靈活寫法,具體取決於你的需求和情境。以下是一些常見的方法:

  1. 條件渲染 (Conditional Rendering):你可以使用 if 條件語句或三元運算符來根據條件來渲染 JSX 元素。這使你可以根據不同的情況渲染不同的內容。
{shouldRenderElement && <MyComponent />}
  1. 迴圈或遞迴:除了 .map,你還可以使用 JavaScript 中的其他迴圈或遞迴方式來動態生成 JSX 元素。
const elements = [];
for (let i = 0; i < 5; i++) {
  elements.push(<div key={i}>Element {i}</div>);
}
  1. 陣列方法:JavaScript 陣列方法像 .map.filter.reduce 等可以幫助你對陣列進行操作,然後返回 JSX 元素。
const filteredElements = items
  .filter(item => item.condition)
  .map(item => <MyComponent key={item.id} data={item.data} />);
  1. 內聯條件 (Inline Conditionals):你可以使用 JSX 中的內聯條件來根據條件動態選擇渲染的內容。
{isUserLoggedIn ? <UserProfile /> : <LoginButton />}
  1. 使用 Fragment:當你需要返回多個元素而不包裝在外層元素中時,可以使用 React 的 Fragment 來包裹這些元素。
<>
  <Element1 />
  <Element2 />
</>
  1. 將邏輯提取到函數中:如果 JSX 元素內的邏輯變得很複雜,你可以將部分邏輯提取到函數中,然後在 JSX 中調用該函數。
function renderElement(condition) {
  return condition ? <Element1 /> : <Element2 />;
}

// 在 JSX 中使用
{renderElement(isConditionTrue)}

這些方法提供了不同的方式來處理 JSX 元素,具體取決於你的應用程式需求和程式碼組織。選擇最適合你的方法,以提高代碼的可讀性和維護性。

ChatGPT提供了好幾個我們上兩個章節沒有使用過的方式
我們針對沒有看過的語法,逐一的進行追問

條件渲染

https://ithelp.ithome.com.tw/upload/images/20230928/20135750gxJwskPXY2.png

這個語法 {shouldRenderElement && <MyComponent />} 是一種條件渲染的寫法,它使用了 JavaScript 中的邏輯運算符 &&

這個寫法的意思是:當 shouldRenderElement 為真(true)時,就會渲染 <MyComponent />,否則不會渲染。

這種條件渲染的技巧利用了 JavaScript 中的短路特性。具體來說:

  • 如果 shouldRenderElement 為假(false),整個表達式就會返回 false,React 不會繼續渲染 <MyComponent />,從而節省了渲染的時間和資源。
  • 如果 shouldRenderElement 為真(true),整個表達式會繼續執行,然後返回 <MyComponent />,React 會將 <MyComponent /> 渲染到 DOM 中。

這種技巧通常用於根據特定條件來選擇是否渲染某個元素,這樣可以幫助你在 React 應用程式中進行動態渲染。

這裡提供一個簡單的範例來示範這種語法的使用:

import React, { useState } from 'react';

const ExampleComponent = () => {
  const [shouldRenderComponent, setShouldRenderComponent] = useState(true);

  const toggleComponent = () => {
    setShouldRenderComponent(!shouldRenderComponent);
  };

  return (
    <div>
      <button onClick={toggleComponent}>Toggle Component</button>
      {shouldRenderComponent && <MyComponent />}
    </div>
  );
};

export default ExampleComponent;

在這個例子中,shouldRenderComponent 是一個狀態(state),並且預設為 true。當按下 "Toggle Component" 按鈕時,toggleComponent 函數會切換 shouldRenderComponent 的值。

在 JSX 中,我們使用 {shouldRenderComponent && <MyComponent />} 這樣的語法來判斷是否要渲染 <MyComponent />。如果 shouldRenderComponenttrue,則會渲染 <MyComponent />,否則不會。

這樣就實現了根據特定條件來控制元件的渲染。

希望這能幫到你理解 {shouldRenderElement && <MyComponent />} 這種語法的用法!如果你還有其他問題,歡迎隨時提出。

學到一招,OK繼續
https://ithelp.ithome.com.tw/upload/images/20230928/20135750ySJR40G9S2.png

這個語法 {isUserLoggedIn ? <UserProfile /> : <LoginButton />} 是一個條件渲染的寫法,它使用了 JavaScript 中的三元運算符 ?

這個寫法的意思是:當 isUserLoggedIn 為真(true)時,就會渲染 <UserProfile />,否則就會渲染 <LoginButton />

這種條件渲染的技巧可以讓你根據特定條件來選擇是否渲染不同的元素,這在 React 應用程式中是非常常見和有用的。

這裡提供一個簡單的範例來示範這種語法的使用:

import React, { useState } from 'react';

const ExampleComponent = () => {
  const [isUserLoggedIn, setIsUserLoggedIn] = useState(false);

  const handleLogin = () => {
    setIsUserLoggedIn(true);
  };

  const handleLogout = () => {
    setIsUserLoggedIn(false);
  };

  return (
    <div>
      {isUserLoggedIn ? <button onClick={handleLogout}>Logout</button> : <button onClick={handleLogin}>Login</button>}
      {isUserLoggedIn ? <UserProfile /> : <LoginButton />}
    </div>
  );
};

export default ExampleComponent;

在這個例子中,我們有一個狀態 isUserLoggedIn 來表示用戶是否已登錄。當按下 "Login" 按鈕時,handleLogin 函數會將 isUserLoggedIn 設為 true,表示用戶已登錄。當按下 "Logout" 按鈕時,handleLogout 函數會將 isUserLoggedIn 設為 false,表示用戶已登出。

在 JSX 中,我們使用 {isUserLoggedIn ? <UserProfile /> : <LoginButton />} 這樣的語法來根據 isUserLoggedIn 的值來渲染不同的元素。如果 isUserLoggedIntrue,則會渲染 <UserProfile />,否則會渲染 <LoginButton />

這樣就實現了根據用戶是否已登錄來顯示不同的內容。

希望這能幫到你理解 {isUserLoggedIn ? <UserProfile /> : <LoginButton />} 這種語法的用法!如果你還有其他問題,歡迎隨時提出。

這個比 && 的概念更容易理解(或是差不多)
OK下面一位~

Fargment

https://ithelp.ithome.com.tw/upload/images/20230928/20135750A0izxhTwfs.png

當你在 React 中需要返回多個元素時,通常需要將它們包裝在一個外層元素中。但有時你可能不想在 DOM 中添加多餘的元素,這時就可以使用 React 的 Fragment 來解決這個問題。

Fragment 是 React 提供的一個空的元素容器,它不會在 DOM 中增加任何額外的元素。它允許你在 JSX 中返回多個元素,而無需包裝在額外的 DOM 元素中。

以下是如何使用 Fragment 的教學:

  1. 引入 Fragment

首先,在你的 React 組件文件頂部引入 Fragment:

import React, { Fragment } from 'react';

或者你也可以這樣寫:

import React from 'react';
const Fragment = React.Fragment;
  1. 使用 Fragment

然後,在你的 JSX 中使用 Fragment 來包裹多個元素:

const ExampleComponent = () => {
  return (
    <Fragment>
      <div>Element 1</div>
      <div>Element 2</div>
    </Fragment>
  );
};

或者你可以使用簡寫的語法:

const ExampleComponent = () => {
  return (
    <>
      <div>Element 1</div>
      <div>Element 2</div>
    </>
  );
};

這樣,你就可以在 JSX 中返回多個元素,而不需要包裝在一個外層元素中。

Fragment 提供了一種簡潔的方式來解決在 React 中返回多個元素的問題,同時保持了 DOM 結構的整潔和一致。

請注意,在 React 16.2 之前,Fragment 需要使用 <React.Fragment> 來引入,而在 React 16.2 之後,你可以直接使用 <>...</> 的簡寫語法。

希望這能幫到你理解和使用 Fragment!如果你還有其他問題,歡迎隨時提出。

以下是我一開始學習的真實疑惑
https://ithelp.ithome.com.tw/upload/images/20230928/20135750JBKeNSbJt7.png

在 React 中,JSX 需要有一個最外層的元素來包裹所有的子元素,這是因為 JSX 本身只能返回單一的表達式,而不是多個獨立的元素。這個最外層的元素通常被稱為「容器元素」或「根元素」。

如果你不使用 Fragment,你也可以使用其他元素來包裹子元素,但這個包裹的元素會成為 DOM 中的一部分。這可能會導致在某些情況下出現不希望的結構。

舉例來說,如果你不使用 Fragment,你可能會這樣寫:

const ExampleComponent = () => {
  return (
    <div>
      <div>Element 1</div>
      <div>Element 2</div>
    </div>
  );
};

這樣,<div> 就會成為最外層的容器元素。雖然這樣也可以工作,但在某些情況下,可能會導致多餘的 DOM 結構,進而影響 CSS 的樣式套用。

因此,使用 Fragment 是一種比較優雅的方式,因為它不會在 DOM 中增加多餘的元素,保持了結構的整潔。

如果你對於外層一定要有單一元素有疑慮,你可以考慮是否有其他方式可以重新組織你的元件結構,以達到你的需求。

總的來說,外層必須要有一個單一的元素來包裹所有的子元素,這是 JSX 規範的一部分。使用 Fragment 是其中一種解決方案,也是一個較為推薦的做法。

最後,我們讓ChatGPT幫我們總結本篇的內容與重點
https://ithelp.ithome.com.tw/upload/images/20230928/20135750jdO8D7f5qp.png

在本次對話中,我們討論了以下重點內容:

  1. 條件渲染:介紹了在 React 中進行條件渲染的兩種常見方式,分別是使用 && 運算符和三元運算符 ?
    • 使用 && 運算符的寫法:{shouldRenderElement && <MyComponent />}
    • 使用三元運算符的寫法:{isUserLoggedIn ? <UserProfile /> : <LoginButton />}
  2. Fragment:解釋了 Fragment 的作用,它允許在 JSX 中返回多個元素,而無需包裝在額外的 DOM 元素中。
    • 引入 Fragment 的寫法:import React, { Fragment } from 'react';
    • 使用 Fragment 的寫法:<Fragment>{/* 子元素 */}</Fragment> 或簡寫語法 <>...</>
  3. 避免使用索引作為 key:強調了在 React 中避免使用索引作為 key 的重要性,並解釋了這樣做可能導致的問題。
  4. 其他靈活的寫法:介紹了除了使用 .map 之外,還有其他方法來處理 JSX 元素的靈活寫法,包括條件渲染、迴圈、陣列方法等。

總結來說,React 提供了許多靈活的方式來處理 JSX 元素,這些技巧可以根據情況和需求來選擇使用,以提高代碼的可讀性和維護性。

如果有任何進一步的問題或需要更詳細的解釋,請隨時提出。祝你在學習 React 的過程中順利!

結語

耶,直接請ChatGPT幫我做整個章節的總結,我覺得效果還可以
可以總覽本次討論的內容

如果我往後幾天沒有忘記的話
結尾可能都會請ChatGPT統整一次內容

結合JS的語法之後,能夠寫出的JSX語法就更加靈活
有了這些基礎,就能挑戰一些簡單的練習,像是製作圈圈叉叉或是TODO List都是經典的練習題

我是擁抱高階函數的前端工程師一宵三筵,我們明天見~


上一篇
【Day16】ChatGPT請教教我:React入門(二)- State(狀態)
下一篇
【Day18】ChatGPT請教教我:React入門(四)- Hook!副作用(Side Effect)?useEffect!
系列文
用ChatGPT詠唱來完成工作與點亮前後端技能樹56
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言