iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 24
0
Modern Web

使用 React 製作簡易專案管理網站:從基礎到實戰系列 第 24

[Day 24] React 攻城戰 - 根元件和全局樣式

  • 分享至 

  • xImage
  •  

前幾篇文章中,我們定義好資料儲存的結構作為 initialState,以及資料更新的邏輯 reducer,接下來進入元件的部分。

所有的元件都是從根元件長出來的,所以我們要先建立根元件取名叫做 App。首先在 src 資料夾中新增 components 資料夾,components 資料夾中再新增 App 資料夾,最後在 App 資料夾底下新增名稱為 App.js 的檔案:

import React from 'react';

const App = () => {
  return (
    <div>
      <h1>我的專案</h1>
    </div>
  );
};

export default App;

然後在 index.js 也就是程式進入點,引入 App 根元件並且掛載到 real DOM上:

import React from "react";
import ReactDOM from "react-dom";

import App from "./components/App/App";

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

畫面上應該可以看到 我的專案 的文字。

接下來要使用 useReducer 讓標題的文字改為從 state 中取得。在 src/components/App 資料夾底下新增 reducer.js 檔案,然後把之前定義好的 initialState 和 reducer 加進去,記得在最後要將它們 export 出來:

import shortid from "shortid";

const initialState = {
  title: "我的專案",
  boards: {
    ids: [],
    byId: {}
  },
  cards: {
    byId: {}
  }
};

const reducer = (state, action) => {
  switch (action.type) {
    case "CHANGE_TITLE": {
      const { title } = action.payload;
      return {
        ...state,
        title
      };
    }
    case "ADD_BOARD": {
      const boardId = shortid.generate();
      const { boardName } = action.payload;
      return {
        ...state,
        boards: {
          ids: [...state.boards.ids, boardId],
          byId: {
            ...state.boards.byId,
            [boardId]: {
              name: boardName,
              cardIds: []
            }
          }
        }
      };
    }
    case "CHANGE_BOARD_NAME": {
      const { boardId, boardName } = action.payload;
      return {
        ...state,
        boards: {
          ...state.boards,
          byId: {
            ...state.boards.byId,
            [boardId]: {
              ...state.boards.byId[boardId],
              name: boardName
            }
          }
        }
      };
    }
    case "MOVE_BOARD": {
      const { draggingBoardId, targetBoardIndex } = action.payload;
      const newBoardsIds = [...state.boards.ids];
      const sourceBoardIndex = state.boards.ids.findIndex(
        boardId => boardId === draggingBoardId
      );
      newBoardsIds.splice(sourceBoardIndex, 1);
      newBoardsIds.splice(targetBoardIndex, 0, draggingBoardId);
      return {
        ...state,
        boards: {
          ...state.boards,
          ids: newBoardsIds
        }
      };
    }
    case "REMOVE_BOARD": {
      const { boardId } = action.payload;
      const newBoardsIds = state.boards.ids.filter(id => id !== boardId);
      const newBoardsById = { ...state.boards.byId };
      delete newBoardsById[boardId];

      const newCardsById = { ...state.cards.byId };
      state.boards.byId[boardId].cardIds.forEach(cardId => {
        delete newCardsById[cardId];
      });

      return {
        ...state,
        boards: {
          ids: newBoardsIds,
          byId: newBoardsById
        },
        cards: {
          byId: newCardsById
        }
      };
    }

    case "ADD_CARD": {
      const cardId = shortid.generate();
      const { boardId, cardValue } = action.payload;
      return {
        ...state,
        boards: {
          ...state.boards,
          byId: {
            ...state.boards.byId,
            [boardId]: {
              ...state.boards.byId[boardId],
              cardIds: [...state.boards.byId[boardId].cardIds, cardId]
            }
          }
        },
        cards: {
          byId: {
            ...state.cards.byId,
            [cardId]: cardValue
          }
        }
      };
    }
    case "CHANGE_CARD_VALUE": {
      const { cardId, cardValue } = action.payload;
      return {
        ...state,
        cards: {
          byId: {
            ...state.cards.byId,
            [cardId]: cardValue
          }
        }
      };
    }
    case "MOVE_CARD": {
      const { draggingCardId, targetBoardId, targetCardIndex } = action.payload;
      const sourceBoardId = Object.keys(state.boards.byId).find(boardId => {
        return state.boards.byId[boardId].cardIds.find(
          cardId => cardId === draggingCardId
        );
      });
      const sourceBoardCardIds = [...state.boards.byId[sourceBoardId].cardIds];
      const targetBoardCardIds =
        sourceBoardId === targetBoardId
          ? sourceBoardCardIds
          : [...state.boards.byId[targetBoardId].cardIds];
      const sourceCardIndex = sourceBoardCardIds.findIndex(
        cardId => cardId === draggingCardId
      );
      sourceBoardCardIds.splice(sourceCardIndex, 1);
      targetBoardCardIds.splice(targetCardIndex, 0, draggingCardId);
      return {
        ...state,
        boards: {
          ...state.boards,
          byId: {
            ...state.boards.byId,
            [sourceBoardId]: {
              ...state.boards.byId[sourceBoardId],
              cardIds: sourceBoardCardIds
            },
            [targetBoardId]: {
              ...state.boards.byId[targetBoardId],
              cardIds: targetBoardCardIds
            }
          }
        }
      };
    }
    case "REMOVE_CARD": {
      const { draggingCardId } = action.payload;
      const sourceBoardId = Object.keys(state.boards.byId).find(boardId => {
        return state.boards.byId[boardId].cardIds.find(
          cardId => cardId === draggingCardId
        );
      });
      const sourceBoardCardIds = [...state.boards.byId[sourceBoardId].cardIds];
      const sourceCardIndex = sourceBoardCardIds.findIndex(
        cardId => cardId === draggingCardId
      );
      sourceBoardCardIds.splice(sourceCardIndex, 1);

      const newCardsById = { ...state.cards.byId };
      delete newCardsById[draggingCardId];
      return {
        ...state,
        boards: {
          ...state.boards,
          byId: {
            ...state.boards.byId,
            [sourceBoardId]: {
              ...state.boards.byId[sourceBoardId],
              cardIds: sourceBoardCardIds
            }
          }
        },
        cards: {
          byId: newCardsById
        }
      };
    }
    default:
      throw new Error();
  }
};

export { reducer, initialState };

接著在 App.js 引入並且傳入 useReducer 呼叫,將原本寫死的標題改成從 state 取得:

import React from 'react';

import { reducer, initialState } from './reducer'; 

const App = () => {
  const [state, dispatch] = React.useReducer(reducer, initialState);
  
  return (
    <div>
      <h1>{state.title}</h1>
    </div>
  );
};

export default App;

在畫面上應該一樣會看到 我的專案

接著再套用已經寫好的全局樣式。新增 index.scss 檔案,並且在 index.js 中引入:

* {
  font-size: 16px;
}

body {
  margin: 0;
  padding: 16px;
  background-color: gray;
}

button {
  outline: none;
  border: none;
  background: none;
  cursor: pointer;
}

input {
  padding: 8px;
}

h1, h2 {
  margin: 0;
}
import React from "react";
import ReactDOM from "react-dom";

import App from "./components/App/App";

import "./index.scss";

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

最後的畫面:

https://ithelp.ithome.com.tw/upload/images/20191009/20104727spEve5HuwL.png

下集預告

加入編輯標題的功能。


上一篇
[Day 23] React 攻城戰 - 資料更新邏輯 reducer
下一篇
[Day 25] React 攻城戰 - 加入編輯標題功能
系列文
使用 React 製作簡易專案管理網站:從基礎到實戰30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言