6. React (Hook)

2021. 8. 7. 03:39WEB/React

내가 생각하는 핵심 키워드

: useState, useEffect, Callback function

 

추가 학습이 필요한 키워드

: life cycle (생명주기), Callback function

 

 

Hook함수형 컴포넌트에서 클래스형 컴포넌트의 기능을 사용할 수 있게 해주는 기능이다

클래스형 컴포넌트의 React state, lifecycle features연동(hook into)될 수 있도록 도와준다

 

Hook을 사용하는 이유는 기존 React의 여러 문제점 때문

- 컴포넌트 사이에서 상태와 관련된 로직의 재사용이 어려운 점

- 복잡한 컴포넌트들은 이해하기가 어려운 점(가독성, 로직 등)

- 클래스는 코드의 재사용성과 구성을 어렵게 만든 것 뿐만 아니라, this의 원리를 알아야만 했다

 

위 문제점들에 의해 Hook이 등장하게 되었지만, 클래스형 컴포넌트가 사라지게 된 것은 아니다

(하위 호환성을 중요시하기 때문에 앞으로도 클래스에 대한 업데이트를 염두해 둔다고 한다)

 

 

Hook의 규칙

1. 최상위(at the Top Level), Hook 호출

중첩된 함수 내, 반복문, 조건문과 같은 곳에서 Hook을 호출하면 안된다

렌더링 시 항상 동일한 순서로 호출이 되어야 하기 때문에 지키지 않을 시 버그 발생

 

useState, useEffect가 여러 번 호출이 되어도 Hook의 상태를 유지할 수 있도록 관리해야 한다

최상위에서 호출하는 규칙만 지킨다면 항상 동일한 순서의 올바른 렌더링을 수행한다

 

2. React 함수 내에서만 Hook 호출

일반적인 자바스크립트 함수에서 호출하는 것은 안된다, 아래의 두 곳에서만 호출할 수 있다

- React 함수 컴포넌트(최상위 함수만)

- Custom Hook에서 Hook 호출

 

 

규칙을 지키는 이유

위 코드는 사용자가 만약 폼을 초기화하여 조건을 거짓으로 만든다면 Hook은 동작하지 않는다

렌더링 간 Hook을 건너뛰기 때문에 호출 순서에 영향을 주며, 변경된 정보를 Hook은 알지 못한다

 

특정 state가 어떤 useState( )에서 호출되었는지 알 수 있는 이유는 Hook의 호출 순서에 의존하기 때문

즉, 모든 렌더링에서 Hook의 호출 순서는 같기 때문에 올바르게 동작할 수 있는 것이다

 

하지만 위와 같이 건너뛰는 경우가 생긴다면 Hook을 호출하는 순서가 달라지게 된다

이에 따라 순서가 하나씩 밀리면서 버그를 발생시키기 때문에, 반드시 최상위 컴포넌트에 위치해야 한다

 

만약 위와 같이 조건적으로 side effect를 실행하고 싶다면, 조건 자체를 Hook 내부에 넣으면 된다

 

 

Hook의 장점

- 컴포넌트의 함수가 많아질 때 클래스 구성 요소로 리팩토링할 필요가 없다

  함수 컴포넌트에서 클래스 컴포넌트로 변경하려면 요소의 복잡도에 따라 리팩토링 필요

 

- UI에서 로직을 더 쉽게 분리하여 두 가지 모두 재사용 가능

  Hook 밑 UI를 사용하면 코드를 재사용하기 위한 로직을 쉽게 구성할 수 있다

  코드의 재사용은 전체적으로 작성해야 할 코드의 양을 줄어들게 한다

  (코드가 줄어들면 가독성 또한 상승)

 

- 기존의 코드를 다시 작성할 필요 없이 일부의 컴포넌트들 안에서 Hook을 사용할 수 있다

  클래스 컴포넌트와도 호환이 문제없이 잘 되기 때문

 

- Hook을 사용하면 컴포넌트로부터 상태 관련 로직 추상화가 가능하다

  컴포넌트별 독립적 테스트와 재사용이 가능, 계층 변화 없이 상태 관련 로직의 재사용을 돕는다

 


State Hook

Hook의 등장 이전에는 상태 관리를 하기 위해서는 클래스 기반 React 컴포넌트를 작성해야 했다

사용자 입력과 같은 간단한 상태 관리조차 클래스 기반 컴포넌트로 작성하는 것은 불편사항이였다

또한, 함수 기반 컴포넌트에 비해 복잡하고 오류가 발생하기 쉽기 때문에 유지 보수가 힘들었다

 

하지만 State Hook의 useState( )를 사용하면 이와 같은 문제를 쉽게 해결할 수 있다

클래스 기반 컴포넌트를 함수 기반으로 작성하면 상태 관리 코드 또한 간결해진다

 

useState( ) 함수는 배열 [state, setState]을 리턴한다

const [ 상태 값 저장 변수, 상태 값 갱신 함수 ] = useState(상태 초깃값)

첫 번째 원소는 상태 값을 저장할 변수, 두 번째 원소는 상태 값을 갱신할 때 사용할 함수이다

useState( ) 함수 자체에는 인자로 사용될 초기값을 설정하여 넘길 수 있다

 

여기서 첫 번째 원소 State읽기 전용으로 변수 안 데이터에 직접적인 정이 일어나면 안된다

예로 편의를 위해 변수를 물건을 담는 상자라고 가정,

React는 물건을 담아두는 상자 내부의 변화는 감지하지 못한다

내부의 변화를 감지하기 위해서는 상태 값을 갱신해주는 함수setState값을 변경해주어야 한다

오로지 setState의 변화로 state에 변경점이 적용되었을 때, React는 이를 감지하고 재렌더링을 수행한다

 

useState는 컴포넌트 내 동적인 데이터를 관리해주는 hook(State Hook)으로,

위에 말한 인자의 초기값 설정은 useState가 호출되는 순간 최초 1회를 의미한다

이후 몇 번의 재렌더링이 반복되던 초기값 설정은 무시되며, 컴포넌트의 재호출이 발생되는 순간 1회 변경된다

 

 

State 데이터 변경 방법

- setState 함수에 직접 값을 전달하여 변경된 데이터를 state에 전달

- 현재 값을 매개변수로 받는 함수를 직접 state에 전달

  (함수에서 return되는 반환 값이 state에 반영되는 것)

 


Effect Hook

Effect Hook은 함수형 컴포넌트에서 side effects를 수행할 수 있게 해준다

데이터를 가져오거나, 읽거나, DOM을 직접 조작하는 등의 모든 동작을 side effects라고 한다

즉, 함수의 외부에서 로컬의 상태 값을 변경하는 것이다

하지만 다른 컴포넌트에 영향을 줄 수 있기 때문에 렌더링 과정에서는 구현할 수 없는 작업이다

 

useEffect( ) 이러한 작업을 함수형 컴포넌트 내에서 수행할 수 있게 해준다

 

예를 들어 클래스 컴포넌트에서는 생명주기 메소드의 로직이 분리되어 있기 때문에,

변화되는 데이터의 렌더링, 그리고 State가 업데이트 될 때에 렌더링과 같이 두 번의 동작 수행

(생명주기 기능 사용 시 componentDidMount( ), componentDidUpdate( ) 메소드를 사용)

 

하지만 useEffect( )를 이용하면 쉽게 구현할 수 있다는 장점이 있다

useEffect() => { }

위와 같이 함수의 형태로 사용할 수 있으며, API를 불러오거나 데이터를 가져올 때 사용할 수 있다

 

렌더링 되는 모든 요소마다 원하는 작업을 수행할 수 있어 코드를 중복할 필요 또한 없어졌다

(가독성 ↑, 복잡성 ↓)

 

const App = () => {
  useEffect(EffectCallback, [deps])
}

EffectCallback은 컴포넌트가 최초로 렌더링 될 때, 지정한 state나 Props가 변경될 때 실행된다

지정한 state나 Props는 Deps로 넘겨준다

 

Deps : 변경을 감지할 변수들의 집합(배열)

EffectCallback (Callback function): Deps에 지정된 변수가 변경될 때 실행할 함수

 

 

[Effect Hook 예시]

함수 컴포넌트 내에서 side effect를 수행하고 있음을 확인할 수 있다

컴포넌트가 최초로 렌더링될 때, 지정한 State나 Props가 변경될 때마다 EffectCallback함수가 호출 된다

 

useEffect(() => {
console.log(`버튼을 ${count}회 클릭했습니다`)
  }, [count])

console.log가 EffectCallback 함수, [count] 가 Deps에 해당된다

Deps인 count의 변경이 일어날 때 마다 console.log가 실행되는 것

 

<button onClick={() => setCount(count +1)}>
Click
</button>

Click 버튼을 클릭할 때 마다 count는 1씩 증가하고, 해당 변경점을 setCount에 할당

count는 setCount에 의해 변경된 값을 넘겨받고,

useEffect는 Deps(state)의 변경을 감지하고 EffectCallback을 실행시키는 것이다

 

이후 React는 재렌더링을 수행하고 변경된 값이 화면에 연속해서 출력되게 된다

 

 

[Effect Hook 예시 2]

const [isCreated, setIsCreated] = useState(false);

useState로 isCreated의 초깃값을 false로 할당

 

<button onClick={() => {
  setIsCreated((current) => {
  return !current;
})

버튼을 클릭하면 setIsCreated의 현재값을 매개변수로 넘겨주게 된다

이때, !current의 !  not연산자로 current의 현재값이 true면 false로, false면 true로 변경한다

(useState로 state의 초기값을 false로 설정했기 때문에 클릭 시 true로 변경) 

 

{isCreated && <Greeting />}

자바스크립트 문법으로 && 연산자 앞의 값이 true면 && 뒤의 값을 실행한다는 의미

위에서 버튼을 클릭해서 state의 값을 true로 바꿔주었기 때문에 <Greeting /> 컴포넌트를 호출

 

useEffect로 이벤트가 발생했을 때 console.log가 출력될 수 있게 Callback 함수를 사용

버튼을 클릭하면 state의 값이 true가 되며 생성되었다는 log와 함께 안녕하세요가 화면에 출력

다시 한번 클릭하면 소멸 log와 함께 안녕하세요가 사라지게 된다

이유는 state가 false로 바뀌면서 Greeting을 호출하지 않기 때문에 출력문구가 사라진다

 

위 과정에서 Deps는 빈 배열로 어떤 값도 전달받거나 변경되지 않지만 Callback 함수는 잘 호출된다

State나 Props를 전달받거나 값의 변경이 일어나지 않아도, 컴포넌트의 생성/소멸 단계에서 호출된다

(<Greeting /> 컴포넌트가 호출되며 생성되고, 소멸되는 과정이 있기 때문에 Callback은 잘 동작한다)

 

위와 같이 동작한다면 코드가 정상적으로 잘 작성된 것이다 (좌측의 문구가 console.log)

 

 

 

'WEB > React' 카테고리의 다른 글

8. React (Form)  (0) 2021.08.09
7. React (Hook 2)  (0) 2021.08.08
5. React (Event)  (0) 2021.08.06
4. React (State)  (0) 2021.08.04
3. React (Props)  (0) 2021.08.03