最近在用 Claude Code,换了成本更低的Codex,记录一下配置过程。方便以后查看,快速配置。

配置路径

配置文件位置:

  • macOS/Linux: ~/.codex/config.toml
  • Windows: C:\Users\你的用户名\.codex\config.toml

配置文件

直接上配置:

1
2
3
4
5
6
7
8
9
10
11
model_provider = "codex"
model = "gpt-4.1-mini"
model_reasoning_effort = "medium"
disable_response_storage = true
windows_wsl_setup_acknowledged = true

[model_providers.codex]
name = "codex"
base_url="https://api.burn.hair/v1" #使用官方或第三方的
wire_api = "responses"
env_key = "K_CODEX" #不要改成自己的密钥,在系统环境变量中设置

参数说明

几个关键配置:

  • model_provider: 设置成 "codex" 就行
  • model: 用的模型,这里是 gpt-4.1-mini
  • model_reasoning_effort: 推理强度,low/medium/high 三档,medium 够用
  • disable_response_storage: 不保存历史记录,开启更安全
  • base_url: API 地址,换成你自己的
  • env_key: 环境变量名,密钥别直接写配置文件里

设置步骤

1. 修改配置文件

找到配置文件,把上面的配置复制进去,改一下 base_urlmodel

2. 设置环境变量

macOS/Linux~/.zshrc~/.bashrc 加一行:

1
export K_CODEX="your-api-key-here"

然后 source ~/.zshrc 生效。

Windows PowerShell 执行:

1
[System.Environment]::SetEnvironmentVariable('K_CODEX', 'your-api-key-here', 'User')

3. 重启终端

重启终端就能用了。

前言

在使用 React Hooks 开发时,useEffect 的依赖数组管理是一个常见的痛点。很多开发者遇到过这样的困扰:依赖项过多导致 Effect 频繁执行,或者为了图方便直接禁用 ESLint 规则,最终引发难以调试的 bug。

本文将从 React 官方文档和源码层面深入探讨如何正确管理 Effect 依赖,帮助你写出更高效、更可维护的 React 代码。

核心原则:依赖必须与代码匹配

React 官方文档明确指出:依赖应该与代码匹配。这意味着 Effect 中使用的每一个响应式值(props、state)都必须出现在依赖数组中。

React 的 ESLint 插件 eslint-plugin-react-hooks 会自动检查这一规则,确保你的依赖声明是完整的。

⚠️ 关键警告

永远不要用注释禁用依赖检查:

1
2
3
4
5
// ❌ 危险做法
useEffect(() => {
// ...
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

React 文档强调:应该把依赖检查错误当作编译错误来对待。忽略它会导致微妙且难以诊断的 bug。

六大策略移除不必要的依赖

1. 将逻辑移至事件处理器

如果某段代码应该响应特定的用户交互而非响应式变化,应该放在事件处理器中:

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
// ❌ 不好的做法
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

useEffect(() => {
if (message) {
logVisit(roomId, message);
}
}, [roomId, message]); // message 变化会重复记录

return <input value={message} onChange={e => setMessage(e.target.value)} />;
}

// ✅ 好的做法
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

const handleSend = () => {
logVisit(roomId, message); // 只在发送时记录
};

return (
<>
<input value={message} onChange={e => setMessage(e.target.value)} />
<button onClick={handleSend}>发送</button>
</>
);
}

2. 拆分多目的 Effect

当一个 Effect 同步多个不相关的过程时,应该拆分成多个 Effect:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ❌ 不好的做法
useEffect(() => {
connectToChat(roomId);
trackPageView(page);
}, [roomId, page]); // roomId 变化会触发不必要的页面追踪

// ✅ 好的做法
useEffect(() => {
connectToChat(roomId);
}, [roomId]);

useEffect(() => {
trackPageView(page);
}, [page]);

3. 使用状态更新函数

通过传递更新函数而非直接读取 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
// ❌ 不好的做法
function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1); // 依赖 count
}, 1000);
return () => clearInterval(timer);
}, [count]); // 每次 count 变化都会重置定时器

return <div>{count}</div>;
}

// ✅ 好的做法
function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1); // 使用更新函数,不依赖 count
}, 1000);
return () => clearInterval(timer);
}, []); // 空依赖数组,定时器不会重置

return <div>{count}</div>;
}

4. 使用 Effect Event(实验性)

useEffectEvent 允许你提取非响应式逻辑,读取最新值而不触发重新同步:

1
2
3
4
5
6
7
8
9
10
11
12
// ✅ 使用 Effect Event
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('已连接', theme); // 读取最新的 theme
});

useEffect(() => {
const connection = connectToChat(roomId);
connection.on('connected', onConnected);
return () => connection.disconnect();
}, [roomId]); // theme 变化不会重新连接
}

注意: useEffectEvent 目前仍是实验性 API,尚未包含在稳定版 React 中。

5. 避免对象和函数依赖

对象和函数在每次渲染时都是新的引用,会导致不必要的重新执行:

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
// ❌ 不好的做法
function SearchResults({ query }) {
const options = { // 每次渲染都是新对象
includeArchived: true
};

useEffect(() => {
fetchResults(query, options);
}, [query, options]); // options 每次都会触发
}

// ✅ 解决方案 1: 移到 Effect 内部
function SearchResults({ query }) {
useEffect(() => {
const options = { includeArchived: true };
fetchResults(query, options);
}, [query]);
}

// ✅ 解决方案 2: 移到组件外部
const OPTIONS = { includeArchived: true };

function SearchResults({ query }) {
useEffect(() => {
fetchResults(query, OPTIONS);
}, [query]);
}

// ✅ 解决方案 3: 使用 useMemo
function SearchResults({ query, includeArchived }) {
const options = useMemo(() => ({
includeArchived
}), [includeArchived]);

useEffect(() => {
fetchResults(query, options);
}, [query, options]);
}

6. 提取原始值

当接收对象 props 时,解构出原始值作为依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ❌ 不好的做法
function ChatRoom({ options }) {
useEffect(() => {
connectToChat(options.roomId);
}, [options]); // options 对象每次渲染都是新的
}

// ✅ 好的做法
function ChatRoom({ options }) {
const { roomId } = options;

useEffect(() => {
connectToChat(roomId);
}, [roomId]); // 只依赖原始值
}

源码解析:React 如何初始化 State

让我们深入 React 源码,看看 useState 背后的实现机制。以下代码来自 React v19.1.1 的 ReactFiberHooks.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function mountStateImpl<S>(initialState: (() => S) | S): Hook {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
const initialStateInitializer = initialState;
// 执行初始化函数获取初始值
initialState = initialStateInitializer();
if (shouldDoubleInvokeUserFnsInHooksDEV) {
setIsStrictModeForDevtools(true);
try {
// 严格模式下会执行两次,检测副作用
initialStateInitializer();
} finally {
setIsStrictModeForDevtools(false);
}
}
}
hook.memoizedState = hook.baseState = initialState;
// ...
}

源码要点解读

  1. 惰性初始化支持:当 initialState 是函数时,React 会执行它来获取初始值。这就是为什么我们可以写 useState(() => expensiveComputation())

  2. 严格模式双重调用:在开发模式的严格模式下,初始化函数会被调用两次。这是 React 的一个特性,用于帮助检测不纯的初始化函数中的副作用。

  3. 状态存储:初始值会同时存储在 memoizedState(当前状态)和 baseState(基础状态)中。这是 React 实现状态更新和并发渲染的基础。

与 Effect 依赖的关联

理解 useState 的实现有助于我们更好地管理 Effect 依赖:

1
2
3
4
5
6
7
8
9
10
11
12
// 为什么惰性初始化不需要依赖
function Component() {
// ✅ 初始化函数只在首次渲染时执行一次
const [data] = useState(() => {
return expensiveComputation(); // 不会重复执行
});

useEffect(() => {
// data 来自 state,是响应式的,需要作为依赖
processData(data);
}, [data]);
}

实战案例:聊天室连接管理

让我们通过一个完整的例子整合这些最佳实践:

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
function ChatRoom({ roomId, theme, currentUser }) {
const [messages, setMessages] = useState([]);
const [isConnected, setIsConnected] = useState(false);

// ✅ 使用 Effect Event 处理非响应式逻辑
const onMessage = useEffectEvent((message) => {
setMessages(msgs => [...msgs, message]); // 使用更新函数
showNotification(message, theme); // 读取最新 theme,但不触发重连
});

const onConnectionChange = useEffectEvent((connected) => {
setIsConnected(connected);
if (connected) {
logUserActivity(currentUser.id); // 读取最新 user,但不触发重连
}
});

// ✅ 只在 roomId 变化时重新连接
useEffect(() => {
const connection = createConnection(roomId);

connection.on('message', onMessage);
connection.on('connection', onConnectionChange);
connection.connect();

return () => {
connection.disconnect();
};
}, [roomId]); // 只依赖 roomId

return (
<div className={theme}>
<ConnectionStatus isConnected={isConnected} />
<MessageList messages={messages} />
</div>
);
}

这个例子的优点

  1. 最小化依赖:Effect 只依赖 roomId,只在切换房间时重新连接
  2. 避免不必要的重连themecurrentUser 变化不会导致重新连接
  3. 使用更新函数setMessages(msgs => [...msgs, message]) 避免依赖 messages
  4. 清晰的职责分离:连接逻辑、消息处理、通知展示各司其职

调试技巧

1. 使用 React DevTools

React DevTools 的 Profiler 可以帮你识别哪些组件因为 Effect 重新渲染:

  • 记录渲染原因
  • 查看 Hook 的依赖变化
  • 识别性能瓶颈

2. 添加日志

在开发环境添加日志帮助理解 Effect 执行时机:

1
2
3
4
5
6
7
useEffect(() => {
console.log('Effect 执行:', { roomId, theme });
// ...
return () => {
console.log('Effect 清理:', { roomId, theme });
};
}, [roomId, theme]);

3. 使用 eslint-plugin-react-hooks

确保在项目中启用并配置该插件:

1
2
3
4
5
6
7
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}

常见误区

误区 1:空依赖数组万能

1
2
3
4
// ❌ 错误理解
useEffect(() => {
setCount(count + 1); // count 永远是初始值
}, []); // 缺少 count 依赖

误区 2:依赖越少越好

依赖少不是目标,准确的依赖才是目标。不要为了减少依赖而牺牲正确性。

误区 3:随意禁用 ESLint 规则

1
2
3
4
5
// ❌ 这是技术债务
useEffect(() => {
// ...
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

这会在未来引发难以追踪的 bug,绝对不要这样做。

性能优化建议

  1. 优先使用事件处理器:能用事件处理器就不用 Effect
  2. 拆分细粒度 Effect:每个 Effect 只负责一件事
  3. 合理使用 useMemo/useCallback:对复杂对象和函数进行缓存
  4. 避免过度优化:先保证正确性,再优化性能

总结

Effect 依赖管理是 React Hooks 开发中的重要技能。关键要点:

  1. 依赖必须与代码匹配 - 这是不可妥协的原则
  2. 永远不要禁用 ESLint 规则 - 把依赖警告当作编译错误对待
  3. 优先使用事件处理器 - 不是所有逻辑都需要 Effect
  4. 使用更新函数 - 减少对 state 的依赖
  5. 避免对象和函数依赖 - 它们每次渲染都是新的
  6. 理解源码实现 - 帮助我们更好地使用 API

通过遵循这些最佳实践,你可以写出更高效、更易维护的 React 代码,避免常见的 Effect 依赖陷阱。

参考资料

哭哭,不知道咋了。自从来了武汉,经常发烧头疼。呕吐。
可能是因为到年纪了?BMI超重了?不知道还是因为打了三针科兴。
今天又发烧了37.5度。
早上起床吃了布洛芬,一觉睡到了现在晚上七点多这个点儿。
催了沙雕对象好几次做饭,一直让我等会儿。快饿哭了。头疼,浑身都疼真的就想躺在床上啥也不想动。

前言

作为一个使用 Hexo 搭建博客的开发者,每次写完文章都要手动执行

1
hexo clean && hexo generate && hexo deploy

这一系列命令是不是很麻烦?今天就教大家如何使用 GitHub Actions 实现 Hexo 博客的自动化部署。

什么是 GitHub Actions

GitHub Actions 是 GitHub 提供的持续集成和持续部署(CI/CD)服务。它可以在代码仓库发生特定事件时自动执行预定义的工作流程,比如代码提交、PR 创建等。

自动部署方案

我们的方案是:

  1. 源代码仓库:存放 Hexo 源文件(Markdown 文章、配置文件等)
  2. GitHub Pages 仓库:存放编译后的静态网站文件
  3. GitHub Actions:自动构建并部署到 GitHub Pages 仓库

配置步骤

1. 创建 GitHub Actions 工作流

在 Hexo 博客源码仓库根目录下创建 .github/workflows/deploy.yml 文件:

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
name: Deploy Hexo to GitHub Pages

on:
push:
branches:
- main
paths:
- 'source/_posts/**'
- 'source/**'
- 'themes/**'
- '_config*.yml'
- 'package.json'
workflow_dispatch:

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout source
uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Clean and generate static files
run: |
npm run clean
npm run build

- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
personal_token: ${{ secrets.DEPLOY_TOKEN }}
external_repository: 你的用户名/你的用户名.github.io
publish_branch: main
publish_dir: ./public
user_name: 'github-actions[bot]'
user_email: 'github-actions[bot]@users.noreply.github.com'
commit_message: ${{ github.event.head_commit.message }}

2. 配置触发条件

在上述配置中,工作流会在以下情况触发:

  • 推送到 main 分支
  • 修改了 source/_posts/**source/**themes/** 目录下的文件
  • 修改了配置文件 _config*.ymlpackage.json
  • 手动触发(workflow_dispatch

3. 创建 GitHub Personal Access Token

  1. 访问 GitHub 设置页面:https://github.com/settings/tokens
  2. 点击 “Generate new token” → “Generate new token (classic)”
  3. 填写以下信息:
    • Note: Hexo Blog Deploy(或其他描述)
    • Expiration: 选择过期时间(建议 1 year 或 No expiration)
    • Select scopes: 勾选 repo(完整的仓库权限)
  4. 点击 “Generate token”
  5. 立即复制生成的 token(只会显示一次,关闭页面后无法再次查看)

4. 添加 Secret 到源码仓库

  1. 进入你的 Hexo 源码仓库
  2. 点击 SettingsSecrets and variablesActions
  3. 点击 “New repository secret”
  4. 填写:
    • Name: DEPLOY_TOKEN
    • Secret: 粘贴刚才复制的 token
  5. 点击 “Add secret”

5. 修改配置中的仓库名

deploy.yml 中的 external_repository 改为你的 GitHub Pages 仓库名:

1
external_repository: 你的用户名/你的用户名.github.io

使用流程

配置完成后,使用流程变得非常简单:

  1. source/_posts 目录下创建或修改 Markdown 文章
  2. 提交代码到 Git 仓库:
    1
    2
    3
    git add .
    git commit -m "新增文章:Hexo 自动部署配置"
    git push origin main
  3. GitHub Actions 自动触发,构建并部署
  4. 几分钟后,访问你的 GitHub Pages 网站即可看到更新

查看部署状态

  1. 进入源码仓库的 Actions 页面
  2. 查看最新的工作流运行状态
  3. 点击查看详细日志,排查问题

优势总结

使用 GitHub Actions 自动部署有以下优势:

  1. 省时省力: 不需要手动执行构建和部署命令
  2. 环境一致: 在云端统一的环境中构建,避免本地环境问题
  3. 随时随地: 只要能提交代码就能发布文章,甚至可以在 GitHub 网页端直接编辑
  4. 版本管理: 源文件和部署文件分离,源码有完整的 Git 历史记录
  5. 免费使用: GitHub Actions 对公开仓库完全免费

常见问题

Q: 部署失败怎么办?

A: 查看 Actions 页面的详细日志,常见问题包括:

  • Token 权限不足或过期
  • 仓库名配置错误
  • 依赖安装失败

Q: 能否部署到自定义域名?

A: 可以,在 source 目录下添加 CNAME 文件,内容为你的域名即可。

Q: 构建时间太长怎么办?

A: 可以使用 npm ci 代替 npm install,并启用 npm 缓存(配置中已包含)。

总结

通过 GitHub Actions,我们实现了 Hexo 博客的全自动部署。从此以后,写博客只需要专注于内容创作,提交代码后坐等自动部署完成即可。

0%