这5个错误,React开发者经常犯!
前言
大家好,我是倔强青铜三。是一名热情的软件工程师,我热衷于分享和传播IT技术,致力于通过我的知识和技能推动技术交流与创新,欢迎关注我,微信公众号:倔强青铜三。
经过几年的React应用开发实践,我遇到了许多减缓项目开发速度的错误。 React是构建动态用户界面最受欢迎的库之一,但其灵活性也可能导致新开发者犯一些常见错误。 本文将介绍React开发者可能犯的五大错误,并提供实用的技巧来编写更好、更高效的代码。
1. 状态变更
首先,我们来编写一个React组件,该组件用于显示一个项目列表,并添加或移除项目:
import { useState } from "react";
const Home = (props) => {
const [items, setItems] = useState(['item1', 'item2']);
const [itemToAdd, setItemToAdd] = useState('');
function wrongHandleAddItem(item) {
items.push(item);
setItems(items);
}
function goodHandleAddItem(item) {
if (item.length === 0)
return;
const newArray = [...items, item];
setItems(newArray);
setItemToAdd('');
}
function removeItem(item) {
const itemIndex = items.indexOf(item);
if (itemIndex !== -1) {
const newArray = [...items];
newArray.splice(itemIndex, 1);
setItems(newArray);
}
}
return (
<div style={{ padding: '3rem', maxWidth: '12rem' }}>
<div>
{items.map((item, key) => (
<div key={key} style={{ marginTop: '0.5rem', display: 'flex', justifyContent: 'space-between' }}>
{item}
<button onClick={() => removeItem(item)}>-</button>
</div>
))}
</div>
<div style={{ marginTop: '1rem' }}>
<input value={itemToAdd} onChange={event => setItemToAdd(event.target.value)} />
<button onClick={() => wrongHandleAddItem(itemToAdd)} style={{ marginLeft: '1rem' }}>+</button>
</div>
</div>
)
}
export default Home;
这里,我写了两种不同的方法来向items
状态数组中添加项目。让我们一起分析:
function wrongHandleAddItem(item) {
items.push(item);
setItems(items);
}
这个方法首先调用数组的push
函数来添加元素。
然后调用setItems
来更新状态变量。
然而,如果你尝试运行这段代码,它不会工作❌
这段代码违反了React的一个非常重要的规则:状态变更。
React依赖于状态变量的身份来判断状态何时发生变化。当我们向数组中添加一个项目时,我们并没有改变该数组的身份,因此React无法判断值是否已更改,并且不重新渲染数组。
以下是如何修复它✅:
function goodHandleAddItem(item) {
if (item.length === 0)
return;
const newArray = [...items, item];
setItems(newArray);
setItemToAdd('');
}
在这个方法中,我使用spread operator ...
创建了一个新的数组,允许我用items
的内容实例化新数组。第二个参数用于添加新内容(这里是item
)。
最后一步是调用setItems
方法来确认变量items
的新状态✅
2. 列表中未生成key
每个React开发者可能至少在开发过程中看到过一次这个错误。
最常见的情况是在映射数据时发生。这里有一个违反的例子:
items.map((item) => (
<div>
{item}
<button onClick={() => removeItem(item)}>-</button>
</div>
))}
当我们想要渲染一个元素数组时,我们需要给React更多的上下文,以便它能够识别每个项目。在最好的情况下,它应该是一个唯一的标识符。
以下是快速修复的方法,但不是最优的:
items.map((item, index) => (
<div key={index} >
{item}
<button onClick={() => removeItem(item)}>-</button>
</div>
))}
随着你在React中获得更多经验并更好地理解它的工作原理,你将能够根据你的情况判断是否合适。
为了使其完美,可以使用uuid generator
,如crypto.randomUUID()
,将其存储到项目列表中的对象中,如下所示:
const newItemToAdd = {
id: crypto.randomUUID(),
value: item
};
const newArray = [...items, newItemToAdd];
setItems(newArray);
然后在渲染时使用它:
items.map((item, index) => (
<div key={item.id} >
{item.value}
<button onClick={() => removeItem(item)}>-</button>
</div>
))}
现在一切都很完美✅
3. 在useEffect
中使用async
假设我们有一个函数,需要在挂载时从API获取一些数据。我们将使用useEffect
钩子,并希望使用await
关键字。
让我们检查第一次尝试:
正如你所知道的,await
关键字需要在标记有async
关键字的函数中:
useEffect(async () => {
const url = "/api/to/fetch";
const res = await fetch(url);
const json = res.json();
return(json);
}, []);
不幸的是,这不起作用,我们得到这个错误消息:
destroy is not a function
这里是修复方法:在useEffect
钩子内创建一个单独的异步函数✅
useEffect(() => {
async function fetchData() {
const url = "/api/to/fetch";
const res = await fetch(url);
const json = res.json();
return json;
}
fetchData();
}, []);
理解async
关键字的含义非常重要:
它不会返回对象json
,而是返回一个解决对象json
的Promise
。
这实际上是一个问题,因为useEffect
不应该返回一个Promise。它期望我们返回要么什么都没有(就像我们上面的那样),或者是一个清理函数。清理函数很重要,超出了本指南的范围,但这里是如何使用它:
useEffect(() => {
async function fetchData() {
const url = "/api/to/fetch";
const res = await fetch(url);
const json = res.json();
return json;
}
fetchData();
return () => {
// 清理逻辑在这里
}
}, []);
4. 渲染前访问状态
让我们回到状态管理,再讨论一个新开发者经常犯的一个有趣的错误。这将帮助我们更好地理解React状态。
以我们的goodHandleAddItem
方法为例来说明这一点:
function goodHandleAddItem(item) {
if (item.length === 0)
return;
const newArray = [...items, item];
setItems(newArray);
console.log(items);
}
当运行这段代码时,我们可以看到控制台没有记录我们期望的结果。
问题是:状态变量的设置函数是异步的。
当我们调用setItems
方法时,我们实际上是在安排更新,而不是赋值。
这里是修复方法:我们已经知道变量的内容应该是newArray
。这意味着要使用我们想要的items
变量的内容,我们需要使用变量newArray
,即使在setItems
之后✅
5. 使用过时的状态数据
最后一个错误也将涉及React状态管理,你将在本指南后成为专家!🚀
使用React Hooks时的一个常见陷阱是滥用过时的状态数据。这可能发生在我们直接引用状态变量进行连续状态更新时。正如我们在前一个错误中看到的,状态更新可能是异步的,这意味着状态变量在连续调用中引用时可能不反映最新值。
让我们用一个全新的示例来澄清这一点,众所周知的计数器:
const [count, setCount] = useState(0);
setCount(count + 1);
setCount(count + 1);
上述用法是不正确的。实际上,count
是在setCount
调用中直接引用的。在事件处理程序和生命周期方法中,状态更新可以被批处理,并且都将使用相同的初始值作为count
,这将导致最终状态不正确。
我们可以使用另一种形式的setCount
来使事情工作:更新器函数。更新器函数接受前一个状态作为参数,并返回新状态,因此每个连续的更新都将具有正确的值,防止不期望的行为。
以下是如何使用它:
setCount((previousValue) => previousValue + 1);
setCount((previousValue) => previousValue + 1);
现在记录count
的内容显示正确的值✅
结论
避免这些常见错误将使你开发出更高性能的React应用,并掌握状态管理!
如果你喜欢本指南,请留下一个赞,无论你是新的还是有经验的React开发者。