你可能不需要Effect
🎯 核心观点
🧐 Effect的本质
- 🔮 Effect是脱围机制:用于连接React与外部系统
- 🌐 外部系统包括:非React组件、网络API、浏览器DOM等
- ⚠️ 滥用警告:如果没有涉及外部系统,通常不需要Effect
🚫 不需要Effect的常见场景
📊 场景1:转换渲染所需的数据
- ❌ 错误用法:使用Effect来转换props或state并更新另一个state
- ✅ 正确方法:直接在渲染期间计算派生值
jsx
// ❌ 不必要的Effect
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// 多余的state和Effect
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
}
// ✅ 直接在渲染期间计算
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// 在渲染期间直接计算
const fullName = firstName + ' ' + lastName;
}
🧮 场景2:缓存昂贵的计算
- ❌ 错误用法:使用Effect缓存计算结果
- ✅ 正确方法:使用
useMemo
进行性能优化
jsx
// ❌ 不必要的Effect
function TodoList({ todos, filter }) {
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
setVisibleTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);
}
// ✅ 使用useMemo缓存计算结果
function TodoList({ todos, filter }) {
const visibleTodos = useMemo(() => {
return getFilteredTodos(todos, filter);
}, [todos, filter]);
}
🔄 场景3:当props变化时重置state
- ❌ 错误用法:使用Effect监听props变化并重置state
- ✅ 正确方法一:通过key属性重置整个组件
- ✅ 正确方法二:在渲染期间有条件地更新state
jsx
// ✅ 方法一:使用key重置整个组件
<ProfilePage key={userId} userId={userId} />
// ✅ 方法二:在渲染期间直接更新
function List({ items }) {
const [selection, setSelection] = useState(null);
// 当items没有选中项时,重置selection
const hasSelection = selection != null && items.includes(selection);
if (!hasSelection && items.length > 0) {
setSelection(items[0]);
}
}
📝 场景4:处理用户事件
- ❌ 错误用法:使用Effect响应用户交互
- ✅ 正确方法:将逻辑放在事件处理函数中
jsx
// ❌ 不必要的Effect
function Form() {
const [submitted, setSubmitted] = useState(false);
useEffect(() => {
if (submitted) {
// 🔴 避免:在Effect中处理事件逻辑
post('/api/register');
showNotification('注册成功!');
}
}, [submitted]);
}
// ✅ 将逻辑放在事件处理函数中
function Form() {
function handleSubmit() {
// ✅ 事件处理函数是处理交互的好地方
post('/api/register');
showNotification('注册成功!');
}
}
🌟 何时需要使用Effect
🔄 与外部系统同步
- 🌐 浏览器API:管理文档标题、媒体播放等
- 🔌 第三方小部件:集成非React UI库
- 📡 网络:订阅服务器事件
jsx
// ✅ 适合使用Effect:与外部系统同步
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
🔍 数据获取(有替代方案)
- ⚠️ 注意:虽然可以用Effect获取数据,但有局限性
- 🛠️ 更好的选择:使用框架提供的数据获取机制或自定义Hook
jsx
// 使用自定义Hook简化数据获取
function useData(url) {
const [data, setData] = useState(null);
useEffect(() => {
let ignore = false;
fetch(url)
.then(response => response.json())
.then(json => {
if (!ignore) {
setData(json);
}
});
return () => {
ignore = true;
};
}, [url]);
return data;
}
🛠️ 重构技巧总结
💊 移除不必要的Effect的方法
- 🧮 渲染期间计算:直接在组件函数体内计算派生值
- 💾 使用useMemo:缓存昂贵计算结果
- 🔑 使用key属性:通过改变key完全重置组件
- 🎮 使用事件处理函数:在用户交互时执行逻辑
- 🏗️ 状态提升:将共享状态提升到父组件中
📝 记住的规则
- 🧠 如果可以在渲染期间计算,不需要Effect
- 👆 处理用户事件时,把逻辑放在事件处理函数中
- 🌐 只在需要与外部系统同步时使用Effect
- 🧩 在数据获取时,考虑使用专门的Hook或框架