Redux的基礎知識

首先

我将从Redux的基础开始,写一下使用Redux Toolkit进行数据管理的方法。

Redux的概念

简单来说,Redux的概念是将数据集中管理在一个地方,因此可以从任何地方使用,这样很方便。
如果在各个文件中都使用相同的数据,那么在每个文件中获取数据会很麻烦,而且代码量也会增加。

Redux的机制

image.png

商店

这是一个数据存储库。
如果要在其他文件中使用数据,可以从存储库中获取数据。

减速器

在中国人的母语中解释以下内容,只需一个选项:
Reducer是唯一可以修改应用程序状态的东西。正如Redux的基本设计之一Single source of truth(唯一数据源)所示,只有Reducer才能修改State。(这是从其他网站引用的)

行动

如果考虑到操作数据,可以考虑为方法。

调度员(调度)

使用日语是指发送或传递的意思。在操作数据时,使用dispatch来通知数据操作方式(动作)的作用。

国家

这个东西表示存储数据的状态。

观看

这是用户在屏幕上看到的部分。

使用Redux的好处

以下是优点:
– 可以提高代码的可读性。
– 可以轻松地适应功能的添加。
– 思维方式简单,一旦理解就容易使用。

缺点

学习成本可能会对理解机制和概念稍微困难一些。
代码量可能会增加。

安装

我将写下有关Redux安装的方法。

npm install redux

npm install react-redux
yarn add redux

yarn add react-redux

我顺便也写一下ReduxToolkit的安装命令。

npm install @reduxjs/toolkit
yarn add @reduxjs/toolkit

实践

我們將從這裡開始使用ReduxToolkit來編寫代碼。
ReduxToolkit是一個能夠有效地進行Redux開發的工具包。

与Redux相比,最大的优点在于减少了代码量。请参见下面的架构图以了解详细信息。其他优点包括提高了可读性以及与TypeScript的兼容性。

首先,在根目录文件中写入以下代码。

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { store } from "../src/store/store";
import { Provider } from "react-redux";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

reportWebVitals();

现在,我们可以在所有文件中进行数据管理了。

接下来定义store。

import { configureStore } from "@reduxjs/toolkit";
import clipReducer from "./clipSlice";
import favoriteReducer from "./favoriteSlice";

export const store = configureStore({
  reducer: {
    clip: clipReducer,
    favorite: favoriteReducer,
  },
});

导入的slice是ReduxToolkit特有的东西。
slice是一个方便的方法,用于定义Redux Store的状态和动作,并生成Redux的reducer。
Redux Toolkit是一个提供工具的库,可以更高效地编写Redux代码,slice就是其中的一部分。

这家商店中定义了clipSlice和facoriteSlice,但本次我们将对clipSlice进行解释。

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  clips: [],
};

export const clipSlice = createSlice({
  name: "clip",
  initialState,
  reducers: {
    addClip: (state, action) => {
      const newClip = action.payload;
      state.clips.push(newClip);
    },
    deleteClip: (state, action) => {
      const deletingClip = action.payload;
      const currentClip = state.clips;
      const filterClip = currentClip.filter(
        (clip) => clip.publishedAt !== deletingClip.publishedAt
      );
      state.clips = filterClip;
    },
  },
});

export const { addClip, deleteClip } = clipSlice.actions;

export default clipSlice.reducer;

在这个slice中,定义了两个Reducer,分别是”addClip”和”deleteClip”。
我们将从View部分调用这些Reducer。
以下是一个用于标记文章的按钮组件。
通过props接收这些方法。

import React, { FC } from "react";
import { faBookmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { clipButton } from "../types/clipButton";

// iconStyleをpropsとして受け取る
export const BookMarkButton: FC<clipButton> = (props) => {
  const { iconStyle, articleClip } = props;

  return (
    <button className="ml-1 hover:opacity-75" onClick={articleClip}>
      <FontAwesomeIcon
        icon={faBookmark}
        size={"2xl"}
        style={{ color: iconStyle }}
      />
    </button>
  );
};

这一次,我在文章的详细页面上尝试使用了组件。可能有点长,可能有点难看…

import { useState, useEffect } from "react";
import { Link, useLocation } from "react-router-dom";
import { BookMarkButton } from "../components/BookMarkButton";
import { API_URL } from "../lib/client";
import { Loading } from "../components/Loading";
import axios from "axios";
import { addClip, deleteClip } from "../store/clipSlice";
import { useDispatch, useSelector } from "react-redux";

export const ArticleDetail = () => {
  const [filterArticle, setFilterArticle] = useState(null);
  const [loading, setLoading] = useState(true);

  const location = useLocation();
  const currentPath = location.pathname.substring(9);

  // クリップ記事の情報を取得
  const dispatch = useDispatch();
  const clips = useSelector((state) => state.clip.clips);
  const isEnable = clips.some((clip) => clip.publishedAt === currentPath);

   // 記事がクリップされている時とそうでない時に、呼ぶReducerを変える
  const ArticleClip = () => {
    if (isEnable) {
      dispatch(deleteClip(filterArticle));
    } else {
      dispatch(addClip(filterArticle));
    }
  };

    // 記事がクリップされている時とそうでない時とでアイコンの色を変える
  const bookMarkButtonStyle = isEnable ? "orange" : "black";

    // 記事の詳細情報を取得する処理
  useEffect(() => {
    const fetchNewsLists = async () => {
      try {
        const response = await axios.get(API_URL);
        const articles = response.data.articles;
        const filteredArticle = articles.find(
          (article: Article) => article.publishedAt === currentPath
        );
        if (filteredArticle) {
          setFilterArticle(filteredArticle);
          setLoading(false);
        } else {
          setLoading(false);
        }
      } catch (error) {
        console.error("Error fetching news:", error);
        setLoading(false);
      }
    };
    fetchNewsLists();
  }, [currentPath]);

  if (loading) {
    return <Loading />;
  }

  if (!filterArticle) {
    return <div>記事が見つかりませんでした</div>;
  }

  return (
    <div className="container mx-auto py-8">
      <div className="max-w-2xl mx-auto bg-white rounded-lg shadow-lg p-8">
        <div className="mb-10">
          <BookMarkButton
            iconStyle={bookMarkButtonStyle}
            articleClip={ArticleClip}
          />
        </div>
        <h1 className="text-3xl font-semibold mb-4">{filterArticle.title}</h1>
        <p className="text-gray-500 mb-4">著者: {filterArticle.author}</p>
        <img
          src={filterArticle.urlToImage}
          alt={filterArticle.title}
          className="w-full rounded-lg mb-4"
        />
        <p className="text-gray-700 mt-10 mb-5">{filterArticle.description}</p>

        <span className="text-blue-500">
          <Link to={filterArticle.url}>続きはこちら</Link>
        </span>
      </div>
    </div>
  );
};

我会提取代码并进行解释。

Dispatch会导入useDispatch。
然后,我们从导入的slice中指定要使用的reducer作为参数。
如果这里打开的文章没有被剪辑,那么我们将dispatch addClip 将文章添加到store中,如果已经剪辑了,那么我们将dispatch deleteClip 并从store中删除文章,这样reducer就会被通知。

import { useDispatch } from "react-redux";
import { addClip, deleteClip } from "../store/clipSlice";

const dispatch = useDispatch();

// 記事がクリップされている時とそうでない時に、呼ぶメソッドを変える
const ArticleClip = () => {
    if (isEnable) {
      dispatch(deleteClip(filterArticle));
    } else {
      dispatch(addClip(filterArticle));
    }
};

在以下代码中,我们将方法传递给了按钮组件。

 <BookMarkButton
    iconStyle={bookMarkButtonStyle}
    articleClip={ArticleClip}
  />

下面是显示剪贴板中的文章的代码。

import { useState, useEffect } from "react";
import { faBookmark } from "@fortawesome/free-solid-svg-icons";

import { NewsList } from "../components/NewsList";
import { Loading } from "../components/Loading";
import { PageTitle } from "../components/PageTitle";
import { useSelector } from "react-redux";

export const Clip = () => {
  const [loading, setLoading] = useState(true);
  const clips = useSelector((state) => state.clip.clips);

  useEffect(() => {
    const fetchNewsLists = async () => {
      try {
        setLoading(false);
      } catch (error) {
        console.error("Error fetching news:", error);
        setLoading(false);
      }
    };
    fetchNewsLists();
  }, []);

  return (
    <div className="p-8">
      <PageTitle pageTitle="ストック記事" iconName={faBookmark} />
      {loading ? <Loading /> : <NewsList articles={clips} />}
    </div>
  );
};

导入useSelector()函数,并在store.js中指定所需的clip,以获取clip的数据。
通过这样做,可以获取在Store中注册的数据。
获取到的数据传递给以下组件以进行显示。

import { Link } from "react-router-dom";

export const NewsList = ({ articles }) => {
  return (
    <div className="py-8">
      <div className="container mx-auto border-black">
        <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
          {articles.map((article, index) => (
            <div
              className="bg-white rounded-lg transition-transform transform shadow-md hover:opacity-75"
              key={index}
            >
              <div>
                <Link to={`/article/${article.publishedAt}`} className="flex">
                  <img
                    src={article.urlToImage}
                    alt="ニュース1"
                    className="w-60 h-60 object-cover rounded-l-lg"
                  />
                  <div className="px-2 pt-4 pb-2 pr-10">
                    <h2 className="text-xl font-semibold text-gray-800 mb-2">
                      {article.title}
                    </h2>
                    <p className="text-gray-600"></p>
                  </div>
                </Link>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

タイトルなし.gif

最后

由于我还在写其他各种文章,如果可以的话,请阅读一下…
关于基本设计
我使用Vue.js和Node.js创建了一个聊天应用程序
我使用Next.js和TypeScript创建了一个TODO应用程序

广告
将在 10 秒后关闭
bannerAds