1ํ : https://hydeveloper.tistory.com/191
์ด๋ฒ์๋ useState, useEffect, useRef์ json.server๋ฅผ ํ์ฉํด CRUD ๊ธฐ๋ฅ์ ๊ฐ์ง todoList๋ฅผ ๋ง๋ค์ด์ผํ๋๋ฐ
์ผ๋จ ํด๋น ๊ณผ์ ๋ ๊นํ๋ธ์ ์ ๋ก๋ํด๋จ๋ค.. https://github.com/idubusomuch/Todo-app-json
์ต์ ์๊ตฌ ์ฌํญ
- Todo ์์ฑ / ์กฐํ / ์์ / ์ญ์ (CRUD) ๊ธฐ๋ฅ์ ๊ตฌํํ์ธ์.
- ํ์ฌ ์๊ฐ ํ์, ํ์ด๋จธ, ์คํฑ์์น ์ค ํ๋ ์ด์์ ๊ธฐ๋ฅ์ ๊ตฌํํ์ธ์.
- ๋๋ค ๋ช ์ธ์ ํ์ํ ์ ์๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋์ธ์.
- useState, useEffect, useRef๋ฅผ ๊ฐ๊ฐ ํ ๋ฒ ์ด์ ์ฌ์ฉํ์ธ์.
- ์์ ๋กญ๊ฒ ์ ์ฉํด๋ณด๊ณ ์ถ์ CSS๋ฅผ ์์ฑํด๋ณด์ธ์.
์ฌํ ์๊ตฌ ์ฌํญ
- json-server๋ฅผ ์ฌ์ฉํด Todo ์ ๋ณด๋ฅผ ํ์ผ๋ก ์ ์ฅํด ๋ณด์ธ์.
- Custom Hook์ ๋ง๋ค๊ณ ์ฌ์ฉํด ๋ณด์ธ์.
App.jsx
import { useEffect, useState, useRef } from 'react';
import './App.css';
function App() {
const [isLoading, data] = useFetch('http://localhost:3000/todo');
const [todo, setTodo] = useState([]);
useEffect(() => {
if (data) setTodo(data);
}, [isLoading]);
return (
<>
<div className="top-bar">
<Clock />
<Advice />
</div>
{/* <StopWatch />
<Timer /> */}
<TodoInput setTodo={setTodo} />
<TodoList todo={todo} setTodo={setTodo} />
</>
);
}
// ์ปค์คํ
ํ
const useFetch = (url) => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then((res) => res.json())
.then((res) => {
setData(res);
setIsLoading(false);
})
.catch((err) => {
setError(err);
console.log('fetch error : ', err);
setIsLoading(false);
});
}, [url]);
return [isLoading, data, error];
};
// ๋๋ค ๋ช
์ธ ํ์
// https://github.com/gwongibeom/korean-advice-open-api
const Advice = () => {
const [isLoading, data] = useFetch(
'https://korean-advice-open-api.vercel.app/api/advice',
);
return (
<div className="advice">
{!isLoading && (
<>
<div>{data.message}</div>
<div>-{data.author}-</div>
</>
)}
</div>
);
};
// ํ์ฌ ์๊ฐ ํ์
const Clock = () => {
const [time, setTime] = useState(new Date());
useEffect(() => {
setInterval(() => {
setTime(new Date());
}, 1000);
}, []);
return <div className="time-now">{time.toLocaleTimeString()}</div>;
};
// ์๊ฐ ํฌ๋งท ํจ์
const formatTime = (seconds) => {
const hour = String(Math.floor(seconds / 3600)).padStart(2, '0');
const minute = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0');
const second = String(seconds % 60).padStart(2, '0');
const timeString = `${hour}:${minute}:${second}`;
return timeString;
};
// ์คํ์์น
const StopWatch = () => {
const [time, setTime] = useState(0);
const [isOn, setIsOn] = useState(false);
const timeRef = useRef(null);
useEffect(() => {
if (isOn === true) {
const timeId = setInterval(() => {
setTime((prev) => prev + 1);
}, 1000);
timeRef.current = timeId;
} else {
clearInterval(timeRef.current);
}
}, [isOn]);
return (
<div>
{formatTime(time)}
<button onClick={() => setIsOn((prev) => !prev)}>
{isOn ? '๋๊ธฐ' : '์ผ๊ธฐ'}
</button>
<button
onClick={() => {
setTime(0);
setIsOn(false);
}}>
๋ฆฌ์
</button>
</div>
);
};
// ํ์ด๋จธ
const Timer = () => {
// ์์ ์๊ฐ
const [startTime, setStartTime] = useState(0);
// ํ์ด๋จธ ์คํ ์ฌ๋ถ
const [isOn, setIsOn] = useState(false);
// ํ์ฌ ์๊ฐ
const [time, setTime] = useState(0);
// ํ์ด๋จธ ์์ด๋๋ฅผ ์ ์ฅํ๋ useRef
const timeRef = useRef(null);
useEffect(() => {
if (isOn && time > 0) {
const timeId = setInterval(() => {
setTime((prev) => prev - 1);
}, 1000);
timeRef.current = timeId;
} else if (!isOn || time == 0) {
clearInterval(timeRef.current);
}
// ํด๋ฆฐ์
ํจ์
return () => clearInterval(timeRef.current);
}, [isOn, time]);
return (
<div>
<div>
{time ? formatTime(time) : formatTime(startTime)}
<button
onClick={() => {
setIsOn(true);
setTime(time ? time : startTime);
setStartTime(0);
}}>
์์
</button>
<button onClick={() => setIsOn(false)}>๋ฉ์ถค</button>
<button
onClick={() => {
setTime(0);
setIsOn(false);
}}>
๋ฆฌ์
</button>
</div>
<input
type="range"
value={startTime}
min="0"
max="3600"
step="30"
onChange={(e) => setStartTime(e.target.value)}
/>
</div>
);
};
// CRUD ๊ธฐ๋ฅ
// CREATE
const TodoInput = ({ setTodo }) => {
const inputRef = useRef(null);
const addTodo = () => {
const newTodo = {
// json server์์๋ ์์ฒญ์ ๋ณด๋ด๋ฉด ์์ด๋๋ฅผ ์ง์ ์์ฑํด์ค
// id: Number(new Date()),
content: inputRef.current.value,
time: 0,
};
fetch('http://localhost:3000/todo', {
method: 'POST',
body: JSON.stringify(newTodo),
})
.then((res) => res.json())
.then((res) => setTodo((prev) => [...prev, res]));
inputRef.current.value = '';
};
return (
<div className="todo-input">
<input ref={inputRef} />
<button onClick={addTodo}>์ถ๊ฐ</button>
</div>
);
};
// READ
const TodoList = ({ todo, setTodo }) => {
return (
<ul className="todo-list">
{todo.map((el) => (
<Todo key={el.id} todo={el} setTodo={setTodo} />
))}
</ul>
);
};
// UPDATE, DELETE
const Todo = ({ todo, setTodo }) => {
const [isEdit, setIsEdit] = useState(false);
const [editValue, setEditValue] = useState(todo.content);
// ์์ ๋ฒํผ์ ๋๋ ์ ๋
const editHandler = () => {
setIsEdit(true);
setEditValue(todo.content);
};
// ์์ ๊ธฐ๋ฅ
const updateTodo = () => {
if (!editValue.trim()) {
alert('๊ธ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.');
return;
}
fetch(`http://localhost:3000/todo/${todo.id}`, {
// PUT์ ๋ฆฌ์์ค์ ๋ชจ๋ ๊ฒ์ ์
๋ฐ์ดํธ
// PATCH๋ ๋ฆฌ์์ค์ ์ผ๋ถ๋ฅผ ์
๋ฐ์ดํธ
method: 'PATCH',
// json-server์ ๋ณด๋ผ ๋ถ๋ถ
body: JSON.stringify({ content: editValue }),
})
.then((res) => res.json())
.then((res) => {
setTodo((prev) =>
prev.map((el) =>
el.id === res.id ? { ...el, content: res.content } : el,
),
);
setIsEdit(false);
});
// ์์ ๋
setIsEdit(false);
};
// ์ญ์ ๊ธฐ๋ฅ
const deleteTodo = () => {
fetch(`http://localhost:3000/todo/${todo.id}`, {
method: 'DELETE',
}).then((res) => {
if (res.ok) {
setTodo((prev) => prev.filter((el) => el.id !== todo.id));
}
});
};
return (
<li>
{isEdit ? (
<>
<input
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
/>
<div className="btn-container">
<button onClick={updateTodo}>์ ์ฅ</button>
<button onClick={() => setIsEdit(false)}>์ทจ์</button>
</div>
</>
) : (
<>
<div>{todo.content}</div>
<div className="btn-container">
<button onClick={editHandler}>์์ </button>
<button onClick={deleteTodo}>์ญ์ </button>
</div>
</>
)}
</li>
);
};
export default App;
์ผ๋จ ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ๋ค.
App : ์ต์์ ์ปดํฌ๋ํธ
* ๋ช ์ธ ์ถ๋ ฅ
useFetch (์ปค์คํ ํ ) : url์ ๋๊ธฐ๋ฉด fetch ์์ฒญ์ ๋ณด๋ด๊ณ ๋ก๋ฉ / ๋ฐ์ดํฐ / ์๋ฌ ์ํ๋ฅผ ๊ด๋ฆฌํด์ฃผ๋ ์ปค์คํ ํ
Advice : useFetch๋ฅผ ์ด์ฉํด ๋ช ์ธ API๋ฅผ ๋ฐ์์ ๋๋ค ๋ช ์ธ์ ์ถ๋ ฅํ๋ ์ปดํฌ๋ํธ
* ์๊ฐ ๊ด๋ จ
Clock : ์ค์๊ฐ ์๊ณ๋ฅผ ์ถ๋ ฅํ๋ ์ปดํฌ๋ํธ
formatTime ํจ์ : ์คํฑ์์น์ ํ์ด๋จธ์์ ์๊ฐ ํํ๋ฅผ ํฌ๋งทํ๊ธฐ ์ํด ๋ง๋ ํจ์
StopWatch : ์คํ์์น ๊ธฐ๋ฅ์ ๊ฐ์ง ์ปดํฌ๋ํธ (์ด๋ฒ ๊ณผ์ ์์๋ ํ๋ฉด์ ์ํ์ํจ)
Timer : 0~1์๊ฐ ๋ฒ์๋ก ํ์ด๋จธ๋ฅผ ์ค์ ํ ์ ์๊ฒ ํ๋ ์ปดํฌ๋ํธ (์ด๋ฒ ๊ณผ์ ์์๋ ํ๋ฉด์ ์ํ์ํจ)
* todoList ๊ธฐ๋ฅ
TodoInput : CREATE ๊ธฐ๋ฅ
TodoList : READ ๊ธฐ๋ฅ
Todo : UPDATE, DELETE ๊ธฐ๋ฅ
1. useFetch
์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ๋ก๋ฉ/๋ฐ์ดํฐ/์๋ฌ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ์ปค์คํ ํ
์ด๋ฒ์ ๊ทธ๋ฅ json-server๋ฅผ ์ด์ฉํ๊ธฐ ๋๋ฌธ์ error ์ํ ์ค์ ์ ํจ์คํ๊ณ ,
async/await ํจํด์ ์ฌ์ฉํ๋ฉด ๋ ์์ ์ ์ด๋ผ๋๋ฐ ์์ง ์ ๋ชฐ๋ผ์ ํจ์ค..
MDN - fetch() API ์ฌ์ฉ : https://developer.mozilla.org/ko/docs/Web/API/Fetch_API/Using_Fetch
const useFetch = (url) => {
// ๋ด๋ถ ์ํ ๊ด๋ฆฌ
const [isLoading, setIsLoading] = useState(true); // ์์ฒญ ์งํ ์ํ
const [data, setData] = useState(null); // ์ฑ๊ณต ์ ๋ฐ์ json ๋ฐ์ดํฐ
// const [error, setError] = useState(null); // ์คํจ ์ ๋ฐ์ error ๊ฐ์ฒด
useEffect(() => {
fetch(url) // HTTP GET ์์ฒญ (GET์ด ๊ธฐ๋ณธ๊ฐ์)
.then((res) => res.json()) // ์๋ต์ด ์ค๋ฉด JSON์ผ๋ก ํ์ฑ (json->javascript๋ผ๋ ๋ป)
.then((res) => {
setData(res); // ํ์ฑ๋ ๊ฒฐ๊ณผ res๋ฅผ setData๋ก ์ค์
setIsLoading(false); // isLoading์ false๋ก ์ ํ
})
.catch((err) => { // ๋คํธ์ํฌ๋ ํ์ฑ ์๋ฌ ๋ฐ์์
// setError(err); // setError(err)๋ก ์๋ฌ ์ ์ฅ
console.log('fetch error : ', err); // ๋๋ฒ๊ทธ
setIsLoading(false); // isLoading์ false๋ก ์ ํ(๋ก๋ฉ ์ํ ํด์ )
});
}, [url]); // ์์กด์ฑ ๋ฐฐ์ด [url], url์ด ๋ฐ๋๋๋ง๋ค ์๋ก API๋ฅผ ํธ์ถ
return [isLoading, data;
};
2. todo List CRUD ๊ธฐ๋ฅ
์ผ๋จ json-server๊ณผ ์ฐ๊ฒฐํด์ผ ํ๋ฏ๋ก ์ปค์คํ ํ useFetch๋ก ๋ฐํ๊ฐ์ ๋ฐ์์จ๋ค.
json-server ์ค์น๋ https://hydeveloper.tistory.com/196 ์ ์คํฌ๋กค ์ค๊ฐ ์๋ช ์ฃผ๊ธฐ ํจ์์ ํ์์ฑ์ ์๋ค..
App ์ปดํฌ๋ํธ
function App() {
// ๋ก๋ฉ ์ํ
// useFetch(url) => [isLoading, data] ํํ๋ก ๋ฐํ๋จ
const [isLoading, data] = useFetch('http://localhost:3000/todo');
const [todo, setTodo] = useState([]);
// useFetch๋ก ๋ฐ์์จ data๊ฐ ์กด์ฌํ ๊ฒฝ์ฐ setTodo๋ก todo ๊ฐ ์ค์
useEffect(() => {
if (data) setTodo(data);
// isLoading์ ์์กด์ฑ ๋ฐฐ์ด๋ก ๋ฃ์ด ๋ก๋ฉ์ด ๋๋๋ค๋ฉด ๋๊ธฐํ์ํด
}, [isLoading]);
// todoList์๋ setTodo๋ฅผ props์ผ๋ก ๋๊ฒจ ์์ ์ปดํฌ๋ํธ์์ ์ํ๋ฅผ ๊ฐฑ์ ํ ์ ์๊ฒ ํจ
return (
<>
<div className="top-bar">
<Clock />
<Advice />
</div>
<TodoInput setTodo={setTodo} />
<TodoList todo={todo} setTodo={setTodo} />
</>
);
}
TodoInput ์ปดํฌ๋ํธ - CREATE
์์ useFetch์๋ ๋ฌ๋ฆฌ POST ๋ฐฉ์์ ํ์ฉํ๊ณ ์๋๋ฐ,
GET์ ์๋ฒ๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ ๋, POST๋ ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ์๋ก ์์ฑํ๊ฑฐ๋ ์ ๋ฐ์ดํธํ ๋ ์ฌ์ฉ๋๋ค.
์ด ์ปดํฌ๋ํธ์์๋ ์๋ก์ด todo ๋ฐ์ดํฐ๋ฅผ ์์ฑํด ์ ๋ฐ์ดํธํ๋ฏ๋ก method:'POST'๋ก ํ๋ค.
const TodoInput = ({ setTodo }) => {
// useRef(null)๋ก input ์์์ ์ง์ ์ ๊ทผ
// inputRef.current.value๋ก input์ value๊ฐ์ ์ฝ๊ธฐ ๋ฐ ๋ฆฌ์
์ด ๊ฐ๋ฅ
const inputRef = useRef(null);
// json.server์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ๋ ํจ์
const addTodo = () => {
// ์
๋ ฅ๋ฐ์ ๊ฐ์ ๋ด์ newTodo ๊ฐ์ฒด ์์ฑ
const newTodo = {
content: inputRef.current.value,
// time: 0, // ์ด๊ฑด ๊ฐ์์์ ์ถ๊ฐ ๊ธฐ๋ฅ ๊ตฌํํ๊ธฐ ์ํด ๋ง๋ ๊ฑฐ๋ผ ์์ด๋ ๋จ
};
fetch('http://localhost:3000/todo', {
method: 'POST', // POST ์์ฒญ
body: JSON.stringify(newTodo), // newTodo ๊ฐ์ฒด์ ๋ด๊ธด ๊ฐ์ jsonํ์์ผ๋ก ๋ณํ
})
.then(res => res.json()) // json์ผ๋ก ๋ค์ ํ์ฑํด์
.then(res => setTodo(prev => [...prev, res]));
// ๊ธฐ์กด todo ๋ฐฐ์ด ๋ค์ ์๋ต๋ฐ์ ํ์ฑํ ๊ฐ์ฒด ์ถ๊ฐ
// .catch๋ก ์๋ฌ์ถ๊ฐ ํด๋ ๋ ๋ฏ..
inputRef.current.value = ''; // input value๊ฐ ์ด๊ธฐํ
};
return (
<div className="todo-input">
<input ref={inputRef} />
<button onClick={addTodo}>์ถ๊ฐ</button>
</div>
);
};
TodoList ์ปดํฌ๋ํธ - READ
const TodoList = ({ todo, setTodo }) => {
// map์ผ๋ก ๋ฆฌ์คํธ ๋ ๋๋ง
// key = el.id๋ก ๊ตฌ๋ถ
// ๊ฐ ํญ๋ชฉ๋ง๋ค Todo ์ปดํฌ๋ํธ ์์ฑ
// Todo ์ปดํฌ๋ํธ์์ ํ์ํ๋ฏ๋ก ๋ฐ์์จ props(todo, setTodo)๋ฅผ ๋ค์ ๋ณด๋
return (
<ul className="todo-list">
{todo.map(el => (
<Todo key={el.id} todo={el} setTodo={setTodo} />
))}
</ul>
);
};
Todo ์ปดํฌ๋ํธ - UPDATE, DELETE
์ฌ๊ธฐ ์ปดํฌ๋ํธ๋ ๋ง์ด ๊ธธ์ด์ ๊ธฐ๋ฅ๋ณ๋ก ๋๋ด๋ค..
1) ์ํ ๊ด๋ฆฌ
const Todo = ({ todo, setTodo }) => {
const [isEdit, setIsEdit] = useState(false); // ์์ ๋ชจ๋ toggle
// input์์ content๋ฅผ ์์ ํ ๋์ ์์ ๊ฐ ์ํ ๊ด๋ฆฌ
const [editValue, setEditValue] = useState(todo.content);
2) ์์ ํ๋ก์ธ์ค
METHOD๋ฅผ ๋ญ ์จ์ผํ๋ ๊ณ ๋ฏผ์ ํ๋๋ฐ
PUT์ ๋ฆฌ์์ค์ ๋ชจ๋ ๊ฒ์ ์ ๋ฐ์ดํธ, PATCH๋ ๋ฆฌ์์ค์ ์ผ๋ถ๋ฅผ ์ ๋ฐ์ดํธํ๋ค.
๋๋ ๋ฆฌ์์ค์ ์ผ๋ถ(content)๋ง ์ ๋ฐ์ดํธํ๊ธฐ ๋๋ฌธ์ PATCH๋ฅผ ์ฌ์ฉํ๋ค.
(PUT์ ์ฌ์ฉํ ๊ฒฝ์ฐ ์ ๋ฐ์ดํธํ ๋ todo ๊ฐ์ฒด์ ์๋ ๋ชจ๋ ๊ฐ์ ๋ค์ ๋ฃ์ด์ค์ผ ์์ ์ด ๊ฐ๋ฅํด์ง๋ค...)
// 1) ์์ ๋ชจ๋ ํธ๋ค๋ฌ (๋ฒํผ ํด๋ฆญ์ ํธ์ถ)
const editHandler = () => {
// ์์ ๋ชจ๋ : true => return๋ฌธ์์ ํธ์ง UI๋ฅผ ํ์
setIsEdit(true);
// ์์ ๋ชจ๋๊ฐ ๋ง ์์๋์ ๋์ editValue๋ฅผ ๊ธฐ์กด์ content ๊ฐ์ผ๋ก ์ด๊ธฐํ
setEditValue(todo.content);
};
// 2) ์๋ฒ์ PATCH → ๋ก์ปฌ ์ํ ์
๋ฐ์ดํธ
const updateTodo = () => {
// ์์ ํ ๊ฐ์ด ๊ณต๋ฐฑ์ธ์ง ์ฒดํฌ
if (!editValue.trim()) {
alert('๊ธ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.');
return;
}
// url = /todo/${todo.id} (์์ ๋ ํด๋น Todo ์ปดํฌ๋ํธ์ id๋ฅผ ๋ฐ์์ด)
fetch(`/todo/${todo.id}`, {
method: 'PATCH', // ์์ ์ PATCH
body: JSON.stringify({ content: editValue }), // ์์ ํ ๊ฐ์ json์ผ๋ก ๋ณํํด ๋ฃ์ด์ค
})
.then(res => res.json()) // ํ์ฑ ํ
.then(res => {
setTodo(prev => // ์๋ฒ ์๋ต res๋ฅผ setTodo๋ก todo์ ๋ฐ์
prev.map(el => (el.id === res.id ? { // el.id์ res.id๊ฐ ๊ฐ๋ค๋ฉด
...el, content: res.content } // true์ผ ๊ฒฝ์ฐ ์คํ๋ ๋ ์ฐ์ฐ์๋ก ํด๋น ๊ฐ์ฒด๋ฅผ ๋ณต์ฌํ๊ณ content๋ก res.content๋ฅผ ๋ฐ์
: el)) // ์๋๋ฉด ๊ทธ๋ฅ ๊ทธ๋๋ก
);
setIsEdit(false); // ์์ ๋ชจ๋ ๋
});
};
// 3) ์ญ์ ์์ฒญ
const deleteTodo = () => {
// method : DELETE๋ก ์ญ์ ์์ฒญ
fetch(`/todo/${todo.id}`, { method: 'DELETE' })
.then(res => {
if (res.ok) { // ์์ฒญ์ด ์ฑ๊ณตํ๋ค๋ฉด
// setTodo๋ก Todo ๋ฐฐ์ด ์ค ์ญ์ ํ ์์ด๋์ ์ผ์นํ์ง ์๋ ์์๋ค๋ก๋ง ํํฐ๋ง
setTodo(prev => prev.filter(el => el.id !== todo.id));
}
});
};
return (
<li>
{isEdit ? (
// ์์ ๋ชจ๋์ผ ๋
<>
<input
value={editValue}
onChange={e => setEditValue(e.target.value)}
/>
<div className="btn-container">
<button onClick={updateTodo}>์ ์ฅ</button>
<button onClick={() => setIsEdit(false)}>์ทจ์</button>
</div>
</>
) : (
// ์๋ ๋
<>
<div>{todo.content}</div>
<div className="btn-container">
<button onClick={editHandler}>์์ </button>
<button onClick={deleteTodo}>์ญ์ </button>
</div>
</>
)}
</li>
);
};
3. Advice ์ปดํฌ๋ํธ
ํ๊ตญ์ด ๋ช ์ธ api๋ฅผ ์ฌ์ฉํด ๋ช ์ธ์ ํ์ํ๋ค.
isLoading์ด false๊ฐ ๋๋ฉด data.message์ data.author์ด ์ถ๋ ฅ๋๋ค. (&&์ฐ์ฐ์๊ฐ ์ ๋ ๊ฒ ์ฐ์ด๋๊ฒ ์์ง ๋ฏ์ค๋ค...)
const Advice = () => {
const [isLoading, data] = useFetch(
'https://korean-advice-open-api.vercel.app/api/advice',
);
return (
<div className="advice">
{!isLoading && (
<>
<div>{data.message}</div>
<div>-{data.author}-</div>
</>
)}
</div>
);
};
4. Clock ์ปดํฌ๋ํธ
const Clock = () => {
// ์๊ฐ ์ํ ๊ด๋ฆฌ (์ด๊ธฐ๊ฐ์ ํ์ฌ Date๋ฅผ ๊ฐ์ ธ์ด)
const [time, setTime] = useState(new Date());
useEffect(() => {
setInterval(() => {
setTime(new Date());
}, 1000); // 1์ด์ ํ๋ฒ์ฉ new Date๋ก time๊ฐ์ด ์ค์ ๋จ
}, []); // ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ๋ ๋ ํ ๋ฒ ์คํ
// useEffect ์์ฒด๋ ๋ฐ๋ผ์ ํ๋ฒ๋ง ์คํ๋์ง๋ง,
// setInterval๋ก ๋ฑ๋ก๋ ์ฝ๋ฐฑ์ด ๊ณ์ ์คํ๋์ด time์ด ๋งค์ด ๊ฐฑ์ ๋ผ ๋ ๋๋ง๋จ
return <div className="time-now">{time.toLocaleTimeString()}</div>;
};
ํด๋ฆฐ์ ํจ์๋ฅผ ์จ์ฃผ๋ ๊ฒ์ด ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํ ์ ์๊ธด ํ์ง๋ง, ์ด ์ํ๋ก๋ ๋ฌธ์ ์์ด ์ ์๋ํด์ ์๋ตํ๋ค...
๋ง์ฝ ํด๋ฆฐ์ ํจ์๋ฅผ ์จ์ค๋ค๋ฉด ์๋์ฒ๋ผ 1์ด๋ง๋ค ํ์ด๋จธ์ id๋ฅผ ๋ฐ์์ ๊ณ์ ํด๋ฆฐ์ ํด์ค ์ ์๋ค.
const Clock = () => {
const [time, setTime] = useState(new Date());
useEffect(() => {
const timerId = setInterval(() => {
setTime(new Date());
}, 1000);
// ํด๋ฆฐ์
ํจ์: ์ธ๋ง์ดํธ ์ ํ์ด๋จธ ํด์
return () => {
clearInterval(timerId);
};
}, []); // ๋ง์ดํธ ์ ํ ๋ฒ ์คํ
return <div className="time-now">{time.toLocaleTimeString()}</div>;
};
'react' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
React / ์ ์ญ ์ํ ๊ด๋ฆฌ (1) - ๋ด๋ถ (0) | 2025.04.18 |
---|---|
React / openweatherapi ์ฌ์ฉํ๊ธฐ (0) | 2025.04.17 |
React / ์คํ์ผ๋ง (3) - tailwindcss (0) | 2025.04.17 |
React / ์คํ์ผ๋ง (2) - Styled Components (1) | 2025.04.16 |
React / ์คํ์ผ๋ง (1) - SCSS (0) | 2025.04.15 |