前幾篇文章中,我們定義好資料儲存的結構作為 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);
最後的畫面:
加入編輯標題的功能。