๊ด์ฌ์๋ ์ํ๋ฅผ ๋ชจ์๋ ์ ์๋ค.
ํธ์์ ์ฌ๊ธฐ์ '์ฐ' ๊ธฐ๋ฅ์ด๋ผ๊ณ ๋ถ๋ฅด๊ฒ ๋ค.
๐ฆsrc
โฃ ๐api
โ โฃ ๐firebase.jsx // realtime databaase API
โ โ ๐upload.jsx // ์ด๋ฏธ์ง ์
๋ก๋์ฉ storage API
โฃ ๐components
โ โฃ ๐community
โ โ โฃ ๐DeletePost.jsx // ์ญ์ ์ปดํฌ๋ํธ
โ โ โฃ ๐NewPost.jsx // ์ ๊ธ ์์ฑ ์ปดํฌ๋ํธ
โ โ โฃ ๐PostCard.jsx // ๊ฐ๋ณ ๊ฒ์๊ธ
โ โ โฃ ๐PostList.jsx // ์ ์ฒด ๊ฒ์๊ธ
โ โ โฃ ๐PostModal.jsx // ์์ , ์ญ์ ์ฐฝ์ด ์ถ๋ ฅ๋๋ ๋ชจ๋ฌ
โฃ ๐pages
โ โฃ ๐Community.jsx // ์ปค๋ฎค๋ํฐ ํ์ด์ง
โฃ ๐store
โ โฃ ๐community
โ โ โฃ ๐community-actions.jsx
โ โ โ ๐community-slice.jsx
โ โ ๐store.jsx
path: "/community" element: "<Community/>"
์ปค๋ฎค๋ํฐ ๊ฒ์๊ธ CRUD๋ฅผ ๊ตฌํํ๊ธฐ์ ์์ DB๊ตฌ์กฐ(๊ฒฝ๋ก)๋ฅผ ์ด๋ป๊ฒ ์งค ๊ฒ์ธ๊ฐ ์๊ฐํด์ผ๋๋ค. ๋จ์ํ ๊ฒ์๊ธ๋ง ์์ฑํ๋๊ฑด ๊ฒฝ๋ก๊ฐ ํฌ๊ฒ ์ค์ํ์ง ์๊ฒ ์ง๋ง ๋๋ ํ์ ๋๊ธ ๊ธฐ๋ฅ๋ ๊ตฌํํ ์์ ์ด๊ธฐ ๋๋ฌธ์ ์ด๋ป๊ฒํ๋ฉด ๋ ๋ฐ์ดํฐ๋ฅผ ์ ๊ธฐ์ ์ผ๋ก ์ ์ฐ๊ฒฐ๋๋๋ก ํ ์ ์์๊น ๊ณ ๋ฏผํ๋ค.
๊ทธ๋ฆฌ๊ณ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ ๋ ์ฌ๋ ธ๋ค.
/community/posts/๊ฒ์๊ธid/๊ฒ์๊ธ๋ด์ฉ,๋๊ธ
์ด๋ ๊ฒํ๋ฉด ํ๋์ ๊ฒ์๊ธ์ ๋ค๊ณ ์ค๋ฉด ์๋์ผ๋ก ๋ชจ๋ ๋๊ธ ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ธ์ค๊ฒ ๋๋ฏ๋ก ๊ฒ์๊ธ๊ณผ ๋๊ธ ์กฐํ ๊ธฐ๋ฅ์ ๊ฐํธํ๊ฒ ๊ตฌํํ ์ ์์ ๊ฒ ๊ฐ์๋ค.
ํ์ง๋ง ๊ฒ์๊ธ์ ์ถ๊ฐ, ์์ , ์ญ์ ํ ๋๋ง๋ค ๊ฒ์๊ธ์ ๋ชจ๋ ์ ๋ณด๊น์ง ๋ถํ์ํ๊ฒ ์ ๋ฐ์ดํธ๋๊ณ ๋๊ธ์ ๋ค๋ฃจ๊ธฐ ์ํด์ ๊ฒ์๊ธ ์์ฒด์ ์ ๊ทผํด์ผ ํ๋ฏ๋ก ๋นํจ์จ ์ ์ด๋ผ๊ณ ๋๊ผ๋ค.
๊ฒ์๊ธ: /community/posts/๊ฒ์๊ธid/๊ฒ์๊ธ๋ด์ฉ
๋๊ธ: /community/comment/๊ฒ์๊ธid/๋๊ธ๋ด์ฉ
์ค์ ๊ตฌํํ ๋ฐฉ๋ฒ์ด๋ค.
๊ฒ์๊ธ๊ณผ ๋๊ธ์ ์๋ก ๋ค๋ฅธ ๊ฒฝ๋ก์ ์ ์ฅํ๊ณ , ๊ฒ์๊ธ์ ์กฐํํ ๋ /community/comment/๊ฒ์๊ธid ๊ฒฝ๋ก์์ ๋๊ธ์ ๊ฐ์ ธ์จ๋ค.
์ด๋ฌ๋ฉด ๊ฒ์๊ธ์ ์ ๋ฐ์ดํธ ํ ๋ ๊ฒ์๊ธ ๊ฒฝ๋ก๋ง ์ ๊ทผํ๋ฉด ๋๊ณ , ๋๊ธ์ ์ ๋ฐ์ดํธ ํ ๋ ๋๊ธ ๊ฒฝ๋ก์๋ง ์ ๊ทผํ๊ฒ ๋๋ฏ๋ก ๋ถํ์ํ ์ ๋ฐ์ดํธ๋ฅผ ์ค์ผ ์ ์์ ๊ฒ ๊ฐ์๋ค.
์ถ๊ฐ๋ก, ๊ฒฝ๋ก์ userId๋ฅผ ์ถ๊ฐํ ๊น๋ ์๊ฐํด๋ดค๋๋ฐ ๋๊ธ์ด๋ ๊ฒ์๊ธ ๋ฐ์ดํฐ๊ฐ auth(์์ฑ์) ์ ๋ณด๋ฅผ ํฌํจํ๋ฏ๋ก ๊ตณ์ด ๊ฒฝ๋ก์ user์ ๋ณด๊น์ง ์ถ๊ฐํ ํ์๋ ์๋ค๊ณ ์๊ฐํ๋ค.
์ด๋ฏธ์ง๊ฐ ์ฒจ๋ถ๋ ๊ฒ์๊ธ์ ๋ค๋ฃจ๋๊ฒ ์๊ทผ ๊น๋ค๋กญ๋ค๊ณ ๋๊ปด์ก๋ค. ๋ฐ์ดํฐ ๋ฒ ์ด์ค์๋ ์ด๋ฏธ์ง ์์ฒด๋ฅผ ์ ์ฅํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
์์ ์ ์๋ฐ์ ์คํ๋ง์ผ๋ก ๋ฐฑ์๋ ํ๋ก์ ํธ๋ฅผ ํ ๋ ์ด๋ฏธ์ง๋ฅผ ๋ค๋ฃจ๋๋ฐ ๊ฝค ์ ๋ฅผ ๋จน์๋ ๊ธฐ์ต์ด ์์ด์ ์ด๋ฒ์๋ ๊ธด์ฅ๋ถํฐ ๋์ง๋ง ๋ง์ ๋ฐฉ๋ฒ์ ์์ ๋ณด๋ ๊ทธ๋ฅ ์ด๋ ต์ง๋ ์์๋ค.
์ด๋ฏธ์ง๊ฐ ํฌํจ๋ ๊ฒ์๊ธ์ ์ ์ฅํ๊ธฐ ์ํด์๋ ๋จผ์
1. ์ด๋ฏธ์ง๋ฅผ ์๋ฒ์ ์
๋ก๋ ์ํค๊ณ
2. ์
๋ก๋ํ ์๋ฒurl์ ๊ฒ์๊ธ ๋ฐ์ดํฐ์ ์ ์ฅํ๋ค
์ฆ, ๊ฒ์๊ธ ๋ฐ์ดํฐ์๋ ์ด๋ฏธ์ง ์์ฒด๊ฐ ์ ์ฅ๋๋๊ฒ ์๋๋ผ ์ด๋ฏธ์ง๊ฐ ์ ๋ก๋ ๋ url๊ฒฝ๋ก๊ฐ ์ ์ฅ๋๋ ๊ฒ์ด๋ค.
๋๋ ํ๋ก ํธ์๋ ํ๋ก์ ํธ์ด๊ธฐ ๋๋ฌธ์ ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ ํ ์๋ฒ๊ฐ ์์ด ๋๋ค์ firebase์ ๋์์ ๋ฐ์์ผ ํ๋๋ฐ realtime database์๋ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ ์ ์๊ธฐ ๋๋ฌธ์ storage๋ฅผ ์ฌ์ฉํ๋ค.
storage๋ realtime database์ ๊ฑฐ์ ๋น์ทํ๋ฐ ๊ทธ ํ์ฉ์ฑ์ด๋ ์ฐ์๋ฒ์ ์ฐจ์ด๊ฐ ์๋ค๊ณ ๋ณด์๋ค. ์ ๊ทธ๋ ์ด๋ ๋ฒ์ ์ด๋ผ๊ณ ๋ณธ ๋ฏ?
์๋ฌดํผ!
์ด๋ฏธ์ง๋ ์ ์ฅํ ๊ฒฝ๋ก๋ฅผ ์ค์ ํด์ค์ผ ํ๋๋ฐ
/images/๊ฒ์๊ธid/์ด๋ฏธ์งid
์ด๋ ๊ฒ ์ค์ ํด์ฃผ์๋ค.
firebase storage๋ ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํ๋ฉด ์ ๋ก๋ ๊ฒฝ๋ก๋ฅผ ๋ฐํํ๋๋ฐ ๊ทธ ๊ฒฝ๋ก๋ฅผ ๋ฐ์์ ๊ฒ์๊ธ ๋ฐ์ดํฐ์ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ฒ์๊ธ์ ์กฐํํ ๋ img
๊ฒฝ๋ก์ ํด๋น url์ ๋ฃ์ด์ฃผ๋ฉด ์ด๋ฏธ์ง๊ฐ ์กฐํ๋๋ค.
๊ฒ์๊ธ ์์ ์์๋ ์ด๋ฏธ์ง๋ฅผ ๋ณ๊ฒฝํ์ง ๋ชปํ๊ณ ์ญ์ ํ ์ ์๊ฒ๋ง ๋ง๋ค์๋๋ฐ ์ด๋ถ๋ถ์ ๋์ค์ ๋ณ๊ฒฝ๋ ๊ฐ๋ฅํ๋๋ก ๋ฐ๊ฟ ์์ ์ด๋ค.
๊ฒ์๊ธ ์์ ์ด๋ ์ญ์ ๊ณผ์ ๋ ์ ์ฅ ์์ ๊ณผ ๋น์ทํ๋ค. ๋จผ์ ์ด๋ฏธ์ง ํ์ผ์ ๋ค๋ฃจ๊ณ ๊ทธ ํ์ ๊ฒ์๊ธ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃฌ๋ค.
๋ค๋ง ์ญ์ ํ ๋๋ ๋ฌด์กฐ๊ฑด ์ด๋ฏธ์ง ํ์ผ์ ์ญ์ ํ๋ฉด ๋์ง๋ง ์์ ํ ๋๋ ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋๋ก ๋ ๊ฒ์ธ์ง ์ญ์ ํ ๊ฒ์ธ์ง ๊ตฌ๋ถํด์ ๋์ํด์ผ ํ๋ค.
๋๋ ์์ ์ฐฝ์์ ์ด๋ฏธ์ง ์ญ์ ๋ฒํผ(X) ์ ๋๋ฅด๋ฉด isImageDel
state๊ฐ์ true
๋ก ๋ณ๊ฒฝํ๊ณ , ๊ฒ์๊ธ ์
๋ฐ์ดํธ action์ ํด๋น ๊ฐ์ ์ ๋ฌํด์ ๊ฐ์ด true์ด๋ฉด ์ด๋ฏธ์ง ํ์ผ์ ์ญ์ ํ๊ณ , false์ด๋ฉด ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋๋ก ๋๋๋ก ๊ตฌํํ๋ค.
๊ทธ๋ฆฌ๊ณ ๋ง์ฝ ์ญ์ ํ๋ค๋ฉด ๊ฒ์๊ธ ๋ฐ์ดํฐ์ image, imageUrl
๋ฐ์ดํฐ๋ ๋น ๋ฐ์ดํฐ๊ฐ ๋๋๋ก ๋น์์ฃผ์๋ค.
์ง๊ธ๊น์ง ํ๋์ firebase.js ํ์ผ์ ๋ชจ๋ api ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์์๋๋ฐ ์ด๋ฌ๋ค๋ณด๋ ํ์ผ์ด ๋๋ฌด ๊ธธ์ด์ง๊ณ ๊ฐ๋ ์ฑ์ด ์์ข์์ ธ์ realtime datababse API์ storage API๋ฅผ ๋ถ๋ฆฌํ๊ณ ์ถ์๋ค.
์ฌ์ค ์ด๊ฑด ์์ง ํด๊ฒฐํ์ง ๋ชปํ๋ค.
์ ํํ ๋งํ์๋ฉด api ํ์ผ์ ๋ถ๋ฆฌํ์ง๋ง ์ฝ๋๊ฐ ๋ง์ด ๋๋ฝ๋ค...
๋์ ๋ณด์ด๋ ์ ์ผ ํฐ ๋ฌธ์ ๋ firebase configuration
์ค์ ์ด ์ค๋ณต๋๋ค๋ ๊ฒ์ด๋ค.
์ฒ์์๋ ์ด๊ฑธ ํด๊ฒฐํ๊ธฐ ์ํด ์์ ์ ๋ค๋ฅธ ํ๋ก์ ํธ์์ ํ๋ ๋ฐฉ์์ธ ์๋ฐ์คํฌ๋ฆฝํธ ํด๋์ค์ ์ธ์คํด์ค๋ฅผ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ ์๋ํด ๋ณด๋ ค๊ณ ๊ฑด๋๋ ค๋ดค๋๋ฐ ์๊ฐ๋ณด๋ค ์ค๋ ๊ฑธ๋ฆด ๊ฒ ๊ฐ์๋ฐ๋ค ๋ค๋ฅธ ๊ธฐ๋ฅ์ ๊ตฌํํ ์๊ฐ๊น์ง ์ฌ๋ผ์ง ๊ฒ ๊ฐ์ ์์ง์ ํ์ง ๋ชปํ๋ค. ๋์ค์ ๋ฐฐํฌ ํ ๋ฆฌํฉํ ๋ง ํ๋ฉฐ ๊ธฐ๋ฅ์ ๊ฐ์ ํ ๋ ์๋ํด ๋ณผ ์๊ฐ์ด๋ค.
์ผ๋จ ๊ฐ์ธ์ ์ธ ์๊ฐ์ผ๋ก๋ class๋ class extends๋ฅผ ์ฌ์ฉํด์ ์ด์ฐ์ ์ฐ ํด๊ฒฐํ ์ ์์ง ์์๊น ์ถ๋ค
// ์ปค๋ฎค๋ํฐ ๊ฒ์๊ธ ์ ์ฅ
export const addPost = (post) => {
set(ref(db, `/community/posts/${post.id}`), post);
};
// ์ปค๋ฎค๋ํฐ ๊ฒ์๊ธ ๋ค๊ณ ์ค๊ธฐ
export const getPost = async () => {
const snapshot = await get(child(dbRef, "/community/posts"));
if (snapshot.exists()) {
return Object.values(snapshot.val());
}
};
// ๊ฒ์๊ธ ์์
export const updatePost = async (post) => {
update(ref(db, `/community/posts/${post.id}`), post);
};
// ๊ฒ์๊ธ ์ญ์
export const removePost = async (postId) => {
remove(ref(db, `/community/posts/${postId}`));
};
// ์ด๋ฏธ์ง ์ถ๊ฐ
export const addFile = async (postId, file) => {
const attachmentRef = ref(storage, `/images/${postId}/${file.id}`);
await uploadString(attachmentRef, file.url, "data_url");
return getDownloadURL(ref(storage, attachmentRef));
};
// ์ด๋ฏธ์ง ์ญ์
export const removeFile = async (postId, imageUrl) => {
const attachmentRef = ref(storage, `/images/${postId}/${imageUrl}`);
deleteObject(attachmentRef);
};
export default function NewPost({ onClose, isEdit, post }) {
const { user } = useAuthContext();
const [content, setContent] = useState("");
const [fileDataUrl, setFileDataUrl] = useState("");
const [imageUrl, setImageUrl] = useState();
const [isImageDel, setIsImageDel] = useState(false);
const dispatch = useDispatch();
const onClickDel = () => {
if (isEdit) {
setIsImageDel(true);
} else {
setFileDataUrl("");
}
};
const onChangeHandler = (e) => {
const { value, files } = e.target;
// ํ์ผ ์
๋ก๋์ ํ์ํ url(reader.result) ์ป๊ธฐ
if (files) {
// ํ์ผ ์ ๊ฑฐํ์๋ ๋ฐฉ์ด๋ก์ง
if (files[0]) {
const reader = new FileReader();
reader.readAsDataURL(files[0]);
reader.onload = () => {
setFileDataUrl(reader.result);
};
}
return;
}
// ๊ธ๋ด์ฉ
setContent(value);
};
const onSubmitHandler = async (e) => {
e.preventDefault();
// ์์ ์ฐฝ์ผ๊ฒฝ์ฐ
if (isEdit) {
const newPost = {
...post,
content,
};
dispatch(updatePostFetch(newPost, isImageDel));
onClose();
return;
}
const newPost = {
id: uuidv4(),
content,
auth: user.email,
commentCount: 0,
createdAt: new Date().getTime(),
};
dispatch(addPostFetch(newPost, fileDataUrl));
setContent("");
setFileDataUrl("");
};
useEffect(() => {
// ์ฒ์ ์์ ํ์ด์ง์ ๋ค์ด์์ ๋ ํ์ผ ์ ๋ณด๊ฐ ์์ผ๋ฉด ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ณด์ฌ์ฃผ๊ณ ํ์ผ ๋ฐ์ดํฐ ์ ์ง
if (isEdit) {
setContent(post.content);
setImageUrl(post.imageUrl);
}
}, [isEdit, post]);
return (
<>
{user && (
<div className={styles.newPost}>
<p className={styles.user}>{user.email}</p>
<article onSubmit={onSubmitHandler}>
<textarea
type="text"
value={content || ""}
onChange={onChangeHandler}
placeholder="์ต๋ 300์๊น์ง ์
๋ ฅ ๊ฐ๋ฅํฉ๋๋ค."
maxLength="300"
required
/>
{!isImageDel && (fileDataUrl || imageUrl) && (
<div className={styles.imgBox}>
<img
src={imageUrl ? imageUrl : fileDataUrl && fileDataUrl}
alt="์ฒจ๋ถ"
/>
<span onClick={onClickDel}>
<TiDelete className={styles.delBtn} />
</span>
</div>
)}
</article>
{!isEdit && (
<footer className={styles.submitBox}>
<label htmlFor="file">
<BsCardImage />
</label>
<input
type="file"
id="file"
accept="image/*"
onChange={onChangeHandler}
hidden
/>
<Button text="์์ฑ" onClick={onSubmitHandler} />
</footer>
)}
{isEdit && (
<footer>
<Button text="์์ " onClick={onSubmitHandler} />
</footer>
)}
</div>
)}
</>
);
}
export default function DeletePost({ onClose, post }) {
const dispatch = useDispatch();
const onRemoveHandler = () => {
dispatch(removePostFetch(post));
onClose();
};
return (
<div className={styles.deletePost}>
<h1>์ ๋ง ์ญ์ ํ์๊ฒ ์ต๋๊น?</h1>
<div className={styles.buttonBox}>
<Button text="์ทจ์ํ๊ธฐ" onClick={() => onClose()} cancle />
<Button text="์ญ์ ํ๊ธฐ" onClick={onRemoveHandler} />
</div>
</div>
);
}
export default function PostCard({ post }) {
const { user } = useAuthContext();
const [showEdit, setShowEdit] = useState(false);
const [showDelete, setShowDelete] = useState(false);
const toggleShowEdit = () => {
setShowEdit((showEdit) => !showEdit);
};
const toggleShowDelete = () => {
setShowDelete((showDelete) => !showDelete);
};
return (
<Card className={styles.postCard}>
<div className={styles.headerBox}>
<h1>{post.auth}</h1>
<div>
{user.email === post.auth && (
<div className={styles.menu}>
<BiDotsHorizontalRounded
id="menuBtn"
className={styles.menuIcon}
/>
<Card className={styles.subMenu}>
<ul>
<li onClick={toggleShowEdit}>์์ ํ๊ธฐ</li>
<li onClick={toggleShowDelete}>์ญ์ ํ๊ธฐ</li>
</ul>
</Card>
</div>
)}
</div>
</div>
<time
className={styles.timeAgo}
dateTime={new Date(parseInt(post.createAt))}>
{post.createAt}
</time>
<div className={styles.content}>
<p>{post.content}</p>
{post.imageUrl && post.imageUrl.length > 0 && (
// <Link to={post.imageUrl}>
<a href={post.imageUrl} target="_blank" rel="noopener noreferrer">
<img src={post.imageUrl} alt="์ฒจ๋ถ" />
</a>
// </Link>
)}
</div>
{showEdit && (
<PostModal type={EDIT} toggleEdit={toggleShowEdit} post={post} />
)}
{showDelete && (
<PostModal type={DELETE} toggleDelete={toggleShowDelete} post={post} />
)}
</Card>
);
}
const Backdrop = ({ toggleMenu }) => {
return <div className={styles.backdrop} onClick={toggleMenu} />;
};
const ModalOverlay = ({ toggleEdit, toggleDelete, post, type }) => {
if (type === EDIT) {
return (
<div className={`${styles.overlay} ${styles.edit}`}>
<NewPost onClose={toggleEdit} post={post} isEdit />
</div>
);
} else if (type === DELETE) {
return (
<div className={`${styles.overlay} ${styles.delete}`}>
<DeletePost onClose={toggleDelete} post={post} />
</div>
);
}
};
export default function PostModal({ toggleEdit, toggleDelete, post, type }) {
// ํ์ฌ ์์น์์ ๋์ฐ๊ธฐ
useEffect(() => {
document.body.style.cssText = `
top: -${window.scrollY}px;
overflow-y: scroll;
width: 100%;`;
return () => {
const scrollY = document.body.style.top;
document.body.style.cssText = "";
window.scrollTo(0, parseInt(scrollY || "0", 10) * -1);
};
}, []);
return (
<div>
{ReactDOM.createPortal(
<Backdrop toggleMenu={toggleEdit || toggleDelete} />,
document.getElementById("backdrop-root")
)}
{ReactDOM.createPortal(
<ModalOverlay
toggleEdit={toggleEdit}
toggleDelete={toggleDelete}
post={post}
type={type}
/>,
document.getElementById("modal-root")
)}
</div>
);
}
์ปค๋ฎค๋ํฐ ๋ฉ์ธ ํ์ด์ง
๋ด๊ฐ ์์ฑํ ๊ธ๋ง ...
์์ด์ฝ์ด ๋ํ๋๋ค
๊ฒ์๊ธ ์์ , ์ญ์ ๋ ๋ชจ๋ฌ์ฐฝ์ผ๋ก ๋ํ๋๋ค
๐จ Uncaught (in promise) FirebaseError: Firebase Storage: User does not have permission to access 'images/c0a490be-a907-40a5-8571-84ce934d088d/fd60fcc8-6327-4ff6-9798-e68fb404db4e'. (storage/unauthorized)
firebase storage๋ฅผ writeํ๋ ๊ถํ์ด ์์ด์ ๋ฐ์ํ๋ ์๋ฌ
firestore > project > storage > Rulesํญ์์ allow read, write: if false
๋ฅผ allow read, write: if true
๋ก ๋ณ๊ฒฝํ๋ฉด ํด๊ฒฐ
๋ฆฌ๋์ค add์ก์
๋ cannot read properties of undefined (reading โpushโ)
๊ฐ ์๋ฌ๊ฐ ๋ ์ state๊ฐ์ ํ์ธํด๋ณด๋
์ด๋ ๊ฒ Proxy๋ก ๋์ค๋์ค
current(state)
์ฒ๋ฆฌ๋ก ํด๊ฒฐํ๋ค