본문 바로가기
source-code/React

리액트를 다루는 기술 _ 8장

by mattew4483 2021. 3. 10.
728x90
반응형

머리가 핑핑.

7장 컴포넌트의 라이프사이클 메서드는 클래스형 컴포넌트에만 사용된다!

따라서 패스... 그만큼 8장을 더 주의 깊게 봐야지.


8장. Hooks

ko.reactjs.org/docs/hooks-intro.html

 

Hook의 개요 – React

A JavaScript library for building user interfaces

ko.reactjs.org

Hooks는 React v16.8에 새로 도입된 기능으로, 기존의 함수형 컴포넌트에서 할 수 없었던 다양한 작업을 돕는다!

앞서 맛을 봤던 useState, 렌더링 직후 작업을 설정하는 useEffect 등의 기능을 제공하며,

공식 문서에서도 클래스형 컴포넌트보다 함수형 컴포넌트 & Hooks 사용을 권장하는 중!

 

실습 흐름은...

React 내장 Hooks 사용 → 커스텀 Hooks 만들기

 

그리고 그 전에, create react-app을 통해 새로운 프로젝트를 만들어 작업을 진행하자.

 

8-1) useState

useState는 가장 기본적인 Hooks이며, 함수형 컴포넌트에서도 가변적인 상태를 지닐 수 있게 해 준다.

이를 사용해 숫자 카운터를 만들어 볼 예정!

 

Counter.js

이제는 익숙하지만... 또또 복습!

 

useState 함수의 파라미터에는 상태의 기본값을 넣어준다! (현재 카운터의 기본값을 0으로 설정)

 

useState 함수가 호출되면 배열을 반환하는데,

배열의 첫 번째 원소는 상태 값, 두 번째 원소를 상태를 결정하는 함수!

파라미터를 넣어 두 번째 함수를 호출하면 전달받은 파라미터로 값이 바뀌고, 컴포넌트가 리렌더링된다.

 

http://localhost:3000/

App.js 에 Counter 컴포넌트를 렌더링! 잘 작동되는구만.

 

하나의 useState 함수는 하나의 상태 값만 관리할 수 있다.

따라서 컴포넌트에서 관리해야 할 상태가 여러 개라면 useState를 여러 번 사용하면 된다.

 

이번에는 info.js 파일을 생성!

Info.js

name / nickname의 상태 관리는 setName / setNickName 함수를 통해 이뤄지고,

이들 함수는 버튼의 onChange 이벤트 발생 시 작동한다!

따라서 버튼 속에 내용을 입력하면 즉각적으로 반영되는 걸 볼 수 있다. 와우!

 

8-2) useEffect

새로운 녀석 등장!

useEffect는 React 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hooks다.

무슨 소리인지 감이 안 잡히니 코드를 쳐보자!

 

Info.js

useEffect를 추가!

이를 통해 Info 컴포넌트가 렌더링될 때마다 console에 지정한 값이 찍힐 테다.

 

와우! 그런데 너무 정신없지 않나? (게다가 이러면 아무런 쓸모도 없다)

 

1. 마운트 될 때만 실행

마운트...라고 얘기하니 너무 어렵게 느껴진다.

그냥 컴포넌트가 처음 렌더링 될 때만 실행되고, 업데이트될 때는 실행되지 않으려면?

Info.js

useEffect를 이렇게 수정해주자!

 

그럼 이렇게 페이지가 처음(정확히 말하면 Info 컴포넌트가 처음) 렌더링 됐을 때만 작동한다!

 

2. 특정 값이 업데이트될 때만 실행

useEffect 사용 시 특정 값이 변경될 때만 호출하고 싶다면?

Info.js

쨘. useEffect의 두 번째 파라미터로 전달되는 배열 안에, 검사하고 싶은 값을 넣어주면 된다.

배열 안에는 useState를 통해 관리하고 있는 상태를 넣어줘도 되고, props로 전달받은 값을 넣어줘도 된다!

 

 

name 값이 업데이트될 때만 실행되는 모습.

 

3. 뒷정리하기

즉 useEffect는 기본적으로 렌더링 되고 난 직후마다 실행되며,

두 번째 파라미터 배열에 무엇을 넣는지에 따라 실행되는 조건이 달라진다!

 

그렇다면 컴포넌트가 렌더링 되기 전이나 업데이트되기 직전에 어떠한 작업을 수행하고 싶다면?

→ useEffect에서 뒷정리(cleanup) 함수를 반환해줘야 한다!

 

Info.js

후움... 이제 App 컴포넌트에서 Info 컴포넌트의 가시성을 제어해보자!

 

App.js

버튼에 visible이라는 상태를 적용시켜줬다.

초기값을 false로 설정, onClick 이벤트 발생 시 true가 되도록!

? 연산자를 통해 visible의 값에 따라 버튼 문구도 달라지도록!

http://localhost:3000/

쨔잔. 

컴포넌트가 나타날 때 effect가, 사라질 때 cleanup이 console창에 나타난다.

 

또한 렌더링 될 때마다 뒷정리 함수가 나타나는 모습!

뒷정리 함수가 호출될 때는 업데이트되기 직전의 값을 보여준다.

 

오직 언마운트 될 때만 뒷정리 함수를 호출하고 싶다면

→ useEffect 함수의 두 번째 파라미터에 비어있는 배열을 넣으면 된단다(why..?)

 

8-3) useReducer

useReducer은 useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트할 때 사용!

 

reducer는 또 무엇인가 하니...

현재 상태, 업데이트를 위해 필요한 정보를 담은 액션 값을 전달받아 새로운 상태를 반환하는 함수란다!

리듀서 함수에서 새로운 상태를 만들 때는 반드시 불변성을 지켜줘야 한단다!

무슨 소린가!

책에서는 이후(17장) reducer 개념을 제대로 짚고 간단다.

 

1. 카운터 구현하기

다시 Counter 컴포넌트로 돌아가 보자!

Counter.js

우와. useState대신 useReducer를 사용했다.

하나하나 뜯어보자면...

 

우선 useReducer함수!

얘는 첫 번째 파라미터에는 리듀서 함수를, 두 번째 파라미터에는 해당 리듀서의 기본값을 넣어준다.

 

useReducer Hooks를 사용하면 state값과 dispatch 함수를 받아 오며

state는 현재 가리키고 있는 상태, dispatch는 액션을 발생시키는 함수 를 의미!

즉 dispatch(action)과 같은 형태로 함수 안에 파라미터로 액션 값을 넘겨주면 리듀서 함수가 호출되는 구조다.

 

새로운 상태를 반환하는 리듀서 함수! 이 녀석을 통해 현재 상태의 업데이트가 이뤄진다.

즉 액션 값이 넘어오면 reducer함수가 호출되면서, swich문을 돌며 action.type에 따라 다른 작업을 수행하는 것!

 

각 버튼을 누를 때마다 dispatch 함수에 액션 값을 넘겨주는 모습! (이때 해당 action의 type도 정해줬다)

 

useReducer에서 사용하는 액션 객체는 반드시 type을 지닐 필요는 없다.

또한 객체가 아닌, 문자열이나 숫자여도 상관없음!

 

즉 전체적인 작동 흐름은...

useReducer사용(state와 dispatch 생성) button onClick 이벤트 발생 → dispatch함수에 action 넘겨줌

→ 리듀서 함수(reducer) 호출 → 현재 상태(state)를 업데이트 한 새로운 상태 반환

 

useReducer 통해 컴포넌트 업데이트 로직은 컴포넌트 밖으로 빼낼 수 있다는 장점!

 

2. 인풋 상태 관리하기

useReducer를 사용해 Info 컴포넌트의 input 상태를 관리해보자!

기존에는 input이 여러 개여서 useState를 여러 번 사용했었는데...

 

Info.js

useReducer에서 액션은 어떤 값이라도 사용 가능!

따라서 여기서는 이벤트 객체가 지닌 e.target 값 자체를 액션 값으로 사용했다.

 

8-4) useMemo

useMemo를 사용하면 함수형 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있다.

리스트에 숫자를 추가하면 추가된 숫자들의 평균을 보여 주는 Average 컴포넌트를 만들어보자!

 

Average.js

점점점 길어지는 코...드...

 

Average.js

numbers를 넘겨주면 이들의 평균을 구하는 getAverage 함수!

 

Average.js

우선 useState를 통해 숫자들이 들어있는 list와, 이용자가 입력한 숫자를 설정!

 

onChange는 input에 숫자를 입력할 때마다 해당 값을 setNumber 함수로 전달!

onInsert는 입력된 숫자를 추가한 새로운 list를 setList 함수에 전달!

(전달한 후 다시 setNumber는 초기화)

 

Average.js

input에는 onChange를, 버튼에는 onInsert를 연결!

평균값은 getAverage 컴포넌트를 통해!

http://localhost:3000/

작동은 잘 되는데...

숫자를 등록할 때뿐만 아니라 input 내용이 바뀔 때도 getAverage 함수가 작동되고 만다!

 

이는 굉장한 리소스 낭비인데...

따라서 렌더링 시 특정 값이 바뀌었을 때만 연산을 실행하고, 바뀌지 않았다면 이전 연산 결과를 사용하도록!

→ useMemo Hooks를 이용하면 가능하다!

 

Average.js

avg란 함수를 추가하고, 평균값 부분을 변경해줬다!

이를 통해 list 배열의 내용이 바뀔 때만 getAverage 함수가 호출된다! 어째서...? 흑흑

 

어째서냐함은..!

그냥 useMemo Hooks가 그런 용도다!

참고 : https://www.daleseo.com/react-hooks-use-memo/

아하...! 즉 우리가 짠 코드에서 결괏값이 기존의 {list}와 달라질 때마다(평균값이 달라질 때마다),

getAverage(list) 함수가 작동해 새로운 결괏값을 생성해주는 것!

 

8-5) useCallback

useCallback은 이러한 useMemo와 유사한 함수다.

useMemo가 어떠한 값을 저장해 해당 값과 달라졌을 때만 함수를 생성하는 용도라면,

useCallback은 함수 자체를 저장해 값이 달라졌을 때만 해당 함수를 생성하는 용도!

 

Average.js

onChange 함수와 onInsert 함수를 수정해줬다.

 

우선 onChange 함수에서 useCallback을 통해 컴포넌트가 처음 렌더링 될 때만 실행하도록!(빈 리스트를 넘겨줬다)

onInsert 함수에서도 useCallback을 통해 number 또는 list가 바뀌었을 때만 실행하도록 만들었다.

당최 어떻게?

 

useCallback의 첫 번째 파라미터에는 생성하고 싶은 함수를, 두 번째 파라미터에는 배열을 넣어준다.

이때 배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해야 하는지 명시!

 

만약 비어 있는 배열을 넣게 되면 컴포넌트가 렌더링 될 때 단 한 번만 함수가 생성되며(useEffect가 생각난다)

현재 onInsert 함수처럼 number과 list를 넣게 되면 input 내용이 바뀌거나 새로운 항목이 추가될 때마다 함수가 생성!

 

단 함수 내부에서 상태 값에 의존해야 할 때는 그 값을 반드시 두 번째 파라미터 안에 포함시켜 줘야 한다.

즉 onChange의 경우 기존 값을 조회하지 않고, 바로 설정만 하기 때문에 배열이 비어 있어도 상관 X

그러나 onInsert는 기존의 number과 list를 조회해 nextList를 생성하기 때문에 배열 안에 이 둘을 반드시 넣어줘야 한다.

 

8-6) useRef

useRef란 녀석도 등장. 딱 봐도 ref와 관련 있을 것 같다.

얘는 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있도록 돕는 Hooks!

Average.js

useRef를 이용해 ref를 설정!

이로써 useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리키게 된다.

 

8-7) 커스텀 Hooks 만들기

여러 컴포넌트에서 비슷한 기능을 공유할 경우, 직접 이를 Hooks로 작성해 재사용할 수도 있다.

다시 Info 컴포넌트로 돌아와 useReducer 함수를 useInputs라는 Hooks로 분리해보자.

(useReducer가 뭐였더라? → 여러 input의 상태 업데이트 관리를 위해 사용했었다!)

 

우선 useInputs.js란 파일을 생성!

useInputs.js

그냥 코드가 똑같이 이사한...느낌이다...?

 

이를 다시 Info 컴포넌트에서 사용해보자!

Info.js

기존에는 이렇게 작성됐던 코드였는데...

 

Info.js

reducer 부분이 이렇게 간략해졌다!

728x90
반응형