[fastcampus] Ch7. Hooks (2)

productuidev·2022년 3월 15일
0

React Study

목록 보기
15/52
post-thumbnail

패스트캠퍼스 한 번에 끝내는 프론트엔드 개발(Online) 강의 정리


2. Custom Hooks

앞에서 배운 useStateuseEffect를 활용하여, 나만의 Hooks 만들기

Custom Hook

  • 컴포넌트 생성 시 반복되는 로직이 자주 발생함 (비슷한 코드 반복)
  • 그런 상황에서 자신만의 Hook을 만들면 반복되는 로직을 함수로 뽑아내어 쉽게 재사용 가능
  • 컴포넌트 로직 정의 -> Custom Hooks 생성 -> 간편하게 호출 (네이밍, 가독성 고려)
  • 생성 후 그 안 에서 useState, useEffect, useInput, useFetch, useReducer, useCallback 등 기존 내장 Hooks 사용해 원하는 기능을 구현하고, 컴포넌트에서 사용하고 싶은 값들을 반환해주면 됨
  • 기본 컴포넌트를 만들 때와 유사하고, JSX가 아닌 배열 혹은 오브젝트를 return 한다.
    (즉, return [value]; 혹은 return {value}; 한다. 배열의 수와 오브젝트의 수는 필요에 따라 추가 가능)
  • 사용 방법은 useState와 거의 유사 : 초기값을 파라미터로 넘겨주는 게 일반적이고 Custom Hooks이 배열을 return 했느냐 혹은 객체를 return 했느냐에 따라 사용할 컴포넌트에 선언해주면 됨
  • 컴포넌트는 하나 이상의 상태 값이 변경되거나 props의 값이 변경되면 리렌더링된다.

Custom Hook의 이름은 무조건 use로 시작해야한다

import { useState } from 'react';

const useFetch = (initialUrl: string) => {
  const [url, setUrl] = useState(initialUrl);

  return [url, setUrl];
};

export default useFetch;

useWindowWidth()

  • 브라우저 창 가로가 변경되었을 때 변경된 숫자를 받아오는 hook
  • state를 활용해서 재사용할 수 있는 hook 만듬 (따로 관리하는)

src/hooks/withWindowWidth.js

import { useState, useEffect } from 'react';

export default function useWindowWith() {
  const [width, setWidth] = React.useState(window.innerWidth); // 현재 브라우저 가로 값 가져옴

  useEffect( () => {
    window.addEventListner("resize", () => { // resize event
      setWidth(window.innerWidth);
    });

    window.addEventListner("resize", resize); // 실행

    return () => {
      window.removeEventListner("resize", resize); // state갑 변경 되어 return값이 width를 다시 렌더
    }
  }, []); // dependency

  return width;
}

src/App.js

import Example1 from "./components/Example1";
import Example2 from "./components/Example2";
import Example3 from "./components/Example3";
import Example4 from "./components/Example4";
import Example5 from "./components/Example5";
import useWindowWith "./hooks/useWindowWith"; // 추가

function App() {
  const width = useWindowWith(); // 공통 로직으로 사용할 수 있도록 만든 Custom Hook

  return (
    <div className="App">
      <header className="App-header">
        <Example1 />
        <Example2 />
        <Example3 />
        <Example4 />
        <Example5 />
        { /* width 표시 */ }
        {width} // 브라우저 크기를 조정할 때 사이즈 값이 들어옴
      </header>
    </div>
  );
}

useHasMounted vs withHasMounted

로직을 따로 분리하여 사용하고, Hook 과 HOC의 차이점을 알기

  • Hooks : useHasMounted (Mounted됐을 때 알려줌)
  • HOC : withHasMounted (인자로 컴포넌트를 넣었을 때 HasMounted라는 props가 들어간 새로운 컴포넌트 생성)

./src/hocs/withHasMounted.jsx

import React from 'react';

export default function withHasMounted(Component) { // 인자로 Component
  class NewCompnent extends React.Component { // 새로운 컴포넌트 생성
    state = { hasMounted: false, };

    render() {
      const { hasMounted } = this.state;
      return <Component {...this.props} hasMounted={hasMounted} />; // hsMounted를 Props로 내려줌
    }

    // 처음 렌더된 후 해당 stat를 true로 바꿈 (hasMounted props를 바꾸고 다시 렌더)
    componentDidMount() {
      this.setState({ hasMounted:true });
    }
  }

  // 새로운 컴포넌트를 디버깅할 경우 쉽게 알아보기 위해 displayName 설정
  NewComponent.displayName = `withHasMounted(${Component.name})`;

  // 최종적으로 새 컴포넌트를 리턴
  return NewComponent;

}

./src/hooks/useHasMounted.jsx

import { useState } from "react"

export default function useHasMounted() {
  const [hasMounted] = useState(false);

  useEffect( () => {} // 상태를 변경하는 로직
    setHasMounted(true);
  }, []); // dependency

  return hasMounted()
}

src/App.js

import Example1 from "./components/Example1";
import Example2 from "./components/Example2";
import Example3 from "./components/Example3";
import Example4 from "./components/Example4";
import Example5 from "./components/Example5";
import useWindowWith "./hooks/useWindowWith";
import withHasMounted from "./hocs/withHasMounted"; // 추가
import useHasMounted from "./hooks/useHasMounted"; // 추가

function App( {hasMounted} ) {

  // useHasMounted를 통해서 state값을 가져옴
  const width = useWindowWith();
  const hasMountedFromHooks = useHasMounted();

  console.log(hasMounted); // 최초 false, 다시 렌더되면서 true
  console.log(hasMounted, hasMountedFromHooks); // false, false true, true

  return (
    <div className="App">
      <header className="App-header">
        <Example1 />
        <Example2 />
        <Example3 />
        <Example4 />
        <Example5 />
        {width}
      </header>
    </div>
  );
}

export default withHasMounted(App);
// HOC를 사용해 App은 인자로 하고 hasMounted props를 가지게 되는 새로운 컴포넌트로 출력

3. Additional Hooks

  • Basic, Custom 외에 기본적으로 리액트에서 제공하고 있음
  • useReducer, useCallback, useMemo, useRef

useReducer

  • reducer : 즉시 실행 순수 함수
  • useState를 변경하는 useState의 확장판
  • 다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우 사용
  • 다음 state가 이전 state에 의존적인 경우
  • Redux를 안다면 쉽게 사용 가능

src/components/Example6.jsx

import React from "react";

// reducer -> state를 변경하는 로직이 담겨있는 함수
// 인자로 받는 state는 이전 상태 값
const reducer = (state, action) => {
  if (action.type === 'PLUS'){

    // dispatch에서 보내오는 값을 통해서 로직 결정
    return {
      count: state.count + 1,
    };
  }
  return state; // 변경없는 state
}

// dispatch -> action 객체를 넣어서 실행
// action -> 객체이고 필수 프로퍼티로 type을 가짐

export default function Example6() {
  const [state, dispatch ] = useReducer(reducer, {count: 0});

  return (
    <div>
      <p>You clocked {count} times</p>
      <button onClick={click}>Click me</button>
    </div>
  );

  function click() {
    // 요소에서 event를 받으면, dispatch 함수에 특정 type 값을 가지는 action 객체를 넣어 실행
    dispatch({type: "PLUS" });
  }
}

src/App.js

import Example6 from "./components/Example6"; // useReducer 추가
import useWindowWith "./hooks/useWindowWith";
import withHasMounted from "./hocs/withHasMounted";
import useHasMounted from "./hooks/useHasMounted";

function App( {hasMounted} ) {
  const width = useWindowWith();
  const hasMountedFromHooks = useHasMounted();

  console.log(hasMounted);
  console.log(hasMounted, hasMountedFromHooks);

  return (
    <div className="App">
      <header className="App-header">
        <Example 6 /> // Click 시 count
        {width}
      </header>
    </div>
  );
}

useMemo

  • 최적화할 때 주로 사용
  • 다른 state, props의 변화에 의해 Rerender 되는 것을 관리하기 위함
  • 함수안에 callback 함수와, dependency를 넣어서 사용함 (의존성을 통해 특정 값에 대한 변화 줄 수 있음)
  • callback의 경우 최초에는 무조건 실행됨
  • dependency에 해당 state가 없으면 최초 실행만하고,
    dependency에 해당 state가 있으면 최초 실행 + 해당 state가 변할때 만 실행 됨

useCallback

  • useCallback(callback, dependency)
  • dependency에 있는 state만 값을 update(최신값 유지)해서 callback을 실행함
  • dependency에 state가 없으면, 최초 값만 유지해서 callback을 실행함
  • 최적화에 도움됨

src/components/Example7.jsx

import { useState, useMemo, useCallback } React from "react";

// sum 함수 persons 배열을 넣으면 , 해당 사람들의 age를 합산해서 return 함
function sum(persons) {
  console.log("sum...");
  return persons.map(person => person.age).reduce((l, r) => l + r, 0);
}

export default function Example7() {
  const [value, setValue] = useState('');
  const [persons] = useState([

    {name: 'Mark', age: 39},
    {name: 'Hanna', age: 28},

  ]);

  // const count = sum(persons);
  // persons가 변했을 때만 count를 실행함
  const count = useMemo( () => {
    return sum(persons);
  }, [persons]); // dependency


  // 이전의 최초 state 값만 유지하고 있음(value가 update되어도 여기선 update가 안됨)
  const click = useCallback( () => {
    console.log(value);
  }, []); // 최초에만 생성되도록 제한

  const click = useCallback( () => {
    console.log(value);
  }, [value]); // state의 최신 값을 활용해 callback 실행

  return(
    <div>
      <input value={} onChange={} />
      <p>Hello</p>
      <button onClick={click}>click</button>
    </div>
  );

  fucntion change(e) {
    setValue(e.target.value);
  }
}

src/App.js

import Example6 from "./components/Example6";
import Example7 from "./components/Example7"; // 추가
import useWindowWith "./hooks/useWindowWith";
import withHasMounted from "./hocs/withHasMounted";
import useHasMounted from "./hooks/useHasMounted";

function App( {hasMounted} ) {
  const width = useWindowWith();
  const hasMountedFromHooks = useHasMounted();

  console.log(hasMounted);
  console.log(hasMounted, hasMountedFromHooks);

  return (
    <div className="App">
      <header className="App-header">
        <Example 7 /> // input
        {width}
      </header>
    </div>
  );
}

useRef

  • 특정 요소의 ref prop에 붙이면, 해당 element에 접근할 수 있게 해줌
  • useRef 함수의 return에 current에 접근하면 요소에 접근 가능함

useRef vs createRef

  • createRef : render 되면 reference 를 항상 다시 생성해서 render 될때 넣어줌
    (createRef -> null -> null -> null)
  • useRef : render되어도 항상 reference를 계속 유지 함
    (useRef -> undefined -> input -> input)

src/components/Example8.jsx

import { createRef, useRef, useState } React from "react";

export default function Example8() {
  const [value, setValue] = useState('');
  const input1Ref = createRef(); // 렌더되면 reference를 항상 다시 생성해서 렌더될 때 넣어줌
  const input2Ref = useRef(); // 렌더되어도 항상 reference를 계속 유지

  console.log(input1Ref.current, input2Ref.current);

  return(
    <div>
      <input value={} onChange={} /> // Controlled Component
      <input ref={input1Ref} /> // Uncontrolled Component1
      <input ref={input2Ref} /> // Uncontrolled Component2
    </div>
  );

  fucntion change(e) {
    setValue(e.target.value);
  }
}
  • useMemo, useRef, useCallback 모두 render 사이에 어떤 상태를 유지하는 특성

src/App.js

import Example6 from "./components/Example6";
import Example7 from "./components/Example7";
import Example8 from "./components/Example8"; // 추가
import useWindowWith "./hooks/useWindowWith";
import withHasMounted from "./hocs/withHasMounted";
import useHasMounted from "./hooks/useHasMounted";

function App( {hasMounted} ) {
  const width = useWindowWith();
  const hasMountedFromHooks = useHasMounted();

  console.log(hasMounted);
  console.log(hasMounted, hasMountedFromHooks);

  return (
    <div className="App">
      <header className="App-header">
        <Example 8 /> // input
        {width}
      </header>
    </div>
  );
}

참조자료

https://ko.reactjs.org/docs/hooks-custom.html
https://goforit.tistory.com/131
https://react.vlpt.us/basic/21-custom-hook.html
https://velog.io/@kysung95/%EC%A7%A4%EB%A7%89%EA%B8%80-Custom-Hooks%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90
https://ko-de-dev-green.tistory.com/68
https://ko-de-dev-green.tistory.com/71

이어서.

profile
필요한 내용을 공부하고 저장합니다.

0개의 댓글