雑記
モダンな Javascript ~ Trello にあこがれて ~
John
更新日:2024/06/27
こんにちは。
Reactについては
過去にサーバー/ミドルウェアの構築に加えて
バックエンドを Laravel(PHPフレームワーク) にて整備したりした経験がある john です。
そういった経緯がありながらも
コンパイル等の手間の兼ね合いにより
今回はフロントエンドの開発に重点を置いて着手してみました。
結論としては、こんな画面になります。
ディレクトリ構成については、こんな感じ。
1 |
# tree -L 2 |
1 2 3 4 5 6 7 8 9 10 11 12 |
. ├── app │ ├── node_modules │ ├── package.json │ ├── package-lock.json │ ├── react-project # Reactアプリケーション │ └── yarn.lock ├── Docker-compose.yml # コンテナ構成(ポート等の指定) ├── Dockerfile # コンテナ構成(node.jsなどの構成要素指定) └── readme.md 3 directories, 7 files |
では、アプリケーションの構成について。
メインで利用するライブラリ”react-beautiful-dnd” のリファレンス等を参考に複数のコンポーネントにて構成しました。
1 |
tree -I "node_modules" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
. ├── kanban_dnd │ ├── package.json │ ├── package-lock.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── README.md │ ├── src │ │ ├── App.css │ │ ├── App.js # React最上位構成コンポーネント(要はindex.php) │ │ ├── components │ │ │ ├── header # 画面上部の構成コンポーネント │ │ │ │ └── Header.jsx │ │ │ └── task # 画面中央以下の構成コンポーネント │ │ │ ├── button # カード内のボタン関連の構成コンポーネント │ │ │ │ ├── AddTaskCardButton.jsx │ │ │ │ └── DeleteTaskCardButton.jsx │ │ │ ├── input # カード内のインプット関連の構成コンポーネント │ │ │ │ └── TaskAddInput.jsx │ │ │ ├── TaskCard.jsx # カード内部の構成コンポーネント │ │ │ ├── TaskCards.jsx # カード外部の構成コンポーネント │ │ │ ├── TaskCardTitle.jsx # カード上部のタイトル入力構成コンポーネント │ │ │ ├── Task.jsx # カード内のタスク入力の構成コンポーネント │ │ │ └── Tasks.jsx # カード内のタスク表示の構成コンポーネント │ │ ├── images │ │ │ └── MtFuji.jpg │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── reportWebVitals.js │ │ └── setupTests.js │ └── yarn.lock └── tree_i_node_module.txt 9 directories, 31 files |
インフラ環境(コンテナ)については、シンプルにnode.jsが動く環境のみを準備
1 2 3 4 5 6 7 8 9 10 |
version: '3' services: app: build: . image: docker-nodejs volumes: - ./app:/app ports: - "3000:3000" tty: true |
1 2 3 |
FROM node:lts-alpine3.19 WORKDIR /app CMD ["sh"] |
アプリについては以下つらつらと
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import React from 'react' import Task from './Task' import { DragDropContext, Droppable } from "react-beautiful-dnd"; const reorder = (taskList, startIndex, endIndex) => { const remove = taskList.splice(startIndex, 1); taskList.splice(endIndex, 0, remove[0]); }; const Tasks = ({inputText, taskList, setTaskList}) => { const handleDragEnd = (result) => { reorder(taskList, result.source.index, result.destination.index); setTaskList(taskList); } return ( <div> <DragDropContext onDragEnd={handleDragEnd}> <Droppable droppableId="droppable"> {(provided) => ( <div {...provided.droppableProps} ref={provided.innerRef}> {taskList.map((task, index) => ( <div key={task.id}> <Task task={task} index={index} taskList={taskList} setTaskList={setTaskList}/> </div> ))} {provided.placeholder} </div> )} </Droppable> </DragDropContext> </div> ) } export default Tasks |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import React, {useState} from 'react' const TaskCardTitle = () => { const [isClick, setIsClick] = useState(false); const [inputCardTitle, setInputCardTitle] = useState("today task"); const handleClick = () => { setIsClick(true); } const handleChange = (e) => { setInputCardTitle(e.target.value); } const handleSubmit = (e) => { e.preventDefault(); setIsClick(false); } const HandleBlur = () => { setIsClick(false); } return ( <div onClick={handleClick} > {isClick ? (<form onSubmit={handleSubmit} > <input type="text" onChange={handleChange} onBlur={HandleBlur} value={inputCardTitle} maxLength="10"></input></form>) : <h3>{inputCardTitle}</h3>} </div> ) } export default TaskCardTitle |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import React, { useState } from 'react' import TaskCard from './TaskCard' import AddTaskCardButton from './button/AddTaskCardButton' import { DragDropContext, Droppable } from 'react-beautiful-dnd'; const reorder = (taskCardsList, startIndex, endIndex) => { // task sort const remove = taskCardsList.splice(startIndex, 1); taskCardsList.splice(endIndex, 0, remove[0]); }; const TaskCards = () => { const [taskCardsList, setTaskCardsList] = useState([{ id: "0", draggableId: "item0", },]); const handleDragEnd = (result) => { reorder(taskCardsList, result.source.index, result.destination.index); setTaskCardsList(taskCardsList); }; return ( <DragDropContext onDragEnd={handleDragEnd}> <Droppable droppableId="droppable" direction="horizontal"> {(provided) => ( <div className="taskCardsArea"{...provided.droppableProps} ref={provided.innerRef}> {taskCardsList.map((taskCard, index) => ( <TaskCard key={taskCard.id} index={index} taskCardsList={taskCardsList} setTaskCardsList={setTaskCardsList} taskCard={taskCard}/> ))} {provided.placeholder} <AddTaskCardButton taskCardsList={taskCardsList} setTaskCardsList={setTaskCardsList}/> </div> )} </Droppable> </DragDropContext> ) } export default TaskCards |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import React, {useState} from 'react' import TaskCardTitle from './TaskCardTitle' import TaskCardDeleteButton from './button/TaskCardDeleteButton' import TaskAddInput from './input/TaskAddInput' import Tasks from './Tasks' import { Draggable} from 'react-beautiful-dnd'; const TaskCard = ({taskCardsList, setTaskCardsList, taskCard, index}) => { const [inputText, setInputText] = useState(""); const [taskList, setTaskList] = useState([]); return ( <Draggable draggableId={taskCard.id} index={index}> {(provided) => ( <div className="taskCard" ref={provided.innerRef} {...provided.draggableProps}> <div className="taskCardTitleAndTaskCardDeleteButtonArea" {...provided.dragHandleProps}> <TaskCardTitle /> <TaskCardDeleteButton taskCardsList={taskCardsList} setTaskCardsList={setTaskCardsList} taskCard={taskCard}/> </div> <TaskAddInput inputText={inputText} setInputText={setInputText} taskList={taskList} setTaskList={setTaskList}/> <Tasks inputText={inputText} taskList={taskList} setTaskList={setTaskList}/> </div> )} </Draggable> ) } export default TaskCard |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import React from 'react' import { Draggable } from "react-beautiful-dnd"; const Task = ({task, taskList, setTaskList, index}) => { const handleDelete = (id) => { // setTaskList setTaskList(taskList.filter((task) => task.id !== id)) }; return ( <Draggable index={index} draggableId={task.draggableId}> {(provided) => ( <div className="taskBox" key={task.id} ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}> <p className="taskText"> {task.text} </p> <button className="taskTrashButton" onClick={() => handleDelete(task.id)}> <i className="fas fa-trash-alt"></i> </button> </div> ) } </Draggable> ) } export default Task |
利用技術の通りにはなりますが
コンパイルの手間等の兼ね合いにより
Dockerにてコンテナ内にミドルウェア(node.js)等を構築、内部にてパッケージマネージャ(yarn)を利用したビルトインサーバー(開発サーバー)を起動する仕様としています。
※
この開発環境にて開発したReactソースを、そのままReact*Laravelに移管作戦って感じですね。
ただ上手に使いこなせれば、React*Laravel構成でもホットリロード的環境での開発が出来る気がしないでもないですが。。笑
今回はここまでです。
今後ともどうぞよしなに。