20210610 React08 : Custom Hooks & HOC (useWindowWith, useHasMounted, withHasMounted), Additional Hooks(useReducer, useMemo, useCallback, useRef), React-router Hooks(useHistory, useParams)
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을 실행함
 - 최적화에 도움됨
 - 이화랑 블로그 : useMemo&useCallback
 
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
 
 
 - createRef : render 되면 reference 를 항상 다시 생성해서 render 될때 넣어줌
        
 
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>
  );
}