본문 바로가기

WINK-(Web & App)/React.js 스터디

[2024 React.js 스터디] 정호용 #5주차 "React.JS 활용하기"

반응형

리액트 기본 지식을 이용하여 다양한 기능들을 살펴보자

10장. useRef로 특정 DOM 선택하기

JavaScript 사용 시, 우리가 특정 DOM을 선택해야 하는 상황에서는

getElementById나 querySelector 같은 DOM Selector 함수를 이용해서 DOM을 선택한다.

(여러분 아직 기억하고 계시죠...?)

 

물론, 리액트에도 DOM을 선택해야 하는 상황이 온다.

또한,

Video.js 나 JWPlayer 같은 HTML5 Video관련 라이브러리

D3, chart.js같은 그래프 관련 라이브러리

등의 외부 라이브러리를 쓸 때에도 특정 DOM에 적용하기 때문에 결국에는 DOM을 선택해야 하는 상황이 오기 마련이다.

그떄는, 리액트에서는 ref라는 걸 쓴다.

 

함수형 컴포넌트에서는 ref를 쓸 때는 useRef라는 Hook 함수를 쓴다.

클래스형 컴포넌트에서는 콜백함수를 쓰거나, React.createRef라는 함수를 쓴다. (나중에 다룰 예정)

 

 

useRef()를 이용해 Ref객체를 만들고, 선택을 원하는 DOM에 ref값으로 선택한다.

Ref객체의 .current 값은 우리가 원하는 DOM을 가리키게 됨.

즉, onReset함수에서 input에 포커스를 하는 focus() DOM API를 호출해준 것이다.

 

포커스가 이름으로 가버린다!

즉, useRef()로 nameInput이라는 DOM을 선택해주고, 

ref={}로 위치를 잡아준 다음,

onReset 함수에 nameInput.current.focus()로 초기화 시 할 동작을 정해준다.

 

11장.  배열 렌더링하기

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'
  }
];

위 코드를 컴포넌트로 렌더링 하는 방법은?

1. 비효율적이지만, 그대로 코드를 쓴다.

중복 코드를 일일이 넣는 것은 그렇게 좋지 않다! 컴포넌트를 재사용하도록 새로 만들자.

한 파일에 여러 컴포넌트를 선언해도 된다.

이렇게 재사용 코드를 한번만 작성하면 된다.

하지만, 이렇게 하면 동적인 배열을 렌더링하지 못한다. 이럴 땐 map()을 써주면 된다.

map()이란?

배열 안의 각 원소를 변환할 때 사용되며, 이 과정에서 새로운 배열이 만들어 진다.

const array = [1,2,3,4,5,6,7,8]

예를 들어, 이 배열이 있다 치면, 배열 안의 모든 숫자를 제곱해서 새로운 배열을 만들어보자.

const array = [1,2,3,4,5,6,7,8];
const square = n => n*n;
const squared = array.map(square);
console.log(squared);

간단하게 네 줄로 만들 수 있다.

즉, array이라는 배열 안에 있는 원소들을 map()을 이용해서 반환할 건데, square라는 변화를 주는 함수 (이하 변화함수)를 전달해서 반환하는 것이다.

이를 리액트에 적용해서 일반적인 배열을 리액트 엘리먼트로 이뤄진 배열로 반환해 보자.

 

예제 코드에는 개행이 되어 있어 복잡해 보이지만, 한 줄로 바꾸면 이렇게 된다.

<div>
	{users.map(user =>(<User user={user}/>))}
</div>

즉, users라는 배열을 user=>(<User user = {user})라는 변화함수를 이용해서 새로운 배열을 반환한다.

user라는 배열을 User user = {user}라는 꼴로 바꿔 전달한다.

기존 코드는 일일이 출력을 했으므로 인덱스가 들어가지만,

map함수는 배열 내 원소들을 하나하나 반환해 주므로 인덱스를 쓸 필요가 없다. 여러 번 반복해서 쓸 필요도 없다. 이미 map을 한번 썼기 때문에.

 

물론 결과는 같지만, 개발자 도구를 열었을 때 위와 같은 경고메시지를 보게 된다.

리스트 속의 모든 child는 고유한 key 라는 props를 가져야 한다. 

즉, 각 key값은 각 원소들마다 갖고 있는 고유값으로 설정해야 한다. 여기서는 id가 고유값이라고 한다.

(이유를 생각해 봤는데, id는 각 배열 원소마다 하나씩 고유하게 주어지는데, username과 email은 중복될 수도 있어서이지 않을까 추측해본다.)

아무튼 다시 고쳐보자.

 

아까 그 코드에 key={user.id}만 추가해 주면 된다. 이제 경고 메시지가 없어졌다.

만약 id와 같이 고유한 값이 없다면 map()함수 사용 시 설정하는 콜백함수의 두 번째 파라미터 index를 key로 쓰면 된다고 한다.

만약 key설정을 하지 않으면 기본적으로 배열의 index 값을 key로 가져오고, 경고 메시지가 뜨게 된다.

이렇게 메시지가 뜨는 이유는, 각 고유 원소에 key값이 있어야만 배열 업데이트 시 효율적으로 렌더링이 된다고 한다.

 


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

const array = ['a','b','c','d'];

배열이 이렇게 있다고 치자.

array.map(item => <div>{item}</div>);

그리고 이렇게 렌더링 한다고 가정해 보자.

위 배열에서 b,c 사이에 z를 삽입하게 된다면,

<div> c </div> 사이에 새 div태그를 삽입하는 것이 아니라,

기존의 c가 z로 바뀌고, d는 c로 바뀌고, 맨 마지막에 d가 새로 삽입된다.

즉, b 뒤에 z가 들어오고, c부터는 이전 위치에 있었던 값이 한 칸씩 뒤로 바뀌어 들어온다. 그리고 맨 뒤에 밀려나서 없어진 d가 새로 삽입된다.

 

제거할 때도 마찬가지이다.

a를 제거한다면, b가 a자리로 가고, c가 b자리, d가 c자리로 오고, 마지막 d는 제거가 된다.

(상당히 비효율적이다.)

 

암튼 key가 있다면 말이 달라진다.

[
  {
    id: 0,
    text: 'a'
  },
  {
    id: 1,
    text: 'b'
  },
  {
    id: 2,
    text: 'c'
  },
  {
    id: 3,
    text: 'd'
  }
];

위와 같이 key로 쓸 수 있는 id라는 고유한 값이 있고,

array.map(item => <div key={item.id}>{item.text}</div>);

위와 같이 렌더링한다면,

배열 업데이트 (삭제,삽입) 시 수정되지 않는 기존 값은 두고, 원하는 곳에 내용을 삽입 및 삭제한다.

그렇기에 key값이 있어야 렌더링 시 효율적으로 수행할 수 있다.

 

12장.  useRef로 컴포넌트 안의 변수 만들기

ref와 useRef는 10장에서 이미 배웠다.

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

useRef로 관리하는 변수의 값이 바뀐다고 해서 컴포넌트가 리렌더링 되진 않지만, 일반적인 리액트 컴포넌트와 차이점이 존재한다.

리액트 컴포넌트 useRef로 관리하고 있는 변수
상태를 바꾸는 함수 호출
-> 렌더링
-> 업데이트 된 상태 조회
설정 후 바로 조회 가능

 

이 변수를 이용해 다음과 같은 값을 관리할 수 있다.

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

솔직히 설명만 보고서는 바로 이해가 안 됐다. 코드를 직접 수정해 보면서 이해해야 할 것 같다.

이번 강의에서는 useRef를 이용해 배열에 새 항목 추가 시 사용할 고유 id를 관리해보자.

 

useRef 사용 전에 바꿀 것이 하나 있다.

지금까지는 UserList 컴포넌트 내부에서 배열 선언/사용을 했었는데, App에서 배열을 선언하고, UserList에게 props로 전달을 해보자.

App.js UserList.js
UserList 컴포넌트 속에 있던 배열 App 에 선언 UserList 속에 있던 배열 제거

이제 useRef()를 이용해 nextId라는 변수를 만들자.

 

useRef 사용 시 파리미터를 넣어주면 (예: 4) 이 값이 .current의 기본값이 된다.

값 수정/조회 시에는 .current의 값을 수정/조회하면 된다.

**** import {useRef} from "react"; 를 꼭 써주자!! useState때 처럼 말이다. ****

 

13장.  배열에 항목 추가하기

이번에는 배열에 항목을 추가해 보고자 한다. CreateUser.js를 만들어 보자.

이 컴포넌트의 상태관리를 CreateUser에서 하지 않고

부모 컴포넌트인 App에서 하게 하고,

input의 값 및 이벤트로 등록할 함수들을 props로 넘겨받아 사용한다.

31번 째 줄의 ; 는 오타입니다..ㅎㅎㅎ 수정했습니다.

여기서 중요한 기본 지식이 여러개가 나왔다. 한번 더 보고 넘어가야겠다.

1. 두 개 이상의 태그를 쓸 때는 하나의 태그로 감싸줘야 한다.

2. 불필요한 div 사용을 피하기 위해 fragment를 쓴다. <> </>

3. 태그 간 내용이 들어가지 않을 땐 self-closing을 해준다. <CreateUser />

(메모... 또 메모...)

 

이제 CreateUser 컴포넌트에게 필요한 props를 App에서 준비해주자.

실행을 해 보면...

등록 버튼 눌렀을 때 입력 내용의 초기화는 잘 되는데 이메일이 입력이 안된다.........whyrano

일단 계속 해 보자. users 또한 useState를 이용해 관리를 해 준다.

그리고 배열에 변화를 줘 보자. 배열 역시 객체와 마찬가지로 불변성을 지켜줘야 한다. 그렇기에 push, splice, sort등을 쓰면 안 된다.

쓰고 싶다면, 기존의 배열을 복사하고 나서 써야 한다.

 

불변성을 지키면서 배열에 새 항목을 추가하는 방법은 2가지가 있다. 

1. Spread

앞서 배웠듯이 Spread 연산자는 기존 배열 및 객체를 복사해준다.

 

2. Concat

concat함수는 기존 배열의 수정 없이 새로운 원소가 추가된 새로운 배열을 만들어 준다.

 

그리고 오류를 찾았다. 난 바보였다.

email이 오타가 나서 eamil로 된 것이다.........

그렇다. 자주 발생하는 오류이니 이번 한 번만 넘어가도록 하자.

14장.  배열에 항목 제거하기

이제 삭제 버튼을 만들어 주자.

즉, 각 계정에 대해 onClick() 즉, 클릭 시 user.id를 삭제하는 함수를 실행하는 방식이다.

id가 ___인 객체를 삭제해라 라는 뜻이다.

물론, 배열 삭제에 관한 함수를 구현을 아직 안 해줬기 때문에 오류가 뜬다. 버튼만 만든 거다.

(앞으로 제일 많이 볼 화면)

이제 onRemove를 구현해 보자.

삭제 역시 불변성을 지켜야 한다. filter함수를 써 보자. 이 함수는 배열에서 특정 조건이 만족되는 원소들만 추출하여 새로운 변수를 만들어 준다. 약간 map이랑 비슷한데, 조건이 들어가는 map함수 같다.

 

암튼 원소를 새로 추출해서 새로운 변수를 만들어야 하므로, 삭제 대상이 아닌 것들만 모아야 한다. 그렇기 때문에 user.id !== id를 써줘야 한다.

 

삭제가 된다 ~! 근데 에러 메시지에는 onRemove가 함수가 아니라는데, 같은 파일 안에 구현을 하지 않아서 그런거 같기도 하고... 잘 모르겠다. 

5주차 질문 페이지에 질문 남겼습니다..!

15장.  배열에 항목 수정하기

이제는 배열 원소를 수정해보자. 이번에는 User컴포넌트에 계정명을 클릭했을 때 색상을 바뀌도록 해보자.

active라는 새로운 속성 추가 onToggle이라는 새로운 속성 추가

물론 onToggle을 아직 선언하지 않았기 때문에 오류가 뜬다. onToggle는 정의 안되어 있어!

이제 정의해보자

 

뭔지 모르겠다... 한 줄로 만들고 뜯어보자

 

const onToggle = id => {
        setUsers(users.map(user =>user.id === id ? {...user, active : !user.active} : user));
};

setUsers 안에 파라미터로 배열을 넣어줄 건데,

users라는 배열을 map을 써서 새로운 배열로 반환해 줄 거다.

변화함수로는 user 객체에서 id값과 onToggle에 들어온 id와 비교해서,

같으면 기존 user 객체를 스프레드 연산자를 써서 복사하고, active 상태만 기존 user객체의 값을 반전시킨다.

다르면 user 객체를 그냥 반환한다.

 

즉, 기존 user객체의 idonClick했을 때의 user객체 속의 id를 비교해서

같으면 색을 바꿔야 하므로 객체 복사active 값만 반전시키고

다르면 그냥 그대로 복사한다. (색상 변화 없음)

 

 가끔 코드가 이해가 안 간다면 뜯어보자.

 

이제 onToggle에 전달해 줄 id를 받아오는 코드를 작성해야 한다.

클릭시 onToggle 함수에 id값 전달 onToggle 속성 추가

 

이제 실행해 보면..

 

 

잘 된다!

16장.  useEffect를 사용하여 마운트/언마운트/업데이트 시 할 작업 설정하기

이번에는 useEffect를 사용해서

컴포넌트가 마운트(처음 나타났을) 때,

컴포넌트가 언마운트(사라질) 때,

업데이트(특정 props 가 바뀔) 때

처리방법에 대해 다룬다.

 

useEffect의

첫 번째 파라미터는 함수, 

두 번째 파라미터는 의존값이 들어있는 배열 (deps)를 넣어 준다. 이를 비우게 되면 컴포넌트가 처음 나타날 때만(마운트) useEffect에 등록된 함수가 호출된다.

 

useEffect는 함수를 반환할 수도 있는데, 이를 cleanup 함수라 부른다. 이 함수는 useEffect에 대한 뒷정리 역할이다.

deps가 비어 있는 경우에 cleanup함수가 호출된다.

 

처음 화면 user를 추가했을 때의 화면

마운트 시에 하는 작업들

  • props로 받은 값을 컴포넌트의 로컬 상태()로 설정
  • 외부 API 요청 (REST API 등..)
  • 라이브러리 사용(D3, Video.js등...)
  • setInterval을 통한 반복작업 혹은 setTimeout을 통한 작업 예약

 

언마운트 시에 하는 작업들

  • setInterval, setTimeout을 사용하여 등록한 작업들 clear하기 (clearInterval, clearTimeout)
  • 라이브러리 인스턴스 제거

이번에는 deps에 특정 값을 넣어보자. 특정 값을 넣게되면 컴포넌트의 마운트 시, 업데이트 시에 호출이 된다.

deps 안에 특정 값이 있다면 언마운트 시에 호출도 되고, 값의 변동 직전에도 호출이 된다.

 

마운트 시, "user값이 설정됨" 을 출력하고, user객체를 출력한다.

cleanup함수 호출 시, "user 가 바뀌기 전..." 을 출력하고, user객체를 출력한다.

변동 전 변동 후

최초에 있던 모든 유저들의 객체를 출력했다.

username이 hoyong인 유저를 삭제하니, 삭제된 객체가 출력됐다.

 

이번에는 deps 파라미터를 생략해 보자.

deps 생략 시 컴포넌트가 리렌더링 될 때 마다 호출이 된다.

그냥 객체만 출력된다.

최초 화면 유저 추가시

abc라는 유저 추가를 할 때마다 객체가 출력된다. 계속 리렌더링을 하기 때문인 듯 하다.

 

근데 왜 두 번씩 출력이 될까??

https://velog.io/@gene028/%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-3-UseEffect%EB%8A%94-%EC%99%9C-%EB%91%90%EB%B2%88-%EC%8B%A4%ED%96%89%EB%90%98%EB%8A%94%EA%B1%B8%EA%B9%8C

 

[개발일지 #3] UseEffect는 왜 두번 실행되는걸까?

팀원들과 카카오 로그인 구현 중.. 인가코드를 자꾸 두 번 보내게 되는데, 그 원인을 몰라서 한참을 해메다가 UseEffect 때문에 이러한 문제가 발생되는 것을 알 수 있었다.왜 날...괴롭히는거임아

velog.io

위 글을 참고해 보면,

 

index.js 안에 Strict Mode라는 자손을 검사하는 도구에 의해 렌더링이 한 번 되고,

최초 실행될 때 렌더링이 한 번 더 되어서

총 두 번씩 출력하는 것이 아닐까 생각한다!

 

이번 주차 끝!

반응형