2022. 1. 4. 00:05ใWEB Dev/ToyProject
๐ท CloneTodo โ - Todomate Clone Project | Team CloneMate
CloneTodo : ๋ง์ ์ฌ๋๋ค์ด ์ฌ๋ํ๋ ํฌ๋๋ฉ์ดํธ๋ฅผ ํด๋ก ํ์ฌ ์น ์๋น์ค๋ฅผ ๋ฐฐํฌํด๋ณด๋ ํ๋ก์ ํธ
์ด์ ๋ + ๋ชจ์ ๋ฒํผ์ ๋๋ฅด๋ฉด ๋ฆฌ์คํธ๊ฐ ์ถ๊ฐ๋์ด์ผ ํ๋ค. ์ด๊ฒ๋ ๊น๋ค์ ๋์ ์ฑ ์ ์ฐธ๊ณ ํด์ ์ผ๋ถ๋ถ ๋ณํํด์ ๊ตฌํํด๋ณด๋ ค๊ณ ํ๋ค.
์ง๊ธ ๋ฌธ์ ๋ ์๋ todomate์ ์ถ๊ฐ ๋ฒํผ์ด ์๋จ ๋ฐ ์ค๋ฅธ์ชฝ์ ์์ด์ ๋๋ ๊ทธ๋ ๊ฒ ๋ง๋ค์ด์ผ ํ๋ค๋ฉด, route ๋ณ๋ก BasicNavBar๊ฐ ๋ค๋ฅด๊ฒ ๋ณด์ผ ์ ์๋๋ก ํด์ผํ๋๊ฑด์ง, ์๋๋ฉด ๊ทธ๋ฅ ํ๋ฉด์ ๊ตฌํํ๊ณ ์์น๋ง ์๋จ์ผ๋ก ์ฎ๊ฒจ๋๋ ๋๋์ง ์ด๋ค.
๊ณ ๋ฏผํ๋ค๊ฐ ์ผ๋จ ๋ฒํผ์ ๋ง๋ค์ด์ ์๋จ์ ์ฌ๋ ค๋๊ณ , ๋์ค์ ์กฐ๊ฑด๋ฌธ์ ์ฐ๋ ๋ญ ํ๋ ํด์ผ๊ฒ ๋ค๊ณ ์๊ฐ...
1. Todo๋ฅผ ์ถ๊ฐํ๋ ์ปดํฌ๋ํธ ๋ง๋ค๊ธฐ
์ฑ ์์๋ todo ์ถ๊ฐ๋ฅผ ์ํ UI์ ๋ฐฑ์๋๋ฅผ ์ฝํ ํจ์๋ฅผ ์์ฑํ๋๋ฐ ์ด ํจ์๋ ๊ฐ์ง(Mock) ํจ์๊ณ , todo๋ฅผ ์ถ๊ฐํ ์ด๋ฒคํธ ํธ๋ค๋ฌ ํจ์์ ํจ์๋ฅผ UI์ ์ฐ๊ฒฐํ๋ ๊ฒ์ ํ๋์ ์ฑํฐ๋ก ์ผ์๋ค.
๊ฐ์ ๋ฐฉ์์ผ๋ก ์งํํด๋ณด์.
์ฐ์ UI๋ฅผ ์ํด GoalForm (์ฑ ์์๋ AddTodo) ์ด๋ผ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด์ค๋ค.๋ณ๋์ ํ์ผ๋ก ์์ฑํ๋ค.
useState๋ฅผ ์ด์ฉํด ์ฌ์ฉ์์ ์ ๋ ฅ๊ฐ์ ๋ฐ์ ๊ฐ์ฒด๋ฅผ ๋ฏธ๋ฆฌ ์์ฑํด์ค๋ค.
๊ทธ๋ฆฌ๊ณ ์๋ MUI๋ฅผ ์ด์ฉํด UI๋ฅผ ๋์์ธ ํ๋ฉด ๋๋๋ฐ, ์ฌ๊ธฐ์ ๋ง๋๋ + ๋ฒํผ์ '์ ๋ ฅ'์ ํด์ฃผ๋ ๋ฒํผ์ด๊ธฐ ๋๋ฌธ์ ์ฐ๋ฆฌ ํ๋ฉด์์๋ 'ํ์ธ' ์ด๋ผ๋ ํ ์คํธ ๋ฒํผ์ผ๋ก ๋์ฒดํ๋ค.
import { Button, Grid, Box, TextField } from "@mui/material";
import React, { useState } from "react";
export default function GoalForm(){
let [goalTodo, goalTodoChange] = useState({ title : '' });
return(
<Box style={{ margin : 16, padding: 16, position:'relative' }}>
<Grid container>
<Grid xs={11} md={11} item style={{paddingRight: 12}}>
<p className="goalform_title" style={{fontSize: '12px', color: '#aeaeae'}}>์ ๋ชฉ</p>
<TextField fullWidth id="goalform_textfield" variant="standard" />
</Grid>
<Grid xs={1} md={1} item >
<Button fullWidth color="secondary" style={{position:'relative',
top: '-85px', fontSize: '14px', fontWeight: '600', color: '#111'}} >
ํ์ธ
</Button>
</Grid>
</Grid>
</Box>
)
}
์ด๋ ๊ฒ ๋ง๋ GoalForm ์ปดํฌ๋ํธ๋ฅผ Goals ์ปดํฌ๋ํธ์ ์ถ๊ฐํด์ค๋ค. (๋์ค์ ์๋จ + ๋ฒํผ์ ๋๋ฅด๋ฉด ํด๋น ์ปดํฌ๋ํธ๊ฐ ์์ ๋ ์ด์ด์ฒ๋ผ ๋จ๋๋ก ๋ง๋ค์ด๋ณด์)
2. Add ํธ๋ค๋ฌ ์ถ๊ฐ
๋ฆฌ์กํธ๋ก ๋ญ๊ฐ๋ฅผ ๋ง๋ค๋๋ ์ด๋ฒคํธ๋ฅผ '๊ฐ์ง'ํ๊ณ ๊ทธ๊ฒ์ ์ ์กํ๋ ๊ฒ์ด ์ค์ํ๋ค.
๊ทธ๋์ ํค๋ณด๋๋ก TextField์ ๋ญ๊ฐ๋ฅผ ์ ๋ ฅํ๋ฉด ์คํํ ์ด๋ฒคํธ ํธ๋ค๋ฌ์, ํ์ธ ๋ฒํผ์ ๋๋ฅผ ๋ ๋ฐ์ํ ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ํ์ํ๋ค. ์ฑ ์์๋ ์ด ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ค ํจ์์ ์ด๋ฆ์ ์ ๊ณตํด์ฃผ๋๋ฐ ์๋์ ๊ฐ๋ค.
- onInputChange : ์ฌ์ฉ์๊ฐ TextField์ ๋ฌธ์์ด์ ์ถ๊ฐํ๋ฉด ํด๋น ๋ฌธ์์ด์ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด๋ก ์ ์ฅํ๋ค.
- onButtonClick : ์ฌ์ฉ์๊ฐ ํ์ธ(์ฑ ์์๋ +)๋ฒํผ์ ๋๋ฅผ ๋ ์คํ๋๊ณ , onInputChange์์ ์ ์ฅํ ๋ฌธ์์ด์ ๋ฆฌ์คํธ์ ์ถ๊ฐํ๋ค.
- enterKeyEventHandler : onButtonClick๊ณผ ๊ฐ์ ๊ธฐ๋ฅ์ด์ง๋ง ์ํฐ๋ ๋ฆฌํด์ ๋๋ ์ ๋๋ ์คํ๋๋๋ก ํ๋ ํจ์๋ค.
์ด๋ฐ์ useState๋ก ๋ง๋ goalTodo๋ ์ฌ์ฉ์์ ์ ๋ ฅ๊ฐ์ ์์๋ก ์ ์ฅํ๋ ๊ณณ์ด๋ค. TextField์์ ๊ฐ์ด ์ ๋ ฅ๋๋ฉด onChange๋ฅผ props๋ก ์ธ ์ ์๋๋ฐ, ์ด onChange๋ ์ฌ์ฉ์๊ฐ ํค๋ณด๋๋ฅผ ๋๋ฅผ ๋ ๋ง๋ค ์คํ๋๋ค.
๋ฐ๋ผ์ onChange์ ํธ๋ค๋ฌ์ธ onInputChange๋ฅผ ์ฐ๊ฒฐํ๋ฉด ์ฌ์ฉ์ ์ ๋ ฅ ์ ๋ณด๋ฅผ ๊ฐ์ฒด์ ์ ์ฅํ ์ ์๋ค.
์ด๋ฒคํธ์ ๊ฐ์ธ e.target.value๋ฅผ title์ '' ์ ๋์ฒดํด์ฃผ๋ ํจ์๋ฅผ ์ ์ด๋ณด์.
๋ณ์ thisGoal ์ state์ธ goalTodo์ item์ด ํ ๋น๋๋ค. state๋ ๋ฐ๋ก ๋ณ๊ฒฝํ๋ ๊ฒ์ด ์๋๋ผ, ์๋ณธ ๊ฐ์ ๊ทธ๋๋ก ๋๊ณ ๊ทธ๊ฒ์ ์นดํผํด์ ๋ณ์๋ฅผ ํ๋ ํ ๋นํด์ผ ํ๋ค.
๊ทธ๋ฆฌ๊ณ thisGoal์ title์ TextField์ ๊ฐ์ธ e.target.value ์ผ๋ก ๋ฐ๊ฟ์ค๋ค.
์ด๋ ๊ฒ ํ๋ฉด ์๋ณธ์ ๊ทธ๋๋ก ์ด์์๊ณ , thisGoal์ด๋ผ๋ ๋ณ์๋ {{ title: e.target.value }} ๊ฐ ๋๋ค.
state๋ฅผ ์ด๊ฒ์ผ๋ก ๋ฐ๊ฟ์น๊ธฐ ํด์ผ ํ๋๋ฐ, useState์ด๊ธฐ ๋๋ฌธ์ goalTodoChange๋ผ๋ ๋ฏธ๋ฆฌ ์์ฑ๋ useState์ ๋ณ๊ฒฝํจ์๋ฅผ ์ด์ฉํ๋ค.
import { Button, Grid, Box, TextField } from "@mui/material";
import React, { useState } from "react";
export default function GoalForm(){
let [goalTodo, goalTodoChange] = useState({item : { title : '' }});
function onInputChange(e) {
const thisGoal = goalTodo.item;
thisGoal.title = e.target.value;
goalTodoChange({item : thisGoal})
console.log(thisGoal)
}
return(
<Box style={{ margin : 16, padding: 16, position:'relative' }}>
<Grid container>
<Grid xs={11} md={11} item style={{paddingRight: 12}}>
<p className="goalform_title" style={{fontSize: '12px', color: '#aeaeae'}}>์ ๋ชฉ</p>
<TextField fullWidth id="goalform_textfield" variant="standard"
onChange={onInputChange} />
</Grid>
<Grid xs={1} md={1} item >
<Button fullWidth color="secondary" style={{position:'relative',
top: '-85px', fontSize: '14px', fontWeight: '600', color: '#111'}} >
ํ์ธ
</Button>
</Grid>
</Grid>
</Box>
)
}
3. Add ํจ์ ์์ฑ
์ด์ ํ์ธ์ ๋๋ฅด๋ฉด ์์ฑํ ๋ชฉํ๊ฐ ์ถ๊ฐ๋์ด์ผ ํ๋ค.
์ด ๋๋ ํ์ธ ๋ฒํผ์ ๋๋ฅด๊ฒ ๋๋ฉด onButtonClick์ด๋ enterKeyEventHandler์ด ์คํ๋๊ฒ ํ๋ค. ํจ์๊ฐ ์คํ๋๋ฉด App.js์ ๋ง๋ค์ด๋ goal State์ ๋ด์ฉ์ด ์ถ๊ฐ๋๋๋ก ํด์ค๋ค.
์ฌ๊ธฐ์ ๋ฌธ์ ๋ ์ด์ ํ์ธํ ๋ฐ์ ๊ฐ์ด props๋ ์๋์์ ์๋ก ๋ชป ์ฌ๋ผ๊ฐ๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์ ๋ฐ์ดํธ ํ State๊ฐ ์๋ App.js์ ๋ฆฌ์คํธ๋ฅผ ์ถ๊ฐํ๋ ํจ์๋ฅผ ์์ฑํด์ผ ํ๋ค.
๊ทธ๋ฆฌ๊ณ ๊ทธ ํจ์๋ฅผ props๋ก GoalForm ์ปดํฌ๋ํธ์ ๋๊ฒจ ์ฌ์ฉํด์ผ ํ๋ค.
App.js์์ ํจ์๋ฅผ ์์ฑํด๋ณธ๋ค.
์๊น onInputChange์ ๊ฐ๋ค. ์๋ณธ state์ธ goal.items์ thisItems ๋ผ๋ ๋ณ์์ ์นดํผํ๋ค. (์๋ items๋ผ๋ ์ด๋ฆ์ ์ ๋ถ์๋๋ฐ, ์ฑ ๊ณผ ๋ด์ฉ์ ๋ง์ถ๊ธฐ ์ํด ์ ๋ฐ์ดํธ ํ์!)
๊ทธ๋ฆฌ๊ณ item์ id๋ฅผ ๋ง๋ค์ด์ฃผ๊ณ , done์ false๋ก ๋ง๋ค์ด์ค๋ค. ์ด๊ฒ์ ์๊น ์นดํผํ thisItems๋ผ๋ ๋ณ์์ push๋ก ๋ฐ์ด๋ฃ์ด์ฃผ๊ณ ์ด๊ฒ์ goalChange๋ผ๋ ๋ณ๊ฒฝํจ์๋ฅผ ์ด์ฉํด ์๋ณธ state๋ฅผ ๋ฐ๊ฟ์ค๋ค.
// ๋ชฉํgoals ํ์ด์ง goal ์์ดํ
let [goal, goalChange] = useState(
{ items: [{
id : 0,
title: '๋ชฉํ ๋ง๋ค๊ธฐ',
done: true
},
{
id: 1,
title: '๋ชฉํ ๋ง๋ค๊ธฐ 2',
done: false
},
{
id: 2,
title: '๋ชฉํ ๋ง๋ค๊ธฐ 3',
done: false
}]
}
)
function add(item){
const thisItems = goal.items; // goal State ์๋ณธ ์นดํผ
item.id = 'ID-' + thisItems.length; //key๋ฅผ ์ํ id ์ถ๊ฐ
item.done = false; // done false๋ก ์ด๊ธฐํ
thisItems.push(item) // ์นดํผํ goal ๋ฆฌ์คํธ์ ์์ดํ
์ถ๊ฐ
goalChange({items: thisItems}); //goalChange๋ฅผ ์ด์ฉํด state ๋ณ๊ฒฝ
console.log('items :', goal.items)
}
๊ทธ๋ฆฌ๊ณ ์ด add ํจ์๋ฅผ props๋ก ๋ณด๋ธ๋ค.
import React, { useState } from "react";
import "../stylesheets/App.css";
import { Button } from "@mui/material";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import { Switch, Route, useHistory } from "react-router-dom";
import Signin from "./Signin";
import Join from "./Join";
import BasicNavBar from "./BasicNavBar";
import Goals from "./Goals";
import Main from "./Main";
function App() {
let history = useHistory();
// ๋ชฉํgoals ํ์ด์ง goal ์์ดํ
let [goal, goalChange] = useState(
{ items: [{
id : 0,
title: '๋ชฉํ ๋ง๋ค๊ธฐ',
done: true
},
{
id: 1,
title: '๋ชฉํ ๋ง๋ค๊ธฐ 2',
done: false
},
{
id: 2,
title: '๋ชฉํ ๋ง๋ค๊ธฐ 3',
done: false
}]
}
)
function add(item){
const thisItems = goal.items; // goal State ์๋ณธ ์นดํผ
item.id = 'ID-' + thisItems.length; //key๋ฅผ ์ํ id ์ถ๊ฐ
item.done = false; // done false๋ก ์ด๊ธฐํ
thisItems.push(item) // ์นดํผํ goal ๋ฆฌ์คํธ์ ์์ดํ
์ถ๊ฐ
goalChange({items: thisItems}); //goalChange๋ฅผ ์ด์ฉํด state ๋ณ๊ฒฝ
console.log('items :', goal.items)
}
return (
<ThemeProvider theme={theme}>
<Switch>
<Route exact path="/">
<div className="App">
<div className="header">
<img className="main_img" src="images/todomate.jpg" />
<h1>todo mate</h1>
<h3>ํ ์ผ์ ์์ฑ, ๊ณํ, ๊ด๋ฆฌํ์ธ์.</h3>
</div>
<div className="start_btn">
<Button
className="join_btn"
color="btn"
variant="contained"
sx={{ boxShadow: 'none'}}
onClick={() => {
history.push("/join");
}}
>
๊ฐ์
ํ๊ธฐ
</Button>
<Button
className="signin_btn"
color="btn"
variant="contained"
sx={{ boxShadow: 'none'}}
onClick={() => {
history.push("/signin");
}}
>
๋ก๊ทธ์ธ
</Button>
</div>
</div>
</Route>
<Route exact path="/main">
<Main/>
</Route>
<Route exact path="/join">
<BasicNavBar/>
<Join />
</Route>
<Route exact path="/signin">
<BasicNavBar/>
<Signin />
</Route>
<Route exact path="/goals">
<BasicNavBar/>
<Goals goal={goal.items} add={add} key={goal.items.id} />
</Route>
</Switch>
</ThemeProvider>
);
}
export default App;
์ฌ๊ธฐ์ ๋ฌธ์ ๋ ๋๋ App.js -> Goals.js -> GoalForm.js ๋ก ๋ฃ์ด์ค์ผ ํด์ ํ ๋ฒ ๋ props๋ก ์ ๋ฌํ๋ค.
import React from "react";
import { Checkbox, InputBase, ListItem, ListItemText } from "@mui/material";
import GoalForm from "./GoalForm";
export default function Goals(props) {
console.log(props.goal);
let goalItems = props.goal;
let add = props.add;
return (
<>
<GoalForm add={add} />
{
goalItems.map((item, idx) => {
return (
<ListItem className="goals-wrap">
<Checkbox checked={item.done} />
<ListItemText>
<InputBase
inputProps={{ "aria-label": "naked" }}
type="text"
id={item.id}
name={item.id}
value={item.title}
multiline={true}
fullWidth={true}
/>
</ListItemText>
</ListItem>
);
})
}
</>
);
}
๊ทธ๋ฆฌ๊ณ ์ด add ํจ์๋ฅผ onButtonClick ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ ํจ์๋ก ์ฌ์ฉํ๋ค.
import { Button, Grid, Box, TextField } from "@mui/material";
import React, { useState } from "react";
export default function GoalForm(props){
let [goalTodo, goalTodoChange] = useState({item : { title : '' }}, props.add);
function onInputChange(e) {
const thisGoal = goalTodo.item;
thisGoal.title = e.target.value;
goalTodoChange({item : thisGoal})
console.log(thisGoal)
}
function onButtonClick(){
props.add(goalTodo.item);
goalTodoChange({item : {title: ''}});
}
return(
<Box style={{ margin : 16, padding: 16, position:'relative' }}>
<Grid container>
<Grid xs={11} md={11} item style={{paddingRight: 12}}>
<p className="goalform_title" style={{fontSize: '12px', color: '#aeaeae'}}>์ ๋ชฉ</p>
<TextField fullWidth id="goalform_textfield"
variant="standard" onChange={onInputChange} />
</Grid>
<Grid xs={1} md={1} item >
<Button fullWidth color="secondary" onClick={onButtonClick}
style={{position:'relative', top: '-85px',
fontSize: '14px', fontWeight: '600', color: '#111'}} >
ํ์ธ
</Button>
</Grid>
</Grid>
</Box>
)
}
์ฐ์ ๋๋ฐ ์ฑ๊ณตํ๋ค ใ ใ
๊ทธ๋ผ ๋๊ฐ์ด ์ํฐํค๋ก ์ถ๊ฐํ ์ ์๋๋ก enterKeyEventHandler ํจ์๋ฅผ ์ถ๊ฐํ๋ค. ๊ธฐ๋ฅ์ add ํจ์์ ๊ฐ๊ธฐ ๋๋ฌธ์ key์ ๋ ฅ๋ง ์ฒ๋ฆฌํด์ฃผ๋ฉด ๋๋ค.
GoalForm.js ์ onButtonClick ์๋์ ์ถ๊ฐํด์ค๋ค.
function enterKeyEventHandler(e){
if(e.key === 'Enter'){
onButtonClick();
}
}
์ด enterKeyEventHandler ํจ์๋ TextField์ onKeyPress ์ด๋ฒคํธ์ ๋ฐ์ธ๋ฉํด์ค๋ค.
import { Button, Grid, Box, TextField } from "@mui/material";
import React, { useState } from "react";
export default function GoalForm(props){
let [goalTodo, goalTodoChange] = useState({item : { title : '' }}, props.add);
function onInputChange(e) {
const thisGoal = goalTodo.item;
thisGoal.title = e.target.value;
goalTodoChange({item : thisGoal})
console.log(thisGoal)
}
function onButtonClick(){
props.add(goalTodo.item);
goalTodoChange({item : {title: ''}});
}
function enterKeyEventHandler(e){
if(e.key === 'Enter'){
onButtonClick();
}
}
return(
<Box style={{ margin : 16, padding: 16, position:'relative' }}>
<Grid container>
<Grid xs={11} md={11} item style={{paddingRight: 12}}>
<p className="goalform_title" style={{fontSize: '12px', color: '#aeaeae'}}>์ ๋ชฉ</p>
<TextField fullWidth id="goalform_textfield" variant="standard" onChange={onInputChange} />
</Grid>
<Grid xs={1} md={1} item >
<Button fullWidth color="secondary" onClick={onButtonClick}
value={goalTodo.item.title} onKeyPress={enterKeyEventHandler}
style={{position:'relative', top: '-85px', fontSize: '14px', fontWeight: '600', color: '#111'}} >
ํ์ธ
</Button>
</Grid>
</Grid>
</Box>
)
}
๋ค ์ ์์ ์ผ๋ก ํ ๋ฏ ์ถ์๋ฐ ์์ธ์ง ์ํฐ๋ก๋ ์ ๋ ฅ์ด ์๋๊ณ ์๋ค.
์ด์จ๋ ์ถ๋ ฅ์ ์ํค๊ณ ์์ผ๋ !! ์ค๋์ ์ฌ๊ธฐ๊น์ง ใ ใ
'WEB Dev > ToyProject' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[ToyProject-Todomate] ํฌ๋๋ฉ์ดํธ ํด๋ก ํ๋ก์ ํธ 23 (0) | 2022.01.06 |
---|---|
[ToyProject-Todomate] ํฌ๋๋ฉ์ดํธ ํด๋ก ํ๋ก์ ํธ 22 (0) | 2022.01.05 |
[ToyProject-Todomate] ํฌ๋๋ฉ์ดํธ ํด๋ก ํ๋ก์ ํธ 20 (0) | 2022.01.02 |
[ToyProject-Todomate] ํฌ๋๋ฉ์ดํธ ํด๋ก ํ๋ก์ ํธ 19 (0) | 2021.12.28 |
[ToyProject-Todomate] ํฌ๋๋ฉ์ดํธ ํด๋ก ํ๋ก์ ํธ 18 (0) | 2021.12.28 |