React 在 Web 开发领域已经资格不浅了,近年来它作为敏捷 Web 开发工具的角色愈加深入人心。特别是新的 hook API/ 概念发布之后,用 React 编写组件变得非常简单。
尽管 React 背后的团队和庞大的社区在努力推广普及这一框架的相关理念,但很多人在使用它时还是经常会遇到一些陷阱,犯一些常见的错误。我把过去几年中见过的所有 hook 相关的错误用法总结成了一个列表。在本文中,我想向大家展示其中一些最常见的错误,详细解释为什么我认为这些用法不对,并给出较简洁的正确方法的建议。
开始以前我必须声明,下面列举的这些事情大都不是根本性的错误,或者初看上去没什么问题,也不大可能影响应用程序的性能或外观。除了产品的开发人员外,也许没人会注意到这里有些问题。但是我仍然相信,高质量的代码可以带来更好的开发体验,进而打造出更好的产品。
与其他任何软件框架或库一样,这里的不同意见数不胜数。本文的所有内容都基于我的个人观点,不应视为一般性规则。如果你有不同的看法,我洗耳恭听。
React 的一个核心概念是处理状态。你可以通过状态控制整个数据流和渲染过程。每次树被重新渲染时,很可能是因为状态的变化。
使用 useState hook,你现在还可以在函数组件中定义状态,这种方法可以真正简洁地在 React 中处理状态。但正如以下示例所示,它也可能被滥用。
关于下面这个示例我们需要说明一下。假设我们有两个按钮,一个按钮是计数器,另一个按钮使用当前计数发送请求或触发动作。但是,当前编号永远不会显示在组件内。当你单击第二个按钮时才需要这个请求。
复制代码
function ClickButton(props) {
const [count, setCount] = useState(0);
const onClickCount = () => {
setCount((c) => c + 1);
};
const onClickRequest = () => {
apiCall(count);
};
return (
);
}
如果要在组件内部使用一个变量,希望该变量在渲染之间保持其值,但又不强制重新渲染,则可以使用 useRef hook。它将保留值,但不强制重新渲染组件。
复制代码
function ClickButton(props) {
const count = useRef(0);
const onClickCount = () => {
count.current++;
};
const onClickRequest = () => {
apiCall(count.current);
};
return (
);
}
假设你要编写一个按钮,单击该按钮应将用户重定向到另一个页面。由于它是一个 SPA,因此这个动作是客户端路由机制。于是你需要某种库来执行此动作。在 React 中最流行的是 react-router,下面的示例就会使用它。
所以,添加一个点击侦听器会将用户重定向到所需的页面,对吗?
复制代码
function ClickButton(props) {
const history = useHistory();
const onClick = () => {
history.push('/next-page');
};
return ;
}
只要指向其他页面的链接带有某种用户交互,就要尽量用 < Link> 组件或常规的 < a> 标签处理。
复制代码
function ClickButton(props) {
return (
Go to next page
);
}
3. 通过 useEffect 处理动作优点:这也使代码更易读,更短!
React 引入的最好用,最贴心的一个 hook 是 useEffect。它可以处理与 prop 或 state 更改相关的动作。可就算它很好用,人们也不该到处滥用它。
想象一下有一个组件,其获取一个项目列表并将其渲染给 dom。另外,如果请求成功,我们将调用“onSuccess”函数,该函数作为一个 prop 传递给这个组件。
复制代码
function DataList({ onSuccess }) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState(null);
const fetchData = useCallback(() => {
setLoading(true);
callApi()
.then((res) => setData(res))
.catch((err) => setError(err))
.finally(() => setLoading(false));
}, []);
useEffect(() => {
fetchData();
}, [fetchData]);
useEffect(() => {
if (!loading && !error && data) {
onSuccess();
}
}, [loading, error, data, onSuccess]);
return Data: {data};
}
对第一个调用来说这肯定是正确的,并且可能永远不会失败。但你也失去了动作和需要调用的函数之间的直接联系。同样也没有 100%的保证可以说这种情况仅在 fetch 动作成功后才会发生,而这正是我们开发人员不想看到的。
一个简单明了的解决方案是将“onSuccess”函数设置为调用成功的实际位置:
复制代码
function DataList({ onSuccess }) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState(null);
const fetchData = useCallback(() => {
setLoading(true);
callApi()
.then((fetchedData) => {
setData(fetchedData);
onSuccess();
})
.catch((err) => setError(err))
.finally(() => setLoading(false));
}, [onSuccess]);
useEffect(() => {
fetchData();
}, [fetchData]);
return {data};
}
4. 单一责任组件现在一目了然了,在 api 调用成功的情况下才调用 onSuccess。
组合组件可能不是什么轻松的事情。什么时候将一个组件拆分为几个较小的组件?如何构造组件树?使用基于组件的框架时,每天都会遇到这些问题。设计组件时常见的一个错误是将两个用例合并到一个组件中。以一个 header 为例,其在移动设备上显示一个汉堡按钮,或在桌面屏幕上显示标签。(这里的条件通过神奇的 isMobile 函数处理,这里就不深入讲解了。)
复制代码
function Header(props) {
return (
);
}
function HeaderInner({ menuItems }) {
return isMobile() ? : ;
}
将条件提高一级,这样就能更容易看清组件的本来用途,搞明白它们只应该负责一个任务,不管是 Header、Tab 或 BurgerButton 也好,总之不要一心多用。
复制代码
function Header(props) {
return (
{isMobile() ? : }
);
}
但是有时因为粗心而让“useEffect”身兼数职,就会带回那些黑暗的回忆。例如,假设你有一个组件以某种方式从后端获取一些数据,并且还会根据当前位置显示面包屑。(再次使用 react-router 获取当前位置。)
复制代码
function Example(props) {
const location = useLocation();
const fetchData = useCallback(() => {
/* Calling the api */
}, []);
const updateBreadcrumbs = useCallback(() => {
/* Updating the breadcrumbs*/
}, []);
useEffect(() => {
fetchData();
updateBreadcrumbs();
}, [location.pathname, fetchData, updateBreadcrumbs]);
return (
);
}
把效果拆分开来,确保它们只用于一种效果,意外的副作用也就消失了。
复制代码
function Example(props) {
const location = useLocation();
const updateBreadcrumbs = useCallback(() => {
/* Updating the breadcrumbs*/
}, []);
useEffect(() => {
updateBreadcrumbs();
}, [location.pathname, updateBreadcrumbs]);
const fetchData = useCallback(() => {
/* Calling the api */
}, []);
useEffect(() => {
fetchData();
}, [fetchData]);
return (
);
}
小结额外的收获是,这些用例现在也在组件内按顺序排好了。
在 React 中编写组件时有很多陷阱。我们不可能百分百地了解整个机制并避开所有小错,就算是大错误也可能逃不开。但是在学习框架或编程语言时犯错误也是很重要的,可能没有人会 100%摆脱这些错误。
我认为与他人分享你的经验是很有意义的,这样别人就可以避开这些坑了。
如果你有任何疑问,请写信给我([email protected]),我很想听听你的意见。
原文链接:
https://www.lorenzweiss.de/common_mistakes_react_hooks/