React
React
创建项目
html中直接导入
1 |
|
- react.min.js - React 的核心库
- react-dom.min.js - 提供与 DOM 相关的功能
- babel.min.js - Babel 可以将 ES6 代码转为 ES5 代码,这样我们就能在目前不支持 ES6 浏览器上执行 React 代码。Babel 内嵌了对 JSX 的支持。通过将 Babel 和 babel-sublime 包(package)一同使用可以让源码的语法渲染上升到一个全新的水平。
(过时)使用create-react-app构建项目
create-react-app 是来自于 Facebook,通过该命令我们无需配置就能快速构建 React 开发环境
官网:Create React App (create-react-app.dev)
1 |
|
使用框架创建项目
Next.js
充分利用了 React 的架构,支持全栈 React 应用
1 |
|
React Router
最流行的路由库,可以与 Vite 结合创建一个全栈 React 框架
1 |
|
JSX
语法规则
- return 只能返回一个元素,有多个需要用个父元素框起来
- 标签必须闭合
- 可以在标签文本内、属性内用
{ }
来使用JavaScript语法 {{ }}
表示传递一个对象,在属性style中会用到
组件
根组件
程序入口,在root.tsx里
1 |
|
组件导入导出
将Gallery组件放到一个新的Gallery.tsx文件中
1 |
|
在其他组件中导入
1 |
|
具名导出:当要导入一个没有default的组件时,要用 import { 组件名, … } from ‘文件名’;
默认导出:而导入有default的组件时,可用 import 别名 from ‘文件名’
默认导出只能有一个
传递参数给组件
1 |
|
- 组件的唯一参数就是props,在接收参数时通过
{ }
结构出来 {{ }}
传递的是对象
传递子组件给组件
1 |
|
- 通过props中的children参数来接收子组件
- 在父组件中通过
{}
渲染子组件
渲染列表
1 |
|
- 使用
数组.map()
来遍历 - 使用
数组.filter()
来过滤 map()
里如果用=> {}
,那么{}
里要用return
- 直接放在
map()
方法里的 JSX 元素一般都需要指定key
值 - 尽量不修改传进来的参数,要修改也是拷贝一份再修改,保证组件的纯粹
组件渲染
渲染做了什么:
- 重新执行函数组件函数
- 根据返回的JSX,生成新的虚拟DOM树
- 将新虚拟DOM树和旧的比较
- 只更新真实DOM的变化的部分
渲染时机:
- 应用启动时初次渲染
- 组件或祖先组件状态发生改变时重渲染(修改state会被标记要需要重新渲染)
组件间共享状态
提升参数位置
将要共享的state
从子组件提到最近的父组件
缺点:可能会导致要往很深的组件传递参数
Context
让父节点可以为其内部的整个组件树提供数据
步骤:
创建Context
1
2
3
4// 在一个文件中创建context并导出
import { createContext } from "react";
export const LevelContext = createContext(1);使用和提供Context
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// 在要使用的地方引入context
import { LevelContext } from "./LevelContext";
// 给子组件提供这个context时,子组件会找最近的
export function Section({ children, level }) {
return (
<section>
<LevelContext value={level + 1}>
{children}
</LevelContext>
</section>
);
}
// 子组件请求这个context
function Heading({children}){
const level = useContext(LevelContext)
switch (level) {
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
default:
throw Error('未知的 level: ' + level);
}
}
// 下层的子组件可以一起拿到state,不用一个个传递
export default function Home() {
return (
<Section level={1}>
<Heading>子标题</Heading>
<Section level={2}>
<Heading>子标题</Heading>
<Heading>子标题</Heading>
</Section>
</Section>
)
}
交互
添加事件处理函数
1 |
|
事件处理函数通常命名为
handleXXX
事件处理函数参数通常命名为
onXXX
事件传播:如果父组件也有事件,那么会先触发子组件的,然后按向上顺序触发,除了
onScroll
阻止事件传播:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function Button({onClick, children}) {
return (
<button onClick={(e) => {
e.stopPropagation() // 阻止了事件想上传播
onClick() // 执行的是父组件传来的函数
}}>
{children}
</button>
)
}
export default function Home() {
// return <Welcome />;
return (
<div onClick={() => alert("点击了div")}>
<Button onClick={() => alert('点击了按钮')}>按钮</Button>
</div>
)
}
注意:
正确 | 错误 |
---|---|
<button onClick={handleClick}> |
<button onClick={handleClick()}> |
<button onClick={() => alert('...')}> |
<button onClick={alert('...')}> |
state
和普通变量的区别:
更改state会触发组件重新渲染,普通变量不会
普通变量在组件重新渲染时不会保存当前值,state会保留渲染之前的值
应将state视为只读
组件不被渲染时,对应的
state
也会被销毁state
是通过组件在渲染树的位置来保存的,比如相同位置且相同的组件,切换时会保留state
同一个位置指的是像
? <Button/> : <Button/>
、if else
这样的,而<> {<Button/>} {<Button/>} </>
这种的就不是同一个位置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
31function Button(){
const [a, setA] = useState(0)
return (
<button onClick={() => setA(a + 1)}>
{ a }
</button>
)
}
function Button1(){
const [a, setA] = useState(0)
return (
<button onClick={() => setA(a + 1)}>
{ a }
</button>
)
}
export default function Home() {
const [b, setB] = useState(true)
// 如果两个都是Button, 那么会保留state,
// 如果一个Button一个Button1,切换时不会保留state
// 如果key值不同,也不会保留state
return (
<>
<button onClick={() => setB(! b)}>change</button>
{b ? <Button/> : <Button1/>}
</>
)
}
使用:
1 |
|
注意:
- 以
use
开头的函数称为Hook - 不要在循环、条件语句里用。
useState()
是通过给每个组件维护一个数组来保存state
,每次渲染都是按顺序把值重新赋给变量 - 如果是普通变量改变,不会触发重新渲染,新的值不会显示在组件上
- 修改state会生成新的快照,在下次渲染生效
- 可以用更新函数
index => index + 1
,这样会把更新后的值传给下一个函数
更新state
state中可以存放任意JavaScript对象
更新对象:应当创建新的,而不是直接修改源对象
1 |
|
当要更新多层嵌套的对象时,使用Immer更方便
1 |
|
更新数组
- 添加元素: 使用
...
展开
1 |
|
- 删除元素: 使用filter, filter会创建一个新数组
- 修改/替换元素:使用map来遍历并找到要修改的
- 特定位置插入元素:使用
...数组.slice(起始坐标, 终点坐标)
展开前面和后面,然后往中间插入
Reducer管理state
将state迁移至Reducer方便管理
优点:
- 比较好读懂
- 好调试
- 后期代码量比state低
步骤:
- 定义操作:将设置状态的逻辑修改成dispatch的一个action
- 实现操作的具体步骤:编写reducer函数
- 使用
useReducer
代替useState
- 还可以用
useImmerReducer
进一步简化
1 |
|
注意:
- reducer应该只用来计算下一个状态,而不应该做其他事
ref
和state的区别:更新ref时不会触发重新渲染;更改会立即表现出来,而不是等到下次渲染
和普通变量的区别:每次重新渲染时,raect会记住ref的值
何时使用:很少用到,要存储一些值,但不影响渲染逻辑
用ref引用值
1 |
|
用ref操作DOM
1 |
|
当DOM是一个数组,而数组大小未知时:
1 |
|
effect
在渲染后触发的效果,用于和外部系统交互
使用方法
基本方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 导入
import { useEffect } from 'react';
export function VideoPlayer({src, isPlaying}) {
const ref = useRef(null);
// 渲染应该是纯粹的计算,应当将修改dom的操作放到effect中
useEffect(() = {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
});
return <video ref={ref} src={src} loop playsInline />;
}可以指定参数,当有参数与上次渲染时一样时不触发Effect
如果为空
[]
,那么只会在挂载时触发一次1
2
3
4
5
6
7
8// 如果effect里面用到这个参数来决策运行哪段代码,则指定参数时必须要加进去
useEffect(() = {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}, [isPlaying]);可以
return
清除函数,会在每次 Effect 重新运行之前调用清理函数,并在组件卸载(被移除)时最后一次调用清理函数1
2
3
4
5
6
7
8useEffect(() = {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}, [isPlaying]);
return () => { doSomething... }
注意:
- 在effect中使用
setState
会形成死循环
useMemo
用来缓存数据
1 |
|