React入门之描述UI
React
React应用就是被组件的独立UI片段构建,React组件本质就是可以添加任意标签的JavaScript函数。
React组件的基本要素
- 基本的组件js文件存在要素:
1、可以return出存在任意标签的JavaScript函数
2、函数中需要存在return(xxx) html结构
3、最终需要将渲染的组件export出去
1 | function Profile() { |
React 组件导入导出
- 组件导入导出语法
语法 | 导出语句 | 导入语句 |
---|---|---|
默认 | export default function Button() {} | import Button from ‘./Button.js’; |
具名 | export function Button() {} | import { Button } from ‘./Button.js’ |
- 组件导入导出案例解释
- Gallery.js
- 定义了Profile组件
- 该组件采用默认导出(export default function)
- app.js
- 使用默认导入,导入Gallery文件的组件Gallery
- 使用默认导出方式将app组件导出
1 | ## app.js |
JSX书写标签语言
什么是JSX
JSX 是 JavaScript 语法扩展,可以让在 JavaScript 文件中书写类似 HTML 的标签。JSX 看起来和 HTML 很像,但它的语法更加严格并且可以动态展示信息。
为什么出现JSX
随着Web交互性越来越强,逻辑开始决定页面的内容。也就是可以说JavaScript负责HTML内容。因此,在React中,渲染逻辑和标签存在同一个组件文件中。
使用JSX的优势
例如将一个按钮的渲染逻辑和标签放在一起,可以确保他们在编辑的时候保持同步。反之,也可以说彼此无关的细节是隔离的。
JSX与HTML的区别
每个 React 组件都是一个 JavaScript 函数,它会返回一些标签,React 会将这些标签渲染到浏览器上。React 组件使用一种被称为 JSX 的语法扩展来描述这些标签。JSX对于HTML来说,语法更加严格并且可以动态的展示信息。
JSX and React 是相互独立的东西。通常配合使用,也可以单独使用它们中的任意一个,JSX 是一种语法扩展,而 React 则是一个 JavaScript 的库。
HTML转换为JSX
现在存在一个html标签
1 | <h1>海蒂·拉玛的待办事项</h1> |
此时需要将它转化为React组件,可以这么做,是的没错。 采用 *** <> 或者其他标签*** 包裹。
1 | export default function TodoList() { |
JSX语法规则
只能返回一个根元素
- 就是return()中必须只有一个根元素
- 不想在标签中增加一个额外的 ,可以用 <> 和 </> 元素来代替
标签必须闭合
使用驼峰式命名法给大部分属性命名!
JSX 最终会被转化为 JavaScript,而 JSX 中的属性也会变成 JavaScript 对象中的键值对。在组件中,经常会遇到需要用变量的方式读取这些属性的时候。但 JavaScript 对变量的命名有限制。例如,变量名称不能包含 - 符号或者像 class 这样的保留字。
JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。
JSX中通过大括号使用javaScript
JSX可以在JavaScript中编写类似HTML的标签,保证渲染逻辑和内容绑定。在需要在标签中添加Js逻辑或者引用标签的动态属性的时候可以在JSX的大括号里面编写Js。
JSX使用引号字符串
当你想将字符串属性传递给JSX时,放在单引号或者双引号中
当你想动态传值时,可以使用{ 和 } 替代 “ 和 “
1 | // 字符串 |
注意:当使用src={ } 这种写法会去读取花括号中js中这个变量的值。
标签插值
JSX中允许在标签中插入大括号
{}
中使用变量JSX中允许在标签中插入大括号
{}
中使用函数表达式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 变量
export default function TodoList() {
const name = 'Gregorio Y. Zara';
return (
<h1>{name}'s To Do List</h1>
);
}
// 函数表达式
const today = new Date();
function formatDate(date) {
return new Intl.DateTimeFormat(
'zh-CN',
{ weekday: 'long' }
).format(date);
}
export default function TodoList() {
return (
<h1>To Do List for {formatDate(today)}</h1>
);
}
大括号的使用场景
主要场景是字符串、数字、变量、和js表达式。
- 用作JSX标签*内部的文本*:
<h1>{name}'s To Do List</h1>
- 标签***=*后面紧跟的属性**:
src={avatar}
会读取avatar
变量,但是!!!对于src="{avatar}"只会传一个字符串{avatar}
双大括号的使用场景
双大括号其实是{}
传递对象的传递方式。
- 对象也用大括号表示,例如
{ name: "Hedy Lamarr", inventions: 5 }
。因此,为了能在 JSX 中传递,必须用另一对额外的大括号包裹对象:person={{ name: "Hedy Lamarr", inventions: 5 }}
。 - 也可使用嵌套对象,在jsx大括号中使用。
注意:内联 style
属性 使用驼峰命名法编写。例如,HTML <ul style="background-color: black">
在你的组件里应该写成 <ul style={{ backgroundColor: 'black' }}>
。
1 | // 对象传递 |
Props组件传递
React组件使用Props互相通信。每个父组件都可以提供props给他的自组件传递信息。可以通过Props传递认识JavaScript值,包括对象、数组、和函数。
Props传递类似HTML预定义的属性。像组件传递的时候可以传递任意的props。传递的props可以通过不同的方式进行渲染。
1 | // 其中person就是props |
Props的使用,必须要关注你的父子组件。需要考虑好父组件需要向自组件传的props,而子组件需要考虑传递的props通过什么方式去进行渲染。事实上,**就可以总结出来,React函数组件就是接受一个参数,那就是Props对象。**
Props使用妙计
prop可以指定默认值
如果渲染时不存在size的prop,那么size将被赋值100进行渲染
其实就是size的prop属性不存在或者值undefined时会生效
但是,如果传递
size={null}
或size={0}
,默认值将 不 被使用。1
2
3function Avatar({ person, size = 100 }) {
// ...
}可以使用JSX展开语法传递Props
会存在需要传递pros很多,需要声明prop传递
1
2
3
4
5
6
7
8
9
10
11function Profile({ person, size, isSepia, thickBorder }) {
return (
<div className="card">
<Avatar
person={person}
size={size}
isSepia={isSepia}
thickBorder={thickBorder}
/>
</div>
);如果存在将所有的props传递给子组件可以采用简洁展开语法,这种写法,将父组件需要传递的props全部传递。如果滥用,那么建议拆分组件。
1
2
3
4
5
6
7function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}将JSX作为子组件传递
类似HTML标签嵌套,以下例子在父组件card中将children组件作为prop传递,类似vue插槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49// app.js
import Avatar from './Avatar.js';
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
export default function Profile() {
return (
<Card>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
</Card>
);
}
// Avatar.js
import { getImageUrl } from './utils.js';
export default function Avatar({ person, size }) {
return (
<img
className="avatar"
src={getImageUrl(person)}
alt={person.name}
width={size}
height={size}
/>
);
}
// utils.js
export function getImageUrl(person, size = 's') {
return (
'https://i.imgur.com/' +
person.imageId +
size +
'.jpg'
);
}
注意
一个组件可能会随着时间的推移收到不同的 props。 Props 并不总是静态的!Props 反映了组件在任何时间点的数据,并不仅仅是在开始时。
然而,props 是 不可变的(一个计算机科学术语,意思是“不可改变”)。当一个组件需要改变它的 props(例如,响应用户交互或新数据)时,它不得不“请求”它的父组件传递 不同的 props —— 一个新对象!它的旧 props 将被丢弃,最终 JavaScript 引擎将回收它们占用的内存。不要尝试“更改 props”。
条件渲染
组件中会存在很多根据条件渲染的内容。在React中可以使用js中的if
语句、&&
和 ? :
运算符来选择性地渲染 JSX。
如何根据条件返回JSX
根据组件Prop传入的属性值来动态展示内容
- 使用 if/else 语句 去判断
1 | // 其中isPacked是组件接受的Prop |
选择性包含JSX
一般的不同条件会return不同的HTML片段,此刻可以选择包含JSX的书写,一般使用以下运算符:
1、三目运算符(
? :
)1
2
3
4
5
6
7
8
9
10
11
12if (isPacked) {
return <li className="item">{name} ✔</li>;
}
return <li className="item">{name}</li>;
// 以上代码使用三目运算符修改为
return (
<li className="item">
{isPacked ? name + ' ✔' : name}
</li>
);**注意:**简单的内容展示适合使用三目这种运算符来展示,如果展示的逻辑较为复杂,可选择提取自组件来渲染。
2、与运算符(
&&
)1
2
3
4
5
6
7// 当 JavaScript && 表达式 的左侧(我们的条件)为 true 时,它则返回其右侧的值(在我们的例子里是勾选符号)。但条件的结果是 false,则整个表达式会变成 false。在 JSX 里,React 会将 false 视为一个“空值”,就像 null 或者 undefined,这样 React 就不会在这里进行任何渲染
return (
<li className="item">
{name} {isPacked && '✔'}
</li>
);注意:切勿将数字放在
&&
左侧.JavaScript 会自动将左侧的值转换成布尔类型以判断条件成立与否。然而,如果左侧是
0
,整个表达式将变成左侧的值(0
),React 此时则会渲染0
而不是不进行渲染。例如,一个常见的错误是
messageCount && <p>New messages</p>
。其原本是想当messageCount
为 0 的时候不进行渲染,但实际上却渲染了0
。为了更正,可以将左侧的值改成布尔类型:
messageCount > 0 && <p>New messages</p>
。选择性赋值JSX
1、可以选择将需要渲染的JSX表达式文本赋值给变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36// 其中 渲染的JSX文本 itemContent赋值变量
function Item({ name, isPacked }) {
let itemContent = name;
if (isPacked) {
itemContent = name + " ✔";
}
return (
<li className="item">
{itemContent}
</li>
);
}
export default function PackingList() {
return (
<section>
<h1>Sally Ride 的行李清单</h1>
<ul>
<Item
isPacked={true}
name="宇航服"
/>
<Item
isPacked={true}
name="带金箔的头盔"
/>
<Item
isPacked={false}
name="Tam 的照片"
/>
</ul>
</section>
);
}2、对于任意的JSX也是适用的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39// 对于JSX itemContent 提取为变量 在return时便可直接使用变量 迭代修改时容易扩展
function Item({ name, isPacked }) {
let itemContent = name;
if (isPacked) {
itemContent = (
<del>
{name + " ✔"}
</del>
);
}
return (
<li className="item">
{itemContent}
</li>
);
}
export default function PackingList() {
return (
<section>
<h1>Sally Ride 的行李清单</h1>
<ul>
<Item
isPacked={true}
name="宇航服"
/>
<Item
isPacked={true}
name="带金箔的头盔"
/>
<Item
isPacked={false}
name="Tam 的照片"
/>
</ul>
</section>
);
}
渲染列表
一般来说,我们经常通过JavaScript数组方法来操作数组中的数据,从而将数组中的数据渲染为多个类似的组件。
遍历渲染
1 | // 首先我们拥有一个数组数据。 |
注意:这样会导致没有唯一key报错。
过滤渲染
我们有个需求需要将职业为化学家的元素渲染出来
1 | // 数组 |
用key能保证渲染顺序
直接放在 map()
方法里的 JSX 元素一般都需要指定 key
值!
这些key会告诉React,每个组件对应的数组数据那一项,去进行匹配。
这些将在数组项进行移动、插入或者删除等操作是非常重要的。合适的key将保证React正常的更新DMO树。
key值在渲染之前,要存在数据里面key必须存在。
1 | // app.js |
如何在列表项显示多个DOM节点??
我需要将每个列表多渲染其他DOM节点。使用Fragment
的语法简写<></>
无法接受key值。写法存在要不使用
<Fragment>
。
Fragment标签不会出现在DOM上,代码会转成最终的<h1>
、<p>
、<h1>
、<p>
…… 的列表。
1 | import { Fragment } from 'react'; |
如何设置key
不同来源数据对应不同的key值获取方式:
- 来自数据库的数据:数据存在唯一的主键,可以直接使用。
- 本地产生的数据:可以使用自增计数器或者uuid的库生成唯一的key。
key值需要满足的条件
- key在兄弟节点必须是唯一的。不需要全局唯一,不同数组可以使用相同的key。
- key值不可改变。一定不可以在渲染时候的动态生成key。
为什么需要key??
React的key作用,其实都是可以从众多兄弟元素中能够标识出某一项(JSX节点)。这个提供的key提供的信息不止是这个元素所在的位置。即使元素的位置在渲染工程中发现了改变,它对应的key能够让React在整个生命周期一直可以使用。
注意:
- 不可以直接使用数组的索引作为key值来使用。如果没有显式指定key,React会默认讲索引作为key。当数组项的顺序在插入、删除或者重新排序等操作中发生改变。此时会出现一些难以排查的bug。
- 不要使用随机数去动态生成key,这样会导致每次渲染key不一样,React这样会认为你在频繁修改DOM,将会导致所有的组件和DOM元素重新渲染。会导致渲染问题,还会导致用户输入丢失。一定要保证key值的稳定性。
- 组件不会将key当作props的一部分。key值只会对React起到提示作用。
保持组件纯粹
纯函数
纯函数具有以下特征:
- 只负责自己的任务。他不会更改在该函数调用前就已经存在的对象和变量。
- 输入相同,则输出相同。给定相同的输入,纯函数总是返回相同的结果。
React围绕纯函数的概念进行设计。React假设你编写的所有组件都是纯函数。也就是说对于相同的输入,你所编写的React组件将会总是返回相同的JSX。
就像数学公式一样。你传入的值相同,你将会永远得到相同的结果。
再比如,可以将组件比作食谱。如果你遵循正常的菜谱(也就是相同的规律或者规则),在烹饪过程中不引进新食材,那么每次得到的菜肴都是相同的。这个菜肴就是React组件渲染返回的JSX。
副作用:(不符合)预期的结果
React的渲染过程自始至终都是纯粹的。组件就应该只返回他们的JSX,而不是改变,在渲染前,就存在的任何对象或者变量,都会是他们变得不纯粹。
例如:
1 | let guest = 0; |
以上组件,在读取外部guest
的变量,这将导致React组件每次返回的JSX都是不同的。如果这时候,其他组件也在读取这个变量的话,他们也会产生不同的JSX。
这时候就可以这样修改:
我们将变量作为Props
(输入),输入到我们的React组件中。现在对于Props
输入的变量是一样的,这时候React组件返回的JSX都是一样的。这时候React组件返回的JSX只依赖这个guest
prop。
1 | function Cup({ guest }) { |
一般来说,你不应该期望你的组件以特定的顺序去渲染。你要注意,其实每个组件都应该是独立的,同样地,每个组件都应该去“独立思考”去考虑自己的输入,而不是在渲染过程中试图去与其他组件协调,或者依赖于其他组件。
使用严格模式检测不纯的计算
当用户输入改变某些内容时,你应该设置状态,而不是直接写入变量。当组件渲染时,你永远不应该改变预先存在的变量或者对象。
React提供了“严格模式”,在严格模式下开发,他将会调用每个组件函数俩次。通过重复调用组件函数,严格模式有助找到违反这些规则的组件。
纯函数仅仅执行计算,因此调用它们两次不会改变任何东西 — 就像两次调用 double(2)
并不会改变返回值,两次求解 y = 2x 不会改变 y 的值一样。相同的输入,总是返回相同的输出。
严格模式在生产环境下不生效,因此它不会降低应用程序的速度。如需引入严格模式,你可以用 <React.StrictMode>
包裹根组件。一些框架会默认这样做。
局部mutation:组件的小秘密
以上示例的问题是在渲染过程中,组件改变了预先存在的变量的值。
这种现象我们称之为突变(mutation)。纯函数不会改变函数作用域以外的变量、或在函数调用前创建的对象,这会使函数变得不纯粹!
但是,你完全可以在渲染时更改你**刚刚**创建的变量和对象。
看下面示例:
1 | function Cup({ guest }) { |
上面cups
变量是在TeaGathering
函数内部创建的,函数外部不知道发生了什么。这就被称为 “局部 mutation” — 如同藏在组件里的小秘密。
哪些地方可能引发副作用
函数式编程很大程度依赖纯函数,但在某些事物在特定情况下不得不发生改变。这些变动包含更新屏幕、启动动画、更改数据等,他们被称为副作用。这些都是“额外”发生的事情,与渲染过程无关。
在React中,副作用通常属于事件处理程序。事件处理程序是React在你执行某些操作(如单击按钮)时运行的函数。即使事件处理程序是在组件内部定义的,他们也不会在渲染期间运行!**因此事件处理程序无需事纯函数。**
无法为副作用找到合适的事件处理程序,你还可以调用组件中的useEffect
方法将其附加到返回的JSX中,这会将告诉React在渲染结束后执行他。然而,这种方法应该是最后的手段。
为什么React如此侧重于纯函数??
纯函数的编写需要遵循一些习惯和规程。
- 你的组件在不同环境运行。这样针对相同的输入,都会返回相同的结果。
- 可以为那些未更改的组件来跳过渲染,可以提高性能。因为纯函数总是返回相同的结果,所以可以安全的缓存他们。
- 如果在渲染深层组件树的过程中,数据发生了变化,React可以重新开始渲染,不会浪费时间完成过时的渲染。纯粹性使得它随时可以安全的停止计算。
将UI视为树
树是项目和UI之间的关系模型,通常使用树结构来表示UI。例如,浏览器使用树结构来构建HTM(DOM)与CSS(CSSOM)。移动平台也使用树来表示试图层次机构。
与浏览器和移动平台一样,React 还使用树结构来管理和建模 React 应用程序中组件之间的关系。这些树是有用的工具,用于理解数据如何在 React 应用程序中流动以及如何优化呈现和应用程序大小。
渲染树
组件的主要特性是能够根据其他组件组合而成。组件是能够嵌套的,其中一个组件的父组件也可能是其他组件的自组件。
React在渲染的时候,会在渲染树中建模这种层级关系。这棵树是由节点组成,每个节点代表一个组件。
在React渲染树中,根节点是应用程序的跟组件。这种情况下,根组件是App
,他是React渲染的第一个组件。树的每个箭头从父组件指向子组件。
渲染树表示 React 应用程序的单个渲染过程。在 条件渲染 中,父组件可以根据传递的数据渲染不同的子组件。
存在条件渲染的时候,渲染过程的渲染树可能都不同,但是这些树有助于识别React应用程序中的顶级和叶子组件。顶级组件是距离根组件最近的组件,他会影响其下所有的组件渲染性能,通常包含最多复杂性。叶子组件位于树的底部,没有子组件,通常会频繁重新渲染。
识别这些组件类别有助于应用程序的数据流和性能。
模块依赖树
在React应用程序中,可以用树来建模的另一个关系是应用程序的模块依赖关系。当拆分组件和逻辑到不同的文件时,就创建了JavaScript模块,在这些模块中可以导出组件、函数或常量。
模块依赖树中的每个节点都是一个模块,每个分支代表该模块中的 import
语句。
以之前的 Inspirations 应用程序为例,可以构建一个模块依赖树,简称依赖树。
树的根节点是根模块,也称入口文件。它通常包含根组件的模块。
与同一应用程序的渲染树相比,存在相似的结构,但也存在一些显著的差异:
- 构成树的节点代表模块,而不是组件。
- 非组件模块。树中也有体现,渲染树仅封装组件。
- 组件嵌套中,父组件接受JSX作为
children props
,子组件渲染出来,但不导入该模块。
依赖树对于确定React应用程序所需要的模块是很有用的。在生产环境构建React应用时,通常会有构建步骤,该步骤将捆绑所有有必要的JavaScript以供客户端使用。负责操作的工具成为bundler(捆绑器)
,并且bunder将使用依赖树来确定应该包含哪些模块。
随着应用程序的增长,捆绑包大小通常也会增加。大型捆绑包大小对于客户端来说下载和运行成本高昂,并延迟 UI 绘制的时间。了解应用程序的依赖树可能有助于调试这些问题
现在存在一个html标签
1 | <h1>海蒂·拉玛的待办事项</h1> |
此时需要将它转化为React组件,可以这么做,是的没错。 采用 *** <> 或者其他标签*** 包裹。
1 | export default function TodoList() { |
JSX语法规则
只能返回一个根元素
- 就是return()中必须只有一个根元素
- 不想在标签中增加一个额外的 ,可以用 <> 和 </> 元素来代替
标签必须闭合
使用驼峰式命名法给大部分属性命名!
JSX 最终会被转化为 JavaScript,而 JSX 中的属性也会变成 JavaScript 对象中的键值对。在组件中,经常会遇到需要用变量的方式读取这些属性的时候。但 JavaScript 对变量的命名有限制。例如,变量名称不能包含 - 符号或者像 class 这样的保留字。
JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。
JSX中通过大括号使用javaScript
JSX可以在JavaScript中编写类似HTML的标签,保证渲染逻辑和内容绑定。在需要在标签中添加Js逻辑或者引用标签的动态属性的时候可以在JSX的大括号里面编写Js。
JSX使用引号字符串
当你想将字符串属性传递给JSX时,放在单引号或者双引号中
当你想动态传值时,可以使用{ 和 } 替代 “ 和 “
1 | // 字符串 |
注意:当使用src={ } 这种写法会去读取花括号中js中这个变量的值。
标签插值
JSX中允许在标签中插入大括号
{}
中使用变量JSX中允许在标签中插入大括号
{}
中使用函数表达式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 变量
export default function TodoList() {
const name = 'Gregorio Y. Zara';
return (
<h1>{name}'s To Do List</h1>
);
}
// 函数表达式
const today = new Date();
function formatDate(date) {
return new Intl.DateTimeFormat(
'zh-CN',
{ weekday: 'long' }
).format(date);
}
export default function TodoList() {
return (
<h1>To Do List for {formatDate(today)}</h1>
);
}
大括号的使用场景
主要场景是字符串、数字、变量、和js表达式。
- 用作JSX标签*内部的文本*:
<h1>{name}'s To Do List</h1>
- 标签***=*后面紧跟的属性**:
src={avatar}
会读取avatar
变量,但是!!!对于src="{avatar}"只会传一个字符串{avatar}
双大括号的使用场景
双大括号其实是{}
传递对象的传递方式。
- 对象也用大括号表示,例如
{ name: "Hedy Lamarr", inventions: 5 }
。因此,为了能在 JSX 中传递,必须用另一对额外的大括号包裹对象:person={{ name: "Hedy Lamarr", inventions: 5 }}
。 - 也可使用嵌套对象,在jsx大括号中使用。
注意:内联 style
属性 使用驼峰命名法编写。例如,HTML <ul style="background-color: black">
在你的组件里应该写成 <ul style={{ backgroundColor: 'black' }}>
。
1 | // 对象传递 |
Props组件传递
React组件使用Props互相通信。每个父组件都可以提供props给他的自组件传递信息。可以通过Props传递认识JavaScript值,包括对象、数组、和函数。
Props传递类似HTML预定义的属性。像组件传递的时候可以传递任意的props。传递的props可以通过不同的方式进行渲染。
1 | // 其中person就是props |
Props的使用,必须要关注你的父子组件。需要考虑好父组件需要向自组件传的props,而子组件需要考虑传递的props通过什么方式去进行渲染。事实上,**就可以总结出来,React函数组件就是接受一个参数,那就是Props对象。**
Props使用妙计
prop可以指定默认值
如果渲染时不存在size的prop,那么size将被赋值100进行渲染
其实就是size的prop属性不存在或者值undefined时会生效
但是,如果传递
size={null}
或size={0}
,默认值将 不 被使用。1
2
3function Avatar({ person, size = 100 }) {
// ...
}可以使用JSX展开语法传递Props
会存在需要传递pros很多,需要声明prop传递
1
2
3
4
5
6
7
8
9
10
11function Profile({ person, size, isSepia, thickBorder }) {
return (
<div className="card">
<Avatar
person={person}
size={size}
isSepia={isSepia}
thickBorder={thickBorder}
/>
</div>
);如果存在将所有的props传递给子组件可以采用简洁展开语法,这种写法,将父组件需要传递的props全部传递。如果滥用,那么建议拆分组件。
1
2
3
4
5
6
7function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}将JSX作为子组件传递
类似HTML标签嵌套,以下例子在父组件card中将children组件作为prop传递,类似vue插槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49// app.js
import Avatar from './Avatar.js';
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
export default function Profile() {
return (
<Card>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
</Card>
);
}
// Avatar.js
import { getImageUrl } from './utils.js';
export default function Avatar({ person, size }) {
return (
<img
className="avatar"
src={getImageUrl(person)}
alt={person.name}
width={size}
height={size}
/>
);
}
// utils.js
export function getImageUrl(person, size = 's') {
return (
'https://i.imgur.com/' +
person.imageId +
size +
'.jpg'
);
}
注意
一个组件可能会随着时间的推移收到不同的 props。 Props 并不总是静态的!Props 反映了组件在任何时间点的数据,并不仅仅是在开始时。
然而,props 是 不可变的(一个计算机科学术语,意思是“不可改变”)。当一个组件需要改变它的 props(例如,响应用户交互或新数据)时,它不得不“请求”它的父组件传递 不同的 props —— 一个新对象!它的旧 props 将被丢弃,最终 JavaScript 引擎将回收它们占用的内存。不要尝试“更改 props”。
条件渲染
组件中会存在很多根据条件渲染的内容。在React中可以使用js中的if
语句、&&
和 ? :
运算符来选择性地渲染 JSX。
如何根据条件返回JSX
根据组件Prop传入的属性值来动态展示内容
- 使用 if/else 语句 去判断
1 | // 其中isPacked是组件接受的Prop |
选择性包含JSX
一般的不同条件会return不同的HTML片段,此刻可以选择包含JSX的书写,一般使用以下运算符:
1、三目运算符(
? :
)1
2
3
4
5
6
7
8
9
10
11
12if (isPacked) {
return <li className="item">{name} ✔</li>;
}
return <li className="item">{name}</li>;
// 以上代码使用三目运算符修改为
return (
<li className="item">
{isPacked ? name + ' ✔' : name}
</li>
);**注意:**简单的内容展示适合使用三目这种运算符来展示,如果展示的逻辑较为复杂,可选择提取自组件来渲染。
2、与运算符(
&&
)1
2
3
4
5
6
7// 当 JavaScript && 表达式 的左侧(我们的条件)为 true 时,它则返回其右侧的值(在我们的例子里是勾选符号)。但条件的结果是 false,则整个表达式会变成 false。在 JSX 里,React 会将 false 视为一个“空值”,就像 null 或者 undefined,这样 React 就不会在这里进行任何渲染
return (
<li className="item">
{name} {isPacked && '✔'}
</li>
);注意:切勿将数字放在
&&
左侧.JavaScript 会自动将左侧的值转换成布尔类型以判断条件成立与否。然而,如果左侧是
0
,整个表达式将变成左侧的值(0
),React 此时则会渲染0
而不是不进行渲染。例如,一个常见的错误是
messageCount && <p>New messages</p>
。其原本是想当messageCount
为 0 的时候不进行渲染,但实际上却渲染了0
。为了更正,可以将左侧的值改成布尔类型:
messageCount > 0 && <p>New messages</p>
。选择性赋值JSX
1、可以选择将需要渲染的JSX表达式文本赋值给变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36// 其中 渲染的JSX文本 itemContent赋值变量
function Item({ name, isPacked }) {
let itemContent = name;
if (isPacked) {
itemContent = name + " ✔";
}
return (
<li className="item">
{itemContent}
</li>
);
}
export default function PackingList() {
return (
<section>
<h1>Sally Ride 的行李清单</h1>
<ul>
<Item
isPacked={true}
name="宇航服"
/>
<Item
isPacked={true}
name="带金箔的头盔"
/>
<Item
isPacked={false}
name="Tam 的照片"
/>
</ul>
</section>
);
}2、对于任意的JSX也是适用的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39// 对于JSX itemContent 提取为变量 在return时便可直接使用变量 迭代修改时容易扩展
function Item({ name, isPacked }) {
let itemContent = name;
if (isPacked) {
itemContent = (
<del>
{name + " ✔"}
</del>
);
}
return (
<li className="item">
{itemContent}
</li>
);
}
export default function PackingList() {
return (
<section>
<h1>Sally Ride 的行李清单</h1>
<ul>
<Item
isPacked={true}
name="宇航服"
/>
<Item
isPacked={true}
name="带金箔的头盔"
/>
<Item
isPacked={false}
name="Tam 的照片"
/>
</ul>
</section>
);
}
渲染列表
一般来说,我们经常通过JavaScript数组方法来操作数组中的数据,从而将数组中的数据渲染为多个类似的组件。
遍历渲染
1 | // 首先我们拥有一个数组数据。 |
注意:这样会导致没有唯一key报错。
过滤渲染
我们有个需求需要将职业为化学家的元素渲染出来
1 | // 数组 |
用key能保证渲染顺序
直接放在 map()
方法里的 JSX 元素一般都需要指定 key
值!
这些key会告诉React,每个组件对应的数组数据那一项,去进行匹配。
这些将在数组项进行移动、插入或者删除等操作是非常重要的。合适的key将保证React正常的更新DMO树。
key值在渲染之前,要存在数据里面key必须存在。
1 | // app.js |
如何在列表项显示多个DOM节点??
我需要将每个列表多渲染其他DOM节点。使用Fragment
的语法简写<></>
无法接受key值。写法存在要不使用
<Fragment>
。
Fragment标签不会出现在DOM上,代码会转成最终的<h1>
、<p>
、<h1>
、<p>
…… 的列表。
1 | import { Fragment } from 'react'; |
如何设置key
不同来源数据对应不同的key值获取方式:
- 来自数据库的数据:数据存在唯一的主键,可以直接使用。
- 本地产生的数据:可以使用自增计数器或者uuid的库生成唯一的key。
key值需要满足的条件
- key在兄弟节点必须是唯一的。不需要全局唯一,不同数组可以使用相同的key。
- key值不可改变。一定不可以在渲染时候的动态生成key。
为什么需要key??
React的key作用,其实都是可以从众多兄弟元素中能够标识出某一项(JSX节点)。这个提供的key提供的信息不止是这个元素所在的位置。即使元素的位置在渲染工程中发现了改变,它对应的key能够让React在整个生命周期一直可以使用。
注意:
- 不可以直接使用数组的索引作为key值来使用。如果没有显式指定key,React会默认讲索引作为key。当数组项的顺序在插入、删除或者重新排序等操作中发生改变。此时会出现一些难以排查的bug。
- 不要使用随机数去动态生成key,这样会导致每次渲染key不一样,React这样会认为你在频繁修改DOM,将会导致所有的组件和DOM元素重新渲染。会导致渲染问题,还会导致用户输入丢失。一定要保证key值的稳定性。
- 组件不会将key当作props的一部分。key值只会对React起到提示作用。
保持组件纯粹
纯函数
纯函数具有以下特征:
- 只负责自己的任务。他不会更改在该函数调用前就已经存在的对象和变量。
- 输入相同,则输出相同。给定相同的输入,纯函数总是返回相同的结果。
React围绕纯函数的概念进行设计。React假设你编写的所有组件都是纯函数。也就是说对于相同的输入,你所编写的React组件将会总是返回相同的JSX。
就像数学公式一样。你传入的值相同,你将会永远得到相同的结果。
再比如,可以将组件比作食谱。如果你遵循正常的菜谱(也就是相同的规律或者规则),在烹饪过程中不引进新食材,那么每次得到的菜肴都是相同的。这个菜肴就是React组件渲染返回的JSX。
副作用:(不符合)预期的结果
React的渲染过程自始至终都是纯粹的。组件就应该只返回他们的JSX,而不是改变,在渲染前,就存在的任何对象或者变量,都会是他们变得不纯粹。
例如:
1 | let guest = 0; |
以上组件,在读取外部guest
的变量,这将导致React组件每次返回的JSX都是不同的。如果这时候,其他组件也在读取这个变量的话,他们也会产生不同的JSX。
这时候就可以这样修改:
我们将变量作为Props
(输入),输入到我们的React组件中。现在对于Props
输入的变量是一样的,这时候React组件返回的JSX都是一样的。这时候React组件返回的JSX只依赖这个guest
prop。
1 | function Cup({ guest }) { |
一般来说,你不应该期望你的组件以特定的顺序去渲染。你要注意,其实每个组件都应该是独立的,同样地,每个组件都应该去“独立思考”去考虑自己的输入,而不是在渲染过程中试图去与其他组件协调,或者依赖于其他组件。
使用严格模式检测不纯的计算
当用户输入改变某些内容时,你应该设置状态,而不是直接写入变量。当组件渲染时,你永远不应该改变预先存在的变量或者对象。
React提供了“严格模式”,在严格模式下开发,他将会调用每个组件函数俩次。通过重复调用组件函数,严格模式有助找到违反这些规则的组件。
纯函数仅仅执行计算,因此调用它们两次不会改变任何东西 — 就像两次调用 double(2)
并不会改变返回值,两次求解 y = 2x 不会改变 y 的值一样。相同的输入,总是返回相同的输出。
严格模式在生产环境下不生效,因此它不会降低应用程序的速度。如需引入严格模式,你可以用 <React.StrictMode>
包裹根组件。一些框架会默认这样做。
局部mutation:组件的小秘密
以上示例的问题是在渲染过程中,组件改变了预先存在的变量的值。
这种现象我们称之为突变(mutation)。纯函数不会改变函数作用域以外的变量、或在函数调用前创建的对象,这会使函数变得不纯粹!
但是,你完全可以在渲染时更改你**刚刚**创建的变量和对象。
看下面示例:
1 | function Cup({ guest }) { |
上面cups
变量是在TeaGathering
函数内部创建的,函数外部不知道发生了什么。这就被称为 “局部 mutation” — 如同藏在组件里的小秘密。
哪些地方可能引发副作用
函数式编程很大程度依赖纯函数,但在某些事物在特定情况下不得不发生改变。这些变动包含更新屏幕、启动动画、更改数据等,他们被称为副作用。这些都是“额外”发生的事情,与渲染过程无关。
在React中,副作用通常属于事件处理程序。事件处理程序是React在你执行某些操作(如单击按钮)时运行的函数。即使事件处理程序是在组件内部定义的,他们也不会在渲染期间运行!**因此事件处理程序无需事纯函数。**
无法为副作用找到合适的事件处理程序,你还可以调用组件中的useEffect
方法将其附加到返回的JSX中,这会将告诉React在渲染结束后执行他。然而,这种方法应该是最后的手段。
为什么React如此侧重于纯函数??
纯函数的编写需要遵循一些习惯和规程。
- 你的组件在不同环境运行。这样针对相同的输入,都会返回相同的结果。
- 可以为那些未更改的组件来跳过渲染,可以提高性能。因为纯函数总是返回相同的结果,所以可以安全的缓存他们。
- 如果在渲染深层组件树的过程中,数据发生了变化,React可以重新开始渲染,不会浪费时间完成过时的渲染。纯粹性使得它随时可以安全的停止计算。
将UI视为树
树是项目和UI之间的关系模型,通常使用树结构来表示UI。例如,浏览器使用树结构来构建HTM(DOM)与CSS(CSSOM)。移动平台也使用树来表示试图层次机构。
与浏览器和移动平台一样,React 还使用树结构来管理和建模 React 应用程序中组件之间的关系。这些树是有用的工具,用于理解数据如何在 React 应用程序中流动以及如何优化呈现和应用程序大小。
渲染树
组件的主要特性是能够根据其他组件组合而成。组件是能够嵌套的,其中一个组件的父组件也可能是其他组件的自组件。
React在渲染的时候,会在渲染树中建模这种层级关系。这棵树是由节点组成,每个节点代表一个组件。
在React渲染树中,根节点是应用程序的跟组件。这种情况下,根组件是App
,他是React渲染的第一个组件。树的每个箭头从父组件指向子组件。
渲染树表示 React 应用程序的单个渲染过程。在 条件渲染 中,父组件可以根据传递的数据渲染不同的子组件。
存在条件渲染的时候,渲染过程的渲染树可能都不同,但是这些树有助于识别React应用程序中的顶级和叶子组件。顶级组件是距离根组件最近的组件,他会影响其下所有的组件渲染性能,通常包含最多复杂性。叶子组件位于树的底部,没有子组件,通常会频繁重新渲染。
识别这些组件类别有助于应用程序的数据流和性能。
模块依赖树
在React应用程序中,可以用树来建模的另一个关系是应用程序的模块依赖关系。当拆分组件和逻辑到不同的文件时,就创建了JavaScript模块,在这些模块中可以导出组件、函数或常量。
模块依赖树中的每个节点都是一个模块,每个分支代表该模块中的 import
语句。
以之前的 Inspirations 应用程序为例,可以构建一个模块依赖树,简称依赖树。
树的根节点是根模块,也称入口文件。它通常包含根组件的模块。
与同一应用程序的渲染树相比,存在相似的结构,但也存在一些显著的差异:
- 构成树的节点代表模块,而不是组件。
- 非组件模块。树中也有体现,渲染树仅封装组件。
- 组件嵌套中,父组件接受JSX作为
children props
,子组件渲染出来,但不导入该模块。
依赖树对于确定React应用程序所需要的模块是很有用的。在生产环境构建React应用时,通常会有构建步骤,该步骤将捆绑所有有必要的JavaScript以供客户端使用。负责操作的工具成为bundler(捆绑器)
,并且bunder将使用依赖树来确定应该包含哪些模块。
随着应用程序的增长,捆绑包大小通常也会增加。大型捆绑包大小对于客户端来说下载和运行成本高昂,并延迟 UI 绘制的时间。了解应用程序的依赖树可能有助于调试这些问题
什么是JSX
JSX 是 JavaScript 语法扩展,可以让在 JavaScript 文件中书写类似 HTML 的标签。JSX 看起来和 HTML 很像,但它的语法更加严格并且可以动态展示信息。
为什么出现JSX
随着Web交互性越来越强,逻辑开始决定页面的内容。也就是可以说JavaScript负责HTML内容。因此,在React中,渲染逻辑和标签存在同一个组件文件中。
使用JSX的优势
例如将一个按钮的渲染逻辑和标签放在一起,可以确保他们在编辑的时候保持同步。反之,也可以说彼此无关的细节是隔离的。
JSX与HTML的区别
每个 React 组件都是一个 JavaScript 函数,它会返回一些标签,React 会将这些标签渲染到浏览器上。React 组件使用一种被称为 JSX 的语法扩展来描述这些标签。JSX对于HTML来说,语法更加严格并且可以动态的展示信息。
JSX and React 是相互独立的东西。通常配合使用,也可以单独使用它们中的任意一个,JSX 是一种语法扩展,而 React 则是一个 JavaScript 的库。
HTML转换为JSX
现在存在一个html标签
1 | <h1>海蒂·拉玛的待办事项</h1> |
此时需要将它转化为React组件,可以这么做,是的没错。 采用 *** <> 或者其他标签*** 包裹。
1 | export default function TodoList() { |
JSX语法规则
只能返回一个根元素
- 就是return()中必须只有一个根元素
- 不想在标签中增加一个额外的 ,可以用 <> 和 </> 元素来代替
标签必须闭合
使用驼峰式命名法给大部分属性命名!
JSX 最终会被转化为 JavaScript,而 JSX 中的属性也会变成 JavaScript 对象中的键值对。在组件中,经常会遇到需要用变量的方式读取这些属性的时候。但 JavaScript 对变量的命名有限制。例如,变量名称不能包含 - 符号或者像 class 这样的保留字。
JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。
JSX中通过大括号使用javaScript
JSX可以在JavaScript中编写类似HTML的标签,保证渲染逻辑和内容绑定。在需要在标签中添加Js逻辑或者引用标签的动态属性的时候可以在JSX的大括号里面编写Js。
JSX使用引号字符串
当你想将字符串属性传递给JSX时,放在单引号或者双引号中
当你想动态传值时,可以使用{ 和 } 替代 “ 和 “
1 | // 字符串 |
注意:当使用src={ } 这种写法会去读取花括号中js中这个变量的值。
标签插值
JSX中允许在标签中插入大括号
{}
中使用变量JSX中允许在标签中插入大括号
{}
中使用函数表达式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 变量
export default function TodoList() {
const name = 'Gregorio Y. Zara';
return (
<h1>{name}'s To Do List</h1>
);
}
// 函数表达式
const today = new Date();
function formatDate(date) {
return new Intl.DateTimeFormat(
'zh-CN',
{ weekday: 'long' }
).format(date);
}
export default function TodoList() {
return (
<h1>To Do List for {formatDate(today)}</h1>
);
}
大括号的使用场景
主要场景是字符串、数字、变量、和js表达式。
- 用作JSX标签*内部的文本*:
<h1>{name}'s To Do List</h1>
- 标签***=*后面紧跟的属性**:
src={avatar}
会读取avatar
变量,但是!!!对于src="{avatar}"只会传一个字符串{avatar}
双大括号的使用场景
双大括号其实是{}
传递对象的传递方式。
- 对象也用大括号表示,例如
{ name: "Hedy Lamarr", inventions: 5 }
。因此,为了能在 JSX 中传递,必须用另一对额外的大括号包裹对象:person={{ name: "Hedy Lamarr", inventions: 5 }}
。 - 也可使用嵌套对象,在jsx大括号中使用。
注意:内联 style
属性 使用驼峰命名法编写。例如,HTML <ul style="background-color: black">
在你的组件里应该写成 <ul style={{ backgroundColor: 'black' }}>
。
1 | // 对象传递 |
Props组件传递
React组件使用Props互相通信。每个父组件都可以提供props给他的自组件传递信息。可以通过Props传递认识JavaScript值,包括对象、数组、和函数。
Props传递类似HTML预定义的属性。像组件传递的时候可以传递任意的props。传递的props可以通过不同的方式进行渲染。
1 | // 其中person就是props |
Props的使用,必须要关注你的父子组件。需要考虑好父组件需要向自组件传的props,而子组件需要考虑传递的props通过什么方式去进行渲染。事实上,**就可以总结出来,React函数组件就是接受一个参数,那就是Props对象。**
Props使用妙计
prop可以指定默认值
如果渲染时不存在size的prop,那么size将被赋值100进行渲染
其实就是size的prop属性不存在或者值undefined时会生效
但是,如果传递
size={null}
或size={0}
,默认值将 不 被使用。1
2
3function Avatar({ person, size = 100 }) {
// ...
}可以使用JSX展开语法传递Props
会存在需要传递pros很多,需要声明prop传递
1
2
3
4
5
6
7
8
9
10
11function Profile({ person, size, isSepia, thickBorder }) {
return (
<div className="card">
<Avatar
person={person}
size={size}
isSepia={isSepia}
thickBorder={thickBorder}
/>
</div>
);如果存在将所有的props传递给子组件可以采用简洁展开语法,这种写法,将父组件需要传递的props全部传递。如果滥用,那么建议拆分组件。
1
2
3
4
5
6
7function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}将JSX作为子组件传递
类似HTML标签嵌套,以下例子在父组件card中将children组件作为prop传递,类似vue插槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49// app.js
import Avatar from './Avatar.js';
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
export default function Profile() {
return (
<Card>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
</Card>
);
}
// Avatar.js
import { getImageUrl } from './utils.js';
export default function Avatar({ person, size }) {
return (
<img
className="avatar"
src={getImageUrl(person)}
alt={person.name}
width={size}
height={size}
/>
);
}
// utils.js
export function getImageUrl(person, size = 's') {
return (
'https://i.imgur.com/' +
person.imageId +
size +
'.jpg'
);
}
注意
一个组件可能会随着时间的推移收到不同的 props。 Props 并不总是静态的!Props 反映了组件在任何时间点的数据,并不仅仅是在开始时。
然而,props 是 不可变的(一个计算机科学术语,意思是“不可改变”)。当一个组件需要改变它的 props(例如,响应用户交互或新数据)时,它不得不“请求”它的父组件传递 不同的 props —— 一个新对象!它的旧 props 将被丢弃,最终 JavaScript 引擎将回收它们占用的内存。不要尝试“更改 props”。
条件渲染
组件中会存在很多根据条件渲染的内容。在React中可以使用js中的if
语句、&&
和 ? :
运算符来选择性地渲染 JSX。
如何根据条件返回JSX
根据组件Prop传入的属性值来动态展示内容
- 使用 if/else 语句 去判断
1 | // 其中isPacked是组件接受的Prop |
选择性包含JSX
一般的不同条件会return不同的HTML片段,此刻可以选择包含JSX的书写,一般使用以下运算符:
1、三目运算符(
? :
)1
2
3
4
5
6
7
8
9
10
11
12if (isPacked) {
return <li className="item">{name} ✔</li>;
}
return <li className="item">{name}</li>;
// 以上代码使用三目运算符修改为
return (
<li className="item">
{isPacked ? name + ' ✔' : name}
</li>
);**注意:**简单的内容展示适合使用三目这种运算符来展示,如果展示的逻辑较为复杂,可选择提取自组件来渲染。
2、与运算符(
&&
)1
2
3
4
5
6
7// 当 JavaScript && 表达式 的左侧(我们的条件)为 true 时,它则返回其右侧的值(在我们的例子里是勾选符号)。但条件的结果是 false,则整个表达式会变成 false。在 JSX 里,React 会将 false 视为一个“空值”,就像 null 或者 undefined,这样 React 就不会在这里进行任何渲染
return (
<li className="item">
{name} {isPacked && '✔'}
</li>
);注意:切勿将数字放在
&&
左侧.JavaScript 会自动将左侧的值转换成布尔类型以判断条件成立与否。然而,如果左侧是
0
,整个表达式将变成左侧的值(0
),React 此时则会渲染0
而不是不进行渲染。例如,一个常见的错误是
messageCount && <p>New messages</p>
。其原本是想当messageCount
为 0 的时候不进行渲染,但实际上却渲染了0
。为了更正,可以将左侧的值改成布尔类型:
messageCount > 0 && <p>New messages</p>
。选择性赋值JSX
1、可以选择将需要渲染的JSX表达式文本赋值给变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36// 其中 渲染的JSX文本 itemContent赋值变量
function Item({ name, isPacked }) {
let itemContent = name;
if (isPacked) {
itemContent = name + " ✔";
}
return (
<li className="item">
{itemContent}
</li>
);
}
export default function PackingList() {
return (
<section>
<h1>Sally Ride 的行李清单</h1>
<ul>
<Item
isPacked={true}
name="宇航服"
/>
<Item
isPacked={true}
name="带金箔的头盔"
/>
<Item
isPacked={false}
name="Tam 的照片"
/>
</ul>
</section>
);
}2、对于任意的JSX也是适用的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39// 对于JSX itemContent 提取为变量 在return时便可直接使用变量 迭代修改时容易扩展
function Item({ name, isPacked }) {
let itemContent = name;
if (isPacked) {
itemContent = (
<del>
{name + " ✔"}
</del>
);
}
return (
<li className="item">
{itemContent}
</li>
);
}
export default function PackingList() {
return (
<section>
<h1>Sally Ride 的行李清单</h1>
<ul>
<Item
isPacked={true}
name="宇航服"
/>
<Item
isPacked={true}
name="带金箔的头盔"
/>
<Item
isPacked={false}
name="Tam 的照片"
/>
</ul>
</section>
);
}
渲染列表
一般来说,我们经常通过JavaScript数组方法来操作数组中的数据,从而将数组中的数据渲染为多个类似的组件。
遍历渲染
1 | // 首先我们拥有一个数组数据。 |
注意:这样会导致没有唯一key报错。
过滤渲染
我们有个需求需要将职业为化学家的元素渲染出来
1 | // 数组 |
用key能保证渲染顺序
直接放在 map()
方法里的 JSX 元素一般都需要指定 key
值!
这些key会告诉React,每个组件对应的数组数据那一项,去进行匹配。
这些将在数组项进行移动、插入或者删除等操作是非常重要的。合适的key将保证React正常的更新DMO树。
key值在渲染之前,要存在数据里面key必须存在。
1 | // app.js |
如何在列表项显示多个DOM节点??
我需要将每个列表多渲染其他DOM节点。使用Fragment
的语法简写<></>
无法接受key值。写法存在要不使用
<Fragment>
。
Fragment标签不会出现在DOM上,代码会转成最终的<h1>
、<p>
、<h1>
、<p>
…… 的列表。
1 | import { Fragment } from 'react'; |
如何设置key
不同来源数据对应不同的key值获取方式:
- 来自数据库的数据:数据存在唯一的主键,可以直接使用。
- 本地产生的数据:可以使用自增计数器或者uuid的库生成唯一的key。
key值需要满足的条件
- key在兄弟节点必须是唯一的。不需要全局唯一,不同数组可以使用相同的key。
- key值不可改变。一定不可以在渲染时候的动态生成key。
为什么需要key??
React的key作用,其实都是可以从众多兄弟元素中能够标识出某一项(JSX节点)。这个提供的key提供的信息不止是这个元素所在的位置。即使元素的位置在渲染工程中发现了改变,它对应的key能够让React在整个生命周期一直可以使用。
注意:
- 不可以直接使用数组的索引作为key值来使用。如果没有显式指定key,React会默认讲索引作为key。当数组项的顺序在插入、删除或者重新排序等操作中发生改变。此时会出现一些难以排查的bug。
- 不要使用随机数去动态生成key,这样会导致每次渲染key不一样,React这样会认为你在频繁修改DOM,将会导致所有的组件和DOM元素重新渲染。会导致渲染问题,还会导致用户输入丢失。一定要保证key值的稳定性。
- 组件不会将key当作props的一部分。key值只会对React起到提示作用。
什么是JSX
JSX 是 JavaScript 语法扩展,可以让在 JavaScript 文件中书写类似 HTML 的标签。JSX 看起来和 HTML 很像,但它的语法更加严格并且可以动态展示信息。
为什么出现JSX
随着Web交互性越来越强,逻辑开始决定页面的内容。也就是可以说JavaScript负责HTML内容。因此,在React中,渲染逻辑和标签存在同一个组件文件中。
使用JSX的优势
例如将一个按钮的渲染逻辑和标签放在一起,可以确保他们在编辑的时候保持同步。反之,也可以说彼此无关的细节是隔离的。
JSX与HTML的区别
每个 React 组件都是一个 JavaScript 函数,它会返回一些标签,React 会将这些标签渲染到浏览器上。React 组件使用一种被称为 JSX 的语法扩展来描述这些标签。JSX对于HTML来说,语法更加严格并且可以动态的展示信息。
***JSX and React 是相互独立的东西。通常配合使用,也可以单独使用它们中的任意一个,JSX 是一种语法扩展,而 React 则是一个 JavaScript 的库。 ***
HTML转换为JSX
现在存在一个html标签
1 | <h1>海蒂·拉玛的待办事项</h1> |
此时需要将它转化为React组件,可以这么做,是的没错。 采用 *** <> 或者其他标签*** 包裹。
1 | export default function TodoList() { |
JSX语法规则
只能返回一个根元素
- 就是return()中必须只有一个根元素
- 不想在标签中增加一个额外的 ,可以用 <> 和 </> 元素来代替
***JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。 ***
标签必须闭合
使用驼峰式命名法给大部分属性命名!
JSX 最终会被转化为 JavaScript,而 JSX 中的属性也会变成 JavaScript 对象中的键值对。在组件中,经常会遇到需要用变量的方式读取这些属性的时候。但 JavaScript 对变量的命名有限制。例如,变量名称不能包含 - 符号或者像 class 这样的保留字。
JSX中通过大括号使用javaScript
JSX可以在JavaScript中编写类似HTML的标签,保证渲染逻辑和内容绑定。在需要在标签中添加Js逻辑或者引用标签的动态属性的时候可以在JSX的大括号里面编写Js。
JSX使用引号字符串
当你想将字符串属性传递给JSX时,放在单引号或者双引号中
当你想动态传值时,可以使用{ 和 } 替代 “ 和 “
1 | // 字符串 |
注意:当使用src={ } 这种写法会去读取花括号中js中这个变量的值。
标签插值
JSX中允许在标签中插入大括号
{}
中使用变量JSX中允许在标签中插入大括号
{}
中使用函数表达式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 变量
export default function TodoList() {
const name = 'Gregorio Y. Zara';
return (
<h1>{name}'s To Do List</h1>
);
}
// 函数表达式
const today = new Date();
function formatDate(date) {
return new Intl.DateTimeFormat(
'zh-CN',
{ weekday: 'long' }
).format(date);
}
export default function TodoList() {
return (
<h1>To Do List for {formatDate(today)}</h1>
);
}
大括号的使用场景
主要场景是字符串、数字、变量、和js表达式。
- 用作JSX标签*内部的文本*:
<h1>{name}'s To Do List</h1>
- 标签***=*后面紧跟的属性**:
src={avatar}
会读取avatar
变量,但是!!!对于src="{avatar}"只会传一个字符串{avatar}
双大括号的使用场景
双大括号其实是{}
传递对象的传递方式。
- 对象也用大括号表示,例如
{ name: "Hedy Lamarr", inventions: 5 }
。因此,为了能在 JSX 中传递,必须用另一对额外的大括号包裹对象:person={{ name: "Hedy Lamarr", inventions: 5 }}
。 - 也可使用嵌套对象,在jsx大括号中使用。
注意:内联 style
属性 使用驼峰命名法编写。例如,HTML <ul style="background-color: black">
在你的组件里应该写成 <ul style={{ backgroundColor: 'black' }}>
。
1 | // 对象传递 |
Props组件传递
React组件使用Props互相通信。每个父组件都可以提供props给他的自组件传递信息。可以通过Props传递认识JavaScript值,包括对象、数组、和函数。
Props传递类似HTML预定义的属性。像组件传递的时候可以传递任意的props。传递的props可以通过不同的方式进行渲染。
1 | // 其中person就是props |
Props的使用,必须要关注你的父子组件。需要考虑好父组件需要向自组件传的props,而子组件需要考虑传递的props通过什么方式去进行渲染。事实上,**就可以总结出来,React函数组件就是接受一个参数,那就是Props对象。**
Props使用妙计
prop可以指定默认值
如果渲染时不存在size的prop,那么size将被赋值100进行渲染
其实就是size的prop属性不存在或者值undefined时会生效
但是,如果传递
size={null}
或size={0}
,默认值将 不 被使用。1
2
3function Avatar({ person, size = 100 }) {
// ...
}可以使用JSX展开语法传递Props
会存在需要传递pros很多,需要声明prop传递
1
2
3
4
5
6
7
8
9
10
11function Profile({ person, size, isSepia, thickBorder }) {
return (
<div className="card">
<Avatar
person={person}
size={size}
isSepia={isSepia}
thickBorder={thickBorder}
/>
</div>
);如果存在将所有的props传递给子组件可以采用简洁展开语法,这种写法,将父组件需要传递的props全部传递。如果滥用,那么建议拆分组件。
1
2
3
4
5
6
7function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}将JSX作为子组件传递
类似HTML标签嵌套,以下例子在父组件card中将children组件作为prop传递,类似vue插槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49// app.js
import Avatar from './Avatar.js';
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
export default function Profile() {
return (
<Card>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
</Card>
);
}
// Avatar.js
import { getImageUrl } from './utils.js';
export default function Avatar({ person, size }) {
return (
<img
className="avatar"
src={getImageUrl(person)}
alt={person.name}
width={size}
height={size}
/>
);
}
// utils.js
export function getImageUrl(person, size = 's') {
return (
'https://i.imgur.com/' +
person.imageId +
size +
'.jpg'
);
}
注意
一个组件可能会随着时间的推移收到不同的 props。 Props 并不总是静态的!Props 反映了组件在任何时间点的数据,并不仅仅是在开始时。
然而,props 是 不可变的(一个计算机科学术语,意思是“不可改变”)。当一个组件需要改变它的 props(例如,响应用户交互或新数据)时,它不得不“请求”它的父组件传递 不同的 props —— 一个新对象!它的旧 props 将被丢弃,最终 JavaScript 引擎将回收它们占用的内存。不要尝试“更改 props”。