tech.junha.kr

[React Hook 시리즈 3]: 10가지 Hook들 (useMemo, useRef 등)

1. useMemo()

useMemo(() => value, deps), useMemo(fn, deps)

const memoizedValue = useMemo(()=>computedExpensiveValue(a,b),[a,b])
  • 랜더링 될 때마다 값을 계속 계산하지 않고, 이전에 계산된 값을 메모이제이션하여 불필요한 연산을 줄인다.

  • 새롭게 계산하는 기준은 두번째 인자, dependency에 적는다.

import { useMemo, useState } from 'react';

const App = () => {
  console.log('리랜더링');
  const [isOn, setIsOn] = useState(false);
  const [users, setUsers] = useState([
    { active: true },
    { active: true },
    { active: false },
  ]);

  const countActiveUser = () => {
    console.log('사용자 수 세는중...');
    return users.filter((user) => user.active).length;
  };

  const count = useMemo(countActiveUser, [users]);

  return (
    <div>
      <h1>{`활성 사용자 수: ${count}`}</h1>
      <button onClick={() => setIsOn((isOn) => !isOn)}>리랜더링 하기</button>
      <button
        onClick={() => {
          setUsers(Array.from(users).concat({ active: true }));
        }}
      >
        사용자 추가하기
      </button>
    </div>
  );
};

2. useCallback()

useMemo(() => fn, deps) 와 useCallback(fn, deps) 는 같다.

  • 컴포넌트가 리렌더링될 때 불필요하게 함수가 다시 생성되는 것을 방지한다.

  • 함수 생성 자체가 오래 걸리는 경우, 함수를 자식 컴포넌트에 props로 넘겨줄 때 useCallback()을 사용하는 것이 좋다.

import { useState, useCallback } from 'react';

const App = () => {
  const [firstName, setFirstName] = useState('junha');
  const [lastName, setLastName] = useState('kim');
  // 이름과 성이 바뀔 때마다 풀네임을 메모이제이션

  const getfullName = useCallback(() => {
    return `${firstName} ${lastName}`;
  }, [firstName, lastName]);

  return (
    <>
      {getfullName()}
      <button onClick={() => setFirstName('준하')}>버튼</button>
    </>
  );
};
export default App;

3. useRef()

  • DOM Element에 접근 할 때 사용하며, **getElementById()**와 같은 기능을 한다.

  • DOM Element의 ref 속성을 이용한다.

import { useRef } from 'react';

const App = () => {
  const inputRef = useRef(null);
  const focusHandler = () => {
    inputRef.current.focus();
  };
  const clickHandler = () => {
    inputRef.current.value += '값이 바꼈어용';
  };
  return (
    <div>
      <input ref={inputRef} type='text' />
      <button onClick={clickHandler}>값 바꾸기</button>
      <button onClick={focusHandler}>input으로 포커스</button>
    </div>
  );
};
export default App;

useRef의 리턴 값

  • useRef의 리턴값에 current 속성을 이용하여 DOM을 컨트롤 할 수 있다.

  • 이 값은 컴포넌트 생애 주기 내에서 유지한다.

  • useRef 객체가 변경되어도 컴포넌트가 재렌더링되지 않는다.

4. useTitle

: title 태그를 업데이트하는 기능

import { useEffect, useState } from "react";

const useTitle = (initialTitle) => {
  const [title, setTitle] = useState(initialTitle);
  const updateTitle = () => {
    const htmlTitle = document.querySelector("title");
    htmlTitle.innerText = title;
  };
  useEffect(updateTitle, [title]);
  return setTitle;
};

const App = () => {
  const titleUpdater = useTitle("Loading...");
  setTimeout(() => titleUpdater("home"), 3000);
  return <div>Hi</div>;
};

export default App;

5. useClick

: 해당 element를 클릭 시 주어진 callBack함수 출력하기

: useRef() 사용

import { useEffect, useState, useRef } from "react";

const useClick = (onClick) => {
  const element = useRef();
  useEffect(() => {
    if (element.current) {
      element.current.addEventListener("click", onClick);
    }
    return () => {
      element.current.removeEventListener("click", onClick);
    };
  });
  return element;
};

const App = () => {
  const sayHello = () => console.log("그걸 누르누");
  const title = useClick(sayHello);
  return (
    <div className="APP">
      <h1 ref={title}>Click me</h1>
    </div>
  );
};

export default App;

6. useConfirm

: 메세지를 확인시켜주고, 응답에 따라 콜백함수를 호출해주는 기능
: 사용자가 버튼클릭 작업 전 메세지 보여주고 싶을 때

const useConfirm = (message = "", callback, rejection) => {
  if (!callback || typeof callback !== "function") {
    return;
  }

  const confirmAction = () => {
    if (confirm(message)) {
      callback();
    } else {
      rejection();
    }
  };

  return confirmAction;
};

const App = () => {
  const deleteWorld = () => console.log("Deleting the World");
  const abort = () => console.log("Aborted");
  const confirmDelete = useConfirm(
    "Are you sure to delete the World?",
    deleteWorld,
    abort
  );
  return (
    <div className="APP">
      <button onClick={confirmDelete}>Delete the World</button>
    </div>
  );
};

export default App;

7. usePreventLeave

: 버튼에 따라 이벤트리스너를 추가시켜주는 기능

const usePreventLeave = () => {
  const listener = (event) => {
    event.preventDefault();
    event.returnValue = "";
  };
  const enablePrevent = () => window.addEventListener("beforeunload", listener);
  const disablePrevent = () =>
    window.removeEventListener("beforeunload", listener);
  return { enablePrevent, disablePrevent };
};

const App = () => {
  const { enablePrevent, disablePrevent } = usePreventLeave();
  return (
    <div className="APP">
      <button onClick={enablePrevent}>Protect</button>
      <button onClick={disablePrevent}>Unprotect</button>
    </div>
  );
};

export default App;

beforeunload

: allows you to execute a function before the window gets closed, or before the person leaves your page

8. useBeforeLeave

: 마우스가 페이지를 벗어나면 실행시키는 기능

Cleanup Function

useEffect에서 return한 함수는 componentWillUnmount 때 호출된다.

이벤트리스너가 호출되거나 업데이트될 때마다 이벤트리스너가 add되는걸 원하는게 아니다. 고로 dependency는 []로 didmount될때한번만 호출하고, willunmount될 때 클린업함수로 리턴 함수로 써주자

import { useEffect } from "react";

const useBeforeLeave = (onBefore) => {
  if (typeof onBefore !== "function") {
    return;
  }
  const handle = (event) => {
    const { clientY } = event;
    if (clientY <= 0) {
      onBefore();
    }
  };
  useEffect(() => {
    document.addEventListener("mouseleave", handle);
    return () => document.removeEventListener("mouseleave", handle);
  }, []);
};

const App = () => {
  const sayHello = () => console.log("떠나지마용");
  useBeforeLeave(sayHello);
  return (
    <div className="APP">
      <h1>Hello</h1>
    </div>
  );
};

export default App;

9. useFadein

component의 reference를 얻는 방법이 특이하다.

useXXX함수에 useRef()를 설정하고 다시 element return하는 방식으로 해당 element를 조작한다. 이 방법이 효율적인 이유는 뭘까? : 예를들어 fadein효과를 주기 위해서, const el = useFadeIn()을 썼다고 해보자. 그럼 fadein효과를 주고 싶은 태그에 ref={el}만 넣으면 바로 그 element를 조작할 수 있다. 하나 알아둬야할 것은 html요소를 조작하기 위해 js부분은 함수로 모듈화시키고, 적용할 때는 html태그에 미리 선언해둔 변수이름만 prop에 넣으면 된다는 것. 이 디자인패턴이 효율적으로 보이긴한다. 왜냐면 vanilia js에서 작동시킬 때는 정해진 규격이 없어서 산발적으로 써져있었기 때문이다.

import { useEffect, useRef } from "react";

const useFadeIn = (duration = "1") => {
  const element = useRef();
  useEffect(() => {
    if (element.current) {
      const { current } = element;
      current.style.transition = `opacity ${duration}s`;
      current.style.opacity = 1;
    }
  }, []);
  return { ref: element, style: { opacity: 0 } };
};

const App = () => {
  const fadeIn3s = useFadeIn(2);
  const fadeIn7s = useFadeIn(7);
  return (
    <div className="APP">
      <h1 {...fadeIn3s}>Hello</h1>
      <h5 {...fadeIn7s}>MR. JUNHA</h5>
    </div>
  );
};

export default App;

10. useNetwork

import { useEffect, useState } from "react";

const useNetwork = (onChange) => {
  const [status, setStatus] = useState(navigator.onLine);
  const handleChange = () => {
    setStatus(navigator.onLine);
    onChange(navigator.onLine);
  };
  useEffect(() => {
    window.addEventListener("online", handleChange);
    window.addEventListener("offline", handleChange);
    () => {
      window.removeEventListener("online", handleChange);
      window.removeEventListener("offline", handleChange);
    };
  }, []);
  return status;
};

const App = () => {
  const handleNetworkChange = (onLine) => {
    console.log(onLine ? "Online" : "Offline");
  };
  const onLine = useNetwork(handleNetworkChange);
  return (
    <div className="APP">
      <h1>{onLine ? "Online" : "Offline"}</h1>
    </div>
  );
};

export default App;

navigator.onLine

  • Tells you true or false if your website is online
    It's always good to think "maybe people wouldn't just do this" when you're making a library for other people

11. useNotification

import { useEffect, useState, useRef } from "react";

const useNotification = (title, options) => {
  if (!("Notification" in window)) return;
  const fireNotif = () => {
    if (Notification.permission !== "granted") {
      Notification.requestPermission().then((permission) => {
        if (permission === "granted") {
          new Notification(title, options);
        } else {
          return;
        }
      });
    } else {
      new Notification(title, options);
    }
  };
  return fireNotif;
};

const App = () => {
  const triggerNotif = useNotification("Enter Summoner", {
    body: "Explore the World that you dream"
  });
  return (
    <div className="APP">
      <button onClick={triggerNotif}>Hello</button>
    </div>
  );
};

export default App;

12. useAxios

**: about httpRequest
**

import React from "react";
import useAxios from "./useAxios";

const App = () => {
  const { loading, data, error, refetch } = useAxios({
    url: "https://yts.mx/api/v2/list_movies.json"
  });
  console.log(
    `Loading: ${loading}\nError:${error}\nData:${JSON.stringify(data)}`
  );

  return (
    <div>
      <h1>{data && data.status}</h1>
      <h2>{loading && "Loading"}</h2>
      <button onClick={refetch}>Refetch</button>
    </div>
  );
};
export default App;
import defaultAxios from "axios";
import { useState, useEffect } from "react";

const useAxios = (options, axiosInstance = defaultAxios) => {
  const [state, setState] = useState({
    loading: true,
    error: null,
    data: null
  });
  const [trigger, setTrigger] = useState(0);
  if (!options.url) {
    return;
  }
  const refetch = () => {
    setState({
      ...state,
      loading: true
    });
    setTrigger(Date.now());
  };
  useEffect(() => {
    axiosInstance(options)
      .then((data) => {
        setState({
          ...state,
          loading: false,
          data
        });
      })
      .catch((error) => {
        setState({
          ...state,
          loading: false,
          error
        });
      });
  }, [trigger]);
  return { ...state, refetch };
};

export default useAxios;

More about Hooks

More about

https://sangcho.tistory.com/entry/ReactHooks%EC%9D%98%EB%B9%99%EC%82%B0

React Hooks의 커다란 빙산