📢 어렵고 정석적인 개념 설명보다는 저같은 초보자도 이해하기 쉽게 정리하는 것을 원칙으로 포스팅하고 있습니다. 😄
What
이 글을 읽기 전 먼저 컴포넌트의 구조와 각 컴포넌트마다 어떤 동작을 수행할 지에 대해서 이해해야 한다.
컴포넌트는 총 4개로 최상위 컴포넌트인 App, 그 아래 자식 컴포넌트인 Editor와 List, 최하위 컴포넌트인 Item으로 구성되어 있다. 최상위 컴포넌트인 App에서는 useState를 통해 state를 배열화시켜 그 안에 객체로서 입력된 데이터들을 저장할 것이다. 즉, App의 state가 출력될 데이터라고 생각하면 된다.
자식 컴포넌트인 Editor에서는 사용자가 입력한 데이터들을 App의 state에 저장하는 역할을 한다.
App의 props로 전달받은 useState의 setState(상태 변화 함수)를 이용해서 부모 컴포넌트의 state를 변경할 수 있다.
List 컴포넌트에서는 Editor 컴포넌트로부터 전달받은 App 컴포넌트의 state를 props로 전달받아 출력하는 역할을 한다. 현재 App 컴포넌트의 state는 [{ }, { }, { } ...]의 배열 안에 객체 형태이며 객체를 하나씩 꺼내기 위해서 map()을 이용하여 또 다른 자식 컴포넌트를 만들고 그곳에 App 컴포넌트의 state를 전달해줘야 한다.
정리하자면, 위의 그림과 같다. Editor 컴포넌트에게는 App의 상태 변화 함수를 props로 전달하여 App의 state를 업데이트하고 List 컴포넌트에게는 업데이트된 App의 state를 props로 전달하여 Item이라는 컴포넌트로 데이터를 출력하는 구조인 것이다. 이런 식으로 위의 부모 컴포넌트의 useState를 통해 형제 컴포넌트끼리 데이터를 주고 받을 수 있다.
How
데이터 추가하기
/* App 컴포넌트 */
const [data, setData] = useState([]);
const dataId = useRef(0);
const onCreate = (name, content) => {
const date = new Date().getTime();
const newItem = {
id: dataId.current,
name,
content,
date
};
dataId.current += 1;
setData([newItem, ...data]);
};
onCreate()를 Editor 컴포넌트에게 props로 전달할 것이다. onCreate()에는 setData 상태 변화 함수가 포함되어 있다.
따라서, onCreate()를 통해 Editor 컴포넌트에서 전달받은 데이터를 App 컴포넌트로 전달할 수 있게 된다.
데이터를 입력받을 때 보통 한 가지 이상의 정보를 받기 때문에 위 코드에서 newItem 변수처럼 데이터 객체를 따로 만들어준다. 여기서 id는 useRef를 통해 초깃값을 0으로 주었고, dataId.current += 1로 1씩 올라가게 해서 받아오는 데이터들의 key 역할로 사용되게끔 했다. 이제 onCreate()가 실행될 때마다 data가 바뀔 것이고, ...data를 통해 이전의 데이터들을 state에 계속 저장하게 해주었다.
/* Editor 컴포넌트 */
const [state, setState] = useState({
name: "",
content: ""
})
const inputHandler = (e) => {
setState({
...state,
[e.target.name]: e.target.value
})
}
const submitHandler = () => {
onCreate(state.name, state.content);
setState({
name: "",
content: ""
})
}
데이터를 입력받는 Editor 컴포넌트에서는 자신의 state를 부모 컴포넌트에게 보내주게 된다. 위 코드에서는 name과 content라는 항목의 데이터를 받고, 그 데이터를 state에 저장하였다.
그리고 저장하는 과정에서 name이 저장되면 content가 날라가고, content가 저장되면 name이 날라가는 현상을 방지하고자 ...state를 통해 name이든 content든 이전에 입력된 데이터를 state에 저장하도록 하였다.
/* List 컴포넌트 */
const List = ({ onList }) => {
return (
<div>
<div>
{onList.map((it) => (
<Item key={it.id} {...it} />
))
</div>
</div>
)
}
List 컴포넌트에서는 App 컴포넌트의 state를 받아서 출력하는 역할을 하는데, 현재 App의 state 값은 [{.....}, {.....}, {......}]로 배열 안의 객체 형식을 되어있고, 이 객체 하나가 name과 content가 담겨진 데이터 세트라고 생각하면 된다. 따라서 출력될 때 객체를 하나씩 꺼내야 하는건데, 이를 위해서는 map()을 이용하여 또 다른 자식 컴포넌트에 배열 안의 객체를 전달하면 된다. 또한 전개 연산자를 이용하면 객체 안의 모든 key 값들을 쉽게 전달할 수 있다.
/* Item 컴포넌트 */
const Item = ({ name, content, date, id }) => {
return (
<div>
<p>{name}</p>
<p>{content}</p>
<p>{new Date(date).toLocaleString()}</p>
</div>
)
}
Item 컴포넌트는 App의 state를 나타내주는 컴포넌트로 입력받은 데이터의 결과물(값)이라고 생각하면 된다.
데이터 삭제하기
/* App 컴포넌트 */
const onDelete = (targetId) => {
const newData = data.filter((it) => it.id !== targetId);
setData(newData);
}
삭제하는 방법은 state 배열에서 삭제하길 원하는 데이터를 filter()를 통해 제거해주는 방식이다.
onDelete()를 List의 Item 컴포넌트로 전달해서 '삭제하기' 버튼을 클릭했을 시 onDelete()가 작동하도록 이벤트를 주기만 하면 데이터 삭제는 간단히 할 수 있다.
데이터를 추가하고 삭제하는 데 포인트는 바로 '배열'에 있다. 우리가 화면에 출력되는 데이터는 최상위 컴포넌트의 state 값이다. 이 state는 배열 형태로 되어 있고, 그 배열 안에 객체 형태로 데이터가 저장되어 있는 셈이다. 즉 데이터를 추가할 땐 배열 안의 객체를 추가하는 것이고, 삭제할 땐 배열 안의 객체를 삭제하는 것이다.
데이터 변경하기
/* App 컴포넌트 */
const onEdit = (targetId, newContent) => {
setState(
state.map((it) => {
it.id === targetId ? {...it, content: newContent} : it
})
)
}
App 컴포넌트의 state가 출력되는 데이터 값이므로 변경할 때에도 App 컴포넌트에 state 변경을 위한 함수가 필요하다.
onEdit() 함수는 targetId라는 수정할 데이터의 id와 데이터의 수정된 내용인 newContent를 매개 변수로 받고, setState() 상태 변경 함수를 통해 state가 담고 있는 데이터들 중 id가 targetId와 같은 데이터가 있다면 그 데이터의 content 항목을 newContent로 바꾸겠다라는 동작을 실행한다.
/* Item 컴포넌트 */
// 수정하기 버튼을 눌렀냐 여부에 대한 useState
const [isEdit, setIsEdit] = useState(false);
// 수정하기 버튼을 눌렀을 때 눌렀냐 여부에 대한 state를 변경
const toggleEdit = () => setIsEdit(!isEdit);
// 수정한 content를 다루는 state
const [localContent, setLocalContent] = useState(content);
// 수정한 content를 localContent에 저장하는 함수
const editLocalContent = (e) => {
setLocalContent(e.target.value);
}
// 수정하기 버튼을 눌렀을 때 App 컴포넌트로 수정된 데이터를 전송하고 출력
const edit = () => {
if(isEdit){
onEdit(id, localContent);
}
}
return (
<div>
{isEdit ? (
<>
<textarea value={localContent} onChange={editLocalContent} />
</>
)}
</div>
)
수정하기 동작을 수행하기 위해서는 몇 가지 준비물이 필요하다.
1) 수정하기 버튼을 눌렀냐 안 눌렀냐를 체크하는 useState와 함수
2) 수정된 데이터를 새로운 state로 저장해주는 useState와 함수
3) 최종적으로 수정된 데이터를 App 컴포넌트로 전송하고 실행시키는 함수
1번은 state 초깃값에 boolean 값을 넣어주고 toggleEdit() 함수를 통해 스위치처럼 true-false로 다룰 수 있다.
2번은 localContent라는 state에 수정된 데이터의 내용을 담는다. (e.target.value를 사용한다.)
3번은 props로 받은 onEdit() 함수를 실행시켜 id와 localContent를 매개 변수로 설장하여 부모에게 데이터를 전달한다.
데이터 수정하기 동작은 선언해야할 함수와 useState는 많지만, 하나하나 구조를 따져가며 코딩하다보면 데이터 삭제하기 버금가게 쉬운 동작 수행이다.
1. 추가/삭제/변경 등과 같은 데이터의 변동이 있다 싶으면 useState 선언
2. 상태 변경 함수는 함수에 담아서 onChange와 함께 써주기
3. 데이터가 여러 개면 state를 배열로 하고, 배열 안의 데이터를 다룰 땐 map() 메서드 사용
4. input, textarea와 같은 입력 태그의 value 값을 가지고 올 땐 e.target.value
'React' 카테고리의 다른 글
[React] 복잡한 상태 관리는 useReducer()로 해결하기 (0) | 2022.05.09 |
---|---|
[React] 리액트 생명주기를 제어하는 useEffect() (0) | 2022.05.04 |
[React] 리액트 map()을 이용한 컴포넌트 반복하기 (0) | 2022.04.30 |
[React] useState가 여러 개일 때 깔끔하게 정리하는 방법 (0) | 2022.04.29 |
[React] 입력 데이터 저장하기 (useState, onChange) (0) | 2022.04.28 |