React-技巧积累
使用 await fetch
在 React 中, 若想使用 await
关键词, 则需要将包含其的函数设置为 async
, 如:
1 |
|
状态变量与 input 输入绑定
1 |
|
组件的两种标签写法
1 |
|
基础事件绑定
语法为:
1 |
|
整体上为驼峰命名法. 如
1 |
|
使用事件对象
1 |
|
onClick={clickHandler}
会自动给 clickHandler
传递一个参数, 就是事件对象.
传递参数
1 |
|
同时使用自定义参数和事件对象
1 |
|
这里 onClick
同样给 () => {}
这一匿名函数传递了一个事件对象.
JSX 常见使用场景
JSX 使用 JavaScript 对象作为内联样式
1 |
|
注意 JSX 中只能是表达式, 而不能是语句与 if
, switch
.
列表渲染
1 |
|
条件渲染
可以通过逻辑与运算符 &&
(似乎不能用逻辑或), 也可以用三元表达式 ?:
.
如:
1 |
|
复杂条件渲染
用单独的函数处理:
1 |
|
React 哲学
构建页面时, 将其拆分为一个个组件, 只需要把组件连接在一起, 使数据流经它们.
常用步骤:
- 在原型图的每个组件和子组件周围绘制盒子并命名
- 在原型中辨别好组件之后, 将其转化为层级结构
- 之后使用 React 构建一个静态版本, 此时不考虑交互和 state (构建一个静态版本需要写大量的代码, 并不需要什么思考; 但添加交互需要大量的思考, 却不需要大量的代码)
- 找出需要用到 state 的最小集合
- 随时间保持不变的不是 state
- 通过 props 从父组件传递的不是 state
- 可以由其他 state 组件计算得出的不是 state
- 验证 state 应该放置在哪里, 一般是共同的父组件或父组件上层的组件
React 渲染 UI 的流程
- 触发渲染
- 渲染组件
- 提交到 DOM
也就是说, 组件是先分别渲染好了, 才显示到页面上. (在渲染完成并且 React 更新 DOM 之后, 浏览器才会重新绘制屏幕)
React 中的渲染必须是一次 “纯计算”, 即:
- 输入相同, 输出相同. 给定相同的输入, 组件应始终返回相同的 JSX
- 只做它自己的事情. 它不应更改任何存在于渲染之前的对象或变量
为什么需要 state
- 局部变量无法在多次渲染中持久保存
- 更改局部变量不会触发渲染
state 更新的时机
1 |
|
也就是说, 当次渲染中, count
的值不会立即改变, 而是在重新渲染时更新值.
总结来说:
- 设置 state 只会为下一次渲染变更 state 的值
- 一个 state 变量的值永远不会在一次渲染的内部发生变化
state 的批处理更新
React 会等到事件处理函数中的 所有代码都运行完毕再处理 state 更新
这可以更新多个 state 变量, 甚至来自多个组件的 state 变量, 而不会触发太多的重新渲染. 但这也意味着只有在事件处理函数及其中任何代码执行完成之后, UI 才会更新. 这种特性也就是批处理.
示例:
1 |
|
对当次渲染的影响是, count
值不变, 对下一次渲染的影响是 count
值加一.
这里实际上是先执行了:
1 |
|
然后再执行:
1 |
|
即批处理. 在原来的执行顺序中, 每到 setCount()
, 就将其加入到更新队列中, 在下一次渲染期间, React 会遍历队列并给你更新之后的最终 state.
组件位置对 state 的影响
对 React 来说重要的是组件在 UI 树中的位置, 而不是在 JSX 中的位置, 如:
- 相同位置的相同组件会使得 state 被保留下来
如: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
63import { useState } from 'react';
export default function App() {
const [isFancy, setIsFancy] = useState(false);
if (isFancy) {
return (
<div>
<Counter isFancy={true} />
<label>
<input
type="checkbox"
checked={isFancy}
onChange={e => {
setIsFancy(e.target.checked)
}}
/>
使用好看的样式
</label>
</div>
);
}
return (
<div>
<Counter isFancy={false} />
<label>
<input
type="checkbox"
checked={isFancy}
onChange={e => {
setIsFancy(e.target.checked)
}}
/>
使用好看的样式
</label>
</div>
);
}
function Counter({ isFancy }) {
const [score, setScore] = useState(0);
const [hover, setHover] = useState(false);
let className = 'counter';
if (hover) {
className += ' hover';
}
if (isFancy) {
className += ' fancy';
}
return (
<div
className={className}
onPointerEnter={() => setHover(true)}
onPointerLeave={() => setHover(false)}
>
<h1>{score}</h1>
<button onClick={() => setScore(score + 1)}>
加一
</button>
</div>
);
} - 相同位置的不同组件会使 state 重置, 如:
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
47import { useState } from 'react';
export default function App() {
const [isPaused, setIsPaused] = useState(false);
return (
<div>
{isPaused ? (
<p>待会见!</p>
) : (
<Counter />
)}
<label>
<input
type="checkbox"
checked={isPaused}
onChange={e => {
setIsPaused(e.target.checked)
}}
/>
休息一下
</label>
</div>
);
}
function Counter() {
const [score, setScore] = useState(0);
const [hover, setHover] = useState(false);
let className = 'counter';
if (hover) {
className += ' hover';
}
return (
<div
className={className}
onPointerEnter={() => setHover(true)}
onPointerLeave={() => setHover(false)}
>
<h1>{score}</h1>
<button onClick={() => setScore(score + 1)}>
加一
</button>
</div>
);
}
也就是说, 如果想保留 state, 就需要确保渲染的树形结构就应该相互 “匹配”, 结构不同就会导致 state 的销毁, 因为 React 会在将一个组件从树中移除时销毁它的 state.
在在相同位置重置 state
可以使用 key
来让 React 区分任何组件, 这样就不会是同一位置的同一组件了. 如:
1 |
|
(注意 key 不是全局唯一的, 它们只能指定父组件内部的顺序)
在下次渲染前多次更新同一个 state
用 setNumber(n => n + 1)
这种, 传入一个根据队列中的前一个 state
计算下一个 state
的 函数, 而不是像 setNumber(number + 1)
这样传入 下一个 state
值.
这里的 n => n + 1
就被称为 “更新函数”.
可以理解为 setCount
函数中的匿名函数, 会自动传入一个变量表示 state.
这里有个命名规范: 通常可以通过相应 state 变量的第一个字母来命名更新函数的参数.
1 |
|
传递事件函数的误区
如:
1 |
|
其会在 React 渲染时就触发.
正确为:
1 |
|
其会在点击时才触发.
事件的传播以及处理方法
事件处理函数还将捕获任何来自子组件的事件. 通常, 说事件会沿着树向上 “冒泡” 或 “传播”.
在 React 中所有事件都会传播, 除了 onScroll
.
如:
1 |
|
如果点击任一按钮, 它自身的 onClick
将首先执行, 然后父级 <div>
的 onClick
会接着执行.
阻止传播
事件处理函数都会默认接收一个 “事件对象” 作为唯一参数 (e, event), 可以通过这个参数来获取该事件的一些相关信息, 也可用于阻止传播, 如:
1 |
|
阻止默认行为
某些事件具有一些默认行为, 如点击 <form>
表单内部的按钮会触发表单提交事件, 默认情况下将重新加载整个页面:
1 |
|
可以通过调用事件对象中的 e.preventDefault()
来阻止:
1 |
|
返回 null
return null
可以不做任何渲染.
三目运算符
将:
1 |
|
写为:
1 |
|
与运算符 (&&
) 的经典示例
1 |
|
用 JSX 展开语法传递 props
1 |
|
其会将所有 Profile
接收到的 props 转发到 Avatar
, 而不需要一个个列出名字.
将 JSX 作为子组件传递
如:
1 |
|
将内容嵌套在 JSX 标签中时, 父组件将在名为 children 的 prop 中接收到该内容. 如:
1 |
|
不要嵌套定义组件
在组件中定义另一个组件会导致代码运行慢, 且容易导致 bug. 应该在顶层定义每一个组件, 如:
1 |
|
列表 key
在创建列表时, 每一个列表项需要有一个唯一的 key 来将其与其他列表项区分开:
1 |
|
key 便于 React 在渲染时更新组件的状态, 如果组件的 key 发生变化, 组件将被销毁, 新 state 将重新创建.
key 是 React 中一个特殊的保留属性. 创建元素时, React 提取 key 属性并将 key 直接存储在返回的元素上.
key 不需要是全局唯一的, 它们只需要在组件及其同级组件之间是唯一的. (一般不用数组的 index 作为 key, 因为可能会改变)
可以用 uuid
库来生产 key.
() => 语法
1 |
|
会导致无限调用. handleClick()
函数会调用 setSquares()
更新 state, 导致再次渲染, 即再次调用 handleClick()
.
而:
1 |
|
能解决这个问题. 其传递一个函数而非运行一个函数.
指定 Class
同 HTML 有所不同, JSX 使用 className
属性来指定, 如:
1 |
|
Hook
以 use
开头的函数被称为 Hook. 如 useState
就是 React 提供的一个内置 Hook. 注意 Hook 只有在 React 渲染时有效.
Hook 是 React 的特性, 因此与普通 JavaScript 函数有区别:
- Hook 只能在 React 函数组件或自定义 Hook 中调用
- 只能在顶层调用 Hook, 即不要在循环, 条件判断或嵌套函数中调用 Hooks
- 自定义 Hook 必须以
use
开头
state 变量
在 React 中,state 是一个用于管理组件内部状态的数据结构. state 允许组件动态地响应用户输入, 网络请求, 时间变化等, 进而更新 UI.
需要先从 React
中导入 useState
:
1 |
|
使用如:
1 |
|
useState
返回两个值:
- 当前的 state, 这里是
count
- 更新 state 的函数, 这里是
setCount
而 useState(0)
, 这里的 0
是传递给 state 的初始值.
若想改变 state, 则用 setCount()
传递新的值. 如:
1 |
|
嵌入 JavaScript 变量
使用大括号:
1 |
|
JSX 规则
- 必须使用闭合标签
- 一个组件不能返回多个 JSX 标签 (只能返回一个根元素)
- 使用驼峰式命名法给大部分属性命名
如:
1 |
|
就会报错. 若想同时返回这两个标签, 则用一个空标签 <>...</>
包裹:
1 |
|
JSX 中嵌入 JavaScript 的规则
使用大括号 {}
嵌入, 但只有两种场景能使用:
- 用作 JSX 标签内的文本:
<h1>{name}'s To Do List</h1>
是有效的, 但是<{tag}>Gregorio Y. Zara's To Do List</{tag}>
无效 - 用作紧跟在
=
符号后的 属性:src={avatar}
会读取avatar
变量, 但是src="{avatar}"
只会传一个字符串{avatar}
组件命名约定
React 组件必须以大写字母开头, 而 HTML 标签则必须是小写字母.
组件的嵌套使用
一个组件 MyButton
:
1 |
|
另一组件 MyApp
:
1 |
|
若将 MyButton
嵌入到 MyApp
中, 则为:
1 |
|
组件返回值用 ()
包裹
若不使用括号, 如:
1 |
|
此时 JavaScript 会在 return
语句后自动插入一个 ;
, 变为:
1 |
|
因此返回值变成了 undefined
添加括号可以避免这种问题:
1 |
|
使用 uuid 库生成唯一 key
1 |
|