Akawa

ETY001的博客

之前在我的 用 Docker 快速部署 PPTP VPN 和 L2TP + IPSEC VPN 一文中,我介绍了如何使用 Docker k快速署 L2TP IPSec VPN,但是在纯血鸿蒙下一直无法成功。

发现是因为无法匹配通讯加密方法。

进入容器,如下修改一下 /etc/ipsec.conf 文件的通讯加密方法,

1
2
3
4
5
conn l2tp-psk
...
ike=aes128-sha1-modp1024,aes256-sha1-modp1024 # 匹配客户端的AES-CBC + SHA1 + MODP1024
phase2alg=aes128-sha1 # 第二阶段算法也需匹配
...

之后重启服务即可,service ipsec restart

背景

手里有一台 dzire 的廉价独服,配置如下:

项目 参数
CPU Intel Xeon E31270(8 核)
内存 16GB DDR3
磁盘 机械硬盘(HDD)
用途 Hivemind 同步 Steem 区块数据

Hivemind 是 Steem 链的社交层索引器,通过 Docker Compose 部署,其中 PostgreSQL 数据库承载了大量的写入和查询操作。同步一段时间后,数据库体积达到了 431GB,其中最大的几张表:

表名 大小
hive_posts_cache 216GB
hive_trxid_block_num 110GB
其他表 ~105GB

问题来了:同步进程始终落后链头几百个区块,追不上。

问题诊断

症状

tophtop 里最显眼的指标是 **IO Wait 持续 25%~35%**,CPU 大量时间在等磁盘。

确认瓶颈

iostatvmstat 进一步确认:

1
2
3
4
5
6
7
8
# iostat -x 1 观察
Device: rrqm/s wrqm/s r/s w/s rMB/s wMB/s %util await
sda 0.00 45.20 12.30 120.50 0.15 3.80 95.2 28.5

# vmstat 1 观察
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 3 0 2100000 450000 8500000 0 0 120 380 2500 4500 15 8 42 32 3

关键信号:

  • **%util 接近 100%**:磁盘已经满负荷
  • **wa(IO Wait)持续 25~35%**:CPU 大量时间在等 IO
  • **w/s 高达 120+**:大量小随机写(WAL + 数据文件)

结论:磁盘 IO 是瓶颈,毫无疑问。

在 SSD 上这些问题可能根本不会出现,但在 HDD 上,PostgreSQL 默认配置的检查点频率、WAL 刷写策略会导致大量小随机写,对机械硬盘来说是灾难性的。

调优思路

核心策略:减少磁盘写频率,增大批量写入,降低 fsync 开销

具体来说:

  1. **增大 shared_buffers**:让更多热数据留在内存,减少磁盘读取
  2. 增大 max_wal_size + **延长 checkpoint_timeout**:减少检查点频率,让每次检查点写出更多数据(顺序写更友好)
  3. 放宽 WAL 刷写策略wal_writer_delaywal_writer_flush_after 调大,减少 fsync 次数
  4. 激进调优 bgwriter:让后台写出进程多干活,减少检查点时的突发写入
  5. 关闭 full_page_writes:HDD 上这个特性会导致每次检查点后首次修改页写出完整页,极大放大写入量。注意:这依赖文件系统支持原子写(如 ZFS),否则断电有数据损坏风险。 本场景是同步数据,可以接受这个风险。

⚠️ 警告:第二轮激进配置的前提是——这台机器只做同步,不对外提供服务。如果你在生产环境中使用,请谨慎评估每一个参数。

具体配置

第一轮调优(保守)

初步调整,先把 PostgreSQL 的默认参数改到合理范围:

参数 默认值 调整后
shared_buffers 128MB(或 4GB) 6GB
max_wal_size 1GB(或 2GB) 4GB
checkpoint_timeout 5min(或 15min) 30min

这一轮调完后,IO Wait 有所下降但仍然不够,同步仍然落后。

第二轮调优(激进)

因为这台机器的唯一任务就是追赶同步,不需要担心崩溃恢复后的数据一致性(大不了重新同步),于是上了激进方案:

参数 第一轮值 激进值 说明
shared_buffers 6GB 8GB 占总内存 50%
max_wal_size 4GB 8GB 允许 WAL 累积更多再检查点
checkpoint_timeout 30min 1h 每小时才做一次检查点
wal_writer_delay 200ms 2s WAL writer 每 2 秒才醒来一次
wal_writer_flush_after 1MB 16MB 累积 16MB 才 fsync
bgwriter_delay 200ms 5s 后台写出进程降低唤醒频率
bgwriter_lru_maxpages 100 5000 每次醒来多写出一些脏页
bgwriter_lru_multiplier 2.0 10.0 更激进地预写出
full_page_writes on off 关闭全页写,大幅减少 IO

完整的 postgresql.conf(16g-postgres.conf)

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
# =============================================================
# PostgreSQL 16GB HDD 独服调优配置 - Hivemind 同步专用
# 注意:激进配置,仅适用于同步/非生产场景
# =============================================================

# ---- 内存 ----
shared_buffers = 8GB
work_mem = 64MB
maintenance_work_mem = 1GB
effective_cache_size = 12GB

# ---- WAL / 检查点 ----
max_wal_size = 8GB
min_wal_size = 2GB
checkpoint_timeout = 1h
checkpoint_completion_target = 0.9
wal_writer_delay = 2s
wal_writer_flush_after = 16MB
full_page_writes = off
wal_buffers = 64MB

# ---- 后台写出 ----
bgwriter_delay = 5s
bgwriter_lru_maxpages = 5000
bgwriter_lru_multiplier = 10.0

# ---- 查询规划 ----
random_page_cost = 4.0
effective_io_concurrency = 2
max_parallel_workers_per_gather = 2
max_parallel_workers = 4
max_parallel_maintenance_workers = 2

# ---- 连接 ----
max_connections = 100

# ---- 日志(方便观察) ----
log_checkpoint = on
log_connections = on
log_disconnections = on
log_lock_waits = on
log_temp_files = 0

# ---- autovacuum ----
autovacuum = on
autovacuum_max_workers = 4
autovacuum_naptime = 30s

几个值得解释的点:

  • **random_page_cost = 4.0**:HDD 的随机访问代价远高于 SSD(SSD 通常设 1.1),告诉规划器尽量走顺序扫描或索引扫描。
  • **effective_io_concurrency = 2**:HDD 的并发 IO 能力有限,设高了没意义。
  • **autovacuum_naptime = 30s**:Hivemind 同步产生大量 UPDATE/INSERT,频繁 vacuum 有助于避免表膨胀。
  • **full_page_writes = off**:这个最激进。正常情况下 PostgreSQL 在检查点后首次修改一页时会写出完整页(防止部分写撕裂)。关闭后每次只写差异,大幅减少写入量。如果你的文件系统是 ZFS 或使用了带电池保护的 RAID 控制器,风险可控。

Docker Compose 配置片段

PostgreSQL 配置文件放在 docker-compose.yml 同目录下,通过 volume 挂载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
services:
postgres:
image: postgres:15
restart: unless-stopped
shm_size: '2gb'
ports:
- "5432:5432"
environment:
POSTGRES_USER: hivemind
POSTGRES_PASSWORD: your_password_here
POSTGRES_DB: hivemind
volumes:
- ./data/postgresql:/var/lib/postgresql/data
- ./16g-postgres.conf:/etc/postgresql/postgresql.conf
command: postgres -c config_file=/etc/postgresql/postgresql.conf
deploy:
resources:
limits:
memory: 14g
reservations:
memory: 10g

注意 shm_size: '2gb' 不能省——PostgreSQL 的 shared_buffers 依赖共享内存,默认的 64MB 远远不够。

效果对比

指标 默认配置 第一轮(保守) 第二轮(激进)
IO Wait 25~35% ~22% ~15%
同步速度 落后链头数百块 略有改善 稳定追赶,差距持续缩小
检查点写入突发 频繁且剧烈 减少但仍有突发 平滑,几乎无感

IO Wait 从 25~35% 降到 15%,看似降幅不大,但对于 HDD 来说这已经是巨大的改善。关键改善在于写入模式——从大量小随机写变成了少量大批量顺序写,这正是机械硬盘最擅长的工作模式。

总结

  1. 先诊断再动手:用 iostatvmstatpg_stat_bgwriter 定位瓶颈,不要盲目改参数
  2. HDD 的核心敌人是随机写:所有调优方向都围绕”把随机写变顺序写、把频繁写变批量写”
  3. 激进配置有代价full_page_writes = off 在断电时有数据损坏风险,生产环境慎用
  4. 资源要给够shared_buffers 给到总内存的 50%,shm_size 别忘了调大
  5. 同步场景可以激进:最坏情况就是重新同步,不用过于保守

如果你的独服也是 HDD,且在跑重 IO 的数据库同步任务,希望这篇能帮到你。

背景

日常开发中,本地工作机的磁盘空间和 CPU 资源往往比较紧张,尤其是在编译大型 Docker 镜像时,构建缓存和中间层会占用大量磁盘空间。与此同时,家里可能有一台配置更强的服务器,磁盘充裕但平时处于闲置状态。

有没有一种优雅的方式,能让本地开发机把 Docker 构建任务分发到远程服务器上执行,同时又不影响本地开发体验?

答案是 Docker Context + Buildx Remote Builder

原理

Docker CLI 原生支持通过 SSH 连接远程的 Docker daemon。配合 buildx,可以创建一个远程 builder,所有 docker buildx build 命令会自动通过 SSH 把构建任务发送到远程机器上执行。

1
2
3
4
5
6
7
8
9
本地工作机                         远程服务器
┌──────────────┐ SSH 隧道 ┌──────────────┐
│ docker CLI │ ─────────────────▶│ Docker D │
│ buildx │ ssh://remote │ BuildKit │
│ 源码目录 │ │ 构建缓存 │
└──────────────┘ └──────────────┘
│ ▲
│ docker buildx build ────────────┘
│ (自动通过 SSH 发送构建指令)

整个过程中,源码通过 SSH 上下文传递给远程 BuildKit,构建在远程服务器上执行,构建缓存也累积在远程机器上。本地工作机只负责发送构建指令和接收构建日志。

具体步骤

1. 创建 Docker Context

1
docker context create remote --docker "host=ssh://user@remote-server"

这样 docker -c remote ps 就等价于 ssh user@remote-server docker ps,Docker CLI 自动走 SSH 隧道。

如果已经在 ~/.ssh/config 中配置了主机别名(比如 Host remote),直接写 ssh://remote 即可。

2. 创建远程 buildx builder

1
docker buildx create --name remote-builder remote --use

这会在远程服务器上启动一个 BuildKit 容器,所有 docker buildx build 都会走这个 builder。

首次创建时需要拉取 moby/buildkit 镜像,取决于网络情况可能需要一些时间。

3. 使用方式

在项目目录下执行:

1
2
3
4
5
6
7
8
# 构建镜像(镜像存在远程服务器上)
docker buildx build -t myapp:latest -f Dockerfile .

# 构建并推送到 registry
docker buildx build -t myapp:latest -f Dockerfile --push .

# 构建后拉回本地(镜像通过 SSH 传回工作机)
docker buildx build -t myapp:latest -f Dockerfile --load .

4. 切换回本地构建

1
docker buildx use default

注意事项

镜像位置

默认情况下,docker buildx build 构建出来的镜像存在于远程服务器上,本地工作机的 docker images 是看不到的。

如果需要在本地运行该镜像,需要加 --load 参数,但这会把镜像通过 SSH 拉回本地,占用本地磁盘空间。更推荐的做法是加 --push 直接推送到 registry。

首次 bootstrap 较慢

首次创建 builder 时,远程服务器需要拉取 moby/buildkit 镜像。如果通过跳板机连接,网络延迟较高,拉取可能需要几分钟。耐心等一下就好。

构建缓存

远程 builder 有自己的 BuildKit 缓存,同一个项目第二次构建时会命中缓存,速度明显更快。不同项目的缓存互相隔离,不会互相干扰。

不侵入现有流程

这个方案不需要修改 Dockerfile,不需要改 CI/CD 配置。只需要在构建时指定用哪个 builder,其他一切照旧。

总结

通过 Docker Context + Buildx,可以零成本地把重型构建任务卸载到闲置的远程服务器上,释放本地工作机的磁盘和 CPU 资源。对于本地磁盘紧张但又需要频繁构建大型镜像的场景,这是一个非常优雅的解决方案。

简介

hclient-cli 是官方推出的一个面向无图形界面的机器的接入方案。

有这个工具的情况下,我们可以让我们远端的服务器接入到懒猫的网络中,访问懒猫上的数据资源。

使用

官方库文档有接口的使用说明,这里就不再阐述,我提交了一个 PR,对官方的接口简单的用 bash 脚本封装了一下,减少使用过程中的输入量。同时还包含了一个构建 Docker 镜像的方案,其实就是把 cli 程序加入到镜像中。

目前我的远端服务是 Docker 容器形式运行的,因此这里分享一下 Docker 使用样例。

这里的远端应用以我的远端服务器上的青龙面板为例。

首先给出一个 Docker 容器启动命令示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
docker run -itd \
--privileged \
--name lazycat \
--hostname lazycat_in_docker \
--restart always \
--network lnmp \
--ip 172.20.0.66 \
-p 127.0.0.1:7777:7777 \
-p 127.0.0.1:61090:61090 \
-v /data/cfg:/app/cfg \
ety001/lazycat-cli:latest \
/app/hclient-cli \
-api-addr "172.20.0.66:7777" \
-http-addr "172.20.0.66:61090"
  1. 请注意不要暴露你的 7777 管理端口和 61090 代理端口给全局网络
  2. 容器启动后,所有 lnmp 网络中的容器可以通过 172.20.0.66:61090 代理访问懒猫资源。
  3. 宿主机可以通过 127.0.0.1:7777 管理,通过 127.0.0.1:61090 代理访问。

在宿主机中,调用 cmd.sh (在我的那个 PR 中)完成懒猫网络的登陆。

1
2
3
./cmd.sh add_box
./cmd.sh add_tfa
./cmd.sh client_info

登陆成功后,打开远端的青龙面板,安装依赖 axioshttps-proxy-agent。之后增加环境变量配置如下:

1
2
3
LAZYCAT_PROXY=http://172.20.0.66:61090
INFLUXDB_TOKEN=xxxx
INFLUXDB_URL=http://influxdb.ecat.heiyu.space:8086

创建一个新的测试 js 脚本如下:

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
const axios = require('axios');
const { HttpsProxyAgent } = require('https-proxy-agent');

const proxy = process.env.LAZYCAT_PROXY;
const agent = new HttpsProxyAgent(proxy);

// 自定义 HTTP 客户端,使用 axios 和代理
const customHttpClient = axios.create({
httpAgent: agent,
httpsAgent: agent,
});

const token = process.env.INFLUXDB_TOKEN;
const url = process.env.INFLUXDB_URL;
const org = 'default';
const bucket = 'steem';


// 定义 Flux 查询
const fluxQuery = `
from(bucket: "${bucket}")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "price")
|> filter(fn: (r) => r._field == "lowest_ask")
`;

// 发送查询请求
async function queryData() {
try {
const response = await customHttpClient.post(
`${url}/api/v2/query?org=${org}`,
{
query: fluxQuery,
type: 'flux',
},
{
headers: {
Authorization: `Token ${token}`,
'Content-Type': 'application/json',
},
}
);
console.log('查询结果:', response.data);
} catch (error) {
console.error('查询数据时出错:', error.response ? error.response.data : error.message);
}
}

queryData();

调试运行,成功获取到懒猫微服上的 Influxdb 中的数据。

image.png

今天发现了一个有意思的工具 —— 青龙面板

平时经常会有一些零零碎碎的脚本需要运行,放在宿主机运行,就要安装 nodejs 的环境,如果放在 docker 里运行,就要写 Dockerfile ,太繁琐。

使用这个青龙面板,相当于直接拥有一个容器空间,自带 python, nodejs 环境。可以理解为是云服务商的那种 server less 服务。

有这些特性很方便

1.按计划拉取指定的 repo 的指定分支。

image.png

这样我们可以把零碎的脚本放在一个独立的代码库里面,进行版本管理。

当然,如果你想要立即执行拉取任务,也提供了单独的运行按钮可以立即执行。

2.共享依赖。

06bbe3ec9a2257829a8383ed4c432d88.jpg

独立的依赖管理面板,可以只需要安装一次依赖,就可以所有脚本都使用。

3.在线编辑/调试代码。

image.png

这是拉取下来的代码,可以在线编辑。

image.png

这是调试界面,可以实时调试。

4.核心功能——计划任务

image.png

编辑好的脚本,可以在这里设置计划任务,让脚本按计划运行。

在 React 中,useEffect 的依赖项决定了其什么时候执行。

基本语法

useEffect 接受两个参数:

  • 副作用函数:在组件渲染后或依赖项发生变化时执行的函数。
  • 依赖项数组(可选):决定副作用函数何时重新执行。
1
2
3
useEffect(() => {
// 副作用逻辑
}, [dependency1, dependency2, ...]);

依赖项的常见场景与解释

1.无依赖项数组(每次渲染都会执行): 如果不传入依赖项数组,useEffect 中的副作用函数将在每次组件渲染后都执行。

1
2
3
useEffect(() => {
console.log('This runs after every render');
});

场景:这种用法不常见,通常用于希望在每次渲染时执行某些逻辑,但要注意性能开销。

2.空依赖项数组(只在组件挂载和卸载时执行): 如果传入一个空数组 [],则 useEffect 只会在组件挂载时运行一次,并在组件卸载时运行清理函数(如果提供了)。

1
2
3
4
5
6
useEffect(() => {
console.log('This runs only on mount');
return () => {
console.log('This runs on unmount');
};
}, []);

场景:通常用于初始化数据(例如组件挂载时的 API 请求),或在组件卸载时执行清理操作(例如清理定时器、取消订阅等)。

3.具有依赖项的数组(依赖项变化时执行): 当传入特定的依赖项时,useEffect 只有在这些依赖项的值发生变化时才会重新执行。

1
2
3
useEffect(() => {
console.log('This runs when count changes');
}, [count]); // 当 count 变化时副作用重新执行

场景:用于根据某个特定状态或 prop 变化来执行副作用逻辑。例如,当 count 变化时,可能需要重新获取某些数据或触发其他副作用。

4.多个依赖项: 可以在依赖项数组中传入多个依赖项,useEffect 将会在其中任何一个依赖发生变化时重新执行。

1
2
3
useEffect(() => {
console.log('This runs when count or user changes');
}, [count, user]);

场景:用于当多个状态或 prop 变化时,重新执行某个逻辑。例如,可能需要在 count 或 user 变化时重新发起某个 API 请求。

如何选择依赖项

1.依赖项的选择要遵循以下原则:

  • 副作用函数中使用的所有外部变量:任何在 useEffect 中使用的外部变量(函数组件中的状态、props 等)都应该作为依赖项传入。这是因为这些变量在每次渲染时都可能更新,你需要确保 useEffect 在依赖的值发生变化时执行。
  • dispatch 和其他稳定函数:通常像 dispatch(来自 useDispatch)这样的函数引用是稳定的(即在多次渲染之间不会改变),所以可以安全地放入依赖项数组中。如果某个函数引用在不同渲染周期中保持不变,就可以把它作为依赖项。

2.常见的误区与优化:

  • 忘记依赖项:如果 useEffect 中依赖的某些值没有包含在依赖项数组中,可能会导致副作用函数使用了过时的值(也称为“闭包陷阱”)。例如,依赖于某个 state 却没有将其加入依赖项数组,这样的结果是 useEffect 中的逻辑不会随着 state 的变化重新执行。
  • 过度依赖不必要的变量:有时候不小心将不必要的变量放入依赖项数组,导致 useEffect 过于频繁地执行,浪费性能。例如,不必要地将某些稳定的变量放入依赖项。

ET碎碎念,每周更新,欢迎订阅,点赞,转发!


好用不贵的VPS推荐

https://1hour.win

前情

Steemit 的几个前端项目(condenser, wallet, faucet)都使用了 redux 和 redux-saga。

这次升级 faucet 所有依赖库,发现新版本 redux 推荐使用官方的 @reduxjs/toolkit 工具集来实现 redux 的功能。

既然 redux 的官方推荐使用工具集,那么这次升级我们也要相对应的把原有的 redux 相关代码做改动。

其中,为了应对复杂的异步请求而引入的 redux-saga,我不是很确定是否与工具集兼容,因此投入了时间来研究了一下。

如何将 redux-saga@reduxjs/toolkit 一起使用

  • 创建 Redux Store: @reduxjs/toolkit 提供了 configureStore 来简化 store 的创建过程。你可以将 redux-saga 中间件添加到 store 中。

  • 创建并运行 Saga 中间件: 使用 redux-sagacreateSagaMiddleware 来创建 saga 中间件,然后将其添加到 configureStore 中。

  • 运行你的 Saga: 在 configureStore 创建 store 后,使用 sagaMiddleware.run 来启动你的 saga

下面是简单的例子。

store.js 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// store.js
import { configureStore } from '@reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './reducers'; // 你的 reducer
import rootSaga from './sagas'; // 你的 saga

// 创建 Saga 中间件
const sagaMiddleware = createSagaMiddleware();

// 配置 store 并添加 saga 中间件
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(sagaMiddleware), // 添加 saga 中间件
});

// 运行 rootSaga
sagaMiddleware.run(rootSaga);

export default store;

saga.js 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// sagas.js
import { call, put, takeLatest } from 'redux-saga/effects';
import axios from 'axios';

// worker saga
function* fetchUser(action) {
try {
const response = yield call(axios.get, `/api/user/${action.payload.userId}`);
yield put({ type: 'USER_FETCH_SUCCESS', payload: response.data });
} catch (error) {
yield put({ type: 'USER_FETCH_FAILURE', error: error.message });
}
}

// watcher saga
function* rootSaga() {
yield takeLatest('USER_FETCH_REQUEST', fetchUser);
}

export default rootSaga;

ET碎碎念,每周更新,欢迎订阅,点赞,转发!


好用不贵的VPS推荐

https://1hour.win

由于 faucet 项目全部是手动搭建的环境,所以在把 react15 升级到 react18 后,

1
2
3
4
5
import { createRoot } from 'react-dom/client';

const appElement = document.getElementById('app');
const root = createRoot(appElement);
root.render(<h1>Hello, world</h1>);

使用上面的代码测试环境是否搭建成功的时候,报 React is not defined 错误。

原因是:在 React 18 中,虽然可以使用 createRoot 来渲染组件,
但仍然需要显式地导入 React 以支持 JSX 语法。

在 JSX 中,<h1>Hello, world</h1> 会被编译成 React.createElement('h1', null, 'Hello, world')
因此,即使你没有直接使用 React,它仍然需要被导入。

由于 babel 我也升级到最新了,在 7.9 版本后,可以使用 @babel/preset-react 来自动引入 JSX 转换。
而不用去显式的导入 React 了。

具体方法就是在 babel.config.js 中对 @babel/preset-react 增加配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"presets": [
[
"@babel/preset-env",
{
"targets": "defaults"
}
],
[
"@babel/preset-react",
{
"runtime": "automatic" // 使用自动引入模式
}
]
]
}

如此设置后,再次编译执行,报错就没有了。


ET碎碎念,每周更新,欢迎订阅,点赞,转发!


好用不贵的VPS推荐

https://1hour.win

在 React 18 中,createStoreRedux 提供的一个函数,用于创建 Redux store,但从 Redux Toolkit v5 开始,createStore 已被标记为弃用,并建议使用 configureStore 作为替代。

configureStoreRedux Toolkit 中提供的一个函数,它简化了 Redux 的配置过程,内置了 Redux DevTools、默认的中间件配置等。

下面是如何使用 configureStore 替代 createStore 的一个简单示例:

1
2
3
4
5
6
7
8
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './reducers';

const store = configureStore({
reducer: rootReducer,
});

export default store;

这次重构 faucet 将会替换掉 createStore 方法。

Redux、React-Redux 和 Redux-Saga 是前端开发中常用的状态管理和异步数据处理工具。它们各自有不同的功能和用途。

1. Redux

Redux 是一个用于 JavaScript 应用的状态管理库。它提供了一种可预测的方式来管理应用的全局状态。

Redux 的核心概念包括:

  • Store: 存储应用的状态,是唯一的数据源。
  • Actions: 是描述状态变化的普通 JavaScript 对象。
  • Reducers: 是纯函数,接收当前状态和 action,返回新的状态。
  • Dispatch: 用于触发 action,从而引发状态的改变。

Redux 的特点:

  • 单一数据源: 整个应用只有一个状态树(store)。
  • 状态是只读的: 不能直接修改状态,必须通过 action 来描述状态变化。
  • 纯函数更新状态: Reducers 必须是纯函数,不得有副作用。

2. React-Redux

React-Redux 是官方提供的 Redux 和 React 的绑定库。它允许 React 组件与 Redux store 进行连接,使得组件能够访问 Redux 的状态并分发 actions。

React-Redux 的特点:

  • <Provider> 组件: 这个组件将 Redux store 提供给应用内所有的组件。
  • connect() 函数: 将 React 组件连接到 Redux store,允许组件从 store 中读取状态和分发 actions。
  • Hooks: useSelector 和 useDispatch 是 React-Redux 提供的 hooks,用于替代 connect(),更符合函数组件的使用方式。

Demo Code

1
2
3
4
5
6
7
8
9
10
11
12
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import App from './App';
import rootReducer from './reducers';

const store = createStore(rootReducer);

const Root = () => (
<Provider store={store}>
<App />
</Provider>
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment } from './actions';

const Counter = () => {
const count = useSelector(state => state.count);
const dispatch = useDispatch();

return (
<div>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>Increment</button>
</div>
);
};

3. Redux-Saga

Redux-Saga 是一个用于处理 Redux 应用中的异步操作的中间件。它基于 ES6 的 Generator 函数,使得处理复杂的异步逻辑(如异步 API 请求、并发请求、失败重试等)变得更直观和可管理。

Redux-Saga 的特点:

  • Sagas: Generator 函数,用于定义异步操作的逻辑。
  • Effects: Redux-Saga 提供了一系列 effects 函数(如 take, call, put 等)用于处理副作用(例如异步调用)。
  • 非阻塞调用: 通过 Generator 的 yield 机制,可以使异步操作的代码写起来像同步代码。

Demo Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { call, put, takeEvery } from 'redux-saga/effects';
import { fetchDataSuccess, fetchDataFailure } from './actions';
import api from './api';

// 定义 Saga
function* fetchDataSaga(action) {
try {
const data = yield call(api.fetchData, action.payload);
yield put(fetchDataSuccess(data));
} catch (error) {
yield put(fetchDataFailure(error));
}
}

// 监听特定的 action
function* watchFetchData() {
yield takeEvery('FETCH_DATA_REQUEST', fetchDataSaga);
}

export default watchFetchData;

总结

  • Redux: 用于管理全局状态,提供一个规范化的状态管理框架。
  • React-Redux: 是 Redux 和 React 的连接工具,让 React 组件可以访问 Redux 的状态和 actions。
  • Redux-Saga: 处理复杂的异步操作,让异步逻辑的管理更加简单和可维护。

这三个工具通常配合使用,以实现复杂的状态管理和异步数据处理。


ET碎碎念,每周更新,欢迎订阅,点赞,转发!


好用不贵的VPS推荐

https://1hour.win

0%