React中管理state的方式

使用useState

使用useReducer

为什么需要useReducer?

  1. 当组件有很多状态变量和状态更新,这些状态和更新分散在组件的多个事件处理函数中时

    • 在这种情况下,使用 useState 可能会导致状态逻辑分散,难以管理和维护。每个状态都有自己的更新函数,这可能导致代码难以跟踪和调试。
  2. 当需要在同一时间对多个状态进行更新(作为对同一事件的反应,例如“开始游戏”)

    • 如果一个事件需要同时更新多个状态,使用 useState 可能会导致问题,因为每个状态更新都是独立的,可能会引起不一致的状态。例如,如果一个事件同时更新了得分和时间,而这两个状态的更新依赖于彼此的新值,使用 useState 可能会导致竞态条件。
  3. 当更新一个状态依赖于一个或多个其他状态时

    • 如果状态之间存在依赖关系,使用 useState 可能会导致复杂性增加,因为每个状态更新都是独立的,可能需要在多个事件处理函数之间进行协调。

 useReducer工作流程

 reducer如何更新state?

 

 reducer的工作方式非常类似JavaScript中的reduce方法,随着时间的退役,reducer会将所有的action累积为一种状态。

银行类比

 ​​​​

如果你要去银行取钱的话,你不会直接去银行的金库里面取钱,而是有一个柜员,你 告诉他你需要取多少钱,然后哪个柜员会进行一系列操作,将钱从金库取出来,然后再交给你。

react中的state就相当于银行金库——其中的钱会被更新;

dispacher就相当于取银行要钱的“你”;

reducer就相当于银行柜员;

action就相当与“你”给柜员的信息——柜员是如何知道要取多少钱的?当然是“你”告诉他的;相当于通过dispatcher才能知道reducer中的action值是多少;

useReducer vs useState 

使用useRef 

介绍

useRef 是一个特殊的钩子,它用于创建一个可变的引用对象,其 .current 属性在组件的整个生命周期内持续存在,即使在组件重新渲染时也不会被重置。这与常规变量不同,常规变量在每次渲染时都会被重置。

使用场景

  1. 创建一个在渲染之间保持不变的变量

    • useRef 可以用来存储那些在组件重新渲染时不应该改变的值,比如前一个状态、setTimeout 的 ID 等。这些值存储在 .current 属性中,可以在组件的任何地方访问和修改。
  2. 选择和存储 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 };

State管理思考

是否应该使用state?

state的类型

state放置选项

选择相应的工具 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值