onChange 이벤트(+이벤트 버블링 막기)
# 시작하면서
이전에 배운걸 복습겸 새로 다 짜봤고 뭐 디자인도 다르니 혼동을 안하길 바란다
솔까말 이 블로그 나만 보는거라..뭐..
# 일단 이렇게 생겼다.
<input type="text" onChange={handleTitle} />
인풋 태그에다가 onChange옵션을 달아주었고 그곳에 handleTitle함수를 지정해줬다.
예전에 vue를 사용할때는 input에다가 v-model을 사용했던것 같은데
react의 방식이 조금 더 복잡한 듯 하다.
컴포넌트는 비교적 쉬웠던만큼 데이터바인딩이나 인풋데이터값을 쓰는거는 더 어렵고 뭐 이런거겠지?
근데 vue reactive도 갖다쓴거니 react도 이런 메서드가 있지않을까 쉽기도 하고..
암튼 뭐 지금 배우는 입장에서는 아 그렇구나 하고 배울뿐
# 암튼 저게 뭐냐?
<input type="text" onChange={handleTitle} />
handleTitle함수를 onChange에 넣어줬는데 저 함수는 아래와 같이 생겼다.
const handleTitle = (event) => {
setTitle(event.target.value);
};
handleTitle이라는 함수에는 매개변수를 받는데 event라는 매개변수를 받는다.
저기에 인풋값이 넣어진다?정도로만 알고 넘어가면 될것같다.
때문에 인풋값이 변경될때마다 handleTitle함수가 호출된다는 소리임.
그럼 setTitle로 title이라는 변수(state)를 수정하게 된다.
강의에서는 함수리터럴로
<input type="text" onChange={(e)=>{e.target.value}} />
이렇게 바로 넘겨줬따.
사실 아래 방법이 더 나은것 같다.
onChange의 event 첫번째 매개변수로 받는거에서 어떤 기능이 더 있는지 더 알아보면 좋을듯
암튼 그렇게 하고
대충 title이라는 변수(state)를 태그 아무데나 넣어보면
v-model에서 했던것처럼 입력값을 바꾸면 똑같이 따라 바뀌는걸 확인 할 수 있다.
빌드 할 경우 어떻게 빌드한 코드가 튀어나오는지 모르겠지만 onChange로 보자면 계속 입력이 있을때마다 함수가 두번 호출되는거라 좀 무거울듯 하다.. vue는 뭔가 되게 가벼워보였는데 useState도 그렇고 react는 좀 무거워보임.
# 이걸로 글쓰기 기능을 만들어보자. (전체코드)
import './App.css';
import React, { useState } from 'react'; // useState를 임포트
function App() {
const [posts, setPosts] = useState([
{
title: '강남역',
date: '2024/07/01',
likeCount: 0,
content: '강남역 다녀왔어요',
},
{
title: '회현역',
date: '2024/07/02',
likeCount: 0,
content: '회현역 다녀왔어요',
},
{
title: '고디역',
date: '2024/07/03',
likeCount: 0,
content: '고디역 다녀왔어요',
},
]);
const [state, setState] = useState({
isPostModal: false,
postModalIndex: null,
isWriteModal: false,
});
const openPostModal = (i) => {
const copiedState = { ...state };
copiedState.postModalIndex = i;
copiedState.isPostModal = true;
setState(copiedState);
};
const openWriteModal = (i) => {
const copiedState = { ...state };
copiedState.isWriteModal = true;
setState(copiedState);
};
const clickLike = (i) => {
const copiedPosts = [...posts];
copiedPosts[i].likeCount++;
setPosts(copiedPosts);
};
const deletePost = (i) => {
const copiedPosts = [...posts];
copiedPosts.splice(i, 1);
setPosts(copiedPosts);
};
return (
<div className="App">
{state.isWriteModal ? (
<WriteModal
state={state}
posts={posts}
setState={setState}
setPosts={setPosts}
/>
) : null}
{state.isPostModal ? (
<PostModal
state={state}
posts={posts}
setState={setState}
setPosts={setPosts}
/>
) : null}
<nav className="nav-top">
<p>무찌 앱</p>
<p>#######</p>
</nav>
<div className="lists">
{posts.map((list, i) => {
return (
<div className="list" key={i}>
<h4
onClick={() => {
openPostModal(i);
}}
>
{list.title}
</h4>
<p className="date">{list.date}</p>
<p
className="likeBtn"
onClick={() => {
clickLike(i);
}}
>
따봉
</p>
<p>{list.likeCount}</p>
<p
className="deleteBtn"
onClick={() => {
deletePost(i);
}}
>
삭제
</p>
</div>
);
})}
</div>
<div className="appBtn">
<p className="writeBtn" onClick={openWriteModal}>
글쓰기
</p>
</div>
</div>
);
}
const PostModal = ({ posts, state, setState, setPosts }) => {
const closeModal = () => {
const copiedState = { ...state };
copiedState.isPostModal = false;
setState(copiedState);
};
const edit = () => {
const copiedPosts = [...posts];
const editedContent = prompt(
'새로운 글내용',
copiedPosts[state.postModalIndex].content,
);
if (editedContent) {
copiedPosts[state.postModalIndex].content = editedContent;
setPosts(copiedPosts);
}
};
return (
<div className="detailPost-back">
<div className="detailPost">
<div className="title">
<h4>{posts[state.postModalIndex].title}</h4>
<p>{posts[state.postModalIndex].date}</p>
</div>
<div className="inner-back">
<div className="inner-front">
<p>{posts[state.postModalIndex].content}</p>
</div>
</div>
<div className="btn-box">
<p className="btn" onClick={edit}>
수정
</p>
<p className="btn" onClick={closeModal}>
닫기
</p>
</div>
</div>
</div>
);
};
const WriteModal = ({ posts, state, setState, setPosts }) => {
const closeModal = () => {
const copiedState = { ...state };
copiedState.isWriteModal = false;
setState(copiedState);
};
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const getCurrentDate = () => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0'); // 월은 0부터 시작하므로 +1 필요
const day = String(today.getDate()).padStart(2, '0');
return `${year}/${month}/${day}`;
};
const write = () => {
const copiedPosts = [...posts];
const newPost = {};
newPost.title = title;
newPost.content = content;
newPost.date = getCurrentDate();
newPost.likeCount = 0;
copiedPosts.unshift(newPost);
if (newPost.title) {
setPosts(copiedPosts);
closeModal();
} else {
closeModal();
}
};
const handleTitle = (event) => {
setTitle(event.target.value);
};
const handleContent = (event) => {
setContent(event.target.value);
};
return (
<div className="write-back">
{title}
<div className="detailPost">
<div className="title">
<input type="text" onChange={handleTitle} />
</div>
<div className="inner-back">
<div className="inner-front">
<textarea onChange={handleContent}></textarea>
</div>
</div>
<div className="btn-box">
<p className="btn" onClick={write}>
확인
</p>
<p className="btn" onClick={closeModal}>
닫기
</p>
</div>
</div>
</div>
);
};
export default App;
강의 듣기전에 미리만들어보니까 이렇게 됐다.
일단 모달이 글 클릭하면 띄워지는 모달 하나였는데 글쓰기 모달을 새로 만들어줬고
제목은 input, 내용은 textarea로 만들어줬다.
각 onChange 이벤트핸들러?를 연결해줬다.
그럼 handle머시기 함수를 호출해주는데 여기엔 set머시기 하면서 해당 input내용을
해당 데이터로 바꿔준다.
이 데이터를 저장해놨다가 확인 버튼 누르면 posts어레이에 해당 글 오브젝트가 새로 생긴다.
*주의 사항
<input/>
인풋태그 만들때 일반적으론 <input>을 사용했는데 react에서는 /로 반드시 끝내줘야한다.
+ 이벤트버블링 없애기
<h4
onClick={() => {
openPostModal(i);
}}
>
위에 게시글 제목 누르면 게시글 내용뜨는 모달 뜨게 만든 기능이 있다.
제목을 누르면 openPostModal이라는 함수가 호출되게 했는데
사실 처음에는 그냥 글 전체적인걸 누르면 함수 호출되게 하려고 헀다.
근데 문제가
저 박스 전체를 누르면
해당 게시글 모달이 떠야하는데
'따봉'버튼만 눌러도 저 모달이 뜬다.
따봉버튼만 눌러지게 하고 싶다면
<p
className="likeBtn"
onClick={() => {
clickLike(i);
}}
>
따봉
</p>
해당 따봉버튼의 onClick 이벤트핸들러 함수 리터럴에
event.stopPropagation메서드도 호출되게 해버리면 된다.
<div
className="list"
key={i}
onClick={() => {
openPostModal(i);
}}
>
<h4>{list.title}</h4>
<p className="date">{list.date}</p>
<p
className="likeBtn"
onClick={(event) => {
event.stopPropagation();
clickLike(i);
}}
>
따봉
</p>
<p>{list.likeCount}</p>
<p
className="deleteBtn"
onClick={() => {
deletePost(i);
}}
>
삭제
</p>
</div>
이렇게 다시 제목에만 있던 모달창 이벤트를 그냥 전체 박스로 다시 바꾸고
따봉버튼에 event.stopPropagation메서드도 호출되게 하면
원하듯이 이벤트버블링을 막아준다.
+ part1 최종정리
리액트 초급을 배우면서 대충 중요한점들을 적어보자면
- useState로 변수관리를 하면 데이터바인딩이 된다.
- useState를 바꾸려면 set머시기 함수를 호출하고 인수로 바뀌게 해줘야한다.(ㅈ같음)
- set머시기 함수를 쓸때 객체나 어레이 요소하나 수정할떄는 보통 얕은복사를 해서 쓴다(스프레드문법)
- 컴포넌트는 쉽다. 데이터넘겨주기도 간단하다.
- 반복문은 map을 사용한다
- onClick으로 클릭시 실행할 코드를 연결할 수 있다.
- onClick에는 단순한 함수는 그냥 함수리터럴이 할당된 변수 넣어주면 되고 매개변수가 있는 함수면 부모함수로 한번 더 감싸서 변수를 넘겨주자.
- onChange로 바뀌는 데이터를 관리할 수 있다.
머 더 있을수도?
사실 아직까진 vue를 사용해본 경험이 많아서 크게 어려운점이 없는듯.
여기까지 배운내용만으로도 간단한 어플하나 만들 수 있을것 같다.