错误地使用 React 的五种方式,会导致你被解雇|useState

虽然是一个简单的工具,但 useState 许多开发人员仍然会犯错误。在代码审查期间,我经常看到即使是有经验的人也会犯这些错误。在本文中,我将通过简单实用的示例向您展示如何避免它们。

 

错误地获取以前的值

使用 setState 时,可以将以前的状态作为回调的参数进行访问。不使用它可能会导致意外的状态更新。我们将用一个典型的反例来分解这个错误。

import { useCallback, useState } from "react";
export default function App() {
  const [counter, setCounter] = useState(0);
  const handleIncrement = useCallback(() => {
    setCounter(counter + 1);
  }, [counter]);
  const handleDelayedIncrement = useCallback(() => {
    // counter + 1 is the problem,
    // because the counter can be already different, when callback invokes
    setTimeout(() => setCounter(counter + 1), 1000);
  }, [counter]);
  return (
    <div>
      <h1>{`Counter is ${counter}`}</h1>
      {/* This handler works just fine */}
      <button onClick={handleIncrement}>Instant increment</button>
      {/* Multi-clicking that handler causes unexpected states updates */}
      <button onClick={handleDelayedIncrement}>Delayed increment</button>
    </div>
  );
}

现在让我们在设置状态时使用回调。请注意,它还将帮助我们从useCallback中删除不必要的依赖。请记住解决方案!这个问题在面试中经常被问到)

import { useCallback, useState } from "react";
export default function App() {
  const [counter, setCounter] = useState(0);
  const handleIncrement = useCallback(() => {
    setCounter((prev) => prev + 1);
    // Dependency removed!
  }, []);
  const handleDelayedIncrement = useCallback(() => {
    // Using prev state helps us to avoid unexpected behaviour
    setTimeout(() => setCounter((prev) => prev + 1), 1000);
    // Dependency removed!
  }, []);
  return (
    <div>
      <h1>{`Counter is ${counter}`}</h1>
      <button onClick={handleIncrement}>Instant increment</button>
      <button onClick={handleDelayedIncrement}>Delayed increment</button>
    </div>
  );
}

存储全局状态使用状态

useState 仅适用于存储组件的本地状态。这可能包括输入值、切换标志等。全局状态属于整个应用,它不仅仅与一个特定组件相关。如果你的数据在多个页面或小部件中使用,请考虑将其置于全局状态(React Context、Redux、MobX 等)。

让我们来看看这个例子。这真的很简单,但让我们假设我们很快就会有一个更复杂的应用程序。因此,组件层次结构将非常深入,用户状态将在整个应用程序中使用。在这种情况下,我们应该将我们的状态划分到全局范围内,以便可以从应用程序的任何点轻松访问它(我们不必向下传递 props 20-40 级)。

import React, { useState } from "react";
// Passing props
function PageFirst(user) {
  return user.name;
}
// Passing props
function PageSecond(user) {
  return user.surname;
}
export default function App() {
  // User state will be used all over the app. We should replace useState
  const [user] = useState({ name: "Pavel", surname: "Pogosov" });
  return (
    <>
      <PageFirst user={user} />
      <PageSecond user={user} />
    </>
  );
}

与其在这里使用本地状态,我们应该首选全局状态。让我们使用 React 上下文重写该示例。

import React, { createContext, useContext, useMemo, useState } from "react";
// Created context
const UserContext = createContext();
// That component separates user context from app, so we don't pollute it
function UserContextProvider({ children }) {
  const [name, setName] = useState("Pavel");
  const [surname, setSurname] = useState("Pogosov");
  // We want to remember value reference, otherwise we will have unnecessary rerenders
  const value = useMemo(() => {
    return {
      name,
      surname,
      setName,
      setSurname
    };
  }, [name, surname]);
  return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}
function PageFirst() {
  const { name } = useContext(UserContext);
  return name;
}
function PageSecond() {
  const { surname } = useContext(UserContext);
  return surname;
}
export default function App() {
  return (
    <UserContextProvider>
      <PageFirst />
      <PageSecond />
    </UserContextProvider>
  );
}

忘记初始化状态

这可能会(并且可能会)在代码执行期间导致错误。您可能已经看到这种类型的错误,它被命名为“无法读取未定义的属性

import React, { useEffect, useState } from "react";
// Fetch users func. I don't handle error here, but you should always do it!
async function fetchUsers() {
  const usersResponse = await fetch(
    `https://jsonplaceholder.typicode.com/users`
  );
  const users = await usersResponse.json();
  return users;
}
export default function App() {
  // No initial state here, so users === undefined, until setUsers
  const [users, setUsers] = useState();
  useEffect(() => {
    fetchUsers().then(setUsers);
  }, []);
  return (
    <div>
      {/* Error, can't read properties of undefined */}}
      {users.map(({id, name, email}) => (
        <div key={id}>
          <h4>{name}</h4>
          <h6>{email}</h6>
        </div>
      ))}
    </div>
  );

纠正和犯错误一样容易!我们应该将状态设置为空数组。如果你想不出任何初始状态,你可以放置 null 并处理它。

import React, { useEffect, useState } from "react";
async function fetchUsers() {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/users`
  );
  const users = await response.json();
  return users;
}
export default function App() {
  // If it doesn't cause errors in your case, it's still a good tone to always initialize it (even with null)
  const [users, setUsers] = useState([]);
  useEffect(() => {
    fetchUsers().then(setUsers);
  }, []);
  // You can also add that check
  // if (users.length === 0) return <Loading />
  return (
    <div>
      {users.map(({id, name, email}) => (
        <div key={id}>
          <h4>{name}</h4>
          <h6>{email}</h6>
        </div>
      ))}
    </div>
  );
}

改变状态而不是返回新状态

你这辈子都不能变异 React 状态!当状态发生变化时,React 会做很多聪明而重要的事情,并且它是根据浅层比较(通过引用而不是值进行比较)来做的。

import { useCallback, useState } from "react";
export default function App() {
  // Initialize State
  const [userInfo, setUserInfo] = useState({
    name: "Pavel",
    surname: "Pogosov"
  });
  // field is either name or surname
  const handleChangeInfo = useCallback((field) => {
    // e is input onChange event
    return (e) => {
      setUserInfo((prev) => {
        // Here we are mutating prev state.
        // That simply won't work as React doesn't recognise the change
        prev[field] = e.target.value;
        return prev;
      });
    };
  }, []);
  return (
    <div>
      <h2>{`Name = ${userInfo.name}`}</h2>
      <h2>{`Surname = ${userInfo.surname}`}</h2>
      <input value={userInfo.name} onChange={handleChangeInfo("name")} />
      <input value={userInfo.surname} onChange={handleChangeInfo("surname")} />
    </div>
  );
}

解决方案非常简单。我们应该避免改变状态,而简单地返回一个新状态。

 具体解决代码,请戳👉:错误地使用 React 的五种方式,会导致你被解雇

posted @ 2023-11-28 10:31  cybozu开发者  阅读(10)  评论(0编辑  收藏  举报