这5个错误,React开发者经常犯!

13 阅读6分钟

这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,而是返回一个解决对象jsonPromise

这实际上是一个问题,因为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开发者。