JavaScript >> Javascript 文檔 >  >> JavaScript

如何使用 Redux 管理狀態

如何使用 Redux 作為管理應用程序狀態的全局存儲。了解如何在基於 React 的 UI 中使用基於類的組件和通過鉤子的函數式組件與 Redux 存儲進行交互和管理。

開始使用

對於本教程,我們將使用 CheatCode Next.js 樣板作為起點。上面顯示的下面代碼塊的路徑映射到本教程在 Github 上的存儲庫。要訪問該存儲庫,請單擊上面的“在 Github 上查看”按鈕(注意:需要 CheatCode Pro 訂閱才能訪問有關 CheatCode 教程的存儲庫)。

首先,從 Github 克隆 Next.js 樣板的副本:

git clone [email protected]:cheatcode/nextjs-boilerplate.git

然後運行:

cd nextjs-boilerplate && npm install

接下來,如果您要跳過樣板文件或將其構建為另一個應用程序的一部分,則可以選擇安裝 0411

npm i react react-redux

了解 Redux 中的數據流

Redux 的目的是創建一個可以在整個應用程序中訪問的商店(保存數據的地方)。通常,Redux 用於創建一個 global 商店,或者,您的整個應用(而不是特定頁面或組件)都可以訪問的商店。

const store = createStore();

使用 22 創建商店時 從 37 導出的函數 我們在上面安裝的包,它傳遞了另一個稱為 reducer 的函數 . reducer 負責決定如何修改 store 中包含的當前狀態以響應發生的某些操作。

const store = createStore((state = {}, action) => {
  switch (action.type) {
    case "LOGIN":
      return {
        ...state,
        authenticated: true,
        user: action.user,
      };
    case "LOGOUT":
      return {
        ...state,
        authenticated: false,
        user: null,
      };
    default:
      return {
        ...state,
      };
  }
}, {});

在這裡,我們將一個示例 reducer 函數傳遞給 45 .這裡有幾點需要注意。

首先,我們要注意 reducer 函數有兩個參數:546377 這裡的語法是我們為 86 設置默認值 如果其值為 null 或未定義)。

90 這裡的參數包含 current Redux 商店的狀態。 105 參數包含正在調度的當前操作,該操作將對商店的狀態進行更改。

現在,事情變得有趣——並且可能令人困惑——是當我們開始根據一個動作修改我們的狀態時。這裡可能看起來很奇怪的語法是 119 部分(在 JavaScript 中技術上稱為 case-switch 語句):

(state = {}, action) => {
  switch (action.type) {
    case "LOGIN":
      return {
        ...state,
        authenticated: true,
        user: action.user,
      };
    case "LOGOUT":
      return {
        ...state,
        authenticated: false,
        user: null,
      };
    default:
      return {
        ...state,
      };
  }
}

在這裡,為了清楚起見,我們從上面提取了 reducer 函數(相同的代碼)。我們要查看的第一部分是 120 .這就是說“接受 130 並嘗試在此語句中找到它的匹配項。”

這就是 case-switch 語句的工作方式。這個想法是,給定一些值(142 在這種情況下),嘗試找到一個 153 自己的值等於傳遞給 165 的值的語句 .

所以,在這裡,如果我們假設存儲在 170 中的值 等於 180 ,第二個192 此處聲明—204 — 將與 213 之後的代碼匹配 228 後的冒號 將被執行。

在此示例中,我們返回一個 JavaScript 對象,該對象將表示狀態的更新副本。我們說它被更新是因為我們從 switch 中返回的值——最終是我們的 reducer 函數——是一個 copy 原始狀態(記住,這是傳遞給我們的 reducer 函數的第一個參數)。我們說它是一個副本,因為在這裡,我們使用的是 231 JavaScript 中稱為擴展語法的語法。

const state = { food: 'Apple', animal: 'Red Panda' };

console.log(state);

// { food: 'Apple', animal: 'Red Panda' }

const newState = {
  ...state,
  animal: 'Turkey',
};

console.log(newState);
// { food: 'Apple', animal: 'Turkey' }

console.log(state);
// { food: 'Apple', animal: 'Red Panda' }

擴展語法允許我們將一個對象“解包”到另一個對像上。一個很好的類比是,當您將裝著衣服的手提箱帶到酒店並將它們拆開放入酒店房間的抽屜中時。在這裡,手提箱是245259 在我們“拉開拉鍊、打開包裝,然後把衣服搬進酒店的抽屜裡”之前。

這樣做的最終結果是我們得到了一個新對象(我們將現有對象解包到的那個對象)。從那裡,我們可以通過在 262 下添加其他屬性來修改對像中的特定值 .

所以,我們在這裡完成的是獲取我們之前的內容,創建它的副本,然後修改該對像上相對於正在執行的操作的特定屬性。

縮小,然後,我們可以看到 Redux 中 reducer 函數的目標是修改狀態以響應某些操作 .如果我們的 271289 ,我們知道我們要修改狀態以反映當前用戶(以商店的當前狀態表示)已註銷。

那麼,在上面的例子中,我們創建了當前 295 的副本 然後設置 307317327338 .因為我們在這裡返回一個對象,作為 345 的一部分 語句的行為,該返回值將“冒泡”到我們的 reducer 函數的主體並從 reducer 函數返回。然後,reducer 函數返回的任何內容都將成為 store 的新狀態。

為全局狀態定義存儲

讓我們更具體一點。接下來,我們將為我們的應用程序創建一個全局商店,它將為購物車保存一些物品。稍後,我們將為購物車創建一個 React 組件,我們將從該組件將事件分派到全局商店。

首先,讓我們在之前克隆的樣板文件中創建我們的全局存儲:

/lib/appStore.js

import { createStore } from "redux";

const appStore = createStore((state = {}, action) => {
  // We'll define the functionality for our reducer here.
}, {
  cart: [],
});

export default appStore;

與我們之前了解的類似,我們正在使用 354 為我們的應用創建一個 Redux 商店 從 365 導入的方法 包(包含在您克隆的樣板文件中,或者如果您選擇,早先手動安裝)。

在這裡,而不是使用通用名稱 371 對於存儲我們商店的變量,我們使用名稱 383 反映其內容(我們整個應用程序的全局狀態)。如果我們跳到文件的底部,我們會看到我們的 393 .稍後當我們將 store 連接到我們的主 406 時,這將派上用場 組件。

我們對之前看到的代碼所做的一個重大更改是我們將另一個參數傳遞給我們的 417 稱呼。作為第二個參數(除了我們的 reducer 函數),我們傳遞了一個表示 default 的 JavaScript 對象 我們商店的狀態。雖然我們不必這樣做,但這是一種使用數據初始化商店的便捷方式。

為您的全局狀態存儲定義減速器

接下來,我們需要構建我們的 reducer 函數來決定當我們的 store 收到一個 action 時會發生什麼:

/lib/appStore.js

import { createStore } from "redux";

const appStore = createStore(
  (state = {}, action) => {
    switch (action.type) {
      case "ADD_TO_CART":
        return {
          ...state,
          cart: [...state.cart, action.item],
        };
      case "REMOVE_FROM_CART":
        return {
          ...state,
          cart: [...state.cart].filter(({ _id }) => {
            return _id !== action.itemId;
          }),
        };
      case "CLEAR_CART":
        return {
          ...state,
          cart: [],
        };
      default:
        return {
          ...state,
        };
    }
  },
  {
    cart: [],
  }
);

export default appStore;

將我們之前學到的知識應用起來,這裡我們介紹了一個 case-switch 語句,它接收 427 並定義了一系列436 聲明來決定我們將做出哪些改變(如果有的話)。

在這裡,我們定義了四個 441 語句和一個 454 案例:

  • 462 479 用戶將商品添加到購物車時的操作。
  • 486 496 用戶從購物車中移除商品時的操作。
  • 500 516 用戶清除購物車中所有商品時的操作。

對於每個 526 ,我們使用與之前看到的類似的模式。我們返回一個 JavaScript 對象,其中包含我們現有 539 的副本 然後進行必要的修改。

因為我們正在構建一個購物車,所以我們關注的值是 541 可以預見,其中包含當前購物車中的商品。

查看555 在這種情況下,我們創建一個狀態副本,然後設置 568 屬性等於包含現有 575 的數組 (如果有的話)到數組。接下來,我們預計我們的 583 將傳遞一個 598 除了我們的類型並將該項目連接或附加到數組的末尾。此處的最終結果是我們將購物車中的現有商品取出並在最後添加新商品。

將相同的邏輯應用於 602 在這種情況下,我們可以看到採取了類似的方法,但是這一次,我們的目標不是添加 612 的項目 數組,但要刪除或過濾掉一個。首先,我們將現有項目的副本創建到一個新數組中,然後使用 JavaScript 過濾器方法說“僅保留我們當前正在循環的項目,如果它的 627 屬性 等於 634 我們預計通過 647 。”

對於 654 情況下,事情要簡單一些;我們在這裡要做的就是完全清空 665 大批。要做到這一點,因為我們不關心保留任何項目,我們可以覆蓋 670 有一個空數組。

使用 Redux Provider 訪問 React 應用中的狀態

現在我們已經建立了我們的 Redux store 併計劃好了我們的 reducer,現在,我們需要實際使用我們的 store。

我們將看到的第一個選項是使用 685 697 中的組件 包裹。這是一個官方包,為在基於 React 的 UI 中使用 Redux 提供幫助。

使用 706 ,我們需要將它放在組件樹的頂部。通常,這是傳遞給我們對 710 的調用的組件 或 729 .對於本教程,因為我們使用的是 CheatCode Next.js 樣板,我們將把它放在 734 文件是 Next.js 渲染的主要組件,代表我們組件樹的“頂部”。

/pages/_app.js

import React from "react";
import PropTypes from "prop-types";
import Head from "next/head";
import { Provider as ReduxProvider } from "react-redux";
import { ApolloProvider } from "@apollo/client";
import Navigation from "../components/Navigation";
import loginWithToken from "../lib/users/loginWithToken";
import appStore from "../lib/appStore";
import client from "../graphql/client";

import "../styles/styles.css";

class App extends React.Component {
  state = {
    loading: true,
  };

  async componentDidMount() {
    [...]
  }

  render() {
    const { Component, pageProps } = this.props;
    const { loading } = this.state;

    if (loading) return <div />;

    return (
      <React.Fragment>
        <Head>
          <meta
            name="viewport"
            content="width=device-width, initial-scale=1.0"
          />
          <title>App</title>
        </Head>
        <ReduxProvider store={appStore}>
          <ApolloProvider client={client}>
            <Navigation />
            <div className="container">
              <Component {...pageProps} />
            </div>
          </ApolloProvider>
        </ReduxProvider>
      </React.Fragment>
    );
  }
}

App.propTypes = {
  Component: PropTypes.object.isRequired,
  pageProps: PropTypes.object.isRequired,
};

export default App;

這裡有一些注意事項。首先,CheatCode Next.js Boilerplate 默認使用 Redux 作為全局存儲。它還使用 748 組件將存儲傳遞到組件樹。

在這裡,為了讓我們的工作更清晰,我們將改變兩件大事:

  1. 替換752761 .
  2. 770782的方法 組件,替換正在傳遞給 796 的變量的名稱 809 上的道具 組件為 813 .

值得注意的是,當我們導入 820 832 中的組件 包,我們也將其重命名為 847 幫助我們更好地了解它是什麼類型的提供者(使用名稱 856 在 React 庫中很常見,因此這樣做有助於我們避免命名空間衝突並理解每個 861 的意圖 )。

通過這樣做,雖然看起來可能不多,但我們已經完成的是讓我們應用程序中的任何組件都可以訪問 872 我們作為 888 傳遞的 892 上的道具 零件。如果我們沒有 這樣做,我們能夠訪問商店的唯一方法是將其直接導入到我們的組件文件中(我們稍後會看看這種模式)。

<ReduxProvider store={appStore}>
  [...]
</ReduxProvider>

接下來,我們將看看如何從in訪問商店 我們樹中的組件使用三種不同的方法:通過 903 訪問組件中的存儲 914 HOC(高階組件),通過函數式組件鉤子,以及通過我們剛剛暗示的直接導入方法。

使用 Redux Connect 在基於類的 React 組件中訪問您的商店

就像我們之前討論的那樣,我們的目標是創建一個購物車來展示我們的全球商店。但是,在我們構建購物車之前,我們需要一些可以添加到購物車的物品。展示 922 的用法 來自 938 的 HOC ,我們將把我們的店面構建為一個基於類的 React 組件。

首先,讓我們修改 940 CheatCode Next.js Boilerplate 中的組件為我們提供了一個簡單的列表,列出了我們可以從購物車中添加或刪除的物品:

/pages/index.js

import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";

import StyledStorefront from "./styles";

const storefrontItems = [
  {
    _id: "turkey-sandwich",
    image: "https://loremflickr.com/640/480/turkeysandwich",
    title: "Turkey Sandwich",
    price: "$2.19",
  },
  {
    _id: "potato-chips",
    image: "https://loremflickr.com/640/480/potatochips",
    title: "Potato Chips",
    price: "$1.19",
  },
  {
    _id: "soda-pop",
    image: "https://loremflickr.com/640/480/popcan",
    title: "Soda Pop",
    price: "$1.00",
  },
];

class Index extends React.Component {
  render() {
    const { cart, addToCart, removeFromCart } = this.props;

    return (
      <StyledStorefront>
        <ul>
          {storefrontItems.map((item) => {
            const { _id, image, title, price } = item;
            const itemInCart =
              cart && cart.find((cartItem) => cartItem._id === _id);

            return (
              <li key={_id}>
                <img src={image} alt={title} />
                <header>
                  <h4>{title}</h4>
                  <p>{price}</p>
                  <button
                    className="button button-primary"
                    onClick={() =>
                      !itemInCart ? addToCart(item) : removeFromCart(_id)
                    }
                  >
                    {!itemInCart ? "Add to Cart" : "Remove From Cart"}
                  </button>
                </header>
              </li>
            );
          })}
        </ul>
      </StyledStorefront>
    );
  }
}

Index.propTypes = {
  cart: PropTypes.array.isRequired,
  addToCart: PropTypes.func.isRequired,
  removeFromCart: PropTypes.func.isRequired,
};

export default connect(
  (state) => {
    return {
      cart: state.cart,
    };
  },
  (dispatch) => {
    return {
      addToCart: (item) => dispatch({ type: "ADD_TO_CART", item }),
      removeFromCart: (itemId) =>
        dispatch({ type: "REMOVE_FROM_CART", itemId }),
    };
  }
)(Index);

這裡有很多東西要看,但讓我們從 950 開始 稱呼。這個960 方法正在我們的 971 頂部導入 文件。顧名思義,985 方法連接 我們正在寫入 Redux 存儲的組件。更具體地說,它需要我們傳遞給 997 的商店 並將其狀態和調度方法映射到我們包裝的組件。

在這個例子中,我們包裝了 1008 1014 的組件 這樣我們就可以將店面 UI 連接到 Redux 商店。

如果我們仔細觀察,1026 方法有兩個參數:

  1. 首先,一個稱為 1036 的函數 這允許我們訪問 Redux 存儲的當前狀態並將其內容映射到我們正在包裝的組件的 props(即,允許我們有選擇地從狀態中挑選出我們想要讓組件訪問的數據)。
  2. 第二個,稱為1049的函數 這允許我們訪問 1050 我們組件中 Redux 存儲的方法。

查看 1061 ,這裡的想法很簡單:定義一個接收當前 1079 的函數 Redux 存儲作為參數,然後返回一個 JavaScript 對象,其中包含我們要公開給組件的道具名稱。現在,仔細看。我們在這裡所做的是說“我們想要獲取 1084 值並映射它1097 支持我們的組件。

通過這樣做,現在,在我們的 1105 內部 方法(以及組件上的其他生命週期方法),我們可以說 1112 ,或者,如果我們使用解構 1129 .

有趣的是,隨著我們商店的更新,現在,1139 也會更新。這裡的好處是我們得到的本質上是 UI 中的實時更新。

查看傳遞給 1143 的第二個參數 ,再一次,我們有另一個函數叫做 1159 .這與 1160 幾乎相同 函數,除了它接受單個參數 1172 這是一個函數本身。此函數用於向我們的商店發送操作(還記得嗎?)。

記得之前我們是如何使用像 1182 這樣的東西的 case-switch 語句 ?這是我們將這些東西與我們的用戶界面連接起來的地方。在這裡,在我們的 1198 函數,我們正在做的是嘗試將 props 傳遞給我們的組件(由我們對 1201 的調用包裝的組件 ) 表示我們嘗試調度的不同操作。

在這裡,我們傳遞了兩個道具:12161226 .我們將這些 props 設置為一個期望通過 1231 的函數 或 1243 (分別)。

1255 函數被稱為 1260 發生的事情是對像傳遞給 1274 被移交給這個函數被設置為 1280 prop 然後交給對 1295 的調用 我們的 Redux 商店中的方法。

如果我們看一下對 1308 的調用 ,我們可以看到我們在這裡也傳遞了一個對象,但是這次我們添加了一個 1313 財產。看起來熟悉?是的,1328 映射回 1331 我們在 1349 的 reducer 函數中看到的 !

有意義嗎?

1353 同樣適用於此 , 但是,當我們調用它時,我們只是傳遞 1360 而不是傳遞整個項目以添加到購物車 或 1374 來自 item 對象。

為了更清楚地說明這一點,讓我們看一下 1381 我們組件的方法。

/pages/index.js

class Index extends React.Component {
  render() {
    const { cart, addToCart, removeFromCart } = this.props;

    return (
      <StyledStorefront>
        <ul>
          {storefrontItems.map((item) => {
            const { _id, image, title, price } = item;
            const itemInCart =
              cart && cart.find((cartItem) => cartItem._id === _id);

            return (
              <li key={_id}>
                <img src={image} alt={title} />
                <header>
                  <h4>{title}</h4>
                  <p>{price}</p>
                  <button
                    className="button button-primary"
                    onClick={() =>
                      !itemInCart ? addToCart(item) : removeFromCart(_id)
                    }
                  >
                    {!itemInCart ? "Add to Cart" : "Remove From Cart"}
                  </button>
                </header>
              </li>
            );
          })}
        </ul>
      </StyledStorefront>
    );
  }
}

這應該更有意義。請注意,在此文件的頂部,我們使用解構來“提取”1393 (我們從 1404 中的狀態映射 ), 1413 (我們在 1426 中添加到道具 ) 和 1439 (我們在 1442 中添加到道具中 )。

使用所有這些,首先,我們使用 1453 的靜態數組 我們在上面看到並對其進行映射(這些只是模仿我們可能從數據庫中返回的項目)。

當我們映射每個項目時,我們想問一個問題“這個項目是否已經添加到購物車中?”

這是變量 1463 在我們的 1475 中發揮作用 方法。在這裡,我們將變量分配給對 1480 的調用 . 1499 是一個原生 JavaScript 函數,它允許我們調用一個試圖在某個數組中找到匹配元素的函數。

在這裡,我們想看看是否可以在 1505 中找到 JavaScript 對象 1515 的數組 屬性等於 1521 當前在我們的地圖中循環的店面項目。

如果我們找到匹配項?這意味著該商品在我們的購物車中!

接下來,利用這個值,我們做了兩件事,涉及下方的“添加到購物車”按鈕。首先,我們分配一個 1532 處理程序說“單擊此按鈕時,將此項目添加到購物車,或者,如果它已經在購物車中,則將其刪除。”請注意,這裡我們調用 15461558 我們在 1566 中映射到 props 的函數 功能更早。

請記住,根據我們正在執行的操作(將商品添加到購物車或移除現有商品),我們將向 1572 傳遞不同的數據 .

那是下降的一部分!現在,如果您單擊每個項目的“添加到購物車”按鈕,您應該會看到它翻轉為“從購物車中刪除”,如果再次單擊它,反之亦然!

使用 Redux Hooks 在功能性 React 組件中訪問您的商店

在 React 中訪問 Redux 存儲的另一種方法是使用 1581 中包含的鉤子實現之一 包裹。 Hooks 是 React 中的一種約定,用於處理功能組件內的狀態或響應功能組件中道具或狀態更改的副作用。

1591 ,可用的鉤子之一稱為 1601 .它允許我們直接從我們的 Redux 存儲中“選擇”一個值(或多個值)。

例如,我們將更新 1611 CheatCode Next.js Boilerplate 中的組件,用於包含購物車商品計數(帶有指向我們接下來將構建的購物車頁面的鏈接),當商品從我們的購物車中添加或刪除時會自動更新。

/components/Navigation/index.js

import React, { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { useSelector } from "react-redux";
import NavigationLink from "../NavigationLink";
import Link from "next/link";

import StyledNavigation from "./styles";

const Navigation = () => {
  const cart = useSelector((state) => state.cart);
  const router = useRouter();
  const [navigationOpen, setNavigationOpen] = useState(false);

  const handleRouteChange = () => {
    setNavigationOpen(false);
  };

  useEffect(() => {
    router.events.on("routeChangeStart", handleRouteChange);

    return () => {
      router.events.off("routeChangeStart", handleRouteChange);
    };
  }, []);

  return (
    <StyledNavigation className={`navigation ${navigationOpen ? "open" : ""}`}>
      <div className="container">
        <Link href="/" passHref>
          <a className="brand">BigBox</a>
        </Link>
        <i
          className="fas fa-bars"
          onClick={() => setNavigationOpen(!navigationOpen)}
        />
        <div className="navigation-items">
          <ul>
            <NavigationLink href="/">Storefront</NavigationLink>
          </ul>
          <p className="cart" onClick={() => router.push("/cart")}>
            <i className="fas fa-shopping-cart" /> {(cart && cart.length) || 0}{" "}
            Cart
          </p>
        </div>
      </div>
    </StyledNavigation>
  );
};

Navigation.propTypes = {};

export default Navigation;

這看起來有點不同。我們在這裡所做的重大改變是,我們使用的是函數式組件,而不是使用基於類的組件。這是一種定義本質上更簡單的 React 組件的技術。函數式組件是不需要 JavaScript 類的生命週期方法和結構的組件。

為了填補缺少的生命週期方法和偶爾需要訪問狀態之間的空白,在版本 16 中,React 引入了鉤子。一種無需引入基於類的組件的全部權重即可訪問組件級狀態的方法。

我們的導航非常適合這種需求。它依賴於一些簡單的狀態設置和數據獲取,但並不需要更多;非常適合功能組件和掛鉤。

在這裡,我們要注意的是我們對1629的調用 靠近我們組件的頂部。這是從 1630 導入的 包並負責幫助我們從狀態中提取一些價值(類似於我們在 1641 中看到的概念 在我們的店面)。

hook 的工作方式是它接受一個函數作為參數,當我們的組件渲染時,該函數被調用,接收我們 Redux 存儲的當前狀態。

等待?什麼 Redux 商店?我們通過 1654 傳遞的那個 .雖然我們看不到,但在幕後,1660 此處的鉤子會檢查我們組件樹的 props 中是否存在現有的 Redux 存儲。如果找到,則調用成功,並返回我們從 1679 請求的值 (假設它存在於狀態中)。

如果我們做了 有我們的 1683 在我們的組件樹中,我們會從 React 中得到一個錯誤,說 1691 hook 需要訪問 store 並且我們需要設置提供程序。

從這裡開始,事情是不言自明的。我們取檢索到的1701 值,把它放在我們的 1710 變量,然後朝向我們組件的底部,渲染當前的 1724 1730 數組。

而已!儘管看起來可能不多,但請返回店面頁面並將一些商品添加到購物車中。請注意,即使我們正在調度我們的 17461755 來自店面的操作,對 Redux 存儲的更改會傳播到我們應用程序中的任何其他組件,這些組件會檢索和偵聽 Redux 存儲中數據的更改。

這就是 Redux 的魅力所在。您可以從一個地方更改數據,並讓這些更改自動反映在另一個地方。借助諸如購物車之類的功能,這是一種向用戶添加視覺反饋的好方法,即他們執行的操作成功了,而無需彈出警報或其他不和諧的用戶界面元素。

在基於類的 React 組件中直接訪問您的商店

現在我們已經看到了訪問 Redux 存儲的兩種最常用的方法,讓我們再看一種。在我們的最後一個示例中,我們將為我們的購物車連接一個頁面,渲染購物車中的商品,並讓自己能夠一次移除一件商品,或者完全清除購物車。

/pages/cart/index.js

import React from "react";
import appStore from "../../lib/appStore";

import StyledCart from "./styles";

class Cart extends React.Component {
  state = {
    cart: [],
  };

  componentDidMount() {
    this.handleStoreStateChange();
    this.unsubscribeFromStore = appStore.subscribe(this.handleStoreStateChange);
  }

  componentWillUnmount() {
    this.unsubscribeFromStore();
  }

  handleStoreStateChange = () => {
    const state = appStore.getState();
    this.setState({ cart: state && state.cart });
  };

  render() {
    const { cart } = this.state;

    return (
      <StyledCart>
        <header>
          <h1>Cart</h1>
          <button
            className="button button-warning"
            onClick={() =>
              appStore.dispatch({
                type: "CLEAR_CART",
              })
            }
          >
            Clear Cart
          </button>
        </header>
        {cart && cart.length === 0 && (
          <div className="blank-state bordered">
            <h4>No Items in Your Cart</h4>
            <p>To add some items, visit the storefront.</p>
          </div>
        )}
        {cart && cart.length > 0 && (
          <ul>
            {cart.map(({ _id, title, price }) => {
              return (
                <li key={_id}>
                  <p>
                    <strong>{title}</strong> x1
                  </p>
                  <div>
                    <p className="price">{price}</p>
                    <i
                      className="fas fa-times"
                      onClick={() =>
                        appStore.dispatch({
                          type: "REMOVE_FROM_CART",
                          itemId: _id,
                        })
                      }
                    />
                  </div>
                </li>
              );
            })}
          </ul>
        )}
      </StyledCart>
    );
  }
}

export default Cart;

這裡我們要注意的是,如果我們查看文件頂部的導入,我們將不再從 1762 導入任何函數 包。

相反,在這裡,我們引入了 1770 直接。

Redux 最酷的地方在於它的用途相當廣泛。雖然我們可以 使用有用的工具,例如 1784 方法或 1792 hooks,我們可以直接訪問我們的商店。

這種方法的優點是控制、清晰和簡單。通過直接訪問您的商店,您不會對如何感到困惑 商店正在尋找通往我們組件的方式(例如,使用 1800 ) 並且我們不需要額外的代碼來將我們映射到我們想要的東西。

相反,我們只是訪問它!

上面,一旦我們導入了 1813 ,我們想看看我們的 1825 上定義的三個方法 類:1831 , 1841 , 和 1859 .

前兩種方法,18691874 是 React 中內置的生命週期方法。就像它們的名字所暗示的那樣,這些是我們想要在 之後調用的函數 我們的組件已安裝在 DOM 中(文檔對像模型,或者,在屏幕上呈現給用戶的內存中表示),或者,就在我們的組件將要被卸載之前卸載 來自 DOM。

1886 內部 ,我們正在做兩件事:首先,我們正在調用 1898 .讓我們暫時忽略它。

接下來,我們分配 1904 調用 1917 的結果 .這是什麼?

在 Redux 中,訂閱是一種註冊回調函數的方法,每當我們的商店發生更改時就會觸發該回調函數。在這裡,我們調用 1923 傳入 1938 .該函數負責更新我們的 1942 每當對我們的商店進行更改時組件。

如果我們查看 1954 ,我們將看到它做了兩件事:首先,它調用 1962 1971 上的方法 store 以獲取我們的 Redux 存儲的當前狀態。接下來,因為我們在這個視圖中只關心購物車中的商品,所以它需要 1980 值,然後將其複製到 1993 的狀態 組件。

這使我們能夠完成類似於我們在上一節中看到的 2003 的事情 ,但不是通過鉤子直接訪問值,而是首先使用 2017 訪問整個商店的當前狀態 然後 摘下我們想要的東西。我們使用 React 基於類的組件的 2024 (2036 ) 作為我們渲染數據的機制。

使用此方法時,有一個問題:我們如何設置 initial 2047 2057 的值 零件。這是調用 2069 的地方 在 2072 派上用場了。

在這裡,我們說“當組件掛載時,去獲取 store 的當前狀態並將其彈出到 2082 組件的狀態。”這確保我們是第一次加載購物車頁面,還是在之後接收更改 掛載後,我們組件的狀態已正確更新。

相反,當我們的組件要卸載 從 DOM(意味著我們要離開頁面),我們調用 2096 其中包含我們從 2107 收到的函數 方法較早。此函數在調用時會停止存儲的偵聽器,將它們從內存中刪除。這被稱為“清理”,以確保我們不會在後台為不再為用戶顯示屏幕的頁面運行不必要的代碼。

現在我們有了這些片段,在我們的 2118 方法,我們可以關閉所有這些循環:

/pages/cart/index.js

[...]

class Cart extends React.Component {
  state = {
    cart: [],
  };

  [...]

  render() {
    const { cart } = this.state;

    return (
      <StyledCart>
        <header>
          <h1>Cart</h1>
          <button
            className="button button-warning"
            onClick={() =>
              appStore.dispatch({
                type: "CLEAR_CART",
              })
            }
          >
            Clear Cart
          </button>
        </header>
        {cart && cart.length === 0 && (
          <div className="blank-state bordered">
            <h4>No Items in Your Cart</h4>
            <p>To add some items, visit the storefront.</p>
          </div>
        )}
        {cart && cart.length > 0 && (
          <ul>
            {cart.map(({ _id, title, price }) => {
              return (
                <li key={_id}>
                  <p>
                    <strong>{title}</strong> x1
                  </p>
                  <div>
                    <p className="price">{price}</p>
                    <i
                      className="fas fa-times"
                      onClick={() =>
                        appStore.dispatch({
                          type: "REMOVE_FROM_CART",
                          itemId: _id,
                        })
                      }
                    />
                  </div>
                </li>
              );
            })}
          </ul>
        )}
      </StyledCart>
    );
  }
}

export default Cart;

早些時候,我們學習了使用我們創建的命名函數將操作分派到我們的 Redux 商店,並使用 2123 映射到我們的店面組件的道具 .

當我們調用 2139 方法(我們從傳遞給 2145 的參數中收到的方法 函數),我們在技術上所做的是調用我們的 2158 方法。

就像我們之前看到的,這個方法負責調度 對我們的 Redux 商店的操作。我們使用 2164 所做的工作 純粹是為了方便。方便的是我們能夠創建一個命名函數來表示正在執行的操作,而不是傳遞一個通用的 2176 prop 到我們的組件(這可能更令人困惑)。

在這裡,而不是使用 2189 ,我們去突擊隊,只使用 2191 直接地。這裡很酷的是我們將完全相同的東西傳遞給 2209 正如我們對 2215 所做的那樣 和 2224 早些時候。這次的不同之處在於我們只是調用了 2234 直接。

如果我們現在嘗試通過單擊商品旁邊的“x”來從購物車中刪除商品,或者單擊頁面頂部附近的“清除購物車”按鈕,我們的操作將被調度,並且 2244 我們的 Redux 商店的價值已更新!

總結

在本教程中,我們學習了與 Redux 交互的三種不同方法,在 React 中使用了兩種不同類型的組件樣式:基於類的組件和函數式組件。

Redux 是處理應用程序中全局狀態並為您的應用程序添加一些“實時”風格潤色的好方法。正如我們在這裡所看到的,它的優點在於它的靈活性。我們並沒有局限於一種做事方式,這意味著 Redux 可以輕鬆適應新項目和現有項目(基於 React 或其他)。


Tutorial JavaScript 教程
  1. Formik + TypeScript 的表單

  2. 定義 JavaScript 類的 3 種方法

  3. 如何在 Blitz.js(Next.js) 中使用內聯 SVG

  4. 錯誤:重新渲染過多。因為我改變了 setState

  5. 帶有 React 和 100ms SDK 的 Zoom 克隆應用程序(第二部分)

  6. jQuery 驗證 [數量]

  7. 科技和英語點燃?

  1. 我如何開始使用 GatsbyJS 以及為什麼你也應該這樣做

  2. React Hooks 中 useEffect() 的兩種通用模式

  3. 簡化 Javascript 中的作用域

  4. 後端開發人員的 Javascript 開發簡明指南

  5. 使用 Vue 重新創建 Twitter 心臟動畫

  6. JavaScript 函數聲明與表達式

  7. 找到您的第一份開發人員工作

  1. 在 React 中思考:2020 版

  2. Kubernetes 簡介:如何部署 Node.js Docker 應用程序

  3. 了解 Javascript 過濾器

  4. 使用 AWS Amplify 構建和部署無服務器 GraphQL React 應用程序