全文約5萬字。
1.hello,..
// App.jsx
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'function App() {const [count, setCount] = useState(0)return (<><Greeting name="world" /></>)
}function Greeting(props) {return <h1>Hello, {props.name}!</h1>
}export default App
2.創建一個應用的方法。
創建一個 React 應用 – React中文文檔 | React中文文檔鏡像
?3.我的學習過程
步驟 1: 安裝構建工具
npm create vite@latest my-app -- --template react
可能需要選擇。我的選擇是 react javascript+swc
4.vscode的官方推薦的插件
eslint? prettier
5.react開發者工具
React 開發者工具 – React中文文檔 | React中文文檔鏡像
根據要求完成,不同瀏覽器可能不一樣的方法。
React 組件是常規的 JavaScript 函數,但 組件的名稱必須以大寫字母開頭,否則它們將無法運行!
?6.組件
?組件 是 React 的核心概念之一。它們是構建用戶界面(UI)的基礎,是你開始 React 之旅的最佳起點!
?7.定義組件
React 組件是一段可以 使用標簽進行擴展 的 JavaScript 函數。如下所示(你可以編輯下面的示例):
//Greeting.jsx
import React from 'react';
function Greeting(props) {return <h1>Hello, {props.name}!</h1>}
export default Greeting;
7.1導出組件
export default
前綴是一種 JavaScript 標準語法(非 React 的特性)。它允許你導出一個文件中的主要函數以便你以后可以從其他文件引入它。
7.2 定義函數
function Greeting(props)定義名為Greeting 的 JavaScript 函數。
React 組件是常規的 JavaScript 函數,但 組件的名稱必須以大寫字母開頭,否則它們將無法運行!
?7.3添加標簽
?返回語句可以全寫在一行上,如下面組件中所示:
return <img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />;
但是,如果你的標簽和 return
關鍵字不在同一行,則必須把它包裹在一對括號中,如下所示:
return (<div><img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" /></div>
);
沒有括號包裹的話,任何在
return
下一行的代碼都 將被忽略https://stackoverflow.com/questions/2846283/what-are-the-rules-for-javascripts-automatic-semicolon-insertion-asi!
7.4使用組件
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'function App() {const name = 'world'return (<><Greeting name={name} /></>)
}export default App
?8.組件的導入和導出
以下三個步驟對組件進行拆分:
- 創建?一個新的 JS 文件來存放該組件。
- 導出?該文件中的函數組件(可以使用?默認導出?或?具名導出)
- 在需要使用該組件的文件中?導入(可以根據相應的導出方式使用?默認導入?或?具名導入)。
該示例中需要注意的是,如何將組件拆分成兩個文件:
Gallery.js
:
- 定義了?
Profile
?組件,該組件僅在該文件內使用,沒有被導出。- 使用?默認導出?的方式,將?
Gallery
?組件導出App.js
:
- 使用?默認導入?的方式,從?
Gallery.js
?中導入?Gallery
?組件。- 使用?默認導出?的方式,將根組件?
App
?導出。
?通常,文件中僅包含一個組件時,人們會選擇默認導出,而當文件中包含多個組件或某個值需要導出時,則會選擇具名導出。 無論選擇哪種方式,請記得給你的組件和相應的文件命名一個有意義的名字。我們不建議創建未命名的組件,比如
export default () => {}
,因為這樣會使得調試變得異常困難。
// Gallery.jsx
import svg from './assets/react.svg';function Profile() {return (<div className="profile"><h1>Profile</h1><img src={svg} alt="SVG" /><p>Here is an SVG image.</p></div>);
}
function Gallery() {return (<> <Profile /></>);}export default Gallery;
// import React from 'react';
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'function App() {const name = 'world'return (<><Greeting name={name} /><Gallery /></>)
}export default App
8.1從同一文件中導出和導入多個組件
如果你只想展示一個
Profile
組,而不展示整個圖集。你也可以導出Profile
組件。但Gallery.js
中已包含 默認 導出,此時,你不能定義 兩個 默認導出。但你可以將其在新文件中進行默認導出,或者將Profile
進行 具名 導出。同一文件中,有且僅有一個默認導出,但可以有多個具名導出!
為了減少在默認導出和具名導出之間的混淆,一些團隊會選擇只使用一種風格(默認或者具名),或者禁止在單個文件內混合使用。這因人而異,選擇最適合你的即可!?
首先,用具名導出的方式,將
Profile
組件從Gallery.js
導出(不使用default
關鍵字):
export function Profile() {
// ...
}
接著,用具名導入的方式,從
Gallery.js
文件中 導入Profile
組件(用大括號):
import { Profile } from './Gallery.js';
好像現在不加{}也可以直接使用了。
最后,在
App
組件里 渲染<Profile />
:
export default function App() {
return <Profile />;
}
?現在,
Gallery.js
包含兩個導出:一個是默認導出的Gallery
,另一個是具名導出的Profile
。App.js
中均導入了這兩個組件。嘗試將<Profile />
改成<Gallery />
,回到示例中:
8.2相關代碼
// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'function App() {const name = 'world'return (<><Greeting name={name} /><Gallery /><Profile /></>)
}export default App
// Gallery.jsx
import svg from './assets/react.svg';export function Profile() {return (<div className="profile"><h1>Profile</h1><img src={svg} alt="SVG" /><p>Here is an SVG image.</p></div>);
}
function Gallery() {return (<> <Profile /></>);}export default Gallery;
// import React from 'react';
?
在本章節中,你學到了:
- 何為根組件
- 如何導入和導出一個組件
- 何時和如何使用默認和具名導入導出
- 如何在一個文件里導出多個組件
?9.使用 JSX 書寫標簽語言
9.1為什么 React 將標簽和渲染邏輯耦合在一起
隨著 Web 的交互性越來越強,邏輯越來越決定頁面中的內容。JavaScript 控制著 HTML 的內容!這也是為什么 在 React 中,渲染邏輯和標簽共同存在于同一個地方——組件。
?
將一個按鈕的渲染邏輯和標簽放在一起可以確保它們在每次編輯時都能保持互相同步。反之,彼此無關的細節是互相隔離的,例如按鈕的標簽和側邊欄的標簽。這樣我們在修改其中任意一個組件時會更安全。
每個 React 組件都是一個 JavaScript 函數,它會返回一些標簽,React 會將這些標簽渲染到瀏覽器上。React 組件使用一種被稱為 JSX 的語法擴展來描述這些標簽。JSX 看起來和 HTML 很像,但它的語法更加嚴格并且可以動態展示信息。了解這些區別最好的方式就是將一些 HTML 標簽轉化為 JSX 標簽。
注意
JSX and React 是相互獨立的 東西。但它們經常一起使用,但你 可以 單獨使用它們中的任意一個,JSX 是一種語法擴展,而 React 則是一個 JavaScript 的庫。
?9.2將HTML代碼轉成JSX
如有以下代碼
<h1>海蒂·拉瑪的待辦事項</h1>
<img src="https://i.imgur.com/yXOvdOSs.jpg" alt="Hedy Lamarr" class="photo"
>
<ul><li>發明一種新式交通信號燈<li>排練一個電影場景<li>改進頻譜技術
</ul>
9.3JSX規則
9.3.1只能返回一個根元素
JSX 雖然看起來很像 HTML,但在底層其實被轉化為了 JavaScript 對象,你不能在一個函數中返回多個對象,除非用一個數組把他們包裝起來。這就是為什么多個 JSX 標簽必須要用一個父元素或者 Fragment 來包裹。
9.3.2標簽必須閉合
<><img src="https://i.imgur.com/yXOvdOSs.jpg" alt="Hedy Lamarr" class="photo"/><ul><li>發明一種新式交通信號燈</li><li>排練一個電影場景</li><li>改進頻譜技術</li></ul>
</>
這個空標簽被稱作 Fragment。React Fragment 允許你將子元素分組,而不會在 HTML 結構中添加額外節點。
9.3.3?使用駝峰式命名法給?所有?大部分屬性命名!
JSX 最終會被轉化為 JavaScript,而 JSX 中的屬性也會變成 JavaScript 對象中的鍵值對。在你自己的組件中,經常會遇到需要用變量的方式讀取這些屬性的時候。但 JavaScript 對變量的命名有限制。例如,變量名稱不能包含 - 符號或者像 class 這樣的保留字。
這就是為什么在 React 中,大部分 HTML 和 SVG 屬性都用駝峰式命名法表示。例如,需要用 strokeWidth 代替 stroke-width。由于 class 是一個保留字,所以在 React 中需要用 className 來代替。這也是 DOM 屬性中的命名:
<img?
? src="https://i.imgur.com/yXOvdOSs.jpg"?
? alt="Hedy Lamarr"?
? className="photo"
/>
你可以 在 React DOM 元素中找到所有對應的屬性。如果你在編寫屬性時發生了錯誤,不用擔心 —— React 會在 瀏覽器控制臺 中打印一條可能的更正信息。
10.?在 JSX 中通過大括號使用 JavaScript
你將會學習到
- 如何使用引號傳遞字符串
- 在 JSX 的大括號內引用 JavaScript 變量
- 在 JSX 的大括號內調用 JavaScript 函數
- 在 JSX 的大括號內使用 JavaScript 對象
10.1使用單引號或雙引號把一個字符串傳遞給JSX
export function Profile() {return (<div className="profile"><h1>Profile</h1><img src={svg} alt='SVG' /><p>Here is an SVG image.</p></div>);
}
?但是如果你想要動態地指定
src
或alt
的值呢?你可以 用{
和}
替代"
和"
以使用 JavaScript 變量
// Gallery.jsx
import svg from './assets/react.svg';export function Profile() {const title= 'Profile';const description = 'Here is an SVG image.';const image = svg;return (<div className="profile"><h1>{title}</h1><img src={image} alt='SVG' /><p>{description}</p></div>);
}
function Gallery() {return (<> <Profile /></>);}export default Gallery;
// import React from 'react';
10.2可以在哪使用大括號
在 JSX 中,只能在以下兩種場景中使用大括號:
- 用作 JSX 標簽內的文本:
<h1>{name}'s To Do List</h1>
?是有效的,但是?<{tag}>Gregorio Y. Zara's To Do List</{tag}>
?無效。- 用作緊跟在?
=
?符號后的?屬性:src={avatar}
?會讀取?avatar
?變量,但是?src="{avatar}"
?只會傳一個字符串?{avatar}
。
10.3使用 “雙大括號”:JSX 中的 CSS 和 對象?
除了字符串、數字和其它 JavaScript 表達式,你甚至可以在 JSX 中傳遞對象。對象也用大括號表示,例如
{ name: "Hedy Lamarr", inventions: 5 }
。因此,為了能在 JSX 中傳遞,你必須用另一對額外的大括號包裹對象:person={{ name: "Hedy Lamarr", inventions: 5 }}
。你可能在 JSX 的內聯 CSS 樣式中就已經見過這種寫法了。React 不要求你使用內聯樣式(使用 CSS 類就能滿足大部分情況)。但是當你需要內聯樣式的時候,你可以給
style
屬性傳遞一個對象:
// TodoList.jsx
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>);}export default TodoList;
// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'
import TodoList from './TodoList.jsx'function App() {const name = 'world'return (<><Greeting name={name} /><Gallery /><Profile /><TodoList /></>)
}export default App
<ul style="background-color: black; color: pink;"><li>Improve the videophone</li><li>Prepare aeronautics lectures</li><li>Work on the alcohol-fuelled engine</li></ul>?
內聯
style
屬性 使用駝峰命名法編寫。例如,HTML<ul style="background-color: black">
在你的組件里應該寫成<ul style={{ backgroundColor: 'black' }}>
。?
?本節優化后的代碼如下
?
// TodoListMore.jsx
const pserson={name: '張老三',age: 30,now: new Date(),hobbies: ['reading', 'gaming', 'hiking'],theme: {backgroundColor: 'black',color: 'pink'}}
const today = pserson.now.toLocaleDateString('en-US', {month: 'long',day: 'numeric',year: 'numeric'
});function TodoListMore() {return (<ul style={pserson.theme}><h1>{pserson.name}日程表:{today}</h1><h2>他今年有{pserson.age}歲了</h2><ul>{pserson.hobbies.map((hobby, index) => (<li key={index}>{hobby}</li>))}</ul></ul>);}export default TodoListMore;
// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'
import TodoList from './TodoList.jsx'
import TodoListMore from './assets/TodoListMore.jsx'function App() {// const name = 'world'return (<>
<TodoListMore /></>)
}export default App
?JSX 中,`style` 和 `className` 是兩種不同的樣式控制方式-CSDN博客
https://blog.csdn.net/weixin_42771529/article/details/147094039?sharetype=blogdetail&sharerId=147094039&sharerefer=PC&sharesource=weixin_42771529&spm=1011.2480.3001.8118
JSX 是一種模板語言的最小實現,因為它允許你通過 JavaScript 來組織數據和邏輯。
摘要
現在你幾乎了解了有關 JSX 的一切:
- JSX 引號內的值會作為字符串傳遞給屬性。
- 大括號讓你可以將 JavaScript 的邏輯和變量帶入到標簽中。
- 它們會在 JSX 標簽中的內容區域或緊隨屬性的?
=
?后起作用。{{
?和?}}
?并不是什么特殊的語法:它只是包在 JSX 大括號內的 JavaScript 對象。
11.?將 Props 傳遞給組件
將會學習到
- 如何向組件傳遞 props
- 如何從組件讀取 props
- 如何為 props 指定默認值
- 如何給組件傳遞 JSX
- Props 如何隨時間變化
?Props 是你傳遞給 JSX 標簽的信息。例如,
className
、src
、alt
、width
和height
便是一些可以傳遞給<img>
的 props:
11.1普通傳參的示例
?相關代碼如下:
// Avatar.jsx
function Avatar({person, size=100}) {return (<div><imgsrc="https://www.w3schools.com/w3css/img_avatar3.png"// alt={`${person.name}'s avatar`}style={{width: size,height: size,borderRadius: "50%",border: "2px solid #000",}}/><p>{`${person.name}'s avatar`}</p><hr /><p>Size: {size}px</p></div>);
}
export default Avatar;
注意的是size不要用{}。用了無效果。和站點上介紹的不一樣。可能改了。?后面的要用。
要注意size可以給一個默認值。
// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'
import TodoList from './TodoList.jsx'
import TodoListMore from './assets/TodoListMore.jsx'
import Avatar from './Avatar.jsx'function App() {// const name = 'world'return (<>
<TodoListMore />
<Avatar person={{name: 'John Doe'}} size={80} /></>)
}export default App
傳入的參數要注意一下寫法,特別是?
如果
person=
后面的雙花括號讓你感到困惑,請記住,在 JSX 花括號中,它們只是一個對象。
注意
在聲明 props 時, 不要忘記
(
和)
之間的一對花括號{
和}
:
function Avatar({ person, size }) {
// ...
}
這種語法被稱為 “解構”,等價于于從函數參數中讀取屬性:
function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}
11.2?將 JSX 作為子組件傳遞?
有時你會希望以相同的方式嵌套自己的組件:
<Card>
<Avatar />
</Card>
?本節學習示例
// GetImageUrl.jsx
export function GetImageUrl(size=1) {return (// https://www.w3schools.com/w3css/img_avatar3.png'https://www.w3schools.com/w3css/img_avatar' +size +'.png');}
// Avatar.jsx
import { GetImageUrl } from "./GetImageUrl";
function Avatar({person, size=100,num=1}) {return (<div><imgsrc={GetImageUrl(num)}// alt={`${person.name}'s avatar`}style={{width: size,height: size,borderRadius: "50%",border: "2px solid #000",}}/><p>{`${person.name}'s avatar`}</p><hr /><p>Size: {size}px</p><p>Num: {num}</p><p>Image URL: {GetImageUrl(num)}</p></div>);
}
export default Avatar;
// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'
import TodoList from './TodoList.jsx'
import TodoListMore from './assets/TodoListMore.jsx'
import Avatar from './Avatar.jsx'function App() {// const name = 'world'return (<>
{/* <TodoListMore /> */}
<Avatar person={{name: '張三'}} size={80} num={1}/>
<Avatar person={{name: '李四'}} size={80} num={2}/>
<Avatar person={{name: '王五'}} size={80} num={3}/></>)
}export default App
添加一個時鐘,驗證props的性質 。站上的信息與代碼不全。以下是完全的
?為了讓網頁上的時鐘自動更新,你需要借助 React 的?
useState
?和?useEffect
?鉤子。useState
?用來管理時鐘的時間狀態,而?useEffect
?則負責在組件掛載時啟動一個定時器,定時更新時間狀態。
代碼解釋:
?
- 導入鉤子:引入?
useState
?和?useEffect
?鉤子。- 狀態管理:借助?
useState
?鉤子創建一個名為?time
?的狀態變量,初始值為當前日期和時間。- 副作用處理:利用?
useEffect
?鉤子在組件掛載時設置一個定時器,每秒更新一次?time
?狀態。- 清除定時器:在?
useEffect
?的返回函數里清除定時器,避免出現內存泄漏問題。- 渲染時間:在組件的返回值中使用?
time.toLocaleTimeString()
?渲染當前時間。經過這些修改,時鐘就會每秒自動更新一次。
import React, { useState, useEffect } from 'react';function Clock({ color }) {const [time, setTime] = useState(new Date());useEffect(() => {const intervalId = setInterval(() => {setTime(new Date());}, 1000);return () => {clearInterval(intervalId);};}, []);return (<div><h1>Hello, world!</h1><h2 style={{ color: color }}>It is {time.toLocaleTimeString()}.</h2></div>);
}export default Clock;
// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'
import TodoList from './TodoList.jsx'
import TodoListMore from './assets/TodoListMore.jsx'
import Avatar from './Avatar.jsx'
import Clock from './Clock.jsx'function App() {// const name = 'world'return (<>
{/* <TodoListMore /> */}
{/* <Avatar person={{name: '張三'}} size={80} num={1}/>
<Avatar person={{name: '李四'}} size={80} num={2}/>
<Avatar person={{name: '王五'}} size={80} num={3}/> */}
<Clock color="red"/>
<Clock color="blue"/>
<Clock color="green"/></>)
}export default App
可以實現自動更新了。
?
明天再接著學習。
12.條件渲染
通常你的組件會需要根據不同的情況顯示不同的內容。在 React 中,你可以通過使用 JavaScript 的
if
語句、&&
和? :
運算符來選擇性地渲染 JSX。
?12.1條件返回jsx
結合以上代碼進行解釋。
對人物的性別進行判斷
// IsMan.jsx
function IsMan(person) {if (person.gender === 'male') {return <li className="item">男?</li>;} else if (person.gender === 'female') {return <li className="item">女</li>;}return '未知';
}export default IsMan;
在Avatar.jsx中使用。注意傳進去的是數組對象
// Avatar.jsx
import { GetImageUrl } from "./GetImageUrl";
import IsMan from "./IsMan";function Avatar({ person, size = 100, num = 1 }) {const gender = IsMan(person);return (<div><imgsrc={GetImageUrl(num)}style={{width: size,height: size,borderRadius: "50%",border: "2px solid #000",}}/><p>{person.name} is {gender}`</p><hr /><p>Size: {size}px</p><p>Num: {num}</p><p>Image URL: {GetImageUrl(num)}</p></div>);
}export default Avatar;
最后在App.jsx中使用。注意性別的寫法。一定要在person對象中。
// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'
import TodoList from './TodoList.jsx'
import TodoListMore from './assets/TodoListMore.jsx'
import Avatar from './Avatar.jsx'
import Clock from './Clock.jsx'function App() {return (<><Avatar person={{ name: '張三', gender: 'male' }} size={80} num={1} /><Avatar person={{ name: '李四', gender: 'female' }} size={80} num={2} /><Avatar person={{ name: '王五', gender: 'male' }} size={80} num={3} /><Clock color="red" /></>)
}export default App
?
我感覺也可以直接返回所有需要的值。下面是修改的部分
// IsMan.jsx
function IsMan(person) {if (person.gender === 'male') {return <li className="item">{person.name}:男?</li>;} else if (person.gender === 'female') {return <li className="item">{person.name}:女</li>;}return '未知';
}export default IsMan;
// Avatar.jsx
import { GetImageUrl } from "./GetImageUrl";
import IsMan from "./IsMan";function Avatar({ person, size = 100, num = 1 }) {const name_gender = IsMan(person);return (<div><imgsrc={GetImageUrl(num)}style={{width: size,height: size,borderRadius: "50%",border: "2px solid #000",}}/><p>{name_gender}`</p><hr /><p>Size: {size}px</p><p>Num: {num}</p><p>Image URL: {GetImageUrl(num)}</p></div>);
}export default Avatar;
是不是可以優化呢? 我認為可以如下:
function IsMan(person) {return person.gender === 'male' ? <li className="item">{person.name}:男?</li> : <li className="item">{person.name}:女</li>;
}export default IsMan;
本節學習的內容
- 在 React,你可以使用 JavaScript 來控制分支邏輯。
- 你可以使用?
if
?語句來選擇性地返回 JSX 表達式。- 你可以選擇性地將一些 JSX 賦值給變量,然后用大括號將其嵌入到其他 JSX 中。
- 在 JSX 中,
{cond ? <A /> : <B />}
?表示?“當?cond
?為真值時, 渲染?<A />
,否則?<B />
”。- 在 JSX 中,
{cond && <A />}
?表示?“當?cond
?為真值時, 渲染?<A />
,否則不進行渲染”。- 快捷的表達式很常見,但如果你更傾向于使用?
if
,你也可以不使用它們。
下面將本節所學的內容做成如下的效果
?
相關代碼如下:
// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';function App() {const people = [{ name: '張三', gender: 'male', education: '本科', married: true, skills: '編程、設計', num: 1 },{ name: '李四', gender: 'female', education: '碩士', married: false, skills: '營銷、管理', num: 2 },{ name: '王五', gender: 'male', education: '大專', married: true, skills: '機械維修', num: 3 },{ name: '趙六', gender: 'female', education: '博士', married: false, skills: '數據分析', num: 4 }];return (<div className="container mx-auto p-4"><h1 className="text-3xl font-bold text-center mb-8">簡歷列表</h1><div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">{people.map((person, index) => (<Avatarkey={index}person={person}size={80}num={person.num}/>))}</div></div>);
}export default App;
// // Avatar.jsx
// import { GetImageUrl } from "./GetImageUrl";
// import IsMan from "./IsMan";// function Avatar({ person, size = 100, num = 1 }) {
// const name_gender = IsMan(person);
// return (
// <div>
// <img
// src={GetImageUrl(num)}
// style={{
// width: size,
// height: size,
// borderRadius: "50%",
// border: "2px solid #000",
// // }}
// />
// <p>{name_gender}`</p>
// <hr />
// {/* <p>Size: {size}px</p> */}
// {/* <p>Num: {num}</p> */}
// {/* <p>Image URL: {GetImageUrl(num)}</p> */}
// </div>
// );
// }// export default Avatar;
import { GetImageUrl } from "./GetImageUrl";
import IsMan from "./IsMan";function Avatar({ person, size = 100, num = 1 }) {const gender = IsMan(person);return (<div className="flex flex-col items-center p-4 border border-gray-300 rounded-md shadow-md m-4"><imgsrc={GetImageUrl(num)}alt={`${person.name}'s avatar`}style={{width: size,height: size,borderRadius: "50%",border: "2px solid #000",}}/>{/* <p className="text-lg font-bold mt-2">{person.name}</p> */}<p className="text-gray-600">{gender}</p><li className="text-gray-600">學歷: {person.education}</li><li className="text-gray-600">婚否: {person.married ? '已婚' : '未婚'}</li><li className="text-gray-600">擅長: {person.skills}</li></div>);
}export default Avatar;
// // Avatar.jsx
// import { GetImageUrl } from "./GetImageUrl";
// import IsMan from "./IsMan";// function Avatar({ person, size = 100, num = 1 }) {
// const name_gender = IsMan(person);
// return (
// <div>
// <img
// src={GetImageUrl(num)}
// style={{
// width: size,
// height: size,
// borderRadius: "50%",
// border: "2px solid #000",
// // }}
// />
// <p>{name_gender}`</p>
// <hr />
// {/* <p>Size: {size}px</p> */}
// {/* <p>Num: {num}</p> */}
// {/* <p>Image URL: {GetImageUrl(num)}</p> */}
// </div>
// );
// }// export default Avatar;
import { GetImageUrl } from "./GetImageUrl";
import IsMan from "./IsMan";function Avatar({ person, size = 100, num = 1 }) {const gender = IsMan(person);return (<div className="flex flex-col items-center p-4 border border-gray-300 rounded-md shadow-md m-4"><imgsrc={GetImageUrl(num)}alt={`${person.name}'s avatar`}style={{width: size,height: size,borderRadius: "50%",border: "2px solid #000",}}/>{/* <p className="text-lg font-bold mt-2">{person.name}</p> */}<p className="text-gray-600">{gender}</p><li className="text-gray-600">學歷: {person.education}</li><li className="text-gray-600">婚否: {person.married ? '已婚' : '未婚'}</li><li className="text-gray-600">擅長: {person.skills}</li></div>);
}export default Avatar;
// GetImageUrl.jsx
export function GetImageUrl(size=1) {return (// https://www.w3schools.com/w3css/img_avatar3.png'https://www.w3schools.com/w3css/img_avatar' +size +'.png');}
以上內容將在下一節及后序渲染列表中進一步講解。
13,渲染列表Render the list.
13.1學習內容:
你可能經常需要通過 JavaScript 的數組方法 來操作數組中的數據,從而將一個數據集渲染成多個相似的組件。在這篇文章中,你將學會如何在 React 中使用 filter() 篩選需要渲染的組件和使用 map() 把數組轉換成組件數組。
你將會學習到
- 如何通過 JavaScript 的?
map()
?方法從數組中生成組件- 如何通過 JavaScript 的?
filter()
?篩選需要渲染的組件- 何時以及為何使用 React 中的 key
?13.2從數組中渲染數據
13.2.1,首先把數據存儲在數組中。或從數據庫中得到。
import { GetImageUrl } from "./GetImageUrl"// RendreList.jsxconst people = [{ id:1,name: 'Alice', age: 25, isMan: true },{ id:2,name: 'Bob', age: 30, isMan: true },{ id:3,name: 'Charlie', age: 28, isMan: true },{ id:4,name: 'David', age: 35, isMan: false },{ id:5,name: 'Eve', age: 27, isMan: false },
]const listItems = people.map((person) => (<li className="item" key={person.name}><img src={GetImageUrl(person.id)} alt={person.name} style={{width: 40,height: 40,borderRadius: "50%",border: "2px solid #000",}}/>{person.name}:{person.age}{person.isMan ? '男?' : '女'}</li>
))function RenderList() {return (<div><ul>{listItems}</ul></div>)
}
export default RenderList
以上代碼中擬定了一個數組。然后把數據使用map轉成獨立的person,然后取各個person的值,中間用一個三目方式取出了性別。注意層級關系。
// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';
import RenderList from './RenderList.jsx';function App() {// const people = [// { name: '張三', gender: 'male', education: '本科', married: true, skills: '編程、設計', num: 1 },// { name: '李四', gender: 'female', education: '碩士', married: false, skills: '營銷、管理', num: 2 },// { name: '王五', gender: 'male', education: '大專', married: true, skills: '機械維修', num: 3 },// { name: '趙六', gender: 'female', education: '博士', married: false, skills: '數據分析', num: 4 }// ];return (// <div className="container mx-auto p-4">// <h1 className="text-3xl font-bold text-center mb-8">簡歷列表</h1>// <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">// {people.map((person, index) => (// <Avatar// key={index}// person={person}// size={80}// num={person.num}// />// ))}// </div>// </div><RenderList />);
}export default App;
// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';
import RenderList from './RenderList.jsx';function App() {// const people = [// { name: '張三', gender: 'male', education: '本科', married: true, skills: '編程、設計', num: 1 },// { name: '李四', gender: 'female', education: '碩士', married: false, skills: '營銷、管理', num: 2 },// { name: '王五', gender: 'male', education: '大專', married: true, skills: '機械維修', num: 3 },// { name: '趙六', gender: 'female', education: '博士', married: false, skills: '數據分析', num: 4 }// ];return (// <div className="container mx-auto p-4">// <h1 className="text-3xl font-bold text-center mb-8">簡歷列表</h1>// <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">// {people.map((person, index) => (// <Avatar// key={index}// person={person}// size={80}// num={person.num}// />// ))}// </div>// </div><RenderList />);
}export default App;
注意我把多余的內容給注釋了,
效果圖:
陷阱
因為箭頭函數會隱式地返回位于
=>
之后的表達式,所以你可以省略return
語句。
const listItems = chemists.map(person =>
<li>...</li> // 隱式地返回!
);
不過,如果你的
=>
后面跟了一對花括號{
,那你必須使用return
來指定返回值!
const listItems = chemists.map(person => { // 花括號
return <li>...</li>;
});
箭頭函數
=> {
后面的部分被稱為 “塊函數體”,塊函數體支持多行代碼的寫法,但要用return
語句才能指定返回值。假如你忘了寫return
,那這個函數什么都不會返回!
?key的作用
用?
key
?保持列表項的順序?如果把上面任何一個沙盒示例在新標簽頁打開,你就會發現控制臺有這樣一個報錯:
Console
Warning: Each child in a list should have a unique “key” prop.
這是因為你必須給數組中的每一項都指定一個
key
——它可以是字符串或數字的形式,只要能唯一標識出各個數組項就行:
<li key={person.id}>...</li>
注意
直接放在
map()
方法里的 JSX 元素一般都需要指定key
值!這些 key 會告訴 React,每個組件對應著數組里的哪一項,所以 React 可以把它們匹配起來。這在數組項進行移動(例如排序)、插入或刪除等操作時非常重要。一個合適的
key
可以幫助 React 推斷發生了什么,從而得以正確地更新 DOM 樹。用作 key 的值應該在數據中提前就準備好,而不是在運行時才隨手生成:
更多知識
如何設定?
key
?值?不同來源的數據往往對應不同的 key 值獲取方式:
- 來自數據庫的數據:?如果你的數據是從數據庫中獲取的,那你可以直接使用數據表中的主鍵,因為它們天然具有唯一性。
- 本地產生數據:?如果你數據的產生和保存都在本地(例如筆記軟件里的筆記),那么你可以使用一個自增計數器或者一個類似?uuid?的庫來生成 key。
key 需要滿足的條件?
- key 值在兄弟節點之間必須是唯一的。?不過不要求全局唯一,在不同的數組中可以使用相同的 key。
- key 值不能改變,否則就失去了使用 key 的意義!所以千萬不要在渲染時動態地生成 key。
React 中為什么需要 key??
設想一下,假如你桌面上的文件都沒有文件名,取而代之的是,你需要通過文件的位置順序來區分它們———第一個文件,第二個文件,以此類推。也許你也不是不能接受這種方式,可是一旦你刪除了其中的一個文件,這種組織方式就會變得混亂無比。原來的第二個文件可能會變成第一個文件,第三個文件會成為第二個文件……
React 里需要 key 和文件夾里的文件需要有文件名的道理是類似的。它們(key 和文件名)都讓我們可以從眾多的兄弟元素中唯一標識出某一項(JSX 節點或文件)。而一個精心選擇的 key 值所能提供的信息遠遠不止于這個元素在數組中的位置。即使元素的位置在渲染的過程中發生了改變,它提供的
key
值也能讓 React 在整個生命周期中一直認得它。陷阱
你可能會想直接把數組項的索引當作 key 值來用,實際上,如果你沒有顯式地指定
key
值,React 確實默認會這么做。但是數組項的順序在插入、刪除或者重新排序等操作中會發生改變,此時把索引順序用作 key 值會產生一些微妙且令人困惑的 bug。與之類似,請不要在運行過程中動態地產生 key,像是
key={Math.random()}
這種方式。這會導致每次重新渲染后的 key 值都不一樣,從而使得所有的組件和 DOM 元素每次都要重新創建。這不僅會造成運行變慢的問題,更有可能導致用戶輸入的丟失。所以,使用能從給定數據中穩定取得的值才是明智的選擇。有一點需要注意,組件不會把
key
當作 props 的一部分。Key 的存在只對 React 本身起到提示作用。如果你的組件需要一個 ID,那么請把它作為一個單獨的 prop 傳給組件:<Profile key={id} userId={id} />
。
?13.3過濾
在?
RenderList
?組件上方添加按性別篩選顯示的功能。你可以通過選擇 “全部”“男”“女” 來篩選列表中的人員。
- 引入?
useState
:借助?useState
?鉤子來管理篩選狀態,初始值設為?'all'
。- 篩選人員列表:依據當前的篩選狀態對?
people
?數組進行過濾,生成?filteredPeople
?數組。- 添加篩選下拉框:在組件上方添加一個下拉框,提供 “全部”“男”“女” 三個選項,當選擇發生變化時,更新篩選狀態。
- 渲染篩選后的列表:對?
filteredPeople
?數組進行映射,渲染出篩選后的人員列表。
?修改后的代碼
import { GetImageUrl } from "./GetImageUrl"
import React,{useState} from "react"// RendreList.jsxconst people = [{ id:1,name: 'Alice', age: 25, isMan: true },{ id:2,name: 'Bob', age: 30, isMan: true },{ id:3,name: 'Charlie', age: 28, isMan: true },{ id:4,name: 'David', age: 35, isMan: false },{ id:5,name: 'Eve', age: 27, isMan: false },
]// const listItems = people.map((person) => (
// <li className="item" key={person.id}>
// <img src={GetImageUrl(person.id)} alt={person.name} style={{
// width: 40,
// height: 40,
// borderRadius: "50%",
// border: "2px solid #000",
// }}/>
// {person.name}:{person.age}
// {person.isMan ? '男?' : '女'}
// <hr />
// </li>
// ))function RenderList() {const [filteredPeople, setFilteredPeople] = useState("all");const filteredPeopleList = people.filter((person) => {if (filteredPeople === "all") {return true;} else if (filteredPeople === "man") {return person.isMan;} else if (filteredPeople === "woman") {return !person.isMan;}});const listItems = filteredPeopleList.map((person) => (<li className="item" key={person.id}><img src={GetImageUrl(person.id)} alt={person.name} style={{width: 40,height: 40,borderRadius: "50%",border: "2px solid #000",}}/>{person.name}:{person.age}{person.isMan ? '男?' : '女'}<hr /></li>))return (<> <div><select name="" id=""value={filteredPeople} onChange={(e) => setFilteredPeople(e.target.value)}><option value="all">全部</option><option value="man">男</option><option value="woman">女</option></select></div><ul className="list">{listItems}</ul></>)
}
export default RenderList
將?
?const listItems = filteredPeopleList.map((person) => {... })
?放在函數內部有幾個合理的原因,主要涉及到數據的動態更新和作用域相關的問題:?
- 動態更新:在你的?
RenderList
?組件中,使用了?useState
?鉤子來管理?filteredPeople
?狀態。當用戶通過?select
?元素選擇不同的篩選選項時,filteredPeople
?的值會發生變化,進而導致?filteredPeopleList
?數組的內容也發生變化(因為?filteredPeopleList
?是根據?filteredPeople
?的值對?people
?數組進行過濾得到的)。如果?
?listItems
?的計算是在函數外部,那么它只會在組件第一次渲染時被計算一次,后續?filteredPeople
?的變化不會影響到?listItems
?的值,也就無法實現動態的篩選效果。而將?listItems
?的計算放在函數內部,每次組件因為狀態更新而重新渲染時,listItems
?都會根據最新的?filteredPeopleList
?重新計算,從而確保顯示的是篩選后正確的列表內容。?
- 作用域問題:
filteredPeopleList
?是在?RenderList
?函數內部通過對?people
?數組的過濾操作得到的變量。如果?listItems
?的計算在函數外部,那么它無法訪問到?filteredPeopleList
?變量(因為?filteredPeopleList
?的作用域僅限于?RenderList
?函數內部),會導致代碼報錯。將?listItems
?的計算放在函數內部,它就可以在正確的作用域內訪問到?filteredPeopleList
?并進行映射操作。綜上所述,將?
listItems
?的計算放在?RenderList
?函數內部是為了確保篩選功能的正確實現,能夠根據狀態的變化動態更新顯示的列表內容。
?效果如下:
14,保持組件純粹
部分 JavaScript 函數是 純粹 的,這類函數通常被稱為純函數。純函數僅執行計算操作,不做其他操作。你可以通過將組件按純函數嚴格編寫,以避免一些隨著代碼庫的增長而出現的、令人困擾的 bug 以及不可預測的行為。但為了獲得這些好處,你需要遵循一些規則。
你將會學習到
- 純函數是什么,以及它如何幫助你避免 bug
- 如何將數據變更與渲染過程分離,以保持組件的純粹
- 如何使用嚴格模式發現組件中的錯誤
14.1 純函數:組件作為公式
在計算機科學中(尤其是函數式編程的世界中),純函數 通常具有如下特征:
- 只負責自己的任務。它不會更改在該函數調用前就已存在的對象或變量。
- 輸入相同,則輸出相同。給定相同的輸入,純函數應總是返回相同的結果。
舉個你非常熟悉的純函數示例:數學中的公式。
考慮如下數學公式:y = 2x。
若 x = 2 則 y = 4。永遠如此。
若 x = 3 則 y = 6。永遠如此。
若 x = 3,那么 y 并不會因為時間或股市的影響,而有時等于 9 、 –1 或 2.5。
若 y = 2x 且 x = 3, 那么 y 永遠 等于 6.
我們使用 JavaScript 的函數實現,看起來將會是這樣:
function double(number) {
return 2 * number;
}
上述例子中,
double()
就是一個 純函數。如果你傳入3
,它將總是返回6
。React 便圍繞著這個概念進行設計。React 假設你編寫的所有組件都是純函數。也就是說,對于相同的輸入,你所編寫的 React 組件必須總是返回相同的 JSX。
14.2副作用:不符合預期的后果
?React 的渲染過程必須自始至終是純粹的。組件應該只 返回 它們的 JSX,而不 改變 在渲染前,就已存在的任何對象或變量 — 這將會使它們變得不純粹!
以下為違反規則的組件
// CupNo.jsx
let guest = 0;function Cup() {// Bad:正在更改預先存在的變量!guest = guest + 1;return <h2>Tea cup for guest #{guest}</h2>;
}export default function TeaSet() {return (<><Cup /><Cup /><Cup /></>);
}
// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';
import RenderList from './RenderList.jsx';
import TeaSet from './CupNo.jsx';function App() {// const people = [// { name: '張三', gender: 'male', education: '本科', married: true, skills: '編程、設計', num: 1 },// { name: '李四', gender: 'female', education: '碩士', married: false, skills: '營銷、管理', num: 2 },// { name: '王五', gender: 'male', education: '大專', married: true, skills: '機械維修', num: 3 },// { name: '趙六', gender: 'female', education: '博士', married: false, skills: '數據分析', num: 4 }// ];return (// <div className="container mx-auto p-4">// <h1 className="text-3xl font-bold text-center mb-8">簡歷列表</h1>// <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">// {people.map((person, index) => (// <Avatar// key={index}// person={person}// size={80}// num={person.num}// />// ))}// </div>// </div><><RenderList /><TeaSet /></>);
}export default App;
很明顯,已經失去了預期。
該組件正在讀寫其外部聲明的
guest
變量。這意味著 多次調用這個組件會產生不同的 JSX!并且,如果 其他 組件讀取guest
,它們也會產生不同的 JSX,其結果取決于它們何時被渲染!這是無法預測的。
我們可以修改一下,如下
// CupNo.jsx
// let guest = 0;function Cup({guest}) {// Bad:正在更改預先存在的變量!
// guest = guest + 1;return <h2>Tea cup for guest #{guest}</h2>;
}export default function TeaSet() {return (<><Cup guest={1} /><Cup guest={2} /><Cup guest={3} /></>);
}
現在你的組件就是純粹的,因為它返回的 JSX 只依賴于
guest
prop。一般來說,你不應該期望你的組件以任何特定的順序被渲染。調用 y = 5x 和 y = 2x 的先后順序并不重要:這兩個公式相互獨立。同樣地,每個組件也應該“獨立思考”,而不是在渲染過程中試圖與其他組件協調,或者依賴于其他組件。渲染過程就像是一場學校考試:每個組件都應該自己計算 JSX!
使用嚴格模式檢測不純的計算?
收起盡管你可能還沒使用過,但在 React 中,你可以在渲染時讀取三種輸入:props,state 和 context。你應該始終將這些輸入視為只讀。
當你想根據用戶輸入 更改 某些內容時,你應該 設置狀態,而不是直接寫入變量。當你的組件正在渲染時,你永遠不應該改變預先存在的變量或對象。
React 提供了 “嚴格模式”,在嚴格模式下開發時,它將會調用每個組件函數兩次。通過重復調用組件函數,嚴格模式有助于找到違反這些規則的組件。
我們注意到,原始示例顯示的是 “Guest #2”、“Guest #4” 和 “Guest #6”,而不是 “Guest #1”、“Guest #2” 和 “Guest #3”。原來的函數并不純粹,因此調用它兩次就出現了問題。但對于修復后的純函數版本,即使調用該函數兩次也能得到正確結果。純函數僅僅執行計算,因此調用它們兩次不會改變任何東西 — 就像兩次調用
double(2)
并不會改變返回值,兩次求解 y = 2x 不會改變 y 的值一樣。相同的輸入,總是返回相同的輸出。嚴格模式在生產環境下不生效,因此它不會降低應用程序的速度。如需引入嚴格模式,你可以用
<React.StrictMode>
包裹根組件。一些框架會默認這樣做。
要注意參數引用 時用{}?
?
14.3局部 mutation:組件的小秘密
上一小節的示例的問題出在渲染過程中,組件改變了 預先存在的 變量的值。為了讓它聽起來更可怕一點,我們將這種現象稱為 突變(mutation) 。純函數不會改變函數作用域外的變量、或在函數調用前創建的對象——這會使函數變得不純粹!
但是,你完全可以在渲染時更改你 剛剛 創建的變量和對象。在本示例中,你創建一個
[]
數組,將其分配給一個cups
變量,然后push
一打 cup 進去:
?我將使用非默認導出的方式用來對比。
// CupNo.jsx
// let guest = 0;function Cup({guest}) {// Bad:正在更改預先存在的變量!
// guest = guest + 1;return <h2>Tea cup for guest #{guest}</h2>;
}export default function TeaSet() {return (<><Cup guest={1} /><Cup guest={2} /><Cup guest={3} /></>);
}
export function TeaGathering() {let cups=[]for (let i=8; i<13; i++) {cups.push(<Cup guest={i} />)}
// return cupsreturn (<><h2>Tea Gathering</h2>{cups}</>);
}
要注意的是在上一段代碼中,如果返回時,使用單行時,要純粹的寫在一行。如何是多行時,要用()。下面是如何引用
// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';
import RenderList from './RenderList.jsx';
import TeaSet from './CupNo.jsx';
import { TeaGathering } from './CupNo.jsx';function App() {// const people = [// { name: '張三', gender: 'male', education: '本科', married: true, skills: '編程、設計', num: 1 },// { name: '李四', gender: 'female', education: '碩士', married: false, skills: '營銷、管理', num: 2 },// { name: '王五', gender: 'male', education: '大專', married: true, skills: '機械維修', num: 3 },// { name: '趙六', gender: 'female', education: '博士', married: false, skills: '數據分析', num: 4 }// ];return (// <div className="container mx-auto p-4">// <h1 className="text-3xl font-bold text-center mb-8">簡歷列表</h1>// <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">// {people.map((person, index) => (// <Avatar// key={index}// person={person}// size={80}// num={person.num}// />// ))}// </div>// </div><><RenderList /><TeaSet /><TeaGathering /></>);
}export default App;
以上代碼要注意的是引用時,要用{}來使用。
如果
cups
變量或[]
數組是在TeaGathering
函數之外創建的,這將是一個很大的問題!因為如果那樣的話,當你調用數組的 push 方法時,就會更改 預先存在的 對象。但是,這里不會有影響,因為每次渲染時,你都是在
TeaGathering
函數內部創建的它們。TeaGathering
之外的代碼并不會知道發生了什么。這就被稱為 “局部 mutation” — 如同藏在組件里的小秘密。
React 為何側重于純函數??
收起編寫純函數需要遵循一些習慣和規程。但它開啟了絕妙的機遇:
- 你的組件可以在不同的環境下運行 — 例如,在服務器上!由于它們針對相同的輸入,總是返回相同的結果,因此一個組件可以滿足多個用戶請求。
- 你可以為那些輸入未更改的組件來?跳過渲染,以提高性能。這是安全的做法,因為純函數總是返回相同的結果,所以可以安全地緩存它們。
- 如果在渲染深層組件樹的過程中,某些數據發生了變化,React 可以重新開始渲染,而不會浪費時間完成過時的渲染。純粹性使得它隨時可以安全地停止計算。
我們正在構建的每個 React 新特性都利用到了純函數。從數據獲取到動畫再到性能,保持組件的純粹可以充分釋放 React 范式的能力。
請記住,React 無法保證組件函數以任何特定的順序執行,因此你無法通過設置變量在它們之間進行通信。所有的交流都必須通過 props 進行。?
你的 mutation 保持在局部,并使你的渲染函數保持純粹。但你仍然需要小心:例如,當你想要更改數組的任意項時,必須先對其進行拷貝。
記住數組上的哪些操作會修改原始數組、哪些不會,這非常有幫助。例如,push、pop、reverse 和 sort 會改變原始數組,但 slice、filter 和 map 則會創建一個新數組。?
15.將UI視為樹
當 React 應用程序逐漸成形時,許多組件會出現嵌套。那么 React 是如何跟蹤應用程序組件結構的?
React 以及許多其他 UI 庫,將 UI 建模為樹。將應用程序視為樹對于理解組件之間的關系以及調試性能和狀態管理等未來將會遇到的一些概念非常有用。
你將會學習到
- React 如何看待組件結構
- 渲染樹是什么以及它有什么用處
- 模塊依賴樹是什么以及它有什么用處
15.1?將 UI 視為樹?
樹是項目和 UI 之間的關系模型,通常使用樹結構來表示 UI。例如,瀏覽器使用樹結構來建模 HTML(DOM)與CSS(CSSOM)。移動平臺也使用樹來表示其視圖層次結構。

?15.2渲染樹
組件的一個主要特性是能夠由其他組件組合而成。在 嵌套組件 中有父組件和子組件的概念,其中每個父組件本身可能是另一個組件的子組件。
當渲染 React 應用程序時,可以在一個稱為渲染樹的樹中建模這種關系。
?
?這棵樹由節點組成,每個節點代表一個組件。例如,App、FancyText、Copyright 等都是我們樹中的節點。
在 React 渲染樹中,根節點是應用程序的 根組件。在這種情況下,根組件是 App,它是 React 渲染的第一個組件。樹中的每個箭頭從父組件指向子組件。
渲染樹表示 React 應用程序的單個渲染過程。在 條件渲染 中,父組件可以根據傳遞的數據渲染不同的子組件。?
15.3模塊依賴樹
?
在 React 應用程序中,可以使用樹來建模的另一個關系是應用程序的模塊依賴關系。當 拆分組件 和邏輯到不同的文件中時,就創建了 JavaScript 模塊,在這些模塊中可以導出組件、函數或常量。
模塊依賴樹中的每個節點都是一個模塊,每個分支代表該模塊中的
import
語句。以之前的 Inspirations 應用程序為例,可以構建一個模塊依賴樹,簡稱依賴樹。
依賴樹對于確定運行 React 應用程序所需的模塊非常有用。在為生產環境構建 React 應用程序時,通常會有一個構建步驟,該步驟將捆綁所有必要的 JavaScript 以供客戶端使用。負責此操作的工具稱為 bundler(捆綁器),并且 bundler 將使用依賴樹來確定應包含哪些模塊。
隨著應用程序的增長,捆綁包大小通常也會增加。大型捆綁包大小對于客戶端來說下載和運行成本高昂,并延遲 UI 繪制的時間。了解應用程序的依賴樹可能有助于調試這些問題。
16添加交互
界面上的控件會根據用戶的輸入而更新。例如,點擊按鈕切換輪播圖的展示。在 React 中,隨時間變化的數據被稱為狀態(state)。你可以向任何組件添加狀態,并按需進行更新。在本章節中,你將學習如何編寫處理交互的組件,更新它們的狀態,并根據時間變化顯示不同的效果。
本章節
- 如何處理用戶發起的事件
- 如何用狀態使組件“記住”信息
- React 是如何分兩個階段更新 UI 的
- 為什么狀態在你改變后沒有立即更新
- 如何排隊進行多個狀態的更新
- 如何更新狀態中的對象
- 如何更新狀態中的數組
?16.0.響應事件
React 允許你向 JSX 中添加事件處理程序。事件處理程序是你自己的函數,它將在用戶交互時被觸發,如點擊、懸停、焦點在表單輸入框上等等。
<button>
等內置組件只支持內置瀏覽器事件,如onClick
。但是,你也可以創建你自己的組件,并給它們的事件處理程序 props 指定你喜歡的任何特定于應用的名稱。
// Toolbar.jsx
function Toolbar({onPlay, onPause}) {return (<div><Button onClick={onPlay}>Play</Button><Button onClick={onPause}>Pause</Button></div>)};function Button({onClick, children}) {return (<button onClick={onClick} className="bg-blue-500 text-white font-bold py-2 px-4 rounded">{children}</button>);
}export default Toolbar;
以上代碼介紹如下:
文件說明:
這是一個名為?Toolbar.jsx
?的 React 組件文件,.jsx
?后綴表明它是一個包含 JSX 語法的 JavaScript 文件,用于定義 React 組件。
Toolbar
?組件:
function Toolbar({onPlay, onPause})
:定義了一個名為?Toolbar
?的函數式組件,它接受一個包含?onPlay
?和?onPause
?兩個屬性的對象作為參數。onPlay
?和?onPause
?通常是函數類型的屬性,用于處理按鈕點擊事件。return
?語句內部:
<div>
:創建一個?div
?元素作為容器,用于包裹下面的按鈕。<Button onClick={onPlay}>Play</Button>
:使用?Button
?組件創建一個按鈕,按鈕上顯示文本 "Play",并將?onPlay
?函數綁定到按鈕的?onClick
?事件上。當用戶點擊這個按鈕時,onPlay
?函數將會被調用。<Button onClick={onPause}>Pause</Button>
:類似地,創建另一個按鈕,顯示文本 "Pause",并將?onPause
?函數綁定到按鈕的?onClick
?事件上。點擊該按鈕時,onPause
?函數會被觸發。
Button
?組件:
function Button({onClick, children})
:定義了一個名為?Button
?的函數式組件,接受一個包含?onClick
?和?children
?屬性的對象作為參數。onClick
?是一個函數,用于處理按鈕的點擊事件;children
?表示在按鈕標簽內部的內容(例如文本)。return
?語句內部:
<button onClick={onClick} className="bg-blue-500 text-white font-bold py-2 px-4 rounded">
:創建一個原生的 HTML?button
?元素,將?onClick
?函數綁定到按鈕的點擊事件上,并通過?className
?屬性為按鈕添加了一些樣式,包括藍色背景、白色文字、加粗字體、內邊距和圓角。{children}
:在按鈕內部顯示傳遞進來的?children
?內容,例如 "Play" 或 "Pause"。導出組件:
export default Toolbar;
:將?Toolbar
?組件作為默認導出,這樣在其他文件中可以通過?import
?語句導入并使用這個組件,例如?import Toolbar from './Toolbar.jsx';
。
// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';
import RenderList from './RenderList.jsx';
import TeaSet from './CupNo.jsx';
import { TeaGathering } from './CupNo.jsx';
// import Copyright from './Copyright.jsx';
import Toolbar from './Toolbar.jsx';function App() {// const people = [// { name: '張三', gender: 'male', education: '本科', married: true, skills: '編程、設計', num: 1 },// { name: '李四', gender: 'female', education: '碩士', married: false, skills: '營銷、管理', num: 2 },// { name: '王五', gender: 'male', education: '大專', married: true, skills: '機械維修', num: 3 },// { name: '趙六', gender: 'female', education: '博士', married: false, skills: '數據分析', num: 4 }// ];return (// <div className="container mx-auto p-4">// <h1 className="text-3xl font-bold text-center mb-8">簡歷列表</h1>// <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">// {people.map((person, index) => (// <Avatar// key={index}// person={person}// size={80}// num={person.num}// />// ))}// </div>// </div><>{/* <RenderList /><TeaSet /><TeaGathering /> */}<Toolbar onPlay={() => alert('Play')}onPause={() => alert('Pause')}/>{/* <Copyright year={2004} /> */}</>);
}export default App;
分解理解
?16.0.1分步理解
- 在?
Button
?組件?內部?聲明一個名為?handleClick
?的函數。- 實現函數內部的邏輯(使用?
alert
?來顯示消息)。- 添加?
onClick={handleClick}
?到?<button>
?JSX 中。
// Button.jsx
function Button(){function handleClick(){alert('clicked');}return (<button onClick={handleClick} >Click Me</button>);
}
export default Button;
// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';
import RenderList from './RenderList.jsx';
import TeaSet from './CupNo.jsx';
import { TeaGathering } from './CupNo.jsx';
// import Copyright from './Copyright.jsx';
import Toolbar from './Toolbar.jsx';
import Button from './Button.jsx';function App() {return (<Button />);
}export default App;
結果
還可以用以下兩個代碼進行替換。結果一樣。
// Button.jsx
function Button(){// function handleClick(){// alert('clicked');return (// <button onClick={handleClick} >// Click Me// </button><button onClick={() => alert('clicked22')} >Click Me</button>);
}export default Button;
// Button.jsx
function Button(){// function handleClick(){// alert('clicked');return (// <button onClick={handleClick} >// Click Me// </button>// <button onClick={() => alert('clicked22')} >// Click Me// </button><button onClick={function handleClick(){alert('clicked33')}}>333</button> );
}export default Button;
如果你需要3個及不固定數量的話,你需要使用本節開頭處提到的方法
關于箭頭函數的知識
?箭頭函數,基礎知識
https://zh.javascript.info/arrow-functions-basics
以上方法是等效的,但要注意
16.0.2在事件處理中讀取props
?由于事件處理函數聲明于組件內部,因此它們可以直接訪問組件的 props。示例中的按鈕,當點擊時會彈出帶有
message
prop 的 alert:
?修改本節最初的文件Toolbar.jsx
// Toolbar.jsx
// function Toolbar({onPlay, onPause}) {
// return (
// <div>
// <Button onClick={onPlay}>Play</Button>
// <Button onClick={onPause}>Pause</Button>
// </div>
// )};// function Button({onClick, children}) {
// return (
// <button onClick={onClick} className="bg-blue-500 text-white font-bold py-2 px-4 rounded">
// {children}
// </button>
// );
// }
function AlertButton({message, children}) {return (<button onClick={() => alert(message)}>{children}</button>);
}
function Toolbar() {return (<div><AlertButton message="Play11" >Play11</AlertButton><AlertButton message="Pause22" >Pause22</AlertButton></div>);
}
export default Toolbar;
效果如下
16.0.3?將事件處理函數作為 props 傳遞
通常,我們會在父組件中定義子組件的事件處理函數。比如:置于不同位置的
Button
組件,可能最終執行的功能也不同 —— 也許是播放電影,也許是上傳圖片。為此,將組件從父組件接收的 prop 作為事件處理函數傳遞,如下所示:
// Toolbar.jsx
// function Toolbar({onPlay, onPause}) {
// return (
// <div>
// <Button onClick={onPlay}>Play</Button>
// <Button onClick={onPause}>Pause</Button>
// </div>
// )};function Button({onClick, children}) {return (<button onClick={onClick} className="bg-blue-500 text-white font-bold py-2 px-4 rounded">{children}</button>);
}
// function AlertButton({message, children}) {
// return (
// <button onClick={() => alert(message)}>{children}</button>
// );
// }
function PlayButton({movieName}) {function handleClick() {alert(`Playing ${movieName}`);}return (<button onClick={handleClick}>播放"{movieName}"</button>);
}
function Toolbar() {return (<div>{/* <AlertButton message="Play11" >Play11</AlertButton><AlertButton message="Pause22" >Pause22</AlertButton> */}<PlayButton movieName="The Godfather" /></div>);
}
export default Toolbar;
效果如下:
示例中,
Toolbar
組件渲染了一個PlayButton
組件和UploadButton
組件:
PlayButton
?將?handlePlayClick
?作為?onClick
?prop 傳入?Button
?組件內部。UploadButton
?將?() => alert('正在上傳!')
?作為?onClick
?prop 傳入?Button
?組件內部。最后,你的
Button
組件接收一個名為onClick
的 prop。它直接將這個 prop 以onClick={onClick}
方式傳遞給瀏覽器內置的<button>
。當點擊按鈕時,React 會調用傳入的函數。如果你遵循某個 設計系統 時,按鈕之類的組件通常會包含樣式,但不會指定行為。而
PlayButton
和UploadButton
之類的組件則會向下傳遞事件處理函數。
16.0.4自定義處理函數
沒留意。丟了近2萬字的記錄。下周了。