iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0
Modern Web

30天打造個人簡易旅遊網站系列 第 21

Day 21:使用Redux完成收藏功能

  • 分享至 

  • xImage
  •  

在昨天我們介紹了基本的Redux概念,那今天就要來實現一樣是很常見的收藏功能,當我們按下愛心後,可以在另一個favorite的頁面中看到剛剛加入收藏的景點,那廢話不多說就開始今天的教學吧!

1.定義基本redux架構:

第一步,我們先定義favoriteSlice的name、初始狀態initialState為空陣列,並用reducers接收了兩種動作(actions),第一個是addFavorite,第二個是removeFavorite。

  • addFavorite:按下愛心後,如果不是已存在的項目,則會添加到名為favorites的陣列做儲存。
  • removeFavorite:再次按下愛心,會使該景點不再是此陣列的一員。
    最後將這兩個actions做輸出,這樣別的地方要使用才能讀取到。
// redux/favoriteSlice.js
import { createSlice } from '@reduxjs/toolkit';

const favoriteSlice = createSlice({
    name: 'favorites',
    initialState: [],
    reducers: {
        addFavorite: (state, action) => {
            const exists = state.some(favorite => favorite.id === action.payload.id);
            if (!exists) {
                state.push(action.payload);
            }
        },
        removeFavorite: (state, action) => {
            return state.filter(favorite => favorite.id !== action.payload.id);
        },
    },
});

export const { addFavorite, removeFavorite } = favoriteSlice.actions;
export default favoriteSlice.reducer;

接下來,我們需要創建 Redux store 並將 favoriteSlice 的 reducer 添加到 store 中。

  • configureStore:這是 Redux Toolkit 提供的函數,用於簡化 store 的創建過程。
  • configureStore:這是 Redux Toolkit 提供的函數,用於簡化 store 的創建過程。
  • reducer:這裡我們將 favoriteReducer 添加到 store 中,並將其命名為 favorites。
// redux/store.js

import { thunk } from 'redux-thunk';
import { configureStore } from '@reduxjs/toolkit';
import { combineReducers } from 'redux';
import favoritesReducer from './favoriteSlice';

const rootReducer = combineReducers({
    favorites: favoritesReducer,
    // 其他 reducers
  });

  export const store = configureStore({
    reducer: rootReducer,
    devTools: process.env.NODE_ENV !== 'production',
    middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware({
        serializableCheck: false,
      }).concat(thunk),
  });

export default store;

再來把store匯入到整個專案的最外層,並用Provider包起來,讓裡面的每個檔案都能讀取到Redux所儲存的各種資料與狀態。

import { Provider } from 'react-redux';
import store from "./components/redux/store";

function App() {
  return (
    <Provider store={store}>
      <BrowserRouter>
        <NavigationButton />
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="places/id/:placeId" element={<Place />} />
          <Route path="/favorite" element={<Favorite />} />
          <Route path="/schedule" element={<Schedule />} />
          <Route path="/map" element={<Map />} />
        </Routes>

      </BrowserRouter>
    </Provider>

  );
}

2.將收藏按鈕獨立成一個元件:

為了方便管理,我們會將一些小功能獨立成一個元件,如果以後有其他頁面需要用到的功能的話,就不用再一行一行的寫,維護起來也比較方便。

  • useSelector:利用這個React hook來讀取favorites中的資料
  • useEffect():當按下愛心時會重新渲染畫面
  • handleClick:判斷isFavorite的狀態,選擇要加入或是刪除景點
  • addFavorite:將景點的id、name、image都儲存到favorites陣列中
//components/AddToFavorite/index.jsx

import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { HeartFilled, HeartOutlined } from '@ant-design/icons';
import { addFavorite, removeFavorite } from '../redux/favoriteSlice';
import styles from './addToFavorite.module.css';

export default function AddToFavorite({ landmark }) {
    const [isFavorite, setIsFavorite] = useState(false);
    const favorites = useSelector((state) => state.favorites);
    const dispatch = useDispatch();
    useEffect(() => {
        const isFav = favorites.some(fav => fav.id === landmark.id);
        setIsFavorite(isFav);
    }, [favorites, landmark.id]);

    const handleClick = (e) => {
        e.stopPropagation(); // 阻止點擊事件冒泡
        if (isFavorite) {
            dispatch(removeFavorite(landmark));
            console.log('removeFavorite:',landmark.name);
        } else {
            dispatch(addFavorite({
                id: landmark.id,
                name: landmark.name,
                image: landmark.image,
            }));
            console.log('addFavorite:',landmark.name)
        }
    };

    return (
        <div className={styles.buttonHeart} onClick={handleClick}>
            {isFavorite ? <HeartFilled /> : <HeartOutlined />}
        </div>
    );
}

原本地圖中的Popup中則改成:

import AddToFavorite from "../AddToFavorite";
.
.
.
<Popup className={styles.popup}>
    <div className={styles.landmarkName}>
        {landmark.name}
    </div>
    <img
        src={landmark.image}
        className={styles.img}
    />
    <div className={styles.buttonContainer}>
        <AddToFavorite landmark={landmark} />
        <Button type="primary">加入行程</Button>
    </div>

</Popup>

3.顯示在收藏頁面:

在FavoriteItem中根據參數傳來的內容去顯示,當按下Button時也會觸發removeFavorite

export default function FavoriteItem({place}) {
  const dispatch = useDispatch();
  const handleRemove =() =>{
    dispatch(removeFavorite(place));
  }
  console.log("placename",place.name);
  return (
    <>
      <div className={styles.favoritebox}>
        <div className={styles.placebox}>
          <img src={place.image} alt={place.image} width={"150px"} />
          <h2 className={styles.name}>{place.name}</h2>
        </div>
        <div>
          {/* <Button onClick={showPromiseConfirm}>With promise</Button> */}
          <Button
            type="text"
            onClick={handleRemove}
            icon={<DeleteTwoTone style={{ fontSize: "30px" }} />}
            danger
          />
        </div>
      </div>
    </>
  );
}

接著在外面的FavoriteList利用useSelector讀取Redux中的內容再傳給FavoriteItem

import styles from "./favorite.module.css";
import { useSelector } from "react-redux";
import { Row, Col } from "antd";
import FavoriteItem from "../FavoriteItem/FavoriteItem";
import Place from "../../pages/Place/Place";

export default function FavoriteList() {
  const favorite = useSelector((state) => state.favorites);
  return (
    <>
      <div className={styles.containerbox}>
        <h1 className={styles.title}>Your Favorite places</h1>
        <Row className={styles.container}>
        {favorite.map((place) => (
          <Col key={place.id} span={24} className={styles.col}>
            <FavoriteItem place={place} />
          </Col>
        ))}
        </Row>
      </div>
    </>
  );
}

4.實際畫面:

按下愛心讓他變成實心的時候,可以在收藏頁面中看到該景點。同時也可以再按一次愛心或是垃圾桶的Button來移除此景點。
https://ithelp.ithome.com.tw/upload/images/20241004/20169447mH5bLCuD7F.png
https://ithelp.ithome.com.tw/upload/images/20241004/20169447DksTZYszou.png

5.總結:

今天一口氣將收藏功能完整的呈現出來,不知道會不會一下子無法吸收。但Redux真的是很實用的工具,可以將很亂的狀態變數全部集中一起管理,相信各位重複看個幾遍,然後再自己操作後很快就能理解的,大家慢慢吸收吧!


上一篇
Day 20:Redux安裝與基本介紹
下一篇
Day 22:使用Redux建立一個旅遊行程(一)
系列文
30天打造個人簡易旅遊網站30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言