본문 바로가기

React

20210610 React08 : Additional Hooks(useReducer, useMemo, useCallback, useRef), React-router Hooks(useHistory, useParams), Custom Hooks & HOC (useWindowWith, useHasMounted, withHasMounted)

React 08



Custom Hooks


customHooks : useWindowWidth()


  • 브라우저 창 가로가 변경 되었을 때 변경된 숫자를 받아오는 hook
  • state를 활용해서 재사용 할 수 있는 hook을 만들어 냄 (따로 관리 함)
  • window.innerWidth -> 현재 브라우저의 가로 값을 가져옴
  • resize event -> resize 함수 실행 (state에 변경된 width값을 지정) -> state 값이 변경되어 return 값이 width를 rerender 함
// useWindowWidth.js
import { useEffect, useState } from "react";

export default function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const resize = () => {
      setWidth(window.innerWidth);
    };
    window.addEventListener("resize", resize);
    return () => {
      window.removeEventListener("resize", resize);
    };
  }, []);

  return width;
}
import logo from "./logo.svg";
import "./App.css";
import useWindowWidth from "./hooks/useWindowWidth";

function App() {
  const width = useWindowWidth();

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        {/* width 표시 */}
        {width}
      </header>
    </div>
  );
}

export default App;



customHooks : useHasMounted()


  • mouted 되었을 때를 알려주는 훅
import { useEffect, useState } from "react";

export default function useHasMounted() {
  const [hasMounted, setHasMounted] = useState(false);
  useEffect(() => {
    setHasMounted(true);
  }, []);
  return hasMounted;
}
import logo from "./logo.svg";
import "./App.css";
import useHasMounted from "./hooks/useHasMounted";

function App() {
  // useHasMouted 훅을 통해서 state값을 가져옴
  const hasMountedFromHooks = useHasMounted();
  console.log(hasMountedFromHooks);
  // true -> false
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
      </header>
    </div>
  );
}
export default App;



HOC : withHasMounted()

  • withHasMounted 에 인자로 컴포넌트를 넣었을 때 HasMounted라는 props가 들어간 새로운 컴포넌트를 만들어 줌
import React from "react";

// 인자로 Component가 들어감
export default function withHasMounted(Component) {
  // 새로운 class Component를 만듦
  class NewComponent extends React.Component {
    // hasMounted state를 가지고 있음
    state = {
      hasMounted: false,
    };

    // 새로운 컴포넌트가 랜더 되는 경우 기존의 Component에 state를 props로 넣음
    render() {
      const { hasMounted } = this.state;
      return <Component {...this.props} hasMounted={hasMounted} />;
    }
    // 처음 render된 후 에 해당 state를 true로 바꿈 -> hasMounted props를 바꾸고 Rerender 함
    componentDidMount() {
      this.setState({ hasMounted: true });
    }
  }
  // 새로운 컴포넌트를 디버깅하는 경우 쉽게 알아보기 위해서 displayName을 설정
  NewComponent.displayName = `withHasMounted(${Component.name}})`;

  // 최종 적으로 NewComponent를 return
  return NewComponent;
}
import logo from "./logo.svg";
import "./App.css";

import withHasMounted from "./hocs/withHasMounted";

function App({ hasMounted }) {
  console.log(hasMounted);
  // 새로운 컴포넌트가 처음 render 됨 : false -> render 되고 난후 : ture
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        {width}
      </header>
    </div>
  );
}
// hoc을 사용해서 App은 인자로 하고 hasMounted porps를 가지게 되는 새로운 컴포넌트로 출력
export default withHasMounted(App);



Additional Hooks

  • basic, custom을 제외하고 기본적으로 react에서 제공하고 있는 훅이 있음
  • useReducer, useCallback, useMemo, useRef

useReducer

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

  • const [state, dispatch] = useReducer(reducer, { count: 0 });
    • state : 값을 저장할 state 이름
    • reducer : state와 action을 인자로 받아 state 값을 어떻게 변경할 것인지에 대한 로직 함수
    • dispatch : reducer 함수를 실행 시킬 조건에 들어갈 값을 관리하는 함수(버튼?)
      • action: 들어갈 값을 가지고 있는 객체
      • type : action안에 들어있는 필수 프로퍼티
    • {count: 0} : state 가 저장할 기본값 (초기값)
import { useReducer } from "react";

// reducer -> state를 변경하는 로직이 담겨 있는 함수
const reducer = (state, action) => {
  if (action.type === "PLUS") {
    // dispath에서 보내오는 값을 통해서 로직을 결정함
    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>Hooks : You clicked {state.count} times</p>
      <button onClick={click}>Click Me! : Hook</button>
    </div>
  );

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



useMemo

  • 다른 state, props의 변화에 의해 Rerender 되는 것을 관리하기 위함
  • useMemo(callback, dependency)
  • 함수안에 callback 함수와, dependency를 넣어서 사용함
    • callback의 경우 최초에는 무조건 실행됨
    • dependency에 해당 state가 없으면 최초 실행만하고,
    • dependency에 해당 state가 있으면 최초 실행 + 해당 state가 변할때 만 실행 됨
import { useMemo, useState } 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: "kim", age: 24 },
    { name: "Park", age: 45 },
  ]);

  // value state만 변경되었는데 JSX부분을 다시 render 함으로써 count를 또 부르게 됨
  // const count = () => {
  //   return sum(persons);
  // }

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

  return (
    <div>
      <input value={value} onChange={change} />
      <p>{count}</p>
      <button onClick={click}>click</button>
    </div>
  );
  function change(e) {
    setValue(e.target.value);
  }
}



useCallback

  • useCallback(callback, dependency)
  • dependency에 있는 state만 값을 update(최신값 유지)해서 callback을 실행함
  • dependency에 state가 없으면, 최초 값만 유지해서 callback을 실행함
  • 최적화에 도움됨
import { useCallback, useMemo, useState } from "react";

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: "kim", age: 24 },
    { name: "Park", age: 45 },
  ]);

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

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

  return (
    <div>
      <input value={value} onChange={change} />
      <p>{count}</p>
      <button onClick={click}>click</button>
    </div>
  );
  function change(e) {
    setValue(e.target.value);
  }
}



useRef (vs createRef)

  • 특정 요소의 ref prop에 붙이면, 해당 element에 접근할 수 있게 해줌
  • useRef 함수의 return에 current에 접근하면 요소에 접근 가능함
  • useRef vs createRef
    • createRef : render 되면 reference 를 항상 다시 생성해서 render 될때 넣어줌
      • createRef -> null -> null -> null
    • useRef : render되어도 항상 reference를 계속 유지 함
      • useRef -> undefined -> input -> input
import { createRef, useRef, useState } from "react";

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

  console.log(input1Ref.current, input2Ref.current);
  // input1Ref(createRef) -> null -> null -> null
  // input2Ref(useRef) -> undefined -> input -> input

  return (
    <div>
      <input value={value} onChange={change} />
      <input ref={input1Ref} />
      <input ref={input2Ref} />
    </div>
  );

  function change(e) {
    setValue(e.target.value);
  }
}
  • 공통적으로 useMemo, useRef, useCallback 모두 render사이에 어떤 상태를 유지하는 특성을 가지고 있다.



React Router Hooks


  • react-router-dom 에서 제공하는 router 관련 hooks

기존 history 접근 방식 (다음에 갈 페이지)


  • withRouter HOC로 감싸서 props까지 연결해서 받아서, history에 접근 가능했음
import { withRouter } from "react-router-dom";

export default withRouter(function LoginButton(props) {
  function Login() {
    setTimeout(() => {
      props.history.push("/");
    }, 1000);
  }
  return <button onClick={Login}>로그인 하기</button>;
});


useHistory 방식

  • props를 받아오지 않고 곧바로 useHistory를 통해서 해당 페이지의 history에 접근 가능함
import { useHistory } from "react-router-dom";

export default function LoginButton() {
  const history = useHistory();
  function Login() {
    setTimeout(() => {
      history.push("/");
    }, 1000);
  }
  return <button onClick={Login}>로그인 하기</button>;
}



기존 params 접근 방식 (id)


  • dynamic router를 사용하는 경우, route 에서 props를 받아서 해당 페이지에서 props를 통해서 route 주소에 적힌 params 부분으로 id에 접근 가능했음
export default function Profile(props) {
  const id = props.match.params.id;
  return (
    <div>
      <h2>Profile 페이지 입니다.</h2>
      {id && <p>id 는 {id} 입니다.</p>}
    </div>
  );
}


useParams


  • useParams를 사용함으로서 props를 받아올 필요 없이, 바로 parmas에 접근 가능함
import { useParams } from "react-router-dom";

export default function Profile() {
  const params = useParams();
  const id = params.id;
  return (
    <div>
      <h2>Profile 페이지 입니다.</h2>
      {id && <p>id 는 {id} 입니다.</p>}
    </div>
  );
}