全文约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>
<img
src="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>
<img
src={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>
<img
src={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>
<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;
是不是可以优化呢? 我认为可以如下:
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) => (
<Avatar
key={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">
<img
src={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">
<img
src={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.jsx
const 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.jsx
const 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 cups
return (
<>
<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">
:创建一个原生的 HTMLbutton
元素,将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万字的记录。下周了。