5 个容易犯的 useState 错误,别再犯了

5 个容易犯的 useState 错误,别再犯了
虽然 useState 是⼀个简单易⽤的⼯具,但仍有许多开发⼈员在使⽤它时犯了错误。在代码审查
中,我经常看到即使是有经验的开发⼈员也会犯这些错误。
在本⽂中,我将通过简单实⽤的⽰例向您展⽰如何避免这些错误。
错误地获取上⼀个值
在使⽤ setState 时,可以将上⼀个状态作为回调的参数进⾏访问。不使⽤它可能会导致意外的
状态更新。我们将通过⼀个典型的计数器⽰例来说明这个错误。JavaScript
1 import { useCallback, useState } from "react";
2
3 export default function App() {
4 const [counter, setCounter] = useState(0);
5
6 const handleIncrement = useCallback(() => {
7 setCounter(counter + 1);
8 }, [counter]);
9
10 const handleDelayedIncrement = useCallback(() => {
11 // 这⾥的counter +1 就是⼀个问题当setTimeout进⾏回调的时候 counter值可能已
经变化了
12 setTimeout(() => setCounter(counter + 1), 1000);
13 }, [counter]);
14
15 return (
16 <div>
17 <h1>{`Counter is ${counter}`}</h1>
18 {/* This handler works just fine */}
19 <button onClick={handleIncrement}>Instant increment</button>
20 {/* Multi-clicking that handler causes unexpected states updates
*/}
21 <button onClick={handleDelayedIncrement}>Delayed increment</butto
n>
22 </div>
23 );
24 }
现在让我们在设置状态时使⽤回调函数。请注意,这也将帮助我们从 useCallback 中删除不必
要的依赖项。请记住这个解决⽅案!这个问题在⾯试中经常被问到。JavaScript
1 import { useCallback, useState } from "react";
2
3 export default function App() {
4 const [counter, setCounter] = useState(0);
5
6 const handleIncrement = useCallback(() => {
7 setCounter((prev) => prev + 1);
8 // Dependency removed!
9 }, []);
10
11 const handleDelayedIncrement = useCallback(() => {
12 // 使⽤回调函数有效的帮我们解决state状态不⼀致的问题
13 setTimeout(() => setCounter((prev) => prev + 1), 1000);
14 // Dependency removed!
15 }, []);
16
17 return (
18 <div>
19 <h1>{`Counter is ${counter}`}</h1>
20
21 <button onClick={handleIncrement}>Instant increment</button>
22 <button onClick={handleDelayedIncrement}>Delayed increment</butto
n>
23 </div>
24 );
25 }
在 useState 中存储全局状态
useState 只适合存储组件的局部状态。这可以包括输⼊值、切换标志等。全局状态属于整个应
⽤程序,不仅仅与⼀个特定的组件相关。如果您的数据在多个⻚⾯或⼩部件中使⽤,请考虑将
其放⼊全局状态中(如 React Context、Redux、MobX 等)。让我们通过⼀个⽰例来说明。这个⽰例⾮常简单,但是假设我们即将拥有⼀个更加复杂的应⽤
程序。因此,组件层次将⾮常深,⽤⼾状态将在整个应⽤程序中使⽤。在这种情况下,我们应
该将我们的状态分离到全局范围,这样它可以轻松地从应⽤程序的任何地⽅访问(⽽且我们不
必将 props 传递到 20-40 级别)。
JavaScript
1 import React, { useState } from "react";
2
3 // Passing props
4 function PageFirst(user) {
5 return user.name;
6 }
7
8 // Passing props
9 function PageSecond(user) {
10 return user.surname;
11 }
12
13 export default function App() {
14 // User state将会到处被使⽤,⽽且组件嵌套层级也会很深
15 const [user] = useState({ name: "Pavel", surname: "Pogosov" });
16
17 return (
18 <>
19 <PageFirst user={user} />
20 <PageSecond user={user} />
21 </>
22 );
23 }
在这⾥,我们应该优先使⽤全局状态,⽽不是使⽤局部状态。让我们使⽤ React Context 重写
这个⽰例。JavaScript
1 import React, { createContext, useContext, useMemo, useState } from "re
act";
2
3 // Created context
4 const UserContext = createContext();
5
6 // That component separates user context from app, so we don't pollute
it
7 function UserContextProvider({ children }) {
8 const [name, setName] = useState("Pavel");
9 const [surname, setSurname] = useState("Pogosov");
10
11 // We want to remember value reference, otherwise we will have unnece
ssary rerenders
12 const value = useMemo(() => {
13 return {
14 name,
15 surname,
16 setName,
17 setSurname
18 };
19 }, [name, surname]);
20
21 return <UserContext.Provider value={value}>{children}</UserContext.Pr
ovider>;
22 }
23
24 function PageFirst() {
25 const { name } = useContext(UserContext);
26
27 return name;
28 }
29
30 function PageSecond() {
31 const { surname } = useContext(UserContext);
32
33 return surname;
34 }35
36 export default function App() {
37 return (
38 <UserContextProvider>
39 <PageFirst />
40 <PageSecond />
41 </UserContextProvider>
42 );
43 }
现在我们可以轻松地从应⽤程序的任何部分访问全局状态。这⽐使⽤纯 useState 要⽅便和清晰
得多。
忘记初始化状态
这个错误可能会在代码执⾏过程中引起错误。您可能已经看到了这种类型的错误,它被命名为
“⽆法读取未定义的属性”。JavaScript
1 import React, { useEffect, useState } from "react";
2
3 // Fetch users func. I don't handle error here, but you should always d
o it!
4 async function fetchUsers() {
5 const usersResponse = await fetch(
6 `https://jsonplaceholder.typicode.com/users`
7 );
8 const users = await usersResponse.json();
9
10 return users;
11 }
12
13 export default function App() {
14 // No initial state here, so users === undefined, until setUsers
15 const [users, setUsers] = useState();
16
17 useEffect(() => {
18 fetchUsers().then(setUsers);
19 }, []);
20
21 return (
22 <div>
23 {/* Error, can't read properties of undefined */}}
24 {users.map(({id, name, email}) => (
25 <div key={id}>
26 <h4>{name}</h4>
27 <h6>{email}</h6>
28 </div>
29 ))}
30 </div>
31 );
32 }纠正这个错误和犯这个错误⼀样容易!我们应该将我们的状态设置为⼀个空数组。如果您想不
出任何初始状态,您可以放置 null 并处理它。
JavaScript
1 import React, { useEffect, useState } from "react";
2
3 async function fetchUsers() {
4 const response = await fetch(
5 `https://jsonplaceholder.typicode.com/users`
6 );
7 const users = await response.json();
8
9 return users;
10 }
11
12 export default function App() {
13 // If it doesn't cause errors in your case, it's still a good tone t
o always initialize it (even with null)
14 const [users, setUsers] = useState([]);
15
16 useEffect(() => {
17 fetchUsers().then(setUsers);
18 }, []);
19
20 // 如果想要有更好的⽤⼾体验可以加上loading
21 // if (users.length === 0) return <Loading />
22
23 return (
24 <div>
25 {users.map(({id, name, email}) => (
26 <div key={id}>
27 <h4>{name}</h4>
28 <h6>{email}</h6>
29 </div>
30 ))}
31 </div>
32 );
33 }改变属性值⽽不是返回新的状态
在任何时候都不应该改变 state 对象的属性值,因为 react 更新的时候对于复杂数据类型是做的
浅⽐较。JavaScript
1 import { useCallback, useState } from "react";
2
3 export default function App() {
4 // Initialize State
5 const [userInfo, setUserInfo] = useState({
6 name: "Pavel",
7 surname: "Pogosov"
8 });
9
10 // field is either name or surname
11 const handleChangeInfo = useCallback((field) => {
12 // e is input onChange event
13 return (e) => {
14 setUserInfo((prev) => {
15 // Here we are mutating prev state.
16 // That simply won't work as React doesn't recognise the change
17 prev[field] = e.target.value;
18
19 return prev;
20 });
21 };
22 }, []);
23
24 return (
25 <div>
26 <h2>{`Name = ${userInfo.name}`}</h2>
27 <h2>{`Surname = ${userInfo.surname}`}</h2>
28
29 <input value={userInfo.name} onChange={handleChangeInfo("name")}
/>
30 <input value={userInfo.surname} onChange={handleChangeInfo("surna
me")} />
31 </div>
32 );
33 }解决⽅法⾮常简单。我们应该避免改变属性值,⽽是返回⼀个新状态。
JavaScript
1 import { useCallback, useState } from "react";
2
3 export default function App() {
4 const [userInfo, setUserInfo] = useState({
5 name: "Pavel",
6 surname: "Pogosov"
7 });
8
9 const handleChangeInfo = useCallback((field) => {
10 return (e) => {
11 // Now it works!
12 setUserInfo((prev) => ({
13 // So when we update name, surname stays in state and vice vers
a
14 ...prev,
15 [field]: e.target.value
16 }));
17 };
18 }, []);
19
20 return (
21 <div>
22 <h2>{`Name = ${userInfo.name}`}</h2>
23 <h2>{`Surname = ${userInfo.surname}`}</h2>
24
25 <input value={userInfo.name} onChange={handleChangeInfo("name")}
/>
26 <input value={userInfo.surname} onChange={handleChangeInfo("surna
me")} />
27 </div>
28 );
29 }Hooks 逻辑的复制粘贴
所有的 React hooks 都是可组合的,意味着它们可以组合在⼀起来封装特定的逻辑。这使您可
以构建⾃定义 hooks,然后在整个应⽤程序中使⽤它们。
看⼀下下⾯的⽰例。对于这个简单的逻辑来说,它不是有点冗余吗?
JavaScript
1 import React, { useCallback, useState } from "react";
2
3 export default function App() {
4 const [name, setName] = useState("");
5 const [surname, setSurname] = useState("");
6
7 const handleNameChange = useCallback((e) => {
8 setName(e.target.value);
9 }, []);
10
11 const handleSurnameChange = useCallback((e) => {
12 setSurname(e.target.value);
13 }, []);
14
15 return (
16 <div>
17 <input value={name} onChange={handleNameChange} />
18 <input value={surname} onChange={handleSurnameChange} />
19 </div>
20 );
21 }
我们如何简化我们的代码?基本上,我们在这⾥做了两次相同的事情Ì声明局部状态,并处
理 onChange 事件。这可以轻松地分离为⼀个⾃定义 hook,让我们称其为 useInput!JavaScript
1 import React, { useCallback, useState } from "react";
2
3 function useInput(defaultValue = "") {
4 // We declare this state only once!
5 const [value, setValue] = useState(defaultValue);
6
7 // We write this handler only once!
8 const handleChange = useCallback((e) => {
9 setValue(e.target.value);
10 }, []);
11
12 // Cases when we need setValue are also possible
13 return [value, handleChange, setValue];
14 }
15
16 export default function App() {
17 const [name, onChangeName] = useInput("Pavel");
18 const [surname, onChangeSurname] = useInput("Pogosov");
19
20 return (
21 <div>
22 <input value={name} onChange={onChangeName} />
23 <input value={surname} onChange={onChangeSurname} />
24 </div>
25 );
26 }
我们将输⼊逻辑分离到了专⽤的 hook 中,现在使⽤起来更加⽅便了。React hooks 是⼀个⾮常
强⼤的⼯具,不要忘记使⽤它们!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值