[벨로퍼트님 개념] 1장(2) 리액트

초록귤·2022년 1월 12일
6

1장이 아직 안끝났다. 사실 1장에 가장 중요한 개념은 다 있는 것 같다. 이것만 알아도..!

깃헙에 정리해두었습니다.

10. useRef로 특정 DOM 선택하기

  • 리액트를 사용하는 프로젝트에서도 가끔씩 DOM을 직접 선택해야하는 상황이 발생할 때가 있다.

( ex )

  • 특정 엘리먼트의 크기를 가져와야 한다던지, 스크롤바 위치를 가져오거나 설정해야된다던지, 또는 포커스를 설정해줘야된다던지
  • Video.js , JWPlayer 같은 HTML5 Video관련 라이브러리, 또는 D3, chart.js 같은 그래프 관련 라이브러리 등의 외부 라이브러리를 사용해야 할 때

=> 그럴 땐, 리액트에서 ref 라는 것을 사용한다.

  • 함수형 컴포넌트에서 ref 를 사용할 때에는 useFef라는 Hook함수를 사용한다.
  • 클래스형 컴포넌트에서는 콜백 함수 or React.createRef 라는 함수를 사용한다.
  • 이에 대해서는 나중에 클래스 컴포넌트를 배울때 다뤄볼 예정이다. ( 이제 별로 중요하지 않다 )

우리가 만든 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;
  • 추가한 부분!
    • HTML에서 버튼을 추가하고, 해당 버튼을 클릭했을때 onNinkReset 함수가 실행되도록 했다.
    • useRef를 이용하기에 추가로 선언해주기
    • onNickReset 함수에서 기본값을 선언해주고, ref에서 nicknameInput 실행되도록 한 함수 작성
    • 함수명.current.focus()로 커서 이동해주기

< 바뀐부분만 >

const nicknameInput = useRef();
const onNickReset = () => {
setInputs({
name: "CountingStar!",
nickname: "밤하늘의 펄",
});
nicknameInput.current.focus();
}
<input
name="nickname"
placeholder="닉네임"
onChange={onChange}
value={nickname}
ref={nicknameInput} //추가 부분
/>

11. 배열 렌더링하기

  • 저장된 데이터를 컴포넌트로 렌더링한다면?

1. 가장 기본적 - 그냥 그대로 코드 작성

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;

2.User 컴포넌트 만들기 + 배열의 인덱스 직접 넣기

그런데, 재사용되는 코드를 일일히 넣는게 별로 좋지 않으니, 컴포넌트를 재사용 할 수 있도록 새로 만들어주자 ( 결과는 같다 )

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;

2.User 컴포넌트 만들기 + map 으로 동적 배열 렌더링 + props key값 설정


배열이 고정적이라면 상관없지만, 배열의 인덱스를 하나하나 조회해가면서 렌더링하는 방법은 동적인 배열을 렌더링하지 못한다.

동적인 배열을 렌더링해야 할 때에는 자바스크립트 배열의 내장함수 map() 을 사용한다.

map() 함수는 배열안에 있는 각 원소를 변환하여 새로운 배열을 만들어준다. 리액트에서 동적인 배열을 렌더링해야 할 때는 이 함수를 사용하여 일반 데이터 배열을 리액트 엘리먼트로 이루어진 배열로 변환해주면 된다.

key 안쓰면 Error나요~!!


리액트에서 배열을 렌더링 할 때에는 key라는 props를 설정해야 한다.

  • key 값은 각 원소들마다 가지고 있는 고유값으로 설정해야 한다. ( 보통 id 가 고유 값으로 사용!)
  • 만약 배열 안의 원소가 고유한 값이 없다면 map() 함수를 사용할 때 설정하는 콜백함수의 두번째 파라미터 index를 key로 사용하면 된다.

( 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;

key의 존재유무에 따른 업데이트 방식

  • key 없을 때 !! 배열로 존재할 때, 중간에 삽입 또는 삭제가 일어난다면 ?
const array = ['a', 'b', 'c', 'd'];
array.map(item => <div>{item}</div>);
  • Map에 key 값이없다면 중간의 값이 바뀌었을때 그 하위 값들이 전부 변하기 때문에 비효율적 (중간 삽입이 일어나면, 다음 값 c 다시 생성, 다음 값 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 로 컴포넌트 안의 변수 만들기

  • 컴포넌트에서 특정 DOM을 선택해야 할 때, ref를 사용해야 한다고 배웠었다. 그리고 함수형 컴포넌트에서 이를 설정할 때 useRef 를 사용하여 설정한다고 배웠었다.

  • useRef Hook은 DOM을 선택하는 용도 외에도, 다른 용도가 한 가지 더 있는데, 바로 컴포넌트 안에서 조회 및 수정할 수 있는 변수를 관리하는 것이다.

    =>

    • useRef 로 관리하는 변수는 값이 바뀐다고 해서 컴포넌트가 리렌더링되지 않는다. 그냥 설정 후 바로 조회할 수 있다.
    • useRef 로 관리 x(안하는) 변수는 , 상태를 바꾸는 함수를 호출하고 나서 그 다음 렌더링 이후로 업데이트 된 상태를 조회할 수 있다.

=> 이 변수를 사용하여 다음 값을 관리할 수 있다

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

이제 App 컴포넌트에서 useRef 를 사용하여 변수를 관리할 거다. 용도는 우리가 앞으로 배열에 새 항목을 추가할건데, 새 항목에서 사용할 고유 id를 관리하는 용도다.

useRef를 사용하여 변수 관리하기

<사전 작업 필요>

  • 지금은 UserList 컴포넌트 내부에서 배열을 직접 선언해서 사용하고 있다. 이렇게 UserList에서 선언해서 사용하는 대신에, 이 배열을 App에서 선언하고 UserList에게 props로 전달하기.

코드 분리

  • App.js
    (저장할 때 App3.js로 할예정)
return <UserList users={users} />
  • UserList3.js
function UserList({ users }) // 로 갖고오기

이제 userRef() 사용하여 nextId 변수 만들기

  • useRef() 를 사용할 때 파라미터를 넣어주면, 이 값이 .current 값의 기본값이 된다.
  • 이 값을 수정 할 때에는 .current 값을 수정하면 되고
    조회할 때에는 .current 를 조회하면 된다.

App.js 에 추가할 부분

  const nextId = useRef(4);
  const onCreate = () => {
    // 나중에 구현 할 배열에 항목 추가하는 로직
    // ...

    nextId.current += 1;
  };

13. 배열에 항목 추가

  • 이번 컴포넌트에서는 상태관리를 CreateUser에서 하지 않고 부모 컴포넌트인 App에서 하게 한다.
  • input 의 값 및 이벤트로 등록할 함수들을 props 로 넘겨받아서 사용하겠다.
  • 배열에 변화를 줄 차례 - 객체와 마찬가지로, 불변성을 지켜주어야 한다.
  • 그렇기 때문에, push, slice, sort 등의 함수를 사용하면 한된다.
  • 만약에 사용한다면? 기존의 배열을 한번 복사하고나서 사용해야 한다.
    => 불변성을 지키면서 배열에 새 항목을 추가하는 방법은 2가지가 있다.

1. spread 연산자 사용하기

setUsers([...users, user]);

2. concat 함수 사용하기

  • 기존의 배열을 수정하지 않고, 새로운 원소가 추가된 새로운 배열을 만들어준다.
setUsers(users.concat(user));

14. 배열에 항목 제거

  • User 컴포넌트의 삭제 버튼이 클릭될 때는 user.id 값을 앞으로 props로 받아올 onRemove 함수의 파라미터로 넣어서 호출해주어야 한다.
  • 여기서 onRemove "id가 x인 객체를 삭제해라" 라는 역할을 가지고 있다.
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;

15. 배열 항목 수정하기

  • 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,
    });
  • 이제 onToggle 이라는 함수 구현하기
    배열의 불변성을 유지하면서 배열을 업데이트 할 때에도 map 함수를 사용할 수 있다.
  • id 값을 비교해서 id가 다르다면 그대로 두고, 같다면 active 값을 반전시키도록 구현하면 된다.

핵심 추가
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장이 생각보다 시간이 오래 걸리네...ㅎㅎ
화이팅~!!

profile
초록색 귤이 노랑색으로 익어가듯, 실력이 익어가기 위해 노력하는 개발자 lahee입니다. 프론트엔드 개발자를 목표로 성장하고 있습니다.

0개의 댓글