React Hooks 정리.

Keun·2022년 6월 24일
0

쓰긴쓰는데..

그렇다. 쓰긴쓰는데 뭔가 그냥 쓰는 기분이다.

성격상 100%는 아니더라도 80~90% 정도는 되어야 쓸때 아 이래서 쓰는구나를 체감하면서 쓰는데, 훅은...아직 나랑 덜 친한 기분이다. 아무 좀 뭔가 뿌듯함이 없다.
왜냐하면 잘 몰라서가 아닐까?

정리가 잘된 블로그를 통해 한번 정리하면서 왜 쓰는건지 정확히 이해하기로 했다.
그리고 공식문서를 확인하면서 정리하였다. 왜냐하면, 가끔 이상하게 해석해서 올린 글들을 확인했..ㅠ

나의 정리를 도와줄 블로그 출처:
https://defineall.tistory.com/900

React Hooks?

내가 느끼는 훅: 편안하게 해준다. 내가 코딩할때, 편하게 해준다. 그런데, 왜그런지 모르겠다. 훅쓰면 편하다. '왜'를 알아야한다는 것을 알았다.

Hook, 이름부터 왜 '훅'인지는 알수없으나, 리액트 16.8 버전에서 새로 추가되었다 (생각보다 젊은 기술이네).

  • 함수형 컴포넌트에서만 사용 가능하고 클래스에서 사용 불가능.
  • 내장 훅 (useEffect, useState, useContext 등등) 이 있고, 커스텀 훅 (직접 만들어서 쓸 수 있다) 이 있다. -> props, state, context, refs 그리고 lifecycle과 같은 개념에 기반을 두었다.
  • 클래스 컴포넌트 문제점을 적극적으로 해결하기 위해서 만들어 진 것이다 (?)

등장배경을 알아야한다.

이상하게 역사를 알아야 할 것 같다. 등장배경? 이 바로 그것이다.

(1) Class 컴포넌트 와 Function 컴포넌트

  • Class 컴포넌트: 지금 내가 사용하고 있는것은 함수형 컴포넌트. 훅이 나오기 전까지 class컴포넌트를 사용했었고, 많은 문제를 떠안고 있었다고 한다. 상태값(state) 에 접근, 생명주기 기능 (lifecycle features)을 사용할때 클래스형 컴포넌트 선언을 해줘야 했다.
    문제점: 컴포넌트간에 로직의 재사용이 불가능 -> 라이프사이클을 기반으로 하는 컴포넌트들과 상태값들. 그리고 써봤지만, 적응되면 편한데, 복잡한 코드를 짤 경우에, 에러가 발생할 경우 찾기가 힘들다. 뭔가 <>안에 뭔가 많다. 보기에도 안좋고 props타고타고 올라가고 해결할때 문제덩어리다. 그래서 내가 아는 사람들은 클래스형 컴포넌트 공부 안하고 바로 함수형부터 본다고한다.
    사실 리액트에서는 함수형컴포넌트 사용을 장려한다. 클래스형 없어진다(?)는 이야기를 듣긴했다.

아무튼 간단히 전문적으로 말하면 -> render props나 HOC(High order component, 고차 컴포넌트)와 같은 패턴으로 코드의 추적이 어렵다. 이렇게 계속 코드를 짤경우 wrapper hell을 겪게되고 (많은 레이어들로 둘러쌓인), 코드가 복잡해진다.
(내가 배우면서 사용했을때의 문제점으로 인하여 함수형으로 전환 되었다는 것을 알고나서 좀 신기했다.)

  • Function 컴포넌트: 대신 함수형 컴포넌트는 메모리상에서 한번만 호출되고 메모리상에서 사라진다고 한다. 그래서 예전같이 상태값접근과 라이프사이클 구현이 불가능하다고한다.
    위에서 언급한 클래스형 컴포넌트의 문제점을 보완하기 위해서 훅이 나왔고, 훅과 함께 함수형 컴포넌트로 전환하는 시대가 왔다.

클래스와 함수 코드 비교.

  1. 클래스형
import React, { Component } from 'react';

class Example extends Component {
  state = {
    count: 0,
  };

setCount(num) {
  this.setState({
    count: num,
  });
}
render() {
  const { count } = this.state;
  return (
    <div>
     <div>
      <p>You clicked {count} times</p>
      <button
       onClick={() => {
        this.setCount(count + 1);
     }}
    >
       Click Me!
     </button>
    </div>
   </div>
  );
 )
}

export default Example;
  1. 함수형
import React, { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
     <p>You clicked {count} times</p>
     <button onClick={() => setCount(count + 1)}>
       Click Me!
       </button>
      </div>
  );
}

export default Example;

클래스형은 보기만해도 숨이 턱턱 막힌다. this.이부분. 저것 보기만해도 싫다. 자바 할때부터 보기싫었다. 나만 그런가. 아무튼 함수형을 보면 뭔가 속이 뚫린다. 편하다. 굳이 저렇게 this.안해줘도 다 내장되어있으니까. render() -> return() 으로 바뀌긴 했지만, 뭔가 렌더 그대로 사용했으면 어떨까 라는 생각이 든다. 뭔가 더 뿌려준다는 느낌이 더 든다.

  1. 훅을 사용할 경우 (함수형)
import React, { useState } from 'react';

funtion App() {
  const [name, setName] = ('');
  const [age, setAge] = (35);
  const [coffee, setCoffee] = ('Latte');
}

export default App;

훅을 통하여 여러개의 state를 사용 하는 것도 가능한다.

HOOK 사용규칙.

  1. 같은 훅을 여러번 호출 가능하다.

  2. 최상위 에서만 훅을 호출해야 한다. 훅을 호출하는 순서는 항상 같아야 한다

  • 반복문, 조건문, 중첩된 함수 내에서 Hook을 사용하면 안된다.
    왜 훅의 호출 순서가 같아야 하는 걸까?
    리액트가 상태값을 구분할 수 있는 유일한 정보는 훅이 사용된 순서이기 때문이다. 리액트가 훅이 호출된 순서에 의존한다는 것이다.
    예시로 반복문 안에서 훅을 호출했을 때 반복문이 true라면 괜찮겠지만 값이 false라면 건너뛰게 된다. 이렇게 하면 실행순서가 바뀔 수 있어 오류를 일으킨다 조건문 혹은 반복문을 사용하고 싶을때는 useEffect안에 넣어 사용하면 된다.
  1. 블록 스코프 안에서는 사용 불가능!
export default function App(){
  return {
    <div>
      // 불가능
      <div>{const [value, setvalue] = useState()}</div>
    </div>
  }
}
  1. 비동기 함수(async 키워드가 붙은 함수)는 콜백함수로 사용할 수 없다.
export default function App(){
  // useEffect Hook 내부에, 비동기 함수가 들어가므로 에러 발생
  useEffect(async () => {
    await Promise.resolve(1)
  }, [])
  
  return {
    <div>
      <div>Test</div>
    </div>
  }
}

React Hook 배우기.

내가 팀 프로젝트건, 혼자 VS code 켜놓고 코딩을 하던 제일 많이 쓰는 훅이 있다.
useState와 useEffect. 사실 정리를 하는 이유는, 어떤 훅들이 있는지 알려고 한거였는데, 처음부터 개념 잡으니까 확실히 도움이 된다.

기본 Hooks.

  1. useState

    state란 -> React에서 사용자의 반응에 따라, 화면을 바꿔주기(렌더링) 위해 사용되는 트리거역할을 하는 변수. React가 state를 감시하고, 바뀐 정보에 따른 화면을 표시해준다.
    ( state와 setState함수의 반환값을 비교 )

사용법.

import { useState } from "react";

// const [state, state변경함수] = useState(기본 state값);

const [isLoggedIn, setIsLoggedIn] = useState(false);

state를 변경하고싶다?

// 전에 만든 "isLoggedIn" state의 값을 true로 변경한다.

setIsLoggedIn(true);

// ** useState함수를 사용해 만든 "state 변경 함수"를 사용하여야 한다.
  1. useEffect / useLayoutEffect
    참고:

    https://medium.com/@jnso5072/react-useeffect-%EC%99%80-uselayouteffect-%EC%9D%98-%EC%B0%A8%EC%9D%B4%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C-e1a13adf1cd5#:~:text=%ED%99%94%EB%A9%B4%EC%9D%B4%20%EA%B9%9C%EB%B9%A1%EA%B1%B0%EB%A6%AC%EB%8A%94%20%EC%83%81%ED%99%A9,%EC%9D%B4%20%EA%B9%9C%EB%B9%A1%EA%B1%B0%EB%A0%A4%EC%A7%80%EA%B8%B0%20%EB%95%8C%EB%AC%B8%EC%97%90

  • Render: DOM Tree를 구성하기 위해 각 엘리먼트의 스타일 속성을 계산하는 과정.
  • Paint: 실제 스크린에 Layout을 표시하고 업데이트하는 과정.

useEffect -> 비동기방식 -> render and paint 이후 실행. Paint 된 이후에 실행되서 화면의 깜빡임을 보게된다.
useLayoutEffect -> 동기방식(끝날때까지 리액트가 기다려줌) -> render된 후에 useLayoutEffect 실행되고 -> paint 한다. 화면깜빡임을 볼 수 없다. -> 레이아웃까지 보는데 시간 오래걸리니까 useEffect 권장.

// React에 기본적으로 내장되어 있는 useState와, useEffect 불러오기

import { setState, useEffect } from "react";

...

function App() {
  const [data, changeData] = setState(false)
  
  // useEffect(실행할 함수, 트리거가 될 변수) 또는 useLayoutEffect(실행할 함수, 트리거가 될 변수) 사용하면된다. 
  
  useEffect(() => {
    if (data.me === null) {
      console.log("Data changed!")
    }
    
    return () => console.log("컴포넌트 파괴, 언마운트 됨")
  }, [data]);  
  
  // data변수가 바뀔때마다, react가 이를 감지해, 콘솔창에 "Data changed!" 출력
  
  return (
    <div>
      <button value="적용" onClick={changeData(!data)} />
    </div>
  )
}

export default App;
  1. useContext
  • React에서 부모와 자식 컴포넌트간에 props나 state 전달이 가능하다.
  • 중간에 컴포넌트들을 하나씩 거치지않고, 한번에 전달하려는 목적지로 전달 가능.
  • context 전용 파일을 만들어준다.

간단한 예시일뿐. 실제로 사용해보면 장난아니다. 그런데 편하긴하다.

// newContext.js

import { createContext } from "react"  // createContext 함수 불러오기

// context안에 homeText란 변수를 만들고, 공백("") 문자를 저장한다.
const newContext = createContext({
  homeText: "",
})
// App.js

import React from "react";
import { View } from "react-native";
import Home from "./Home"; // 자식 컴포넌트 불러오기

import { newContext } from "./newContext"; // context 불러오기


export default function App() {

  // context에 저장할 정보를 입력한다.
  const homeText = "is Worked!"

  // NewContext.Provider로 우리가 만든 context를 사용할 부분을 감싸준다.
  return (
        <newContext.Provider value={{ homeText }}>
          <View>
            <Home />
          </View>
        </newContext.Provider>
  );
}
// Home.js

import React from "react";
import { Text, View } from "react-native";

import { useContext } from "react";
import { newContext } from "../newContext";


export default function Home() {

  // useContext hook 사용해서, newContext에 저장된 정보 가져오기
  const { homeText } = useContext(newContext);
  
  // 불러온 정보 사용하기!!
  return (
    <View>
      <Text>{homeText}<Text>
    </View>
  );
}
  1. useMemo, useCallback

useMemo
Memoization : 과거에 계산한 값을 반복해서 사용할때, 그 값을 캐시에 저장하는 것

  • 재귀함수에 사용하면, 불필요한 반복 수행을 대폭 줄일 수 있다.
  • 의존성을 가지므로, 첫렌더링시에만 저장할 변수를 설정할 수 있다.
export default function App(){
  const data = useMemo(()=> "data",[]);
  // 데이터 변수는 의존성 배열 []에따라 선언된다. ( []사용시, 첫 렌더링 시에 1번만 선언 )
  return <></>
}

useCallback
의존성을 사용해, 첫렌더링시에만 실행할 함수를 설정할 수 있다.

export default function App(){
  const avatarPressed = useCallback(() => Alert.alert('avatar pressed.'), []);
  return <></>
}

써본적 있는데... 이제 뜻을 제대로 알다니...하아...진짜 왜그럴까..

  1. useRef
    HTML요소(태그)나 컴포넌트의 메모리주소를 포인팅해서, 객체(레퍼런스) 형식으로 관리할 수 있다. 그냥 특정 돔을 가르키기 위해서 사용하는 것이라고 보면 된다. 하지만, 재밌는 사실은, 이거는 쓰면 리렌더링이 안일어난다고 한다. 한번 딱쓰면 한번 끝이라고 한다.
export default App(){
  const viewRef = useRef(null)
  
  // viewRef 객체에 있는 메소드를 사용한다.
  function testFunc(){
    viewRef.current?.메소드()
  }
  
  /// viewRef요소에, 해당 메모리 주소 전달
  return <View ref={viewRef}>
    <Text>Test</Text>
  </View>
}

간단히 쓰여있긴한데... 더 찾아봐야겠다. 사용해본적은 있는데, 크게 인상적으로 막 주의깊게 본적은 없고 그냥 썼었다..

  1. forwardRef
    부모 컴포넌트에서, 자식 컴포넌트를 객체 형식으로 관리할 수 있다.
    이것은 마치 useRef와 forwardRef 간 무엇인가 교환하는 느낌? 이건 진짜 직접 써보고 입력해보면서 알아야할것같다.. 어려운 개념이었다.

참고:

https://www.daleseo.com/react-forward-ref/

먼저 자식 컴포넌트와 자식 컴포넌트로 이루어진 부모 컴포넌트가 있다고 가정하자.

// Player.jsx
import React, { useRef } from "react";
import Audio from "./Audio";
import Controls from "./Controls";

function Player() {
  const audioRef = useRef(null);

  return (
    <>
      <Audio ref={audioRef} />
      <Controls audio={audioRef} />
    </>
  );
}

export default Player;

useRef()를 사용하여 audoRef를 만들어서 Audio자식에 넘겨주고, Controls자식에는 audio prop으로 넘겨주고 있다.

Audio 자식이 ref prop을 Player 부모로부터 제대로 받으려면, forwardRef()함수를 사용하여야 한다. 아 이쯤되니까 무엇인가 이해가기 시작. 아 알겠다이제대충.
두번째 인자 ref로 넘어온다..

// Audio.jsx
import React, { forwardRef } from "react";
import music from "./music.mp3";

function Audio(prop, ref) {
  return (
    <figure>
      <figcaption>Eyes on You (Sting) - Network 415:</figcaption>
      <audio src={music} ref={ref}>
        Your browser does not support the
        <code>audio</code> element.
      </audio>
    </figure>
  );
}

export default forwardRef(Audio);

이 외에도, useImerativeHandle, useReducer, useDebugValue 가 있다.

핵심적으로 많이 쓰이는 것들만 정리를 했다. 나머지는 공식문서를 보면서, 구글링을 하면서 연습을 해봐야겠다. 깃헙에서 하나 만들어서 사용해보고 있는데, 내가 이상하게 좀 공포증(?) 같은것이 있나보다. 뭔가 처음에 익숙하지 않으니까, 두려움이 앞선다. 우선 내지르고 코드를 짜봐야 실력이 좋아진다는 것은 코딩하면서 항상 느낀다. 열심히 잘 해봐야지!!

0개의 댓글