Ref는 render 메서드에서 생성된 DOM 노드나 React 엘리먼트에 접근하는 방법을 제공한다. - MDN
함수형 컴포넌트에서 ref
를 사용 할 때 useRef
라는 Hook 함수를 사용한다.
클래스형 컴포넌트에서는 콜백 함수를 사용하거나 React.createRef
라는 함수를 사용한다.
JavaScript 에서는 특정 DOM을 선택할 때 getElementById
나 querySelector
같은 DOM Selector 함수를 사용할 수 있었다.
리액트에서도 일반적인 데이터 플로우(props를 통한 상호작용)에서 벗어나 직접적으로 자식을 수정해야 하는 경우가 있을 수 있다. 컴포넌트의 인스턴스일 수도 있고. DOM 엘리먼트일 수도 있다.
즉, 리액트에서도 특정 엘리먼트의 크기나 스크롤바의 위치 등을 가져와야 할 때 등 DOM을 직접 선택해야 하는 상황이 생길 수 있는데 그럴 때 ref
를 사용할 수 있다.
import React, { useState, useRef } from 'react'; // 추가 !
function InputSample() {
const [inputs, setInputs] = useState({
name: '',
age: '',
});
const nameInput = useRef(); // 1)
const { name, age } = inputs;
const onChange = (e) => {
const { value, name } = e.target;
setInputs({
...inputs,
[name]: value,
});
};
const onReset = () => {
setInputs({
name: '',
age: '',
});
nameInput.current.focus(); // 3)
};
return (
<div>
<input
name='name'
placeholder='이름'
onChange={onChange}
value={name}
ref={nameInput} // 2)
/>
<input
name='age'
placeholder='나이'
onChange={onChange}
value={age}
ref={nameInput} // 2)
/>
<button onClick={onReset}>초기화</button>
<div>
<b>값: </b>
{name} ({age})
</div>
</div>
);
}
1) useRef()
를 사용해서 Ref 객체를 만든다.
2) 선택하고 싶은 DOM에 ref
값으로 설정한다.
→ Ref 객체의 .current
값은 우리가 원하는 DOM을 가리키게 된다.
✍️ 콘솔에 출력해보자.
console.log(nameInput.current)
console.log(nameInput)
3) input에 포커스를 하는 focus()
DOM API를 호출할 수 있다.
→ 초기화 버튼을 누르면 onReset
함수에 의해 nameInput
에 focus
된다.
useRef
Hook은 DOM을 선택하는 용도 외에도, 컴포넌트 안에서 조회 및 수정 할 수 있는 변수를 관리할 수 있다.
useRef
로 관리하는 변수는 값이 바뀌어도 컴포넌트가 리렌더링 되지 않는다.
리액트 컴포넌트에서의 상태 : 상태를 바꾸는 함수를 호출하고 나서 그 다음 렌더링 이후로 업데이트 된 상태를 조회할 수 있다.
→ useRef
로 관리하고 있는 변수는 설정 후 바로 조회 가능하다.
변수를 사용해서 관리할 수 있는 것들
setTimeout
, setInterval
을 통해서 만들어진 id
📌 useRef 사용하기
useRef 함수는 current 속성을 가지고 있는 객체를 반환하는데, 인자로 넘어온 초기값을 current 속성에 할당한다. 이 current 속성은 값을 변경해도 상태를 변경할 때 처럼 React 컴포넌트가 다시 랜더링되지 않는다. React 컴포넌트가 다시 랜더링될 때도 마찬가지로 이 current 속성의 값이 유실되지 않는다.
📌 createRef vs useRef
- createRef : 항상 ref를 생성해서 render될 때 넣어준다.
- useRef : render 사이에도 값을 유지한다. ( useRef는 매번 렌더링을 할 때 동일한 ref 객체를 제공한다. - React 공식사이트 )
✍️ 예제를 통해 알아보자
// App.js
import React, { useRef } from 'react'; // 추가 !
import UserList from './component/UserList';
function App() {
const users = [
{
id: 1,
username: 'seul',
age: 5,
},
{
id: 2,
username: 'kim',
age: 10,
},
{
id: 3,
username: 'lee',
age: 20,
},
];
const nextId = useRef(4); // 1)
const onCreate = () => {
nextId.current += 1;
};
return (
<>
<UserList users={users} />
</>
);
}
// UserList.js
function User({ user }) {
return (
<div>
<b>{user.username}</b> <span>({user.age})</span>
</div>
);
}
function UserList({ users }) {
return (
<div>
{users.map((user) => ( // 2)
<User key={user.id} user={user} /> // 3)
))}
</div>
);
}
1) useRef()
에 파라미터를 넣어주면 이 값은 .current
의 기본값으로 적용된다.
→ 이 값을 수정할 때 .current
값을 수정, 조회할 때도 .current
를 조회한다.
→ 배열의 unique한 id를 만들기 위해 작성되었다.
2) 동적인 배열을 렌더링하기 위해서 자바스크립트 배열의 내장함수 map()
을 사용한다. 일반 데이터 배열을 리액트 엘리먼트로 이루어진 배열로 변환하자.
3) key
는 엘리먼트 리스트를 만들 때 포함해야 하는 특수한 문자열 어트리뷰트이다. 이 코드에서 key 없이 실행하면 이 리스트의 각 항목에 key를 넣어야 한다는 경고가 표시된다.
추가하기
// App.js
const nextId = useRef(4);
// 유저생성 업데이트
const onCreate = () => { // 1-2)
const user = {
id: nextId.current,
username, // 단축 username: username
age,
};
setUsers([...users, user]); // 2)
return (
<>
<CreateUser
username={username}
age={age}
onChange={onChange}
onCreate={onCreate} // 1-1)
/>
<UserList users={users} onRemove={onRemove} />
</>
);
// CreateUser.js
function CreateUser({ username, age, onChange, onCreate }) {
return (
<div>
<input
name='username'
placeholder='이름'
onChange={onChange}
value={username}
/>
<input name='age' placeholder='나이' onChange={onChange} value={age} />
<button onClick={onCreate}>등록</button> // 1)
</div>
);
}
2) 배열에 변화를 줄 때는 객체와 마찬가지로 불변성을 지켜줘야한다. 기존 배열을 복사하고 나서 사용하자.
불변성을 지키면서 배열에 새 항목을 추가하는 방법
(1) spread 연산자 사용하기 ✔️
(2) concat
함수 사용하기
직접적으로 state를 변경하는 것은 좋지 않으므로 새로운 배열 껍데기를 만들기 위해 Spread Operator 사용한 것이다. ...
을 사용하는 순간 새로운 오브젝트를 만들게 된다.
🤔 전개연산자를 통해서 복사를 하면 얕은 복사가 이루어지는 것으로 알고 있다.
삭제하기
// App.js
const onRemove = (id) => { // 2)
setUsers(users.filter((user) => user.id !== id));
};
// User
function User({ user, onRemove }) { // 1)
return (
<div>
<b>{user.username}</b> <span>({user.age})</span>
<button onClick={() => onRemove(user.id)}>❌</button> // 3)
</div>
);
}
1) App.js → UserList → User 컴포넌트를 통해서 onRemove
함수를 전달한다.
2) 버튼이벤트로 가져온 id값이 아닌 것들만 추출해서 새로운 배열을 반환한다.
배열에 있는 항목을 제거할 때도 추가할 때와 마찬가지로 불변성을 지키면서 업데이트 해준다. 이때 filter
를 사용하면 좋다.
❓ 3) 이 코드를 화살표 함수를 사용하지 않고 onClick={onRemove(user.id)}
를 사용하면 동작하지 않는 이유 🤔 ? ▼
onClick={onRemove()}
은 해당 컴포넌트가 렌더링이 되는것과 동시에 onRemove
함수를 실행시켜버린다.onClick={onRemove}
로 작성해서 ()
를 제외하는 방법으로 함수가 즉시실행 되지 않게 하고, 클릭했을때 실행이 되도록 한다.onClick={onRemove(user.id)}
은 해당 컴포넌트가 렌더링됨과 동시에 함수가 실행되어 아무것도 렌더링이 되지 않을 것이다. (콘솔에서도 오류발생)onClick
에 콜백 함수를 넣어주고, 해당 함수가 실행될 때 user.id
를 건네주어 실행시키는 방법으로 처리를 한다고 한다. 👍수정하기
// App.js
const onToggle = (id) => {
setUsers(
users.map((user) => // 3)
user.id === id ? { ...user, active: !user.active } : user
)
);
};
// User.js
function User({ user, onRemove }) {
return (
<div>
<b
style={{ // 1)
cursor: 'pointer',
color: user.active ? 'skyblue' : 'black',
}}
onClick={() => onToggle(user.id)} // 2)
>
{user.username}
</b>{' '}
<span>({user.age})</span>
<button onClick={() => onRemove(user.id)}>❌</button>
</div>
);
}
active
속성을 추가하고, User 컴포넌트에 active
값에 따라 폰트 색상과 커서 속성이 변하도록 설정해보자.user.id
를 상위 컴포넌트로 전달한다.map
함수를 이용할 수 있다.id
값을 비교한 후 id
가 다르다면 그대로, 같다면 active
값을 반전시킨다.Key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 도와준다.
리액트에서는 자식 컴포넌트가 있으면 고유한 키를 가지고 있어야한다.
한 리스트 안에서 동일한 아이디를 가지고 있는 아이템이 들어있으면 안된다. (배열안에 중복되는 key가 있다면 렌더링시에 오류메시지가 뜨고, 업데이트가 제대로 이루어지지 않는다.)
엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 한다.
키를 부여하는 좋은 방법은 리스트의 항목 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이다. 대부분 데이터의 ID를 key로 사용한다.
const userList = users.map((user) =>
<li key={user.id}>
{user.name}
</li>
);
렌더링 한 항목에 안정적인 ID가 없다면 map()
함수를 사용할 때 설정하는 콜백함수의 두 번째 파라미터인 index
를 key
로 사용할 수 있지만 권장되지 않는다.🤔
→ 항목의 순서가 바뀔 수 있는 경우 key에 인덱스를 사용하는 것은 좋지 않다고 한다. (성능 저하문제, state와 관련된 문제 발생 관련 참고 사이트)
❓ key를 사용해야 하는 이유를 조금 더 살펴보자.
✍️ 각각의 컴포넌트에 고유한 id를 부여함으로써, 만약 id가 동일하다면 자식 요소가 변경되어 진 것이 아니므로 나중에 자식 요소가 추가되거나 위치가 변경되어도 리액트가 성능개선을 위해 (불필요한 렌더링을 하지 않는 등) id를 이용해서 계산한다.
→ 즉 각 고유 원소에 key가 있어야만 배열이 업데이트 될 때 효율적으로 렌더링 될 수 있다고 한다.
reference)
React-Hook API
React-Ref와_DOM
react-vlpt
useRef
React-Key