關於我為什麼要寫這篇文章,我有幾句話要講。本身作為一個前端開發人員,我一直有在用 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
檔案中匯出元件:
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 要來得方便。
// 使用 {} 來定義 propsexport default function Profile({avatar, name}) { return ( <> <img src={avatar}/> <div>{name}</div> </> );}
然後在 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-if
和 v-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
。官方提供的步驟如下:
- 創建陣列:
const people = [ 'Creola Katherine Johnson: mathematician', 'Mario José Molina-Pasquel Henríquez: chemist', 'Mohammad Abdus Salam: physicist', 'Percy Lavon Julian: chemist', 'Subrahmanyan Chandrasekhar: astrophysicist'];
- 把陣列 map 到 JSX node 中:
const listItems = people.map(person => <li>{person}</li>);
- 在元件中返回 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> );}
事件處理函式有幾個注意事項:
- 必須定義在元件中。
- 名稱通常使用
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
套件:
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
狀態是獨立的。
如果我希望讓它們兩個能夠同時改變,即要展示就共同展示,要隱藏就共同隱藏。這該如何做呢?
- 從子元件中刪除狀態。
- 從父元件中向子元件傳遞硬編碼資料。
- 將狀態給到父元件。
例如上述例子就可以如下修改:
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 狀態自然就被重置了。
在同一位置重置狀態
上面我們講到,元件遵循的是在同一位置的相同類型元件將會保留狀態。然而如果我們希望在同一位置的相同元件強制重置狀態,我們該如何做呢?
我們有兩種方法:
- 在不同的位置渲染。
- 使用 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,我們就可以使用 push
或 array[index] =
這種形式來修改我們的 state。
只需要將 useReducer()
換成 userImmerReducer()
即可,然後你就可以在你的 reducer 函式中大膽使用上面提到的修改方法了。
使用 context 深入傳遞資料
這個場景在客製化元件庫的時候特別有用。在之前使用 Vue.js 開發元件庫的時候,我總會遇到這樣一個問題:客製化的 radio-box 元件需要監聽其父級 radio-box-group 元件的屬性,來確定自己是不是已經被選中了。
context 就可以做到這一點。通俗講,context 可以讓父級(不管多遙遠)的元件可以跨越千山萬水,為子級元件提供一些資料。
我們就用這個例子來做。現在我有一個 RadioBoxGroup 元件,其有一個屬性是 check,類型是 integer,代表選中的 RadioBox 元件的 id。其 children 為 RadioBox 元件。要求 RadioBox 元件能夠根據其父級的 check,自動獲悉自己是否被選中。
我們首先建立這兩個元件,我分為兩個檔案:
export default function RadioBox({id, children}) { return ( <> <input type="radio"> <span>{children}</span> </> )}
export default function RadioBoxGroup({children}) { return ( <> {children} </> );}
然後再建立一個測試元件:
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:
import { createContext } from 'react';
export const CheckContext = createContext("0");
createContext()
函式唯一的引數,表示預設的 context 值。
然後,我們在 RadioBoxGroup 元件中提供這個 context:
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。