一个自定义Hook彻底改变了我的React开发方式,现在连阿里、字节的前端都在用它
昨天刷脉脉,看到一个腾讯前端发的帖子:*"为什么现在的React代码越写越恶心?"*
底下一片哀嚎。有人说自己的useEffect里嵌套了7层异步调用,有人说每个组件都有30行的loading状态管理...
我笑了。因为一年前的我,也是这样的受害者。
看看你是不是也写过这样的代码:
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then((res) => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
恭喜你,你已经掉进了React异步状态管理的"屎坑"。
去年给一个甲方爸爸做项目,他要求页面"秒级响应"。我盯着满屏的loading状态、error处理、try-catch包装器,差点怀疑人生。
凌晨3点,我做了一个决定:干掉所有重复的异步样板代码。
三个小时后,useAsync诞生了。
从此,我的组件长这样:
const { data, error, loading, run } = useAsync();
useEffect(() => {
run(() => fetchDataFromAPI());
}, []);
甚至更简单:
const { data, loading, error } = useAsync(() => fetchDataFromAPI(), []);
10行代码变成2行,这就是降维打击。
很多人问我,这个Hook到底做了什么黑魔法?
答案很简单:它把所有React开发者都会犯的错误,提前帮你避免了。
让我们剖析一下核心实现:
function useAsync(fn, deps = []) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
// 关键点1:useCallback避免无限重渲染
const run = useCallback(async () => {
setLoading(true);
setError(null); // 关键点2:重置错误状态
try {
const result = await fn();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false); // 关键点3:无论如何都要重置loading
}
}, deps);
useEffect(() => {
if (fn) run();
}, [run]);
return { data, error, loading, run };
}
这就是为什么大厂面试官喜欢考察自定义Hook的原因——它体现了你对React机制的深度理解。
评论区总有人问:*"既然有React Query、SWR这些成熟方案,为什么还要重复造轮子?"*
这个问题问得好。让我告诉你真相:
工具没有对错,只有合适不合适。
在字节的时候,我见过用React Query管理一个简单表单提交的代码——7个文件,200行配置,就为了发一个POST请求。
这不是工程化,这是过度工程化。
让我用一个真实案例展示这个Hook的威力。
改造前(屎山版):
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [updating, setUpdating] = useState(false);
const [updateError, setUpdateError] = useState(null);
useEffect(() => {
setLoading(true);
setError(null);
fetchUser(userId)
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]);
const handleUpdate = async (data) => {
setUpdating(true);
setUpdateError(null);
try {
const updatedUser = await updateUser(userId, data);
setUser(updatedUser);
} catch (err) {
setUpdateError(err);
} finally {
setUpdating(false);
}
};
if (loading) return<div>Loading...</div>;
if (error) return<div>Error: {error.message}</div>;
return (
<div>
<h1>{user?.name}</h1>
<button
onClick={() => handleUpdate({...})}
disabled={updating}
>
{updating ? 'Updating...' : 'Update'}
</button>
{updateError && <div>Update failed: {updateError.message}</div>}
</div>
);
}
改造后(艺术品版):
function UserProfile({ userId }) {
const {
data: user,
loading,
error
} = useAsync(() => fetchUser(userId), [userId]);
const {
loading: updating,
error: updateError,
run: updateUser
} = useAsync();
const handleUpdate = (data) => {
updateUser(() => updateUser(userId, data));
};
if (loading) return<div>Loading...</div>;
if (error) return<div>Error: {error.message}</div>;
return (
<div>
<h1>{user?.name}</h1>
<button onClick={() => handleUpdate({...})} disabled={updating}>
{updating ? 'Updating...' : 'Update'}
</button>
{updateError && <div>Update failed: {updateError.message}</div>}
</div>
);
}
从40行压缩到25行,逻辑更清晰,可读性翻倍。
基础版本已经能满足80%的场景,但如果你想要更极致的体验,这里有几个进阶优化:
function useAsync(fn, deps = []) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const cancelRef = useRef();
const run = useCallback(async () => {
// 取消上一次请求
cancelRef.current?.cancel();
const cancelToken = { cancel: () => {} };
cancelRef.current = cancelToken;
setLoading(true);
setError(null);
try {
const result = await fn(cancelToken);
if (!cancelToken.cancelled) {
setData(result);
}
} catch (err) {
if (!cancelToken.cancelled) {
setError(err);
}
} finally {
if (!cancelToken.cancelled) {
setLoading(false);
}
}
}, deps);
useEffect(() => {
return() => cancelRef.current?.cancel();
}, []);
// 其他逻辑...
}
const run = useCallback(async (optimisticData) => {
if (optimisticData) {
setData(optimisticData);
}
// 执行真实请求...
}, deps);
经过一年多的实践,我总结出几个不适用的场景:
记住:银弹不存在,合适的工具解决合适的问题。
有人说我有代码洁癖。
我承认。
看到重复的样板代码,我浑身难受。看到意大利面条式的useEffect,我夜不能寐。
但这种"洁癖"让我成为了更好的开发者。
useAsync只是开始。当你开始思考如何抽象重复逻辑、如何让代码更优雅时,你就已经从"代码民工"进化成了"代码艺术家"。
你的项目里还在用着什么"屎山代码"?
有没有被这种重复的异步状态管理折磨过?
还是说,你也有自己的"银弹Hook"想分享?
评论区见真章。顺便点个赞,让更多被异步状态折磨的同行看到这个解决方案。
毕竟,拯救一个程序员的理智,功德无量。