1장이 아직 안끝났다. 사실 1장에 가장 중요한 개념은 다 있는 것 같다. 이것만 알아도..!
( ex )
=> 그럴 땐, 리액트에서 ref 라는 것을 사용한다.
우리가 만든 InputSample에서는 초기화 버튼을 누르면 포커스가 초기화 버튼에 그대로 남아있게 된다.
한번, 초기화 버튼을 클릭했을 때 이름 input에 포커스가 잡히도록 useRef를 사용하여 기능을 구현해보겠다.
useRef() 를 사용하여 Ref 객체를 만들고, 이 객체를 우리가 선택하고 싶은 DOM에 ref 값으로 설정해주어야 한다.
그러면 Ref객체의 .current 값은 우리가 원하는 DOM을 가르키게 된다.
예제에서는 onReset 함수에서 input에 포커스를 하는 focus() DOM API를 호출해주었다.
import React, { useState, useRef } from "react";
function InputSample() {
const [inputs, setInputs] = useState({
name: "",
nickname: "",
});
const nameInput = useRef();
const nicknameInput = useRef();
const { name, nickname } = inputs; // 비구조화 할당을 통해 값 추출
const onChange = (e) => {
const { value, name } = e.target; // 우선 e.target 에서 name 과 value 를 추출
setInputs({
...inputs, // 기존의 input 객체를 복사한 뒤
[name]: value, // name 키를 가진 값을 value 로 설정
});
};
const onReset = () => {
setInputs({
name: "",
nickname: "",
});
nameInput.current.focus();
};
const onNickReset = () => {
setInputs({
name: "CountingStar!",
nickname: "밤하늘의 펄",
});
nicknameInput.current.focus();
}
return (
<div>
<input
name="name"
placeholder="이름"
onChange={onChange}
value={name}
ref={nameInput}
/>
<input
name="nickname"
placeholder="닉네임"
onChange={onChange}
value={nickname}
ref={nicknameInput}
/>
<button onClick={onReset}>초기화</button>
<button onClick={onNickReset}>기본값 세팅 + 닉네임변경</button>
<div>
<b>값: </b>
{name} ({nickname})
</div>
</div>
);
}
export default InputSample;
< 바뀐부분만 >
const nicknameInput = useRef();
const onNickReset = () => {
setInputs({
name: "CountingStar!",
nickname: "밤하늘의 펄",
});
nicknameInput.current.focus();
}
<input
name="nickname"
placeholder="닉네임"
onChange={onChange}
value={nickname}
ref={nicknameInput} //추가 부분
/>
src 디렉터리에 UserList.js 컴포넌트를 만들기
[UserList.js]
import React from 'react';
function UserList() {
const users = [
{
id: 1,
username: 'velopert',
email: 'public.velopert@gmail.com'
},
{
id: 2,
username: 'tester',
email: 'tester@example.com'
},
{
id: 3,
username: 'liz',
email: 'liz@example.com'
}
];
return (
<div>
<div>
<b>{users[0].username}</b> <span>({users[0].email})</span>
</div>
<div>
<b>{users[1].username}</b> <span>({users[1].email})</span>
</div>
<div>
<b>{users[2].username}</b> <span>({users[1].email})</span>
</div>
</div>
);
}
export default UserList;
그런데, 재사용되는 코드를 일일히 넣는게 별로 좋지 않으니, 컴포넌트를 재사용 할 수 있도록 새로 만들어주자 ( 결과는 같다 )
import React from 'react';
function User({user}) {
return (
<div>
<b>{user.username}</b><span>({user.email})</span>
</div>
);
}
function UserList() {
const users = [
{
id: 1,
username: 'velopert',
email: 'public.velopert@gmail.com'
},
{
id: 2,
username: 'tester',
email: 'tester@example.com'
},
{
id: 3,
username: 'liz',
email: 'liz@example.com'
}
];
return (
<div>
<User user={users[0]} />
<User user={users[1]} />
<User user={users[2]} />
</div>
);
}
export default UserList;
배열이 고정적이라면 상관없지만, 배열의 인덱스를 하나하나 조회해가면서 렌더링하는 방법은 동적인 배열을 렌더링하지 못한다.
동적인 배열을 렌더링해야 할 때에는 자바스크립트 배열의 내장함수 map() 을 사용한다.
map() 함수는 배열안에 있는 각 원소를 변환하여 새로운 배열을 만들어준다. 리액트에서 동적인 배열을 렌더링해야 할 때는 이 함수를 사용하여 일반 데이터 배열을 리액트 엘리먼트로 이루어진 배열로 변환해주면 된다.
리액트에서 배열을 렌더링 할 때에는 key라는 props를 설정해야 한다.
( ex )
<div>
{users.map((user, index) => (
<User user={user} key={index} />
))}
</div>
여기서는 key={id} !!
import React from "react";
function User({ user }) {
return (
<div>
<b>{user.username}</b> <span>({user.email})</span>
</div>
);
}
function UserList() {
const users = [
{
id: 1,
username: "velopert",
email: "public.velopert@gmail.com",
},
{
id: 2,
username: "tester",
email: "tester@example.com",
},
{
id: 3,
username: "liz",
email: "liz@example.com",
},
{
id: 4,
username: "나는 map을 사용하고 있지",
email: "map@example.com",
},
];
return (
<div>
{users.map((user) => (
<User user={user} key={user.id} />
))}
</div>
);
}
export default UserList;
const array = ['a', 'b', 'c', 'd'];
array.map(item => <div>{item}</div>);
[
{
id: 0,
text: 'a'
},
{
id: 1,
text: 'b'
},
{
id: 2,
text: 'c'
},
{
id: 3,
text: 'd'
}
];
array.map(item => <div key={item.id}>{item.text}</div>);
배열이 업데이트 될 때 key 가 없을 때 처럼 비효율적으로 업데이트 하는 것이 아니라, 수정되지 않는 기존의 값은 그대로 두고 원하는 곳에 내용을 삽입하거나 삭제합니다.
때문에, 배열을 렌더링 할 때에는 고유한 key값이 있는 것이 중요하며, 만약에 배열 안에 중복되는 key가 있을 때에는 렌더링시에 오류메시지가 콘솔에 나타나게 되며, 업데이트가 제대로 이루어지지 않게 된다.
컴포넌트에서 특정 DOM을 선택해야 할 때, ref를 사용해야 한다고 배웠었다. 그리고 함수형 컴포넌트에서 이를 설정할 때 useRef 를 사용하여 설정한다고 배웠었다.
useRef Hook은 DOM을 선택하는 용도 외에도, 다른 용도가 한 가지 더 있는데, 바로 컴포넌트 안에서 조회 및 수정할 수 있는 변수를 관리하는 것이다.
=>
=> 이 변수를 사용하여 다음 값을 관리할 수 있다
이제 App 컴포넌트에서 useRef 를 사용하여 변수를 관리할 거다. 용도는 우리가 앞으로 배열에 새 항목을 추가할건데, 새 항목에서 사용할 고유 id를 관리하는 용도다.
<사전 작업 필요>
return <UserList users={users} />
function UserList({ users }) // 로 갖고오기
const nextId = useRef(4);
const onCreate = () => {
// 나중에 구현 할 배열에 항목 추가하는 로직
// ...
nextId.current += 1;
};
setUsers([...users, user]);
setUsers(users.concat(user));
const onRemove = id => {
// user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
// = user.id 가 id 인 것을 제거함
setUsers(users.filter(user => user.id !== id));
};
이 onRemove 함수는 UserList 에서도 전달 받을것이며, 이를 그대로 User 컴포넌트에게 전달해줄것입니다.
배열에 있는 항목을 제거할 때에는, 추가할 때와 마찬가지로 불변성을 지켜가면서 업데이트를 해주어야 합니다.
불변성을 지키면서 특정 원소를 배열에서 제거하기 위해서는 filter 배열 내장 함수를 사용하는것이 가장 편하다.
App 컴포넌트에서 onRemove 를 구현후, UserList 에게 전달해주세요.
function UserList_remove({ users, onRemove }) {
return (
<div>
{users.map(user => (
<User user={user} key={user.id} onRemove={onRemove} />
))}
</div>
);
}
export default UserList_remove;
User 컴포넌트에 계정명을 클릭했을 때 색상이 초록색으로 바뀌고, 다시 누르면 검정색으로 바뀌도록 구현하기
우선, App 컴포넌트의 users 배열 안의 객체에 active라는 속성 추가
응용 ) 새로운 계정 생성시에도 자동으로 active: true로 설정해주기
const nextId = useRef(4);
const onCreate = () => {
const user = {
id: nextId.current,
username,
email,
active: true
};
setUsers(users.concat(user));
setInputs({
username: "",
email: "",
active: true,
});
핵심 추가
UserList_total
function User({ user, onRemove, onToggle }) {
return (
<div>
<b
style={{
cursor: "pointer",
color: user.active ? "green" : "black",
}}
onClick={() => onToggle(user.id)}
>
{user.username}
</b>
<span>({user.email})</span>
<button onClick={() => onRemove(user.id)}>삭제</button>
</div>
);
}
App.js
const onToggle = id => {
setUsers(
users.map(user =>
user.id === id ? { ...user, active: !user.active } : user
)
);
};
이렇게 5장의 진도를 나갔었다.
하나씩 따라해보고, 응용해보니 코딩의 재미를 느낄 수 있었다.
5장이 생각보다 시간이 오래 걸리네...ㅎㅎ
화이팅~!!