使用useState
使用useReducer
为什么需要useReducer?
-
当组件有很多状态变量和状态更新,这些状态和更新分散在组件的多个事件处理函数中时:
- 在这种情况下,使用
useState
可能会导致状态逻辑分散,难以管理和维护。每个状态都有自己的更新函数,这可能导致代码难以跟踪和调试。
- 在这种情况下,使用
-
当需要在同一时间对多个状态进行更新(作为对同一事件的反应,例如“开始游戏”):
- 如果一个事件需要同时更新多个状态,使用
useState
可能会导致问题,因为每个状态更新都是独立的,可能会引起不一致的状态。例如,如果一个事件同时更新了得分和时间,而这两个状态的更新依赖于彼此的新值,使用useState
可能会导致竞态条件。
- 如果一个事件需要同时更新多个状态,使用
-
当更新一个状态依赖于一个或多个其他状态时:
- 如果状态之间存在依赖关系,使用
useState
可能会导致复杂性增加,因为每个状态更新都是独立的,可能需要在多个事件处理函数之间进行协调。
- 如果状态之间存在依赖关系,使用
useReducer工作流程
reducer如何更新state?
reducer的工作方式非常类似JavaScript中的reduce方法,随着时间的退役,reducer会将所有的action累积为一种状态。
银行类比
如果你要去银行取钱的话,你不会直接去银行的金库里面取钱,而是有一个柜员,你 告诉他你需要取多少钱,然后哪个柜员会进行一系列操作,将钱从金库取出来,然后再交给你。
react中的state就相当于银行金库——其中的钱会被更新;
dispacher就相当于取银行要钱的“你”;
reducer就相当于银行柜员;
action就相当与“你”给柜员的信息——柜员是如何知道要取多少钱的?当然是“你”告诉他的;相当于通过dispatcher才能知道reducer中的action值是多少;
useReducer vs useState
使用useRef
介绍
useRef
是一个特殊的钩子,它用于创建一个可变的引用对象,其 .current
属性在组件的整个生命周期内持续存在,即使在组件重新渲染时也不会被重置。这与常规变量不同,常规变量在每次渲染时都会被重置。
使用场景
-
创建一个在渲染之间保持不变的变量:
useRef
可以用来存储那些在组件重新渲染时不应该改变的值,比如前一个状态、setTimeout
的 ID 等。这些值存储在.current
属性中,可以在组件的任何地方访问和修改。
-
选择和存储 DOM 元素:
useRef
常用于获取对 DOM 元素的引用。通过将useRef
创建的引用与 DOM 元素的ref
属性关联,可以直接访问和操作该 DOM 元素。
注意: 尽管可以通过.current
属性读写引用的值,但不应该在组件的渲染逻辑中这样做,因为这可能会导致性能问题或难以追踪的 bug。.current
应该用于那些与渲染无关的逻辑。
案例
ref vs state
使用url管理state
具体的实现看React-Route的实现
使用Context API
为什么要用Context API?
管理全局State,更好地解决“prop drilling”问题。
工作原理
简单使用
- 创建Context
const PostContext = createContext();
- 创建provider,提供Context中的value
<PostContext.Provider
value={{
posts: searchedPosts,
onAddPost: handleAddPost,
onClearPosts: handleClearPosts,
searchQuery,
setSearchQuery,
}}
>
<section>
<button
onClick={() => setIsFakeDark((isFakeDark) => !isFakeDark)}
className="btn-fake-dark-mode"
>
{isFakeDark ? "☀️" : "🌙"}
</button>
<Header />
<Main />
<Archive />
<Footer />
</section>
</PostContext.Provider>
- 使用Context中的value
const { onClearPosts } = useContext(PostContext);
const { searchQuery, setSearchQuery } = useContext(PostContext);
const { posts } = useContext(PostContext);
const { onAddPost } = useContext(PostContext);
const { posts } = useContext(PostContext);
const { onAddPost } = useContext(PostContext);
进一步的使用
将Context的Provider与相关的state都放在一个文件中。然后自定义一个hook,实现“useContext(PostContext)”的功能,并且在必要时抛出错误,避免使用该hook使用位置不对。
import { createContext, useContext, useState } from "react";
import { faker } from "@faker-js/faker";
function createRandomPost() {
return {
title: `${faker.hacker.adjective()} ${faker.hacker.noun()}`,
body: faker.hacker.phrase(),
};
}
const PostContext = createContext();
function PostProvider({ children }) {
const [posts, setPosts] = useState(() =>
Array.from({ length: 30 }, () => createRandomPost())
);
const [searchQuery, setSearchQuery] = useState("");
// Derived state. These are the posts that will actually be displayed
const searchedPosts =
searchQuery.length > 0
? posts.filter((post) =>
`${post.title} ${post.body}`
.toLowerCase()
.includes(searchQuery.toLowerCase())
)
: posts;
function handleAddPost(post) {
setPosts((posts) => [post, ...posts]);
}
function handleClearPosts() {
setPosts([]);
}
return (
<PostContext.Provider
value={{
posts: searchedPosts,
onAddPost: handleAddPost,
onClearPosts: handleClearPosts,
searchQuery,
setSearchQuery,
}}
>
{children}
</PostContext.Provider>
);
}
function usePosts() {
const context = useContext(PostContext);
return context;
}
export { PostProvider, usePosts, createRandomPost };
使用Porvider时如下所示:
<section>
<button
onClick={() => setIsFakeDark((isFakeDark) => !isFakeDark)}
className="btn-fake-dark-mode"
>
{isFakeDark ? "☀️" : "🌙"}
</button>
<PostProvider>
<Header />
<Main />
<Archive />
<Footer />
</PostProvider>
</section>
使用Provider中的value就可以使用自定义的hook
const { onClearPosts } = usePosts();
const { searchQuery, setSearchQuery } = usePosts();
const { posts } = usePosts();
const { onAddPost } = usePosts();
const { posts } = usePosts();
const { onAddPost } = usePosts();
与UseReducer结合使用
import { createContext, useContext, useReducer } from "react";
const AuthContext = createContext();
const initialState = {
user: null,
isAuthenticated: false,
};
const FAKE_USER = {
name: "Jack",
email: "jack@example.com",
password: "qwerty",
avatar: "https://i.pravatar.cc/100?u=zz",
};
function reducer(state, action) {
switch (action.type) {
case "login":
return {
...state,
user: action.payload,
isAuthenticated: true,
};
case "logout":
return {
...state,
user: null,
isAuthenticated: false,
};
default:
throw new Error("NO THAT TYPE");
}
}
function AuthProvider({ children }) {
const [{ user, isAuthenticated }, dispatch] = useReducer(
reducer,
initialState
);
function login(email, password) {
if (email === FAKE_USER.email && password === FAKE_USER.password) {
dispatch({ type: "login", payload: FAKE_USER });
}
}
function logout() {
dispatch({ type: "logout" });
}
return (
<AuthContext.Provider value={{ user, isAuthenticated, login, logout }}>
{children}
</AuthContext.Provider>
);
}
function useAuth() {
const values = useContext(AuthContext);
if (values === undefined) {
throw new Error("AuthContext was used outside AuthProvider");
}
return values;
}
export { AuthProvider, useAuth };