useRef & Key

seul_velog·2022년 4월 2일
1

React

목록 보기
4/11
post-thumbnail

useRef

Ref는 render 메서드에서 생성된 DOM 노드나 React 엘리먼트에 접근하는 방법을 제공한다. - MDN

  • 함수형 컴포넌트에서 ref 를 사용 할 때 useRef 라는 Hook 함수를 사용한다.

  • 클래스형 컴포넌트에서는 콜백 함수를 사용하거나 React.createRef 라는 함수를 사용한다.

  • JavaScript 에서는 특정 DOM을 선택할 때 getElementByIdquerySelector 같은 DOM Selector 함수를 사용할 수 있었다.

  • 리액트에서도 일반적인 데이터 플로우(props를 통한 상호작용)에서 벗어나 직접적으로 자식을 수정해야 하는 경우가 있을 수 있다. 컴포넌트의 인스턴스일 수도 있고. DOM 엘리먼트일 수도 있다.

  • 즉, 리액트에서도 특정 엘리먼트의 크기나 스크롤바의 위치 등을 가져와야 할 때 등 DOM을 직접 선택해야 하는 상황이 생길 수 있는데 그럴 때 ref 를 사용할 수 있다.



1. useRef로 DOM 선택하기

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 함수에 의해 nameInputfocus 된다.




2. useRef 로 컴포넌트 안의 변수 관리하기

  • useRef Hook은 DOM을 선택하는 용도 외에도, 컴포넌트 안에서 조회 및 수정 할 수 있는 변수를 관리할 수 있다.

  • useRef 로 관리하는 변수는 값이 바뀌어도 컴포넌트가 리렌더링 되지 않는다.

  • 리액트 컴포넌트에서의 상태 : 상태를 바꾸는 함수를 호출하고 나서 그 다음 렌더링 이후로 업데이트 된 상태를 조회할 수 있다.
    useRef 로 관리하고 있는 변수는 설정 후 바로 조회 가능하다.

  • 변수를 사용해서 관리할 수 있는 것들

    • setTimeout, setInterval 을 통해서 만들어진 id
    • 외부 라이브러리를 사용하여 생성된 인스턴스
    • scroll 위치

📌 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를 넣어야 한다는 경고가 표시된다.




✍️ UserList 완성하기

  • 위에서 작성해본 UserList 코드를 정리 및 추가해서 user를 추가, 삭제, 수정이 되도록 만들어보자.

추가하기

// 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>
  );
}
  • 1) 위에서처럼 부모 컴포넌트로 부터 props를 받아와서 활용한다. 버튼을 등록하고 이벤트가 발생하면 부모컴포넌트로 전달된다.
  • 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>
  );
}
  • 1) App 컴포넌트 안에서 users 배열 객체 안에 active 속성을 추가하고, User 컴포넌트에 active 값에 따라 폰트 색상과 커서 속성이 변하도록 설정해보자.
  • 2) 클릭 이벤트와 화살표함수를 통해서 해당 이벤트가 일어나는 user.id 를 상위 컴포넌트로 전달한다.
  • 3) 배열의 불변성을 유지하면서 배열을 업데이트 할 때에도 map 함수를 이용할 수 있다.
    id 값을 비교한 후 id 가 다르다면 그대로, 같다면 active 값을 반전시킨다.




3. Key

Key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 도와준다.

  • 리액트에서는 자식 컴포넌트가 있으면 고유한 키를 가지고 있어야한다.

  • 한 리스트 안에서 동일한 아이디를 가지고 있는 아이템이 들어있으면 안된다. (배열안에 중복되는 key가 있다면 렌더링시에 오류메시지가 뜨고, 업데이트가 제대로 이루어지지 않는다.)

  • 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 한다.

  • 키를 부여하는 좋은 방법은 리스트의 항목 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이다. 대부분 데이터의 ID를 key로 사용한다.

    const userList = users.map((user) =>
      <li key={user.id}>
        {user.name}
      </li>
    );
  • 렌더링 한 항목에 안정적인 ID가 없다면 map() 함수를 사용할 때 설정하는 콜백함수의 두 번째 파라미터인 indexkey 로 사용할 수 있지만 권장되지 않는다.🤔
    → 항목의 순서가 바뀔 수 있는 경우 key에 인덱스를 사용하는 것은 좋지 않다고 한다. (성능 저하문제, state와 관련된 문제 발생 관련 참고 사이트)


key를 사용해야 하는 이유를 조금 더 살펴보자.

✍️ 각각의 컴포넌트에 고유한 id를 부여함으로써, 만약 id가 동일하다면 자식 요소가 변경되어 진 것이 아니므로 나중에 자식 요소가 추가되거나 위치가 변경되어도 리액트가 성능개선을 위해 (불필요한 렌더링을 하지 않는 등) id를 이용해서 계산한다.

→ 즉 각 고유 원소에 key가 있어야만 배열이 업데이트 될 때 효율적으로 렌더링 될 수 있다고 한다.




reference)
React-Hook API
React-Ref와_DOM
react-vlpt
useRef
React-Key

profile
기억보단 기록을 ✨

0개의 댓글