리액트에서는 state에 관련있는 모든 변수에서 불변성 유지가 상당히 중요하다.
단일 Object의 경우 추가에도 수정에도 speard(...)를 사용했지만 List의 경우에는 speard와 더불어 filter와 map을 주로 사용한다.
List useState의 추가, 수정, 제거
//객채의 id를 ref로 사용한다.
const nextId = useRef(1);
//user를 담는 List
const [userList, setResult] = useState([]);
const [text, setText] = useState({
id: nextId.current,
nickname: "",
realname: "",
isActive: false,
});
const onChange = (e)
... //input으로 부터 value를 받아 user를 갱신
const submit = () => {
//List Add
//List의 불변성을 유지하기 위해 speard 혹은 concat 사용
setResult([...userList, user]);
//혹은
//setResult(result.concat(text))
nextId.current += 1;
//User 초기화
const newUser = {
id: nextId.current,
nickname: "",
realname: "",
isActive: false,
};
setText(newUser);
};
//List Remove
const onRemove = (id) => {
setResult(userList.filter((e) => e.id !== id));
};
//List Edit
const active = (id) => {
setResult(
userList.map((e) => (e.id === id ? { ...e, isActive: !e.isActive } : e))
);
};
//List Edit
const onEdited = (editedElement) => {
setResult(
userList.map((e) => (e.id === editedElement.id ? editedElement : e))
);
};
불변성을 유지하는 List의 추가 방법은 spread를 사용하거나 concat을 사용한다.
제거 방법은 보통 filter 함수를 사용하는데 이는 List<object> 형식일 경우 object가 가지고 있는 id 값이 존재해야한다.
object가 아니거나 특정 요소의 값을 아는것이 아니라 List의 index를 알고 있다면 spread로 복사 및 index로 제거 후 state에 넘겨주면 된다. 마지막으로 수정 방법은 map을 사용한다. 마찬가지로 id로 원하는 요소를 변경하는 방법이다.
객체화 된 List라면 id 혹은 index 값을 가지고 있어야 효율적인 렌더링과 연산이 가능하니 가능하면 id를 부여해 List를 만들자.
배열 렌더링
{userList.map((e, index) => (
<ListContainer
element={e}
active={active}
onRemove={onRemove}
onEdited={onEdited}
key={e.id}
></ListContainer>
))}
Javascript에서는 map을 사용해 배열을 렌더링 한다.
이 때, 부모 컴포넌트에 관련된 함수를 모두 선언해주고 예제와 같이 매개변수로 함수를 넘겨주는 것이 일반적이다.
또, map을 사용해 렌더링시 하위 컴포넌트에 반드시 고유한 key를 부여해야한다. key를 부여하지 않으면 효율적인 렌더링이 되지 않는다.
List의 object에 id가 존재한다면 그것을 사용하고 아니라면 map에서 제공하는 index를 사용해 key를 반드시 부여해주자.
유의할 점
하위 컴포넌트에서 요소를 수정할 때 불변성을 깨지 않도록 유의하자.
//부모 컴포넌트의 useState를 거치지 않고 불변성을 무시하고 수정했다.
//이후 setState를 동작시키면 의도한대로 작동은 되나, 잘못된 코드이다.
const onChange = (e) => {
element[e.target.name] = e.target.value;
};
//올바른 코드. 부모 컴포넌트에서 넘겨준 함수로 useState를 사용했다.
const onChange = (e) => {
element = { ...element, [e.target.name]: e.target.value };
onEdited(element);
};
전체코드
import React, { useState, useRef } from "react";
const ListContainer = ({ element, active, onRemove, onEdited }) => {
const { id, nickname, realname, isActive } = element;
const [isEditing, setIsEditing] = useState(false);
const onClickEdit = () => {
setIsEditing(!isEditing);
};
const onChange = (e) => {
element = { ...element, [e.target.name]: e.target.value };
onEdited(element);
};
return (
<div>
{isEditing ? (
<span>
<input
onChange={onChange}
name="realname"
placeholder="realname"
></input>
<input
onChange={onChange}
name="nickname"
placeholder="nickname"
></input>
</span>
) : (
<span
style={{ fontSize: 18, color: isActive ? "Red" : "White" }}
key={id}
onClick={() => active(id)}
>
{id} {nickname} : {realname}
</span>
)}
<button onClick={onClickEdit}>수정</button>
<button onClick={() => onRemove(id)}>삭제</button>
</div>
);
};
function InputBox() {
const nicknameInput = useRef();
const [userList, setResult] = useState([]);
const nextId = useRef(1);
const [user, setText] = useState({
id: nextId.current,
nickname: "",
realname: "",
isActive: false,
});
const { nickname, realname } = user;
const inputBoxStyle = {
color: "red",
};
const onChange = (e) => {
const { value, name } = e.target;
setText({ ...user, [name]: value });
};
const submit = () => {
setResult([...userList, user]);
//혹은
//setResult(result.concat(text))
nextId.current += 1;
const newUser = {
id: nextId.current,
nickname: "",
realname: "",
isActive: false,
};
setText(newUser);
};
const onRemove = (id) => {
setResult(userList.filter((e) => e.id !== id));
};
const active = (id) => {
setResult(
userList.map((e) => (e.id === id ? { ...e, isActive: !e.isActive } : e))
);
};
const onEdited = (editedElement) => {
setResult(
userList.map((e) => (e.id === editedElement.id ? editedElement : e))
);
};
const onKeyPressed = (e) => {
if (e.key === "Enter") {
submit();
}
};
return (
<div>
<h3 style={inputBoxStyle}>Below is a input box!</h3>
{/* placeholder는 hint임 */}
<input
ref={nicknameInput}
value={realname}
onChange={onChange}
name="realname"
placeholder="realname"
></input>
<input
value={nickname}
onChange={onChange}
name="nickname"
placeholder="nickname"
onKeyDown={onKeyPressed}
></input>
<button
onClick={() => {
nicknameInput.current.focus();
}}
>
Get Focus
</button>
<button onClick={submit}>Submit</button>
{userList.map((e, index) => (
<ListContainer
element={e}
active={active}
onRemove={onRemove}
onEdited={onEdited}
key={e.id}
></ListContainer>
))}
</div>
);
}
export default InputBox;
'React > React Basic' 카테고리의 다른 글
[React] useMemo와 useCallback (0) | 2021.08.01 |
---|---|
[React] mount시 실행되는 useEffect (0) | 2021.08.01 |
[리액트] Ref에 관하여 (useRef) (0) | 2021.07.31 |
[React] 컴포넌트간 props 전달 (2) | 2021.07.25 |
[React] 이벤트에서 함수 호출하기 (1) | 2021.03.15 |