React

React应用就是被组件的独立UI片段构建,React组件本质就是可以添加任意标签的JavaScript函数。

React组件的基本要素

  • 基本的组件js文件存在要素:
    1、可以return出存在任意标签的JavaScript函数
    2、函数中需要存在return(xxx) html结构
    3、最终需要将渲染的组件export出去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Profile() {
return (
<img
src="https://i.imgur.com/MK3eW3As.jpg"
alt="Katherine Johnson"
/>
);
}

export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}

React 组件导入导出

  • 组件导入导出语法
语法 导出语句 导入语句
默认 export default function Button() {} import Button from ‘./Button.js’;
具名 export function Button() {} import { Button } from ‘./Button.js’
  • 组件导入导出案例解释
  1. Gallery.js
  • 定义了Profile组件
  • 该组件采用默认导出(export default function)
  1. app.js
  • 使用默认导入,导入Gallery文件的组件Gallery
  • 使用默认导出方式将app组件导出
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
## app.js
import Gallery from './Gallery.js';

export default function App() {
return (
<Gallery />
);
}

## Gallery.js

function Profile() {
return (
<img
src="https://i.imgur.com/QIrZWGIs.jpg"
alt="Alan L. Hart"
/>
);
}

export default function Gallery() {
return (
<section>
<h1>了不起的科学家们</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}


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
2
3
4
5
6
7
8
9
10
11
<h1>海蒂·拉玛的待办事项</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
class="photo"
>
<ul>
<li>发明一种新式交通信号灯
<li>排练一个电影场景
<li>改进频谱技术
</ul>

此时需要将它转化为React组件,可以这么做,是的没错。 采用 *** <> 或者其他标签*** 包裹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default function TodoList() {
return (
<>
<h1>海蒂·拉玛的待办事项</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
className="photo"
/>
<ul>
<li>发明一种新式交通信号灯</li>
<li>排练一个电影场景</li>
<li>改进频谱技术</li>
</ul>
</>

JSX语法规则

  • 只能返回一个根元素

    1. 就是return()中必须只有一个根元素
    2. 不想在标签中增加一个额外的
      ,可以用 <> 和 </> 元素来代替
  • 标签必须闭合

  • 使用驼峰式命名法给大部分属性命名!

    JSX 最终会被转化为 JavaScript,而 JSX 中的属性也会变成 JavaScript 对象中的键值对。在组件中,经常会遇到需要用变量的方式读取这些属性的时候。但 JavaScript 对变量的命名有限制。例如,变量名称不能包含 - 符号或者像 class 这样的保留字。

    JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。

JSX中通过大括号使用javaScript

JSX可以在JavaScript中编写类似HTML的标签,保证渲染逻辑和内容绑定。在需要在标签中添加Js逻辑或者引用标签的动态属性的时候可以在JSX的大括号里面编写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
24
// 字符串
export default function Avatar() {
return (
<img
className="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
);
}

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

注意:当使用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
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
// 对象传递
export default function TodoList() {
return (
<ul style={{
backgroundColor: 'black',
color: 'pink'
}}>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
);
}

// 嵌套对象传递
const person = {
name: 'Gregorio Y. Zara',
theme: {
backgroundColor: 'black',
color: 'pink'
}
};

export default function TodoList() {
return (
<div style={person.theme}>
<h1>{person.name}'s Todos</h1>
<img
className="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
<ul>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
</div>
);
}

Props组件传递

React组件使用Props互相通信。每个父组件都可以提供props给他的自组件传递信息。可以通过Props传递认识JavaScript值,包括对象、数组、和函数。

Props传递类似HTML预定义的属性。像组件传递的时候可以传递任意的props。传递的props可以通过不同的方式进行渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 其中person就是props
/*
其中的花括号也就是props传递对象而已
*/

// 1、传递
export default function Profile() {
return (
<Avatar
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
size={100}
/>
);
}

// 2、使用/读取

/*
1.以下这种方式是采用解构,将person的props解构为单独的props传递
2.prop可以存在默认值,例如下面的prop size
*/
function Avatar({ person, size=100 }) {
// 在这里 person 和 size 是可访问的
}

Props的使用,必须要关注你的父子组件。需要考虑好父组件需要向自组件传的props,而子组件需要考虑传递的props通过什么方式去进行渲染。事实上,**就可以总结出来,React函数组件就是接受一个参数,那就是Props对象。**

Props使用妙计
  • prop可以指定默认值

    如果渲染时不存在size的prop,那么size将被赋值100进行渲染

    其实就是size的prop属性不存在或者值undefined时会生效

    但是,如果传递size={null}size={0},默认值将 不 被使用。

    1
    2
    3
    function Avatar({ person, size = 100 }) {
    // ...
    }
  • 可以使用JSX展开语法传递Props

    会存在需要传递pros很多,需要声明prop传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function 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
    7
    function 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传入的属性值来动态展示内容

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
//	其中isPacked是组件接受的Prop
// 用来动态显示 不同li内容的展示
// 可以选择 return 的组件为null 那么他将不会显示任何内容
function Item({ name, isPacked }) {
if (isPacked) {
return <li className="item">{name} ✔</li>;
}
return <li className="item">{name}</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>
);
}
  • 选择性包含JSX

    一般的不同条件会return不同的HTML片段,此刻可以选择包含JSX的书写,一般使用以下运算符:

    1、三目运算符(? :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if (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
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
// 首先我们拥有一个数组数据。
const people = [
'凯瑟琳·约翰逊: 数学家',
'马里奥·莫利纳: 化学家',
'穆罕默德·阿卜杜勒·萨拉姆: 物理学家',
'珀西·莱温·朱利亚: 化学家',
'苏布拉马尼扬·钱德拉塞卡: 天体物理学家',
];

// 我们可以使用数组的遍历方法map这个数组来遍历元素
const listItem = people.map(person=><li>{person}</li>)

// 用ul将listItem包围
return <ul>{listItem}</ul>

// 最终代码
const people = [
'凯瑟琳·约翰逊: 数学家',
'马里奥·莫利纳: 化学家',
'穆罕默德·阿卜杜勒·萨拉姆: 物理学家',
'珀西·莱温·朱利亚: 化学家',
'苏布拉马尼扬·钱德拉塞卡: 天体物理学家',
];

export default function List() {
const listItems = people.map(person =>
<li>{person}</li>
);
return <ul>{listItems}</ul>;
}

注意:这样会导致没有唯一key报错。

过滤渲染

我们有个需求需要将职业为化学家的元素渲染出来

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
// 数组
const people = [
{
id: 0,
name: '凯瑟琳·约翰逊',
profession: '数学家',
},
{
id: 1,
name: '马里奥·莫利纳',
profession: '化学家',
},
{
id: 2,
name: '穆罕默德·阿卜杜勒·萨拉姆',
profession: '物理学家',
},
{
name: '珀西·莱温·朱利亚',
profession: '化学家',
},
{
name: '苏布拉马尼扬·钱德拉塞卡',
profession: '天体物理学家',
},
];

// 采用数组filter方法进行过滤
const liItem = people.filter(person=>person.profession==='化学家')

// 用ul将liIte包围
return <ul>{liItem}</ul>

// 完整代码
export default function List() {
const listItems = people.map(person =>
<li>{person}</li>
);
return <ul>{listItems}</ul>;
}
用key能保证渲染顺序

直接放在 map() 方法里的 JSX 元素一般都需要指定 key 值!

这些key会告诉React,每个组件对应的数组数据那一项,去进行匹配。

这些将在数组项进行移动、插入或者删除等操作是非常重要的。合适的key将保证React正常的更新DMO树。

key值在渲染之前,要存在数据里面key必须存在。

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// app.js
import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
const listItems = people.map(person =>
<li key={person.id}>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}</b>
{' ' + person.profession + ' '}
因{person.accomplishment}而闻名世界
</p>
</li>
);
return <ul>{listItems}</ul>;
}

// data.js
export const people = [
{
id: 0, // 在 JSX 中作为 key 使用
name: '凯瑟琳·约翰逊',
profession: '数学家',
accomplishment: '太空飞行相关数值的核算',
imageId: 'MK3eW3A',
},
{
id: 1, // 在 JSX 中作为 key 使用
name: '马里奥·莫利纳',
profession: '化学家',
accomplishment: '北极臭氧空洞的发现',
imageId: 'mynHUSa',
},
{
id: 2, // 在 JSX 中作为 key 使用
name: '穆罕默德·阿卜杜勒·萨拉姆',
profession: '物理学家',
accomplishment: '关于基本粒子间弱相互作用和电磁相互作用的统一理论',
imageId: 'bE7W1ji',
},
{
id: 3, // 在 JSX 中作为 key 使用
name: '珀西·莱温·朱利亚',
profession: '化学家',
accomplishment: '开创性的可的松药物、类固醇和避孕药',
imageId: 'IOjWm71',
},
{
id: 4, // 在 JSX 中作为 key 使用
name: '苏布拉马尼扬·钱德拉塞卡',
profession: '天体物理学家',
accomplishment: '白矮星质量计算',
imageId: 'lrWQx8l',
},
];

// util.js
export function getImageUrl(person) {
return (
'https://i.imgur.com/' +
person.imageId +
's.jpg'
);
}

如何在列表项显示多个DOM节点??

我需要将每个列表多渲染其他DOM节点。使用Fragment的语法简写<></>无法接受key值。写法存在要不使用

包裹,或者使用明确的<Fragment>

Fragment标签不会出现在DOM上,代码会转成最终的<h1><p><h1><p>…… 的列表。

1
2
3
4
5
6
7
8
9
10
import { Fragment } from 'react';

// ...

const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);
如何设置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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let guest = 0;

function Cup() {
// Bad: changing a preexisting variable!
guest = guest + 1;
return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
return (
<>
<Cup />
<Cup />
<Cup />
</>
);
}

以上组件,在读取外部guest的变量,这将导致React组件每次返回的JSX都是不同的。如果这时候,其他组件也在读取这个变量的话,他们也会产生不同的JSX。

这时候就可以这样修改:

我们将变量作为Props(输入),输入到我们的React组件中。现在对于Props输入的变量是一样的,这时候React组件返回的JSX都是一样的。这时候React组件返回的JSX只依赖这个guestprop。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Cup({ guest }) {
return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
return (
<>
<Cup guest={1} />
<Cup guest={2} />
<Cup guest={3} />
</>
);
}

一般来说,你不应该期望你的组件以特定的顺序去渲染。你要注意,其实每个组件都应该是独立的,同样地,每个组件都应该去“独立思考”去考虑自己的输入,而不是在渲染过程中试图去与其他组件协调,或者依赖于其他组件。

使用严格模式检测不纯的计算

当用户输入改变某些内容时,你应该设置状态,而不是直接写入变量。当组件渲染时,你永远不应该改变预先存在的变量或者对象。

React提供了“严格模式”,在严格模式下开发,他将会调用每个组件函数俩次。通过重复调用组件函数,严格模式有助找到违反这些规则的组件。

纯函数仅仅执行计算,因此调用它们两次不会改变任何东西 — 就像两次调用 double(2) 并不会改变返回值,两次求解 y = 2x 不会改变 y 的值一样。相同的输入,总是返回相同的输出。

严格模式在生产环境下不生效,因此它不会降低应用程序的速度。如需引入严格模式,你可以用 <React.StrictMode> 包裹根组件。一些框架会默认这样做。

局部mutation:组件的小秘密

以上示例的问题是在渲染过程中,组件改变了预先存在的变量的值。

这种现象我们称之为突变(mutation)。纯函数不会改变函数作用域以外的变量、或在函数调用前创建的对象,这会使函数变得不纯粹!

但是,你完全可以在渲染时更改你**刚刚**创建的变量和对象。

看下面示例:

1
2
3
4
5
6
7
8
9
10
11
function Cup({ guest }) {
return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaGathering() {
let cups = [];
for (let i = 1; i <= 12; i++) {
cups.push(<Cup key={i} guest={i} />);
}
return cups;
}

上面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
2
3
4
5
6
7
8
9
10
11
<h1>海蒂·拉玛的待办事项</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
class="photo"
>
<ul>
<li>发明一种新式交通信号灯
<li>排练一个电影场景
<li>改进频谱技术
</ul>

此时需要将它转化为React组件,可以这么做,是的没错。 采用 *** <> 或者其他标签*** 包裹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default function TodoList() {
return (
<>
<h1>海蒂·拉玛的待办事项</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
className="photo"
/>
<ul>
<li>发明一种新式交通信号灯</li>
<li>排练一个电影场景</li>
<li>改进频谱技术</li>
</ul>
</>

JSX语法规则

  • 只能返回一个根元素

    1. 就是return()中必须只有一个根元素
    2. 不想在标签中增加一个额外的
      ,可以用 <> 和 </> 元素来代替
  • 标签必须闭合

  • 使用驼峰式命名法给大部分属性命名!

    JSX 最终会被转化为 JavaScript,而 JSX 中的属性也会变成 JavaScript 对象中的键值对。在组件中,经常会遇到需要用变量的方式读取这些属性的时候。但 JavaScript 对变量的命名有限制。例如,变量名称不能包含 - 符号或者像 class 这样的保留字。

    JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。

JSX中通过大括号使用javaScript

JSX可以在JavaScript中编写类似HTML的标签,保证渲染逻辑和内容绑定。在需要在标签中添加Js逻辑或者引用标签的动态属性的时候可以在JSX的大括号里面编写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
24
// 字符串
export default function Avatar() {
return (
<img
className="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
);
}

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

注意:当使用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
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
// 对象传递
export default function TodoList() {
return (
<ul style={{
backgroundColor: 'black',
color: 'pink'
}}>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
);
}

// 嵌套对象传递
const person = {
name: 'Gregorio Y. Zara',
theme: {
backgroundColor: 'black',
color: 'pink'
}
};

export default function TodoList() {
return (
<div style={person.theme}>
<h1>{person.name}'s Todos</h1>
<img
className="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
<ul>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
</div>
);
}

Props组件传递

React组件使用Props互相通信。每个父组件都可以提供props给他的自组件传递信息。可以通过Props传递认识JavaScript值,包括对象、数组、和函数。

Props传递类似HTML预定义的属性。像组件传递的时候可以传递任意的props。传递的props可以通过不同的方式进行渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 其中person就是props
/*
其中的花括号也就是props传递对象而已
*/

// 1、传递
export default function Profile() {
return (
<Avatar
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
size={100}
/>
);
}

// 2、使用/读取

/*
1.以下这种方式是采用解构,将person的props解构为单独的props传递
2.prop可以存在默认值,例如下面的prop size
*/
function Avatar({ person, size=100 }) {
// 在这里 person 和 size 是可访问的
}

Props的使用,必须要关注你的父子组件。需要考虑好父组件需要向自组件传的props,而子组件需要考虑传递的props通过什么方式去进行渲染。事实上,**就可以总结出来,React函数组件就是接受一个参数,那就是Props对象。**

Props使用妙计
  • prop可以指定默认值

    如果渲染时不存在size的prop,那么size将被赋值100进行渲染

    其实就是size的prop属性不存在或者值undefined时会生效

    但是,如果传递size={null}size={0},默认值将 不 被使用。

    1
    2
    3
    function Avatar({ person, size = 100 }) {
    // ...
    }
  • 可以使用JSX展开语法传递Props

    会存在需要传递pros很多,需要声明prop传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function 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
    7
    function 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传入的属性值来动态展示内容

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
//	其中isPacked是组件接受的Prop
// 用来动态显示 不同li内容的展示
// 可以选择 return 的组件为null 那么他将不会显示任何内容
function Item({ name, isPacked }) {
if (isPacked) {
return <li className="item">{name} ✔</li>;
}
return <li className="item">{name}</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>
);
}
  • 选择性包含JSX

    一般的不同条件会return不同的HTML片段,此刻可以选择包含JSX的书写,一般使用以下运算符:

    1、三目运算符(? :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if (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
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
// 首先我们拥有一个数组数据。
const people = [
'凯瑟琳·约翰逊: 数学家',
'马里奥·莫利纳: 化学家',
'穆罕默德·阿卜杜勒·萨拉姆: 物理学家',
'珀西·莱温·朱利亚: 化学家',
'苏布拉马尼扬·钱德拉塞卡: 天体物理学家',
];

// 我们可以使用数组的遍历方法map这个数组来遍历元素
const listItem = people.map(person=><li>{person}</li>)

// 用ul将listItem包围
return <ul>{listItem}</ul>

// 最终代码
const people = [
'凯瑟琳·约翰逊: 数学家',
'马里奥·莫利纳: 化学家',
'穆罕默德·阿卜杜勒·萨拉姆: 物理学家',
'珀西·莱温·朱利亚: 化学家',
'苏布拉马尼扬·钱德拉塞卡: 天体物理学家',
];

export default function List() {
const listItems = people.map(person =>
<li>{person}</li>
);
return <ul>{listItems}</ul>;
}

注意:这样会导致没有唯一key报错。

过滤渲染

我们有个需求需要将职业为化学家的元素渲染出来

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
// 数组
const people = [
{
id: 0,
name: '凯瑟琳·约翰逊',
profession: '数学家',
},
{
id: 1,
name: '马里奥·莫利纳',
profession: '化学家',
},
{
id: 2,
name: '穆罕默德·阿卜杜勒·萨拉姆',
profession: '物理学家',
},
{
name: '珀西·莱温·朱利亚',
profession: '化学家',
},
{
name: '苏布拉马尼扬·钱德拉塞卡',
profession: '天体物理学家',
},
];

// 采用数组filter方法进行过滤
const liItem = people.filter(person=>person.profession==='化学家')

// 用ul将liIte包围
return <ul>{liItem}</ul>

// 完整代码
export default function List() {
const listItems = people.map(person =>
<li>{person}</li>
);
return <ul>{listItems}</ul>;
}
用key能保证渲染顺序

直接放在 map() 方法里的 JSX 元素一般都需要指定 key 值!

这些key会告诉React,每个组件对应的数组数据那一项,去进行匹配。

这些将在数组项进行移动、插入或者删除等操作是非常重要的。合适的key将保证React正常的更新DMO树。

key值在渲染之前,要存在数据里面key必须存在。

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// app.js
import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
const listItems = people.map(person =>
<li key={person.id}>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}</b>
{' ' + person.profession + ' '}
因{person.accomplishment}而闻名世界
</p>
</li>
);
return <ul>{listItems}</ul>;
}

// data.js
export const people = [
{
id: 0, // 在 JSX 中作为 key 使用
name: '凯瑟琳·约翰逊',
profession: '数学家',
accomplishment: '太空飞行相关数值的核算',
imageId: 'MK3eW3A',
},
{
id: 1, // 在 JSX 中作为 key 使用
name: '马里奥·莫利纳',
profession: '化学家',
accomplishment: '北极臭氧空洞的发现',
imageId: 'mynHUSa',
},
{
id: 2, // 在 JSX 中作为 key 使用
name: '穆罕默德·阿卜杜勒·萨拉姆',
profession: '物理学家',
accomplishment: '关于基本粒子间弱相互作用和电磁相互作用的统一理论',
imageId: 'bE7W1ji',
},
{
id: 3, // 在 JSX 中作为 key 使用
name: '珀西·莱温·朱利亚',
profession: '化学家',
accomplishment: '开创性的可的松药物、类固醇和避孕药',
imageId: 'IOjWm71',
},
{
id: 4, // 在 JSX 中作为 key 使用
name: '苏布拉马尼扬·钱德拉塞卡',
profession: '天体物理学家',
accomplishment: '白矮星质量计算',
imageId: 'lrWQx8l',
},
];

// util.js
export function getImageUrl(person) {
return (
'https://i.imgur.com/' +
person.imageId +
's.jpg'
);
}

如何在列表项显示多个DOM节点??

我需要将每个列表多渲染其他DOM节点。使用Fragment的语法简写<></>无法接受key值。写法存在要不使用

包裹,或者使用明确的<Fragment>

Fragment标签不会出现在DOM上,代码会转成最终的<h1><p><h1><p>…… 的列表。

1
2
3
4
5
6
7
8
9
10
import { Fragment } from 'react';

// ...

const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);
如何设置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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let guest = 0;

function Cup() {
// Bad: changing a preexisting variable!
guest = guest + 1;
return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
return (
<>
<Cup />
<Cup />
<Cup />
</>
);
}

以上组件,在读取外部guest的变量,这将导致React组件每次返回的JSX都是不同的。如果这时候,其他组件也在读取这个变量的话,他们也会产生不同的JSX。

这时候就可以这样修改:

我们将变量作为Props(输入),输入到我们的React组件中。现在对于Props输入的变量是一样的,这时候React组件返回的JSX都是一样的。这时候React组件返回的JSX只依赖这个guestprop。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Cup({ guest }) {
return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
return (
<>
<Cup guest={1} />
<Cup guest={2} />
<Cup guest={3} />
</>
);
}

一般来说,你不应该期望你的组件以特定的顺序去渲染。你要注意,其实每个组件都应该是独立的,同样地,每个组件都应该去“独立思考”去考虑自己的输入,而不是在渲染过程中试图去与其他组件协调,或者依赖于其他组件。

使用严格模式检测不纯的计算

当用户输入改变某些内容时,你应该设置状态,而不是直接写入变量。当组件渲染时,你永远不应该改变预先存在的变量或者对象。

React提供了“严格模式”,在严格模式下开发,他将会调用每个组件函数俩次。通过重复调用组件函数,严格模式有助找到违反这些规则的组件。

纯函数仅仅执行计算,因此调用它们两次不会改变任何东西 — 就像两次调用 double(2) 并不会改变返回值,两次求解 y = 2x 不会改变 y 的值一样。相同的输入,总是返回相同的输出。

严格模式在生产环境下不生效,因此它不会降低应用程序的速度。如需引入严格模式,你可以用 <React.StrictMode> 包裹根组件。一些框架会默认这样做。

局部mutation:组件的小秘密

以上示例的问题是在渲染过程中,组件改变了预先存在的变量的值。

这种现象我们称之为突变(mutation)。纯函数不会改变函数作用域以外的变量、或在函数调用前创建的对象,这会使函数变得不纯粹!

但是,你完全可以在渲染时更改你**刚刚**创建的变量和对象。

看下面示例:

1
2
3
4
5
6
7
8
9
10
11
function Cup({ guest }) {
return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaGathering() {
let cups = [];
for (let i = 1; i <= 12; i++) {
cups.push(<Cup key={i} guest={i} />);
}
return cups;
}

上面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
2
3
4
5
6
7
8
9
10
11
<h1>海蒂·拉玛的待办事项</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
class="photo"
>
<ul>
<li>发明一种新式交通信号灯
<li>排练一个电影场景
<li>改进频谱技术
</ul>

此时需要将它转化为React组件,可以这么做,是的没错。 采用 *** <> 或者其他标签*** 包裹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default function TodoList() {
return (
<>
<h1>海蒂·拉玛的待办事项</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
className="photo"
/>
<ul>
<li>发明一种新式交通信号灯</li>
<li>排练一个电影场景</li>
<li>改进频谱技术</li>
</ul>
</>

JSX语法规则

  • 只能返回一个根元素

    1. 就是return()中必须只有一个根元素
    2. 不想在标签中增加一个额外的
      ,可以用 <> 和 </> 元素来代替
  • 标签必须闭合

  • 使用驼峰式命名法给大部分属性命名!

    JSX 最终会被转化为 JavaScript,而 JSX 中的属性也会变成 JavaScript 对象中的键值对。在组件中,经常会遇到需要用变量的方式读取这些属性的时候。但 JavaScript 对变量的命名有限制。例如,变量名称不能包含 - 符号或者像 class 这样的保留字。

    JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。

JSX中通过大括号使用javaScript

JSX可以在JavaScript中编写类似HTML的标签,保证渲染逻辑和内容绑定。在需要在标签中添加Js逻辑或者引用标签的动态属性的时候可以在JSX的大括号里面编写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
24
// 字符串
export default function Avatar() {
return (
<img
className="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
);
}

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

注意:当使用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
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
// 对象传递
export default function TodoList() {
return (
<ul style={{
backgroundColor: 'black',
color: 'pink'
}}>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
);
}

// 嵌套对象传递
const person = {
name: 'Gregorio Y. Zara',
theme: {
backgroundColor: 'black',
color: 'pink'
}
};

export default function TodoList() {
return (
<div style={person.theme}>
<h1>{person.name}'s Todos</h1>
<img
className="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
<ul>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
</div>
);
}

Props组件传递

React组件使用Props互相通信。每个父组件都可以提供props给他的自组件传递信息。可以通过Props传递认识JavaScript值,包括对象、数组、和函数。

Props传递类似HTML预定义的属性。像组件传递的时候可以传递任意的props。传递的props可以通过不同的方式进行渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 其中person就是props
/*
其中的花括号也就是props传递对象而已
*/

// 1、传递
export default function Profile() {
return (
<Avatar
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
size={100}
/>
);
}

// 2、使用/读取

/*
1.以下这种方式是采用解构,将person的props解构为单独的props传递
2.prop可以存在默认值,例如下面的prop size
*/
function Avatar({ person, size=100 }) {
// 在这里 person 和 size 是可访问的
}

Props的使用,必须要关注你的父子组件。需要考虑好父组件需要向自组件传的props,而子组件需要考虑传递的props通过什么方式去进行渲染。事实上,**就可以总结出来,React函数组件就是接受一个参数,那就是Props对象。**

Props使用妙计
  • prop可以指定默认值

    如果渲染时不存在size的prop,那么size将被赋值100进行渲染

    其实就是size的prop属性不存在或者值undefined时会生效

    但是,如果传递size={null}size={0},默认值将 不 被使用。

    1
    2
    3
    function Avatar({ person, size = 100 }) {
    // ...
    }
  • 可以使用JSX展开语法传递Props

    会存在需要传递pros很多,需要声明prop传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function 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
    7
    function 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传入的属性值来动态展示内容

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
//	其中isPacked是组件接受的Prop
// 用来动态显示 不同li内容的展示
// 可以选择 return 的组件为null 那么他将不会显示任何内容
function Item({ name, isPacked }) {
if (isPacked) {
return <li className="item">{name} ✔</li>;
}
return <li className="item">{name}</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>
);
}
  • 选择性包含JSX

    一般的不同条件会return不同的HTML片段,此刻可以选择包含JSX的书写,一般使用以下运算符:

    1、三目运算符(? :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if (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
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
// 首先我们拥有一个数组数据。
const people = [
'凯瑟琳·约翰逊: 数学家',
'马里奥·莫利纳: 化学家',
'穆罕默德·阿卜杜勒·萨拉姆: 物理学家',
'珀西·莱温·朱利亚: 化学家',
'苏布拉马尼扬·钱德拉塞卡: 天体物理学家',
];

// 我们可以使用数组的遍历方法map这个数组来遍历元素
const listItem = people.map(person=><li>{person}</li>)

// 用ul将listItem包围
return <ul>{listItem}</ul>

// 最终代码
const people = [
'凯瑟琳·约翰逊: 数学家',
'马里奥·莫利纳: 化学家',
'穆罕默德·阿卜杜勒·萨拉姆: 物理学家',
'珀西·莱温·朱利亚: 化学家',
'苏布拉马尼扬·钱德拉塞卡: 天体物理学家',
];

export default function List() {
const listItems = people.map(person =>
<li>{person}</li>
);
return <ul>{listItems}</ul>;
}

注意:这样会导致没有唯一key报错。

过滤渲染

我们有个需求需要将职业为化学家的元素渲染出来

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
// 数组
const people = [
{
id: 0,
name: '凯瑟琳·约翰逊',
profession: '数学家',
},
{
id: 1,
name: '马里奥·莫利纳',
profession: '化学家',
},
{
id: 2,
name: '穆罕默德·阿卜杜勒·萨拉姆',
profession: '物理学家',
},
{
name: '珀西·莱温·朱利亚',
profession: '化学家',
},
{
name: '苏布拉马尼扬·钱德拉塞卡',
profession: '天体物理学家',
},
];

// 采用数组filter方法进行过滤
const liItem = people.filter(person=>person.profession==='化学家')

// 用ul将liIte包围
return <ul>{liItem}</ul>

// 完整代码
export default function List() {
const listItems = people.map(person =>
<li>{person}</li>
);
return <ul>{listItems}</ul>;
}
用key能保证渲染顺序

直接放在 map() 方法里的 JSX 元素一般都需要指定 key 值!

这些key会告诉React,每个组件对应的数组数据那一项,去进行匹配。

这些将在数组项进行移动、插入或者删除等操作是非常重要的。合适的key将保证React正常的更新DMO树。

key值在渲染之前,要存在数据里面key必须存在。

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// app.js
import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
const listItems = people.map(person =>
<li key={person.id}>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}</b>
{' ' + person.profession + ' '}
因{person.accomplishment}而闻名世界
</p>
</li>
);
return <ul>{listItems}</ul>;
}

// data.js
export const people = [
{
id: 0, // 在 JSX 中作为 key 使用
name: '凯瑟琳·约翰逊',
profession: '数学家',
accomplishment: '太空飞行相关数值的核算',
imageId: 'MK3eW3A',
},
{
id: 1, // 在 JSX 中作为 key 使用
name: '马里奥·莫利纳',
profession: '化学家',
accomplishment: '北极臭氧空洞的发现',
imageId: 'mynHUSa',
},
{
id: 2, // 在 JSX 中作为 key 使用
name: '穆罕默德·阿卜杜勒·萨拉姆',
profession: '物理学家',
accomplishment: '关于基本粒子间弱相互作用和电磁相互作用的统一理论',
imageId: 'bE7W1ji',
},
{
id: 3, // 在 JSX 中作为 key 使用
name: '珀西·莱温·朱利亚',
profession: '化学家',
accomplishment: '开创性的可的松药物、类固醇和避孕药',
imageId: 'IOjWm71',
},
{
id: 4, // 在 JSX 中作为 key 使用
name: '苏布拉马尼扬·钱德拉塞卡',
profession: '天体物理学家',
accomplishment: '白矮星质量计算',
imageId: 'lrWQx8l',
},
];

// util.js
export function getImageUrl(person) {
return (
'https://i.imgur.com/' +
person.imageId +
's.jpg'
);
}

如何在列表项显示多个DOM节点??

我需要将每个列表多渲染其他DOM节点。使用Fragment的语法简写<></>无法接受key值。写法存在要不使用

包裹,或者使用明确的<Fragment>

Fragment标签不会出现在DOM上,代码会转成最终的<h1><p><h1><p>…… 的列表。

1
2
3
4
5
6
7
8
9
10
import { Fragment } from 'react';

// ...

const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);
如何设置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
2
3
4
5
6
7
8
9
10
11
<h1>海蒂·拉玛的待办事项</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
class="photo"
>
<ul>
<li>发明一种新式交通信号灯
<li>排练一个电影场景
<li>改进频谱技术
</ul>

此时需要将它转化为React组件,可以这么做,是的没错。 采用 *** <> 或者其他标签*** 包裹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default function TodoList() {
return (
<>
<h1>海蒂·拉玛的待办事项</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
className="photo"
/>
<ul>
<li>发明一种新式交通信号灯</li>
<li>排练一个电影场景</li>
<li>改进频谱技术</li>
</ul>
</>

JSX语法规则

  • 只能返回一个根元素

    1. 就是return()中必须只有一个根元素
    2. 不想在标签中增加一个额外的
      ,可以用 <> 和 </> 元素来代替

    ***JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。 ***

  • 标签必须闭合

  • 使用驼峰式命名法给大部分属性命名!

    JSX 最终会被转化为 JavaScript,而 JSX 中的属性也会变成 JavaScript 对象中的键值对。在组件中,经常会遇到需要用变量的方式读取这些属性的时候。但 JavaScript 对变量的命名有限制。例如,变量名称不能包含 - 符号或者像 class 这样的保留字。

JSX中通过大括号使用javaScript

JSX可以在JavaScript中编写类似HTML的标签,保证渲染逻辑和内容绑定。在需要在标签中添加Js逻辑或者引用标签的动态属性的时候可以在JSX的大括号里面编写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
24
// 字符串
export default function Avatar() {
return (
<img
className="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
);
}

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

注意:当使用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
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
// 对象传递
export default function TodoList() {
return (
<ul style={{
backgroundColor: 'black',
color: 'pink'
}}>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
);
}

// 嵌套对象传递
const person = {
name: 'Gregorio Y. Zara',
theme: {
backgroundColor: 'black',
color: 'pink'
}
};

export default function TodoList() {
return (
<div style={person.theme}>
<h1>{person.name}'s Todos</h1>
<img
className="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
<ul>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
</div>
);
}

Props组件传递

React组件使用Props互相通信。每个父组件都可以提供props给他的自组件传递信息。可以通过Props传递认识JavaScript值,包括对象、数组、和函数。

Props传递类似HTML预定义的属性。像组件传递的时候可以传递任意的props。传递的props可以通过不同的方式进行渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 其中person就是props
/*
其中的花括号也就是props传递对象而已
*/

// 1、传递
export default function Profile() {
return (
<Avatar
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
size={100}
/>
);
}

// 2、使用/读取

/*
1.以下这种方式是采用解构,将person的props解构为单独的props传递
2.prop可以存在默认值,例如下面的prop size
*/
function Avatar({ person, size=100 }) {
// 在这里 person 和 size 是可访问的
}

Props的使用,必须要关注你的父子组件。需要考虑好父组件需要向自组件传的props,而子组件需要考虑传递的props通过什么方式去进行渲染。事实上,**就可以总结出来,React函数组件就是接受一个参数,那就是Props对象。**

Props使用妙计
  • prop可以指定默认值

    如果渲染时不存在size的prop,那么size将被赋值100进行渲染

    其实就是size的prop属性不存在或者值undefined时会生效

    但是,如果传递size={null}size={0},默认值将 不 被使用。

    1
    2
    3
    function Avatar({ person, size = 100 }) {
    // ...
    }
  • 可以使用JSX展开语法传递Props

    会存在需要传递pros很多,需要声明prop传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function 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
    7
    function 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”。