10. useRef 로 특정 DOM 선택하기
- JavaScript 를 사용 할 때에는, 우리가 특정 DOM 을 선택해야 하는 상황에 getElementById, querySelector 같은 DOM Selector 함수를 사용해서 DOM 을 선택
- 리액트를 사용하는 프로젝트에서도 가끔씩 DOM 을 직접 선택해야 하는 상황이 발생
- ex) 특정 엘리먼트의 크기를 가져올 때, 스크롤바 위치를 가져오거나 설정해야될 때, 포커스를 설정해줘야 될 때 등
- 추가적으로 Video.js, JWPlayer 같은 HTML5 Video 관련 라이브러리, 또는 D3, chart.js 같은 그래프 관련 라이브러리 등의 외부 라이브러리를 사용해야 할 때에도 DOM에다 적용
- 그럴 땐, 리액트에서 ref 사용
- 함수형 컴포넌트에서 ref 를 사용 할 때에는 useRef 라는 Hook 함수를 사용
- 클래스형 컴포넌트에서는 콜백 함수를 사용 / React.createRef 라는 함수를 사용
- 초기화 버튼을 클릭했을 때 이름 input 에 포커스가 잡히도록 useRef 를 사용하여 기능을 구현
// InputSample.js
import React, { useState, useRef } from 'react';
function InputSample() {
const [inputs, setInputs] = useState({
name: '',
nickname: ''
});
const nameInput = 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 = () => { // input 에 포커스를 하는 focus() DOM API 를 호출
setInputs({
name: '',
nickname: ''
});
nameInput.current.focus();
};
return (
<div>
<input
name="name"
placeholder="이름"
onChange={onChange}
value={name}
ref={nameInput}
/>
<input
name="nickname"
placeholder="닉네임"
onChange={onChange}
value={nickname}
/>
<button onClick={onReset}>초기화</button>
<div>
<b>값: </b>
{name} ({nickname})
</div>
</div>
);
}
export default InputSample;
- useRef() 를 사용하여 Ref 객체를 만들고, 이 객체를 우리가 선택하고 싶은 DOM 에 ref 값으로 설정 -> Ref 객체의 .current 값은 우리가 원하는 DOM 을 가르킴
11. 배열 렌더링하기
- 배열이 고정적이라면 상관없겟지만, 배열의 인덱스를 하나하나 조회해가면서 렌더링하는 방법은 동적인 배열을 렌더링 X
- 동적인 배열을 렌더링해야 할 때에는 자바스크립트 배열의 내장함수 map() 사용
- map() 함수는 배열안에 있는 각 원소를 변환하여 새로운 배열을 만들어줌
- 리액트에서 동적인 배열을 렌더링해야 할 때는 이 함수를 사용하여 일반 데이터 배열을 리액트 엘리먼트로 이루어진 배열로 변환
// UserList.js
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>
{users.map(user => (
<User user={user} />
))}
</div>
);
}
export default UserList;
-> 이렇게 하면 배열의 모든 원소가 렌더링됨, 하지만 브라우저에서 콘솔을 열면 에러가 보여짐
- 리액트에서 배열을 렌더링 할 때에는 key 라는 props 를 설정
- key 값은 각 원소들마다 가지고 있는 고유값으로 설정
- 예시에서는 id 가 고유 값
// UserList.js
return (
<div>
{users.map(user => (
<User user={user} key={user.id} />
))}
</div>
);
- 만약 배열 안의 원소가 가지고 있는 고유한 값이 없다면 map() 함수를 사용 할 때 설정하는 콜백함수의 두번째 파라미터 index 를 key 로 사용
<div>
{users.map((user, index) => (
<User user={user} key={index} />
))}
</div>
- 만약에 배열을 렌더링 할 때 key 설정을 하지 않게된다면 기본적으로 배열의 index 값을 key 로 사용하게 되고, 경고 메시지가 뜸 - 각 고유 원소에 key 가 있어야만 배열이 업데이트 될 때 효율적으로 렌더링 될 수 있기 때문
key의 존재유무에 따른 업데이트 방식
const array = ['a', 'b', 'c', 'd'];
// 렌더링
array.map(item => <div>{item}</div>);
- 위 배열의 b 와 c 사이에 z 를 삽입하게 된다면, 리렌더링을 하게 될 때 <div>b</div> 와 <div>c</div> 사이에 새 div 태그를 삽입을 하게 되는 것이 아니라, 기존의 c 가 z 로바뀌고, d 는 c 로 바뀌고, 맨 마지막에 d 가 새로 삽입됨
- 그 다음에 a 를 제거하게 된다면, 기존의 a 가 b 로 바뀌고, b 는 z 로 바뀌고, z는 c로 바뀌고, c는 d 로바뀌고, 맨 마지막에 있는 d 가 제거
- 객체에 다음과 같이 key 로 사용 할 수 있는 고유 값이 있고
[
{
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 가 있을 때에는 렌더링시에 오류메시지가 콘솔에 나타나게 되며, 업데이트가 제대로 이루어지지 않음
12. useRef 로 컴포넌트 안의 변수 만들기
- useRef Hook 은 DOM 을 선택하는 용도 외에도, 컴포넌트 안에서 조회 및 수정 할 수 있는 변수를 관리
- useRef 로 관리하는 변수는 값이 바뀐다고 해서 컴포넌트가 리렌더링 X
- 리액트 컴포넌트에서의 상태는 상태를 바꾸는 함수를 호출하고 나서 그 다음 렌더링 이후로 업데이트 된 상태를 조회 할 수 있는 반면, useRef 로 관리하고 있는 변수는 설정 후 바로 조회 가능
- useRef 로 관리하고 있는 변수를 사용하여 다음과 같은 값을 관리 가능
- setTimeout, setInterval 을 통해서 만들어진 id
- 외부 라이브러리를 사용하여 생성된 인스턴스
- scroll 위치
- 배열을 App 에서 선언하고 UserList 에게 props 로 전달
// App.js
import React, { useRef } from 'react';
import UserList from './UserList';
function App() {
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'
}
];
const nextId = useRef(4);
const onCreate = () => {
// 나중에 구현 할 배열에 항목 추가하는 로직
// ...
nextId.current += 1;
};
return <UserList users={users} />;
}
export default App;
- useRef() 를 사용 할 때 파라미터를 넣어주면, 이 값이 .current 값의 기본값이 됨
- 이 값을 수정 할때에는 .current 값을 수정하면 되고 조회 할 때에는 .current 를 조회
13. 배열에 항목 추가하기
// CreateUser.js
import React from 'react';
function CreateUser({ username, email, onChange, onCreate }) {
return (
<div>
<input
name="username"
placeholder="계정명"
onChange={onChange}
value={username}
/>
<input
name="email"
placeholder="이메일"
onChange={onChange}
value={email}
/>
<button onClick={onCreate}>등록</button>
</div>
);
}
export default CreateUser;
- 컴포넌트에서는 상태관리를 CreateUser 에서 하지 않고 부모 컴포넌트인 App 에서 하게 하고, input 의 값 및 이벤트로 등록할 함수들을 props 로 넘겨받아서 사용
// App.js
import React, { useRef, useState } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
function App() {
const [inputs, setInputs] = useState({
username: '',
email: ''
});
const { username, email } = inputs;
const onChange = e => {
const { name, value } = e.target;
setInputs({
...inputs,
[name]: value
});
};
const [users, setUsers] = useState([
{
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'
}
]);
const nextId = useRef(4);
const onCreate = () => {
// 나중에 구현 할 배열에 항목 추가하는 로직
// ...
setInputs({
username: '',
email: ''
});
nextId.current += 1;
};
return (
<>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} />
</>
);
}
export default App;
- 배열에 변화를 줄 때에는 객체와 마찬가지로, 불변성을 지켜주어야 함
- 배열의 push, splice, sort 등의 함수를 사용 X, 만약에 사용해야 한다면, 기존의 배열을 한번 복사하고 나서 사용
- 불변성을 지키면서 배열에 새 항목을 추가하는 방법
- spread 연산자를 사용
const nextId = useRef(4);
const onCreate = () => {
const user = {
id: nextId.current,
username,
email
};
setUsers([...users, user]);
setInputs({
username: '',
email: ''
});
nextId.current += 1;
};
- concat 함수를 사용
- concat 함수는 기존의 배열을 수정하지 않고, 새로운 원소가 추가된 새로운 배열을 만들어줌
const nextId = useRef(4);
const onCreate = () => {
const user = {
id: nextId.current,
username,
email
};
setUsers(users.concat(user));
setInputs({
username: '',
email: ''
});
nextId.current += 1;
};
14. 배열에 항목 제거하기
// UserList.js
import React from 'react';
function User({ user, onRemove }) {
return (
<div>
<b>{user.username}</b> <span>({user.email})</span>
<button onClick={() => onRemove(user.id)}>삭제</button>
</div>
);
}
function UserList({ users, onRemove }) {
return (
<div>
{users.map(user => (
<User user={user} key={user.id} onRemove={onRemove} />
))}
</div>
);
}
export default UserList;
- User 컴포넌트의 삭제 버튼이 클릭 될 때는 user.id 값을 앞으로 props 로 받아올 onRemove 함수의 파라미터로 넣어서 호출
- 여기서 onRemove "id 가 __인 객체를 삭제해라" 라는 역할을 가지고 있음
- 이 onRemove 함수는 UserList 에서도 전달 받을것이며, 이를 그대로 User 컴포넌트에게 전달
- onRemove 함수를 구현: 배열에 있는 항목을 제거할 때에는, 추가할떄와 마찬가지로 불변성을 지켜가면서 업데이트
- 불변성을 지키면서 특정 원소를 배열에서 제거하기 위해서는 filter 배열 내장 함수를 사용하는것이 가장 편함
- 이 함수는 배열에서 특정 조건이 만족하는 원소들만 추출하여 새로운 배열을 만들어줌
// App.js
import React, { useRef, useState } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
function App() {
const [inputs, setInputs] = useState({
username: '',
email: ''
});
const { username, email } = inputs;
const onChange = e => {
const { name, value } = e.target;
setInputs({
...inputs,
[name]: value
});
};
const [users, setUsers] = useState([
{
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'
}
]);
const nextId = useRef(4);
const onCreate = () => {
const user = {
id: nextId.current,
username,
email
};
setUsers(users.concat(user));
setInputs({
username: '',
email: ''
});
nextId.current += 1;
};
const onRemove = id => {
// user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
// = user.id 가 id 인 것을 제거함
setUsers(users.filter(user => user.id !== id));
};
return (
<>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onRemove={onRemove} />
</>
);
}
export default App;
15. 배열 항목 수정하기
- User 컴포넌트에 계정명을 클릭했을때 색상이 초록새으로 바뀌고, 다시 누르면 검정색으로 바뀌도록 구현
// App.js
import React, { useRef, useState } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
function App() {
const [inputs, setInputs] = useState({
username: '',
email: ''
});
const { username, email } = inputs;
const onChange = e => {
const { name, value } = e.target;
setInputs({
...inputs,
[name]: value
});
};
const [users, setUsers] = useState([
{
id: 1,
username: 'velopert',
email: 'public.velopert@gmail.com',
active: true
},
{
id: 2,
username: 'tester',
email: 'tester@example.com',
active: false
},
{
id: 3,
username: 'liz',
email: 'liz@example.com',
active: false
}
]);
const nextId = useRef(4);
const onCreate = () => {
const user = {
id: nextId.current,
username,
email
};
setUsers(users.concat(user));
setInputs({
username: '',
email: ''
});
nextId.current += 1;
};
const onRemove = id => {
// user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
// = user.id 가 id 인 것을 제거함
setUsers(users.filter(user => user.id !== id));
};
const onToggle = id => {
setUsers(
users.map(user =>
user.id === id ? { ...user, active: !user.active } : user
)
);
};
return (
<>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onRemove={onRemove} onToggle={onToggle} />
</>
);
}
export default App;
// UserList.js
import React from 'react';
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>
);
}
function UserList({ users, onRemove, onToggle }) {
return (
<div>
{users.map(user => (
<User
user={user}
key={user.id}
onRemove={onRemove}
onToggle={onToggle}
/>
))}
</div>
);
}
export default UserList;
- App 컴포넌트의 users 배열 안의 객체 안에 active 라는 속성을 추가
- User 컴포넌트에서 방금 넣어준 active 값에 따라 폰트의 색상을 바꿔주도록 구현
- cursor 필드를 설정하여 마우스를 올렸을때 커서가 손가락 모양으로 변함
- App.js 에서 onToggle 이라는 함수를 구현
- 배열의 불변성을 유지하면서 배열을 업데이트 할 때에도 map 함수를 사용
- id 값을 비교해서 id 가 다르다면 그대로 두고, 같다면 active 값을 반전시키도록 구현
- onToggle 함수를 만들어서 UserList 컴포넌트에게 전달
- UserList 컴포넌트에서 onToggle를 받아와서 User 에게 전달해주고, onRemove 를 구현했었던것처럼 onToggle 에 id 를 넣어서 호출
16. useEffect를 사용하여 마운트/언마운트/업데이트시 할 작업 설정하기
마운트 / 언마운트
// UserList.js
import React, { useEffect } from 'react';
function User({ user, onRemove, onToggle }) {
useEffect(() => {
console.log('컴포넌트가 화면에 나타남');
return () => {
console.log('컴포넌트가 화면에서 사라짐');
};
}, []);
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>
);
}
function UserList({ users, onRemove, onToggle }) {
return (
<div>
{users.map(user => (
<User
user={user}
key={user.id}
onRemove={onRemove}
onToggle={onToggle}
/>
))}
</div>
);
}
export default UserList;
- useEffect 를 사용 할 때에는 첫번째 파라미터에는 함수, 두번째 파라미터에는 의존값이 들어있는 배열 (deps)을 넣음
- 만약, deps 배열을 비우게 된다면, 컴포넌트가 처음 나타날때에만 useEffect 에 등록한 함수가 호출
- useEffect 에서는 함수를 반환 할 수 있는데 이를 cleanup 함수라고 부름
- cleanup 함수는 useEffect 에 대한 뒷정리를 해줌
- deps 가 비어있는 경우에는 컴포넌트가 사라질 때 cleanup 함수가 호출
- 마운트 시에 하는 작업 사항
- props 로 받은 값을 컴포넌트의 로컬 상태로 설정
- 외부 API 요청 (REST API 등)
- 라이브러리 사용 (D3, Video.js 등...)
- setInterval 을 통한 반복작업 혹은 setTimeout 을 통한 작업 예약
- 언마운트 시에 하는 작업 사항
- setInterval, setTimeout 을 사용하여 등록한 작업들 clear 하기 (clearInterval, clearTimeout)
- 라이브러리 인스턴스 제거
deps에 특정 값 넣기
- deps 에 특정 값을 넣게 된다면, 컴포넌트가 처음 마운트 될 때에도 호출이 되고, 지정한 값이 바뀔 때에도 호출
- deps 안에 특정 값이 있다면 언마운트시에도 호출이되고, 값이 바뀌기 직전에도 호출
import React, { useEffect } from 'react';
function User({ user, onRemove, onToggle }) {
useEffect(() => {
console.log('user 값이 설정됨');
console.log(user);
return () => {
console.log('user 가 바뀌기 전..');
console.log(user);
};
}, [user]);
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>
);
}
function UserList({ users, onRemove, onToggle }) {
return (
<div>
{users.map(user => (
<User
user={user}
key={user.id}
onRemove={onRemove}
onToggle={onToggle}
/>
))}
</div>
);
}
export default UserList;
- useEffect 안에서 사용하는 상태나, props 가 있다면, useEffect 의 deps 에 넣어주어야 함 - 규칙
- 만약 useEffect 안에서 사용하는 상태나 props 를 deps 에 넣지 않게 된다면 useEffect 에 등록한 함수가 실행 될 때 최신 props / 상태를 가르키지 않음
deps 파라미터를 생략하기
- deps 파라미터를 생략한다면, 컴포넌트가 리렌더링 될 때마다 호출
import React, { useEffect } from 'react';
function User({ user, onRemove, onToggle }) {
useEffect(() => {
console.log(user);
});
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>
);
}
function UserList({ users, onRemove, onToggle }) {
return (
<div>
{users.map(user => (
<User
user={user}
key={user.id}
onRemove={onRemove}
onToggle={onToggle}
/>
))}
</div>
);
}
export default UserList;
리액트 컴포넌트는 기본적으로 부모컴포넌트가 리렌더링되면 자식 컴포넌트 또한 리렌더링 - 바뀐 내용 X여도
실제 DOM 에 변화가 반영되는 것은 바뀐 내용이 있는 컴포넌트에만 해당 but Virtual DOM 에는 모든걸 다 렌더링
'WINK-(Web & App) > React.js 스터디' 카테고리의 다른 글
[2024 React.js 스터디] 류상우 #5주차 (0) | 2024.05.21 |
---|---|
[2024 React.js 스터디] 이서영 #5주차 리액트 입문 (10장~16장) (0) | 2024.05.21 |
[2024 React.js 스터디] 박건민 #5주차 (0) | 2024.05.20 |
[2024 React.js 스터디] 정호용 #5주차 "React.JS 활용하기" (1) | 2024.05.19 |
[2024 React.js 스터디] 이서영 #4주차 리액트 입문 (0) | 2024.05.14 |