React 使用纯函数,概念非常简单,代价是我们得理解名字和定义都很抽象的钩子们。
React 使用纯函数组件,意思是:输入 Props,输出 UI,不允许任何副作用。这个概念让页面组件理解起来非常简单。父子组件之间通过 props 单向传递状态,不会像普通 JS 那样不同页面操作对同一个状态值一通乱改。
但实际中外部条件页会影响渲染,这不得不引入钩子的概念。useEffect 是其中最常见和最容易搞错的之一。
比如用户可能拖动窗口,可能需要临时存储一些数据,这些外在变化都不是 Props 的一部分——但都会影响渲染效果。useEffect就是负责处理这些与外部系统同步的麻烦事的。
假设我们要做一个 Local-First 的笔记应用。用户的草稿不能因为关闭浏览器就看不到了。笔记组件的状态(note 的值)需要跟浏览器的 localStorage 保持同步。这时候就必须要用到 useEffect:
function NoteApp() { const [note, setNote] = useState(''); // 1. 初始化:组件挂载后,去“外部世界”读数据 useEffect(() => { const saved = localStorage.getItem('draft'); if (saved) setNote(saved); }, []); // 2. 同步:每当 note 变化,同步到“外部世界” useEffect(() => { localStorage.setItem('draft', note); }, [note]); return<textarea value={note} onChange={e => setNote(e.target.value)} />;}
但要注意,React 官方文档说,页面组件里出现多个 useEffect,是臭代码(code smell)的苗头。最好把它用 custom hook 封装起来——
// 🟢 封装后:组件只关心“我要用持久化状态”,不关心“怎么持久化”function NoteApp() { // 一行代码,搞定所有副作用 const [note, setNote] = usePersistentState('draft', ''); return<textarea value={note} onChange={e => setNote(e.target.value)} />;}
数据获取怎么做?
数据获取是和外界同步的典型场景!以前,我们要这么写一个需要从数据库获取数据的页面:“组件挂载 -> useEffect 发请求 -> setLoading(true) -> 数据回来 -> setState(data)”
但从 Next.js 13 开始(现在版本已经是 16 了)中,这套逻辑被 Server Components 替代。不需要像客户端组件一样、等浏览器画完空壳再回头去取数。服务器组件可以在服务器 await 数据,把生成好的、带数据的 HTML 发给浏览器。
// Next.js: 没有 Effect,没有 State,只有渲染好的 htmlexport default async function Page() { const note = await db.notes.get('draft'); // 直接等待数据 return<textarea defaultValue={note} />;}
如果还是感觉不好记和麻烦,有两个速记点——
- • NextJS 14/15/16 的 App Router(市面上的模板默认都使用它),默认就是服务端组件。所以获取数据默认是不使用
useEffect 的! - • 只要是服务器不可能知道、但又会影响渲染结果的——比如 localstorage、鼠标拖动缩放、获取播放器窗口状态等等,就一定会用到钩子。而涉及同步操作的,一定会用到 useEffect。