主题
Day 1:useEffect 入门——配置自动保存
今天完成什么
- 搞懂
useEffect是什么、什么时候执行 - 用
useEffect把配置自动保存到 localStorage - 刷新页面后,配置还在(自动恢复)
接在昨天哪里
基础课做完,你有一个三栏配置页:左边组件列表,中间配置面板,右边实时预览 + JSON。
但有个尴尬的问题:刷新页面,辛辛苦苦调的配置全没了。 因为配置只存在 useState 里,一刷新就重置。
今天要解决它:配置一变就自动存起来,刷新后自动读回来。这就要用到基础课没讲过的 useEffect。
动手前:先把基础课 Day 12 的完整项目复制一份,确认能正常跑起来(三栏页面、改标题预览会变),我们在它上面继续加功能。
核心概念
1. 什么是"副作用"
React 组件的主业是:根据 state/props,返回要渲染的 JSX。这叫"纯渲染"。
但有些事不属于渲染,却又必须做:
- 把数据写进 localStorage
- 发请求拿数据
- 设一个定时器
- 手动改 DOM、订阅事件
这些"渲染之外、跟外部世界打交道"的操作,就叫副作用(side effect)。
副作用不能直接写在组件函数体里(那样每次渲染都会执行,还可能在渲染过程中制造混乱)。React 给了个专门的地方放它——useEffect。
2. useEffect 的基本结构
typescript
useEffect(() => {
// 副作用代码写这里(渲染完之后才执行)
}, [依赖1, 依赖2]);- 第一个参数:一个函数,里面放副作用
- 第二个参数:依赖数组,告诉 React"这些值变了才重新执行"
3. 什么时候执行
关键记住一句话:effect 在"渲染到屏幕之后"才执行。
text
state 变化
↓
组件重新渲染(算出新 JSX)
↓
浏览器把新画面画到屏幕
↓
useEffect 里的函数执行 ← 在这4. 依赖数组决定"要不要再跑一次"
typescript
useEffect(() => {
console.log('config 变了');
}, [config]); // 只有 config 变化时,才再执行今天我们就用它:盯住 config,config 一变就写进 localStorage。
动手前的目录
今天会新增:
text
src/
└─ utils/
└─ storage.ts # localStorage 读写封装 [新增]还会修改:
text
src/
└─ components/
└─ LowCodeConfigPage.tsx # 加自动保存 + 初始化读取 [修改]分步实现
第 1 步:封装 localStorage 读写
直接用 localStorage.setItem 也行,但配置是对象,要 JSON.stringify / JSON.parse,还得处理出错。封装一下更省心。
新建 src/utils/storage.ts:
typescript
import { Config } from '../types/config';
// 存储用的 key,改个版本号能一键作废旧数据
const STORAGE_KEY = 'lowcode-config-v1';
/**
* 保存配置到 localStorage
*/
export function saveConfig(config: Config): void {
try {
const json = JSON.stringify(config);
localStorage.setItem(STORAGE_KEY, json);
} catch (error) {
console.error('保存配置失败:', error);
}
}
/**
* 从 localStorage 读取配置
* 读不到或解析失败时返回 null,让调用方决定用默认值
*/
export function loadConfig(): Config | null {
try {
const json = localStorage.getItem(STORAGE_KEY);
if (!json) return null;
return JSON.parse(json) as Config;
} catch (error) {
console.error('读取配置失败:', error);
return null;
}
}
Config类型用你基础课src/types/config.ts里定义的那个,路径按你项目实际调整。
第 2 步:初始化时从 localStorage 读
找到基础课里管配置的那个 useState(在 LowCodeConfigPage.tsx 里,大概长这样):
typescript
// 基础课原来的写法
const [config, setConfig] = useState<Config>(defaultConfig);改成惰性初始化——useState 传一个函数,只在首次渲染时执行一次:
typescript
import { loadConfig, saveConfig } from '../utils/storage';
// [修改] 首次渲染时,优先读 localStorage,读不到才用默认配置
const [config, setConfig] = useState<Config>(() => {
return loadConfig() ?? defaultConfig;
});为什么传函数而不是
loadConfig() ?? defaultConfig直接当参数?因为直接写的话,每次渲染都会调用loadConfig()(虽然只有首次生效),白白读盘。传函数,React 只在首次执行它,更高效。这叫"惰性初始 state"。
第 3 步:用 useEffect 自动保存
在同一个组件里,加一个 useEffect,盯住 config:
typescript
import { useEffect } from 'react';
// [新增] config 每次变化,自动写进 localStorage
useEffect(() => {
saveConfig(config);
}, [config]); // 依赖 config:config 变了才重新保存就这么简单。逻辑是:
text
用户改配置 → setConfig → config 变化 → 组件重渲染
→ 渲染后 useEffect 检测到 config 变了 → saveConfig 写盘完整代码
src/components/LowCodeConfigPage.tsx 顶部关键部分(其余基础课的代码保持不变):
typescript
import React, { useState, useEffect } from 'react';
import { Config } from '../types/config';
import { loadConfig, saveConfig } from '../utils/storage';
// ...其余 import 保持基础课原样
const defaultConfig: Config = {
// ...基础课里原本的默认配置
};
export function LowCodeConfigPage() {
// [修改] 惰性初始化:优先读本地存储
const [config, setConfig] = useState<Config>(() => {
return loadConfig() ?? defaultConfig;
});
// [新增] 自动保存:config 变了就写盘
useEffect(() => {
saveConfig(config);
}, [config]);
// ...下面渲染三栏的 JSX 保持基础课原样,
// 把 config / setConfig 照旧传给子组件
return (
// ...
);
}外加新增文件 src/utils/storage.ts(见第 1 步完整代码)。
运行效果
- 启动项目
npm run dev - 改一下配置:比如把卡片标题改成"测试标题",加两个字段
- 刷新页面(F5)
- 预期:标题还是"测试标题",字段也还在——配置被恢复了 ✅
- 打开浏览器 DevTools → Application → Local Storage,能看到一条
lowcode-config-v1,值就是你的配置 JSON
常见错误
错误 1:刷新后配置没恢复
原因:useState 还是写死 defaultConfig,没走惰性初始化。
解决:确认写的是 useState(() => loadConfig() ?? defaultConfig),注意是传函数。
错误 2:控制台警告 useEffect 缺少依赖
原因:依赖数组没把用到的值写全。
解决:effect 里用到了 config,依赖数组就得有 [config]。这门课后面会反复强调"依赖要写全"。
错误 3:localStorage 里存进去的是 [object Object]
原因:没 JSON.stringify 直接存了对象。
解决:用第 1 步封装好的 saveConfig,它内部做了 stringify。
错误 4:JSON.parse 报错,页面崩了
原因:localStorage 里有旧的、格式不对的数据。
解决:loadConfig 里用 try-catch 兜住(第 1 步已处理),出错返回 null 用默认配置。实在乱了就手动清 localStorage。
动手改一改
- 清空按钮:加一个"重置配置"按钮,点了清空 localStorage 并恢复默认配置
- 保存时间:每次保存时,在 localStorage 里也存一个
savedAt时间戳,页面上显示"上次保存:xx:xx" - 多份配置:试着用不同的 key 存两套配置,加个下拉切换(为后面多组件埋伏笔)
验收清单
- [ ] 新建了
utils/storage.ts,有saveConfig/loadConfig - [ ]
useState用了惰性初始化,从 localStorage 读初始值 - [ ] 加了
useEffect(() => saveConfig(config), [config]) - [ ] 改配置后刷新页面,配置能恢复
- [ ] Local Storage 里能看到
lowcode-config-v1 - [ ] 能讲清楚"effect 在渲染之后执行"
今日总结
学到了什么
- 副作用:渲染之外、跟外部世界打交道的操作
useEffect结构:useEffect(fn, deps)- 执行时机:渲染到屏幕之后
- 依赖数组:
[config]表示 config 变化才重新执行 - 惰性初始 state:
useState(() => ...)首次才执行
关键代码
typescript
// 读:惰性初始化
const [config, setConfig] = useState(() => loadConfig() ?? defaultConfig);
// 写:副作用自动保存
useEffect(() => {
saveConfig(config);
}, [config]);今天的限制
- 每次改动立刻写盘,连续打字时写太频繁(性能浪费)
- 没有"正在保存 / 已保存"的反馈
明天做什么
Day 2 讲透依赖数组和清理函数,用"防抖"优化自动保存——停止输入 500ms 后才写盘,并加上"已保存"提示。
基础课最大的窟窿开始补了!useEffect 是 React 的分水岭,今天只是开胃,明天更关键。
进行中