[React] 리액트 useRef 에 대해 알아보자
▶리액트 useRef 에 대해 알아보자
오늘은 프로젝트를 진행하면서 구글링을 하다가 처음으로 사용하게 된 useRef에 대해 먼저 자세히 알아보려고 합니다 :)
🔎 useRef ?
useRef는 .current 프로퍼티로 전달한 인자(initialValue)로 초기화된 변경 가능한 ref 객체를 반환합니다.
반환된 객체는 컴포넌트의 전 생애주기를 통해 유지될 것입니다.
- React 공식 홈페이지
useRef는 저장공간 또는 DOM요소에 접근하기 위해 사용되는 리액트 훅인데요.
여기서 Ref는 reference, 즉 참조를 뜻합니다.
우리가 자바스크립트를 사용할 때 특정 DOM을 선택하기 위해서는 querySelector 등의 함수를 사용했었죠?
예를 들어 어떤 DOM 요소를 선택해서 innerText 해서 화면에 보이는 텍스트를 변경하고는 했었습니다.
리액트를 사용하는 프로젝트에서도 가끔씩 DOM을 직접 선택해야 하는 상황이 필요합니다.
그럴때 useRef라는 리액트 훅을 사용하면 됩니다.
→ 💡useRef는 DOM 요소에 접근할 때 사용한다
📌 useRef 사용하기
useRef 함수는 current 속성을 가지고 있는 객체를 반환합니다.
인자로 넘어온 초기값을 current 속성에 할당하는데요.
이 current 속성은 값을 변경해도 상태를 변경할 때처럼 리액트 컴포넌트가 다시 렌더링되지 않습니다.
따라서 리액트 컴포넌트가 다시 렌더링 될 때도 마찬가지로 이 current 속성의 값을 잃지 않습니다.
📌useRef로 DOM 선택하기
useRef()를 사용하여 Ref 객체를 만들고, 선택하고 싶은 DOM에 이 객체를 ref 값으로 설정합니다.
그러면 Ref 객체의 .current 값은 DOM을 가리키게 됩니다.
객체 생성하기
const nameInput = useRef();
DOM API 사용
current가 DOM을 가리키고 있으므로 DOM API 중 하나인 focus를 사용했습니다.
nameInput.current.focus();
DOM 설정을 통해 DOM에 직접 접근하기
nameInput 객체를 선택하고 싶은 DOM에 설정하여 직접 접근할 수 있습니다.
<input
name="name"
placeholder="이름"
onChange={onChange}
value={name}
ref={nameInput}
/>
📄 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;
setInputs({
...inputs,
[name]: value,
});
};
const onReset = () => {
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;
초기화 버튼을 클릭했을 때(onReset 함수 실행) nameInput.current.focus() 때문에nameInput을 Ref 하고 있는
첫 번째 input이 focus 되게 됩니다.
📌 useRef로 컴포넌트 안의 변수 만들기
useRef()를 사용할 때 파라미터를 넣어주면, 이 값이 .current 값의 기본값이 됩니다.
그리고 이 값을 수정할 때는 .current 값을 수정하면 되고 조회할 때는 .current를 조회하면 됩니다.
useRef의 파라미터
현재 3개의 id가 있으므로 다음 id의 기본값을 4로 지정했습니다.
const nextId = useRef(4);
📄 UserList.js 파일
import React from 'react';
function User({ user }) {
return (
<div>
<b>{user.username}</b> <span>({user.email})</span>
</div>
);
}
function UserList({ users }) {
return (
<div>
{users.map((user) => (
<User user={user} key={user.id} />
))}
</div>
);
}
export default UserList;
📄 User.js 파일
import React, { useRef, useState } from 'react';
import UserList from './UserList';
function User() {
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,
};
console.log(user.id);
setUsers(users.concat(user));
setInputs({
username: '',
email: '',
});
nextId.current += 1;
};
console.log('Render');
return (
<>
<div>
<input
name="username"
placeholder="계정명"
onChange={onChange}
value={username}
/>
<input
name="email"
placeholder="이메일"
onChange={onChange}
value={email}
/>
<button onClick={onCreate}>등록</button>
</div>
<UserList users={users} />
</>
);
}
export default User;
id 값으로 nextid.current를 사용합니다.
위 코드를 자세히 리뷰해보면,
useState로 선언한 inputs는 지금 현재 화면에 보이는 계정명과 이메일 input창의 값을 의미하고 있습니다.
onChange라는 함수는 지금 계정명과 이메일 input창에 동시에 걸려있는 onChange함수입니다.
const { name, value } = e.target; 을 통해 계정명과 이메일 input창을 동시에 관리하고 있습니다.
onChange 함수가 실행이 되면 기존 inputs(초기화 했던 값)에 [ name ] : value 가 들어가게 됩니다.
이때 객체 안에 [ name ]: value은 es6부터 도입된 기능으로
변수명 또는 일반 문자열로 객체 키 값을 대괄호로 지정하면 키 값이 됩니다.
즉, 지금 제가 계정명 input을 입력하고 있다면 [ name ] 자리에는 64번째 줄에 보이는 name="username"이 들어가게 되겠죠?
이제 21번 째 줄 이후 코드를 살펴보겠습니다.
지금 useState로 정의한 users는 더미 데이터입니다.
이제 여기에 계정명과 이메일 input값이 들어오고 등록 버튼을 누르면 이 아래에 추가를 하도록 할 것입니다.
39번 째 줄에서는 current 속성에 4를 할당한 객체를 반환하여 nextId 라는 변수에 담았습니다.
41번 째 줄은 등록 버튼을 눌렀을 때의 이벤트 입니다.
user라는 객체에 id라는 키값에는 4가 할당되어 있는 nextId.current를 값으로, username: username으로, email: email을 의미합니다.
그리고 기존 users 라는 배열에 user 객체를 concat 해줍니다.
52번 쨰 줄은 concat 해주고는 다시 계정명과 이메일 input 값을 clear 시켜주는 것을 의미합니다.
📌 리렌더링 방지하는 방법
그런데 위 예시 코드에는 input 값이 변경될 때마다 리렌더링 된다는 단점이 있습니다.
→ 💡onChange 부분을 ref 값으로 대체해서 해결하자
<div>
<input name="username" placeholder="계정명" ref={usernameRef} />
<input name="email" placeholder="이메일" ref={emailRef} />
<button onClick={onCreate}>등록</button>
</div>
📄 User.js 파일
import React, { useRef, useState } from 'react';
import UserList from './UserList';
function User() {
const [inputs, setInputs] = useState({
username: '',
email: '',
});
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 usernameRef = useRef('');
const emailRef = useRef('');
const onCreate = () => {
const user = {
id: nextId.current,
username: usernameRef.current.valueOf,
email: emailRef.current.valueOf,
};
setUsers(users.concat(user));
setInputs({
username: '',
email: '',
});
nextId.current += 1;
};
console.log('Render');
return (
<>
<div>
<input name="username" placeholder="계정명" ref={usernameRef} />
<input name="email" placeholder="이메일" ref={emailRef} />
<button onClick={onCreate}>등록</button>
</div>
<UserList users={users} />
</>
);
}
export default User;
처음 컴포넌트를 불러왔을 때, 등록 버튼을 눌렀을 때 2번만 렌더링 되는 것을 확인할 수 있습니다.
마치며..
이번 글을 통해서 useRef가 무엇인지 그리고 어떻게 사용하는지 그리고 리렌더링을 하지 않기 위해 사용한다고 알아보았습니다.
그런데 useRef를 알면 알수록 useState과 혼동이 되기도 했습니다.
그래서 다음 리액트 글에서는 useRef로의 state 관리와 useState로의 state 관리에 대해 알아보고 정확하게 정리하려고 합니다🙂!