[React] 一文速通 React

2024 年 4 月 17 日
文章摘要
FakeGPT
加載中...
此內容由人工不智慧生成。

關於我為什麼要寫這篇文章,我有幾句話要講。本身作為一個前端開發人員,我一直有在用 Vue.js Framework,鑑於其快速構建大型 Web App 的能力,我對其非常喜歡。然而近期我打算進軍 Multi-Platform App 的領域了,這樣其實作為一個 Web 開發人員來講,就沒有多少可以做的選擇了。Vue.js 在跨裝置方面的應用尚且薄弱,而 React 家族的 React Native 和 Google 家的 Flutter 是這方面的絕對主力。

然而,“得益於” Flutter 噁心的樣式互套機制,對開發人員極度不友好,我毫無猶豫地放棄了它。所以,React Native 就成了我唯一的選擇。

所以讀者可以將這篇文章看作是 React Native 的前置知識。

本文的參考來自 React 的官方網站( https://react.dev/ ),感謝社區成員所作出的努力。

React 元件

React 最重要的概念就是“元件”(Component),一個 React 元件就是一個回傳 UI 組合的 JSX 函式。例如:

function Profile() {
return (
<img
src="https://i.imgur.com/MK3eW3As.jpg"
alt="Katherine Johnson"
/>
);
}

可以將 React 元件抽離成一個檔案,即所謂的 single-file component。例如我們可以在 Profile.js 檔案中匯出元件:

Profile.js
function Profile() {
return (
<img
src="https://i.imgur.com/MK3eW3As.jpg"
alt="Katherine Johnson"
/>
);
}
export default Profile

然後我們就可以在 Main.js 元件中使用這個元件:

import Profile from './Profile.js'
function Main() {
return (
<Profile></Profile>
);
}

React 元件在使用的時候需以大寫字母開頭,以區別普通的 DOM 元素。例如 <Profile></Profile>

JSX

React 使用的是 JS 的語法擴充套件 JSX,這個套件可以允許在 JS 檔案中書寫 HTML 類似的標記。

JSX 有一套嚴格的規則,不同於 HTML,使用時要特別注意:

  • 每一個 React 元件的回傳值必須是單根的。即所有元素必須被包含在一個根元素中。React推薦使用 <></>
function SingleRoot() {
return (
<>
<div>Hello, world.</div>
<img src="https://i.imgur.com/MK3eW3As.jpg"/>
</>
);
}
  • 所有的標籤必須關閉:使用自閉標籤例如 <img/> 或關閉的標籤 <li></li>。這一點對於有良好開發習慣的開發人員來講不成問題。

使用大括弧 {} 來開啟 JavaScript 程式碼窗

類似於 Vue.js 中的 JavaScript Template Reflect 機制,在 JSX 中,我們也可以在返回的 JSX DOM Tree 中嵌入我們的 JS 程式碼。這時候我們需要使用 {} 來開啟 JS 程式碼窗。

export default function Avatar() {
const avatar = 'https://i.imgur.com/7vQD0fPs.jpg';
const description = 'Gregorio Y. Zara';
return (
<>
<img
className="avatar"
src={avatar}
alt={description}
/>
<div>{description}</div>
</>
);
}

JS 程式碼窗可以用在標籤之間的 HTML Text 上,也可以用在標籤的屬性取值上,就如同上面的例子展示的一樣。

我們還可以在大括弧中傳遞 JS 物件:

export default function TodoList() {
return (
<ul style={{
backgroundColor: 'black',
color: 'pink'
}}>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
);
}

我們分析一下 <ul></ul> 這個標籤。其 style 屬性中,第一個大括弧表示開啟 JS 程式碼窗,第二個大括弧則是JS 物件的大括弧

透過向標籤屬性中傳遞 JS 物件,我們可以實現 CSS 的動態傳遞。

向元件中傳遞 props

Vue.js 中也有 props 傳遞的概念,JSX 類比與那個概念。由於 React 元件屬於 JS 函式,因此,props 可以作為函式的引數來傳遞,這一點確實比 Vue.js 要來得方便。

Profile.js
// 使用 {} 來定義 props
export default function Profile({avatar, name}) {
return (
<>
<img src={avatar}/>
<div>{name}</div>
</>
);
}

然後在 main.js 中:

main.js
import Profile from './Profile.js'
function Main() {
const avatarUrl = "https://i.imgur.com/7vQD0fPs.jpg"
const name = "Gregorio Y. Zara"
return (
<Profile
avatar={avatarUrl}
name={name}
></Profile>
);
}

還可以為 props 設定預設值:

function profile({avatar, name = "Gregorio Y. Zara"}) {
...
}

props 透傳

如果一個元件希望將自己的所有 props 傳遞給子元件,那麼我們有一個 props 透傳的簡潔語法:

function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}

這將把 Profile 元件的所有 props 全部透傳到 Avatar 元件。

props 是不可改變的。當我們要 props 改變的時候,我們其實是需要傳遞一個新的 props,而不是改變舊的。這一點將會在下面講到。

條件渲染

類似於 Vue.js 中的 v-ifv-else。由於 JSX 使用函式渲染的模式,因此我們可以很方便地使用 JS 的 if-else 來進行條件渲染。

function Item({ name, isPacked }) {
if (isPacked) {
return <li className="item">{name} ✔</li>;
}
return <li className="item">{name}</li>;
}

可以使用 null 來什麼也渲染空:

if (isPacked) {
return null;
}
return <li className="item">{name}</li>;

列表渲染

類似於 Vue.js 中的 v-for。官方提供的步驟如下:

  1. 創建陣列:
const people = [
'Creola Katherine Johnson: mathematician',
'Mario José Molina-Pasquel Henríquez: chemist',
'Mohammad Abdus Salam: physicist',
'Percy Lavon Julian: chemist',
'Subrahmanyan Chandrasekhar: astrophysicist'
];
  1. 把陣列 map 到 JSX node 中:
const listItems = people.map(person => <li>{person}</li>);
  1. 在元件中返回 map 的結果:
return <ul>{listItems}</ul>;

可以使用 key 屬性在陣列中唯一標示該元素,類似於 Vue.js 中的 :key

return <li key={person.id}>...</li>

這個 key 應該是獨一無二的。

事件處理

透過在元件中定義事件處理函式的方法可以實現事件處理:

export default function Button() {
function handleClick() {
alert('You clicked me!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}

事件處理函式有幾個注意事項:

  1. 必須定義在元件中。
  2. 名稱通常使用 handle 開頭,後接事件名稱。

自然,你也可以直接將函式定義在 JSX node 中:

<button onClick={function handleClick() {
alert('You clicked me!');
}}>

由於事件處理函式是定義在元件內部的,因此該函式也可以直接使用元件的 props。

客製化事件

在 Vue.js 中,我們可以很方便地客製化事件。使用 defineEmits() 函式配合 emit() 函式,我們可以向父元件丟出一個事件。

在 React 中,這個過程大差不差。 defineEmits() 中的定義被轉成了函式類型的 props,而後直接執行之即可。

function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}

事件傳播

我們有這樣一個元件:

export default function Toolbar() {
return (
<div className="Toolbar" onClick={() => {
alert('You clicked on the toolbar!');
}}>
<button onClick={() => alert('Playing!')}>
Play Movie
</button>
<button onClick={() => alert('Uploading!')}>
Upload Image
</button>
</div>
);
}

我們發現,當我們點按任意一個 Button,不僅會觸發 Button 本身的 onClick() 事件,也觸發了其父 div 的 onClick()

要停止事件向其父傳播,我們需要呼叫其預設引數:

export default function Toolbar() {
return (
<div className="Toolbar" onClick={() => {
alert('You clicked on the toolbar!');
}}>
<button onClick={(e) => {
// 呼叫 e.stopPropagation() 函式停止事件傳播
e.stopPropagation()
alert('Playing!')
}}>
Play Movie
</button>
<button onClick={() => alert('Uploading!')}>
Upload Image
</button>
</div>
);
}

與之類似,e.preventDefault() 函式可以避免預設的元素行為。

狀態管理

由於互動的結果,元件通常需要更改螢幕上的內容。在表單中鍵入應更新輸入欄位,單擊影象列上的“Next”應更改顯示的影象,單擊“購買”應將產品放入購物車。元件需要“記住”事情:當前輸入值、當前影象、購物車。在 React 中,這種特定於元件的記憶體被稱為狀態(state)。

在 Vue.js 中,也有類似的概念。例如,當我們需要將某個變數動態渲染到 template 上的時候,我們就需要使用 ref()reactive(),否則,對變數的任何修改都不會被渲染器意識到。

比如在下面的程式中,點按 Change Index 按鈕就不會發生任何渲染改變:

export default function Test() {
let index = 0
function handleClick() {
index = index + 1
}
return (
<>
<div>{index}</div>
<button onClick={handleClick}>Change Index</button>
</>
);
}

在 React 中也是如此,不同的是,在 React 中,我們使用的是 useState() 函式。它的使用過程如下:

import { useState } from 'react';
export default function Test() {
const [index, setIndex] = useState(0)
function handleClick() {
setIndex(index + 1)
}
return (
<>
<div>{index}</div>
<button onClick={handleClick}>Change Index</button>
</>
);
}

我們可以看到,原本的變數被變成了一個變數陣列,其中 index 是原來的變數,而配套的 setIndex 則是變數的 setter。

在 React 中,useState 以及以 “use” 開頭的任何其他函式都被稱為 Hook。Hook 是特殊函式,僅在 React 渲染時可用。

與 Vue.js 類似,React 中狀態也是元件依賴的。也就是說,如果你把一個元件渲染兩次,這兩個元件將會有完全不互通的各自狀態。即狀態是“私有的”。

狀態批次處理

我們來看這樣一個例子:

export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
</>
)
}

你可能會期望,當我點按 “+3” 按鈕的時候,number 能夠連續相加 3 次,畢竟我使用了三次 setNumber() 函式。

然而你會發現,每點按一次,number 僅 +1。這是因為,在每一輪渲染之前,number 的值都是固定的,因此無論在本輪渲染中呼叫了多少次 setNumber() 函式,其都是一樣的效果。

React 的狀態批次處理機制是:每一輪渲染之前,都要將所有的狀態更新乃至於所有的事件處理函式全部執行完成,才會開始這一輪渲染。所以會出現上面的那個問題。

儘管像上面那樣子寫程式不是一個好主意,上面的需求也算是相當小眾,然而 React 仍舊給我們提供了解決的方案,只需要將 setNumber(number + 1) 替換成 setNumber(n => n + 1) 即可。這告訴 React,用上一輪更新的狀態結果來做事,而不是使用預設固定值。

這裡的 n => n + 1 函式稱為“更新函式”,通常情況下,使用被更新變數的第一個字母作為該函式的引數。

物件狀態的更新

遵循一個原則:不要修改已經持有的物件(儘管它們是 mutable 的),而是創造一個新的物件去替換原有的物件。

setPosition({
x: e.clientX,
y: e.clientY
});

以上的更新適用於物件中的每一個鍵的值都發生變化的情況。如果物件中只有少數的鍵發生變化,每一次都要更新整個物件,這顯然太不合理了。解決這個問題,我們可以使用 ... 來拷貝原來的物件變數值。

setPerson({
...person, // Copy the old fields
firstName: e.target.value // But override this one
});

好,問題來了,假如我們有一個複雜的 Nested 物件,我們應該如何去更新它?比如這個物件:

const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});

如果我想要更新 artwork 中的 city 一欄,按照上面說的,我們自然可以這樣子去更新它:

setPerson({
...person,
artwork: {
...person.artwork,
city: 'New Delhi'
}
});

我們不得不使用了兩遍 ...。這個還算好,如果遇到更加複雜的 nested 情況,這樣做顯然來得效率低。

隆重介紹我們寫 React Native 第一個必備的外部套件——use-immer

使用 use-immer 套件,我們可以反直覺地無痛更新物件。

首先我們安裝 use-immer 套件:

Terminal window
npm install use-immer --save

然後我們就可以把 useState() 換成 useImmer() 了:

import { useImmer } from 'use-immer';
export default function Form() {
const [person, updatePerson] = useImmer({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});
function handleNameChange(e) {
updatePerson(draft => {
draft.name = e.target.value;
});
}
function handleTitleChange(e) {
updatePerson(draft => {
draft.artwork.title = e.target.value;
});
}
function handleCityChange(e) {
updatePerson(draft => {
draft.artwork.city = e.target.value;
});
}
function handleImageChange(e) {
updatePerson(draft => {
draft.artwork.image = e.target.value;
});
}
return (
<>
<label>
Name:
<input
value={person.name}
onChange={handleNameChange}
/>
</label>
<label>
Title:
<input
value={person.artwork.title}
onChange={handleTitleChange}
/>
</label>
<label>
City:
<input
value={person.artwork.city}
onChange={handleCityChange}
/>
</label>
<label>
Image:
<input
value={person.artwork.image}
onChange={handleImageChange}
/>
</label>
<p>
<i>{person.artwork.title}</i>
{' by '}
{person.name}
<br />
(located in {person.artwork.city})
</p>
<img
src={person.artwork.image}
alt={person.artwork.title}
/>
</>
);
}

其中的更新語句:

updatePerson(draft => {
draft.artwork.title = e.target.value;
});

允許我們直接更新物件中的鍵。這很酷,對吧?

陣列狀態的更新

陣列可以被看作特殊的物件,因此對於物件的限制也同樣適用於陣列。我們也依然需要將陣列看作 immutable,儘管其依然是 mutable 的。

對於陣列,我們可以使用一些更加簡單的方式去生成新的陣列。

向陣列中新增元素

push() 方法是 mutable 方法,要實現這個內容,需要:

setArtists([
{ id: nextId++, name: name },
...artists
]);

刪除陣列中的元素

可以曲線救國,使用 filter() 函式篩選出非刪除元素組成新陣列。

setArtists(
artists.filter(a =>
a.id !== artist.id
)
);

修改陣列

使用 map() 函式來客製化修改規則,生成新的陣列。

setArtists(shapes.map(shape => {
if (shape.type === 'square') {
// No change
return shape;
} else {
// Return a new circle 50px below
return {
...shape,
y: shape.y + 50,
};
}
}))

陣列中插入元素

配合 slice() 函式來使用:

setArticles([
...artists.slice(0, insertAt),
{ id: nextId++, name: name },
...artists.slice(insertAt)
])

其他修改

其實,對於陣列的修改還有一個最簡單不過的方式:直接拷貝原來的陣列到一個新陣列,然後修改新陣列即可。

const newArticles = [...Articles]
// Here are some change to the newArticle veriable.
setArticles(newArticles)

然而這樣做對於陣列中的物件來說是一個問題。因為這樣的拷貝很淺,無法將物件的邏輯拷貝到新陣列。因為陣列中的物件實際上是一個指向,而不是一個元素。所以當我們修改新陣列中的物件內容,事實上是在修改原陣列,這是應當被避免的。

可以這樣做:

setMyList(myList.map(artwork => {
if (artwork.id === artworkId) {
// Create a *new* object with changes
return { ...artwork, seen: nextSeen };
} else {
// No changes
return artwork;
}
}));

或者你會更喜歡使用 immer:

updateMyList(draft => {
const artwork = draft.find(a =>
a.id === id
);
artwork.seen = nextSeen;
});

狀態管理的高級玩法

對於 React 狀態管理和宣告式程式設計的思考

初學者對於 React 的狀態管理可能會感到非常混亂和迷惑,但是如果習慣起來,會發現 React 的狀態管理功能十分強大。

按照 React 官方的說法,其狀態管理屬於“宣告式程式設計”的典型應用。

說到宣告式,我上次接觸的以它為特點的東西還叫做 NixOS,那個 Linux 發行版給我留下了極其深刻的心理陰影。雖然我承認其一些設計理念確實是超前的且無可比擬的,比如說宣告式設計。然而由於其軟體的缺失和 pack 的過程之複雜,實在是讓我記憶深刻,並且終生不想再碰它。

跑遠了~回到宣告式程式設計這件事上來。什麼叫宣告式呢?React 官方給了一個十分形象的例子:

假如你叫了一輛計程車,想要去到某個地方,使用傳統的命令式程式設計,你就需要一步一步指導司機走哪條路,最後到達你想要到達的地方。如果其中一步錯誤,可能最後就無法到達正確的目的地。

但是宣告式程式設計則不一樣。其相當於你直接告訴司機你要去到哪個地方,然後司機會幫你完成路線規劃,最後去到那裏。

相當於,你只需要宣告結果,過程不需要去管。這在一些大型的程式系統中會顯著提高開發和維護的效率。

回到 React 上來,你只需要宣告頁面的“狀態”,React 就能夠幫助你達到那個狀態,而不需要你親自去操縱頁面上的元件之類的。

事實上,Vue.js 所基於的 template 系統也是一樣。template 預先聲明了頁面的元素,然後我們可以修改 template 的狀態來實現頁面的更新。

在元件之間共享狀態

有的時候我們希望兩個元件的狀態可以同步改變,這時候我們需要進行“提升狀態”作業。

提升狀態,是指將狀態從元件中刪除掉,轉而將其放到共同的最近父元件中的過程。

我們來看這樣一個例子:

import { useState } from 'react';
function Panel({ title, children }) {
const [isActive, setIsActive] = useState(false);
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={() => setIsActive(true)}>
Show
</button>
)}
</section>
);
}
export default function Accordion() {
return (
<>
<h2>Almaty, Kazakhstan</h2>
<Panel title="About">
Children1
</Panel>
<Panel title="Etymology">
Children2
</Panel>
</>
);
}

顯然,這兩個 Panel 的 isActive 狀態是獨立的。

如果我希望讓它們兩個能夠同時改變,即要展示就共同展示,要隱藏就共同隱藏。這該如何做呢?

  1. 從子元件中刪除狀態。
  2. 從父元件中向子元件傳遞硬編碼資料。
  3. 將狀態給到父元件。

例如上述例子就可以如下修改:

import { useState } from 'react';
function Panel({ title, children, isActive, onShow }) { // 2. isActive, onShow:父元件向子元件傳遞硬編碼資料
// 1. 刪除子元件的狀態
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={onShow}>
Show
</button>
)}
</section>
);
}
export default function Accordion() {
// 3. 把狀態轉移到父元件
const [isActive, setIsActive] = useState(false)
return (
<>
<h2>Almaty, Kazakhstan</h2>
<Panel title="About" isActive={isActive} onShow={() => setIsActive(!isActive)}>
Children1
</Panel>
<Panel title="Etymology" isActive={isActive} onShow={() => setIsActive(!isActive)}>
Children2
</Panel>
</>
);
}

提升狀態的過程本質上是父元件統一管理子元件的過程。

保留和重置狀態

只要元件在 UI tree 的相同地方被渲染,React就會保留其狀態,而當其被移除,或者其他元件代替了它在 UI tree 中的位置,它的狀態就會被丟棄。

相同位置的相同類型元件保留狀態

有如下一個例子:

export default function App() {
const [show, setShow] = useState(true)
return (
<div className="Root">
{ show ? (<Test>Children1</Test>) : (<Test>Children2</Test>)}
</div>
<button onClick={() => setShow(!show)}>Click me.</button>
);
}
function Test({children}) {
const [score, setScore] = useState(0)
return (
<div>score<div/>
<div>{children}</div>
<button onClick={() => setScore(score + 1)}>Add</button>
);
}

你會發現當你按下 Click me. 按鈕的時候,即使 Children1 變成了 Children2,元件裡面的狀態 score 也不會改變。這是因為兩個 Test 元件是 UI tree 相同位置的相同類型元件,因此會被 React 當作相同元件,狀態得到保留,

同一位置的不同類型元件重置狀態

根據上面的例子,我們應該十分容易理解它,比如同一位置,把 Test 元件換成其他的什麼元件,再換回來,Test 元件的 score 狀態自然就被重置了。

在同一位置重置狀態

上面我們講到,元件遵循的是在同一位置的相同類型元件將會保留狀態。然而如果我們希望在同一位置的相同元件強制重置狀態,我們該如何做呢?

我們有兩種方法:

  1. 在不同的位置渲染。
  2. 使用 key。

我們來看第一種。還是用上面的那個例子。將:

{ show ? (<Test>Children1</Test>) : (<Test>Children2</Test>)}

修改成:

{ show && <Test>Children1</Test> }
{ !show && <Test>Children2</Test> }

即可。

另一種方式是使用 key。key 可以讓 React 區分不同的元件。只需要給到兩個 <Test></Test> 元件兩個不同的 key 即可。

{ show ? (<Test key="Test1">Children1</Test>) : (<Test key="Test2">Children2</Test>)}

使用 Reducer

當我們的狀態處理邏輯越來越多的時候,難免會顯得非常混亂。這個時候,我們可以將狀態處理邏輯統一提取到元件外面的 reducer 函式中,將針對某一個狀態變數的全部處理邏輯都放到這裡面。然後在元件內部,只需要使用這個 reducer 函式來分發相應的處理邏輯就好。

首先我們需要一個 reducer 函式,它接受兩個引數:state 和 action,state 是要監控的狀態變數,action 是執行的作業。該函式回傳更新後的狀態。

比如我們編寫這樣一個函式:

function tasksReducer(tasks, action) {
if (action.type === 'added') {
return [
...tasks,
{
id: action.id,
text: action.text,
done: false,
},
];
} else if (action.type === 'changed') {
return tasks.map((t) => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
} else if (action.type === 'deleted') {
return tasks.filter((t) => t.id !== action.id);
} else {
throw Error('Unknown action: ' + action.type);
}
}

在元件中使用 reducer:

const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

useReducer() 函式接受兩個引數:taskReducer 是在元件外定義的 reducer 函式,initialTasks 是預設的狀態值。

當需要更新元件的狀態時,我們只需要使用 dispatch() 函式:

function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task,
});
}
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted',
id: taskId,
});
}

傳送給 dispatch() 函式的引數是一個物件,這個物件最後會變成 taskReducer() 函式的第二個引數 action

我們還可以使用 immer 來提升我們使用 Reducer 的體驗。(immer 真是個偉大的發明)

使用 immer,我們就可以使用 pusharray[index] = 這種形式來修改我們的 state。

只需要將 useReducer() 換成 userImmerReducer() 即可,然後你就可以在你的 reducer 函式中大膽使用上面提到的修改方法了。

使用 context 深入傳遞資料

這個場景在客製化元件庫的時候特別有用。在之前使用 Vue.js 開發元件庫的時候,我總會遇到這樣一個問題:客製化的 radio-box 元件需要監聽其父級 radio-box-group 元件的屬性,來確定自己是不是已經被選中了。

context 就可以做到這一點。通俗講,context 可以讓父級(不管多遙遠)的元件可以跨越千山萬水,為子級元件提供一些資料。

我們就用這個例子來做。現在我有一個 RadioBoxGroup 元件,其有一個屬性是 check,類型是 integer,代表選中的 RadioBox 元件的 id。其 children 為 RadioBox 元件。要求 RadioBox 元件能夠根據其父級的 check,自動獲悉自己是否被選中。

我們首先建立這兩個元件,我分為兩個檔案:

RadioBox.js
export default function RadioBox({id, children}) {
return (
<>
<input type="radio">
<span>{children}</span>
</>
)
}
RadioBoxGroup.js
export default function RadioBoxGroup({children}) {
return (
<>
{children}
</>
);
}

然後再建立一個測試元件:

App.js
import RadioBox from './RadioBox.js';
import RadioBoxGroup from './RadioBoxGroup.js';
export default function App() {
return (
<>
<RadioBoxGroup>
<RadioBox id="1">男</RadioBox>
<RadioBox id="2">女</RadioBox>
<RadioBox id="0">保密</RadioBox>
</RadioBoxGroup>
</>
);
}

接下來我們就要開始實現上述目的了。

首先我們需要創建一個 context,我們重新建立一個新的檔案用於專門存放 context:

CheckContext.js
import { createContext } from 'react';
export const CheckContext = createContext("0");

createContext() 函式唯一的引數,表示預設的 context 值。

然後,我們在 RadioBoxGroup 元件中提供這個 context:

RadioBoxGroup.js
export default function RadioBoxGroup({check, children}) {
return (
<>
<CheckContext.provider value={check}>
{children}
</CheckContext.provider>
</>
);
}

這一步是告訴 React:如果我的子元件索要context,請將這個 context 提供給它。 React 將會把距離子元件最近的 context 提供給它。

而後,子元件就可以向父元件索要 context 了:

import { useContext } from 'react';
import { CheckContext } from './CheckContext.js';
export default function RadioBox({id, children}) {
// 索要 CheckContext
const check = useContext(CheckContext)
return (
<>
<input type="radio" checked={check === id}>
<span>{children}</span>
</>
)
}

完成了。接下來,就只要在 App 元件中為 RadioBoxGroup 元件設定 check 屬性即可。

結語

暫時先寫這麼多吧,也很多了。應當注意的是,這些內容僅僅在 React 官方檔案中屬於“中級”,而更高級的功能我暫時用不上,於是就暫時先寫到這裡。以後用到了,以後再說。

另外要再次提醒讀者,React 是一個函式庫,而並非是一個框架。基於 React 的框架,請移步目前最流行的 Next.js

[React] 一文速通 React
https://blog.kynix.tw/posts/1731070772537/
作者
Adrian Chen
建檔時間
2024 年 4 月 17 日
協議
BY-NC-SA 4.0
姓名標示-非商業性-相同方式分享 4.0 國際