개발자를 향해...

[실전형 리액트 Hooks] #2 USEEFFECT 본문

웹 자바스크립트 공부/ReactJS + node.js

[실전형 리액트 Hooks] #2 USEEFFECT

eugeneHwang1124 2021. 3. 21. 22:53
728x90
반응형

#2.0 

useeffectsms 를 사용해보자. 버튼을 누르면 수가 감소하거나 증가하는 코드를 작성해보자.

import React, { StrictMode, useEffect, useState } from "react";
import ReactDOM from "react-dom";

const App = () => {
  const sayHello = () => console.log("hello");
  useEffect(() => {
    sayHello();
  });

  const [number, setNumber] = useState(0);
  const [anumber, setAnumber] = useState(0);

  return (
    <div>
      <div>Hi</div>
      <button onClick={() => setNumber(number + 1)}>{number}</button>
      <button onClick={() => setAnumber(anumber - 1)}>{anumber}</button>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

콘솔창

버튼이 눌릴 때마다 콘솔창에 Hello가 출력되는 것을 볼 수 있다. 여기서 useEffect는 componentDidmount의 역항릉 해서 새로고침을 할 때마다 출력될 뿐만 아니라 componentDidupdate의 역할을 해서 클릭할 때마다 sayHello를 실행하기도 한다.

 

useEffect는 2개의 인자를 받는데 첫번째는 함수로써의 effect이다. 위 코드에서 useEffect부분을 

useEffect(sayHello);

로 바꾸어도 실행이 가능하다. 

두번째 인자는 dependancy의 역할을 한다. 만약 deps가 있다면 effect는 deps의 리스트에 있는 값일 때에만 값이 변하도록 활성화 된다. 

useEffect부분을 number의 값이 바뀔 때에만 실행되도록하면 다음과 같다.

  useEffect(() => {
    sayHello();
  }, [number]);

이론상으로는 위 코드는 증가버튼 이 눌릴 때마다 hello를 출력해주어야하는데 그렇지 않다. 그런데 위치를 바꾸어 주면

import React, { StrictMode, useEffect, useState } from "react";
import ReactDOM from "react-dom";

const App = () => {
  const sayHello = () => console.log("hello");

  const [number, setNumber] = useState(0);
  const [anumber, setAnumber] = useState(0);
  useEffect(() => {
    sayHello();
  }, [anumber]);
  return (
    <div>
      <div>Hi</div>
      <button onClick={() => setNumber(number + 1)}>{number}</button>
      <button onClick={() => setAnumber(anumber - 1)}>{anumber}</button>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

잘 실행되는 것을 볼 수 있다. 

감소 버튼을 누르면 hello가 출려되지 않는다. 이게 기본적인 dependency이다. 

이를 이용해서 만약 mount할 때에만 실행시키고 그 이후에는 실행시키고싶지 않다면 뒤에 인자를 빈 dependency를 전달해주면 된다. 

 

#2.1 useTitle

문서의 제목을 업데이트 해주는 hook를 작성해보자.

useEffect는 component가 mount될 때와 title이 업데이트 될 때 마다 updateTitle을 부른다,

최초의 타이틀은 loading...이고 일단 useEffect가 마운트 되면 htmlTitle은 loading이 된다. 만약 updateTitle을 어디에서 부르게 되면  title이 바뀐다. 그러면 updateTitle이 실행된다.

Loading...이 뜨는 것을 볼 수 있다. 이제 몇초 뒤에 updateTitle을 호출하도록 해보자.

import React, { StrictMode, useEffect, useState } from "react";
import ReactDOM from "react-dom";

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

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

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

 

#2.2 useClick

 화면이 뜨고 5초 뒤에 활성화 되는 코드를 작성하자.

input을 선택하면 이걸 가지고 뭐든 할 수 있다. console.l;og대신 focus를 사용할 수 있다.

import React, { Component, useRef } from "react";
import Mycomponent from "./Mycomponent";

const App = () => {
  const potato = useRef();
  setTimeout(() => potato.current.focus(), 5000);

  return (
    <div>
      <div>Hi</div>
      <input ref={potato} placeholder="la" />
    </div>
  );
};
export default App;

 

useClick

- 누군가 element를 클릭했을 때 함수를 실행시켜준다. 

import React, { Component, useRef } from "react";
import Mycomponent from "./Mycomponent";

const useClick = (onClick) => {
  const element = useRef();
  return element;
};

const App = () => {
  const title = useClick();
  return (
    <div>
      <div ref={title}>Hi</div>
    </div>
  );
};
export default App;

이제 우리는 title에 접근할 수 있다. 이제 이거로 우리는 useEffect를 사용해보자.  우리는 useClick에서 레퍼런스를 만들었다. 그리고 같은 레퍼런스를 return했다. 주어진 레퍼런스를 리턴했다. 

import React, { Component, useEffect, useRef } from "react";
import Mycomponent from "./Mycomponent";

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

const App = () => {
  const sayHello = () => {
    console.log("say Hello");
  };
  const title = useClick(sayHello);
  return (
    <div>
      <div ref={title}>Hi</div>
    </div>
  );
};
export default App;

이제 Hi 부분을 클릭할 때마다 say Hello가 콘솔창에 뜨게 된다.  

이제 useEffect에서 함수를 리턴하도록 해보자. 이제 mount 되거나 update될 때마다 함수가 리턴된다.

리턴 부분에 빈배열[]이 들어간 것을 볼 수 있는데 이것 덕분에 업데이트 때에는 고려하자 않아도 된다. 

즉 componentDidMount, componentDidUpdate 때마다 호출이 된다. 여기에 빈 배열, 즉 dependency가 존재하지 않을 때의 이야기이고 만약 dependency가 존재한다면, if 문은 componentDidMount때에만 호출된다. 컴포넌트가 componentWillUnMount일 때에는 return 함수를 한다. 이 때 useEffect를 리턴받은 함수는 componentDidMount 때 호출된다.

import React, { Component, useEffect, useRef } from "react";
import Mycomponent from "./Mycomponent";

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("say Hello");
  };
  const title = useClick(sayHello);
  return (
    <div>
      <div ref={title}>Hi</div>
    </div>
  );
};
export default App;

#2.3 useConfirm & usePreventLeave

usePreventLeave와 useConfirm을 사용해보자. 이 두개는 hooks는 아니지만 괜찮은 함수형 프로그래밍 만드는 방법을 알 수 있다. useConfirm은 사용자가 무언가를 하기 전에 확인하는 것이다. 버튼 클릭하면 이걸 할건지 물어본다. 브라우저가 함수 실행을 먼저 막는 것이라고 볼 수 있다.

import React from "react";

const useConfirm = (message = "", onConfirm, onCancel) => {
  if (onConfirm || typeof onConfirm !== "function") {
    return;
  }
  if (onCancel && typeof onCancel !== "function") {
    return;
  }
  const confirmAction = () => {
    if (confirm(message)) {
      onConfirm();
    } else {
      oncancel();
    }
  };
  return confirmAction;
};

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

export default App;

버튼을 누르면 알림창이 뜨는데 여기서 확인을 누르면 콜백 함수가 실행되게 된다. 취소를 누르면 aborted가 출력된다.

 

usePreventLeave는 창을 닫을 때 저장했냐고 물어보는 것을 생각할 수 있다. 

두개의 함수를 만든다. api가 아직 끝나지 않아서 사람들이 닫지 못하게 하게 만드는 enablePrevent와 api가 완료되어 나갈 수 있게 하는 disablePrevent가 있다.

import React from "react";

const usePreventLeave = (onLeaving) => {
  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;

prevent를 누르면 창 닫기를 누르면 다음과 같은 안내 문구가 뜬다. 

beforeunload는 창이 닫히기 전에 함수가 실행되는 것을 허락한다. 또는 창을 나가는 것을 허락한다.  returnValue를 넣어주어야 함수가 실행되므로 주의하자.

 

#2.4 useBeforeLeave

탭을 닫을 때 실행되는 함수이다. 마우스가 페이지를 떠나거나 할 때에 실행된다. 

import React, { useEffect } from "react";

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

const App = () => {
  const begForLife = () => console.log("don't leave");
  useBeforeLeave(begForLife);
  return (
    <div className="App">
      <h1>Hello</h1>
    </div>
  );
};

export default App;

useEffect를 사용해서 컴포넌트가 mount 될 때 addEventListener로 마우스가 떠났을 때 leaving을 출력한다. 

removeEventListener를 통해 마우스가 떠났다는 것을 알린다. Hello에 마우스를 가져갔다가 벗어나면 please dont leave가 출력된다. event를 출력해보면 여러가지가 출력되는데 clientY를 보면 마우스 이벤트의 값을 볼 수 있다.  그리고 clientY가 0보다 작거나 같으면 onBefore을 실행시킬 수 있다. 이제 마우스를 상단 탭에만 가져다 대었을 때에만 콘솔창의 문구가 실행될 수 있다. 

#2.5 useFadeIn & useNetwork

useFadeIn은 기본적으로 하나의 element를 가진다. 뭔가를 지연시킬 수 있다. 

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

const useFadeIn = (duration = 1, delay = 0) => {
  if (typeof duration !== "number" || typeof delay !== "number") {
    return;
  }
  const element = useRef();
  useEffect(() => {
    if (element.current) {
      const { current } = element;
      current.style.transition = `opacity ${duration}s ease-in-out ${delay}s`;
      current.style.opacity = 1;
    }
  }, []);
  return { ref: element, style: { opacity: 0 } };
};

const App = () => {
  const fadeInH1 = useFadeIn(1, 2);
  const fadeInP = useFadeIn(5, 5);
  return (
    <div className="App">
      <h1 {...fadeInH1}>Hello</h1>
      <p {...fadeInP}>lorem ~~</p>
    </div>
  );
};

export default App;

두개의 문구는 서로 다른 속도로 fade된다. 이 부분을 css로도 구현이 가능하다.  사용자가 시간이나 투명도를 선택하도록 할 수도 있다.

useNetwork를 사용해보자. 이건 navigator가 온라인이나 오프라인으로 변하는 것을 막아준다. 

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

const useNetwork = (onChange) => {
  const [status, setStatus] = useState(navigator.onLine);
  const handleChange = () => {
    if (typeof onChange === "function") {
      onChange(navigator.onLine);
    }
    setStatus(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 ? "We just went online" : "We are offline");
  };
  const onLine = useNetwork(handleNetworkChange);
  return (
    <div className="App">
      <h1>{onLine ? "Online" : "Offline"}</h1>
    </div>
  );
};

export default App;

useStatus는 새로운 status를 막아준다.

useEffect에서 addEventListener로 컴포넌트가 unmount 할 때 우리가 한 모든것을 지운다. 그리고 온라인 상태이면 handleChange를 실행한다. 오프라인 상태이면 handleChange를 실행한다. 온라인일 때에는 status의 네비게이터의 online은 offline이 된다.  dependency를 추가하지 않기 위해 useEffect에 빈 배열을 선언하지 않는다. 

개발자 도구에서 network를 오프라인으로 바꾸면 online이 offline으로 글자가 바뀌게 된다.

 

#2.6 useScroll & useFullscreen 

사용자가 스크롤해서 무언가를 지나칠 때 함수를 실행할 수 있도록 한다.

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

const useScroll = () => {
  const [state, setState] = useState({
    x: 0,
    y: 0
  });
  const onScroll = () => {
    setState({ y: window.scrollY, x: window.scrollX });
    //console.log(window.scrollY)
  };
  useEffect(() => {
    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return state;
};

const App = () => {
  const { y } = useScroll();
  return (
    <div className="App" style={{ height: "1000vh" }}>
      <h1 style={{ position: "fixed", color: y > 100 ? "red" : "orange" }}>
        Hello
      </h1>
    </div>
  );
};

export default App;

화면을 스크롤하면  스크롤 이벤트를 인식해 마우스의 x,y좌표를 가져와 state를 변경하여 글자의 색이 바뀌게 된다.

 

useFullScreen은 어떤 element를 얻으면 전체 화면으로 바꾸어준다.

버튼을 누르면 div를 크게 만들어준다. 그리고 exitfullscreen을 누르면 전체화면을 종료한다.

useCallback을 이용할 수도 있다. triggerFull은 nonFullScreen으로 만들어주는 함수가 되도록 만들 수도 있다.

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

const useFullscreen = (callback) => {
  const element = useRef();
  const triggerFull = () => {
    if (element.current) {
      element.current.requestFullscreen();
      if (callback && typeof callback === "function") {
        callback(true);
      }
    }
  };
  const exitFull = () => {
    document.exitFullscreen();
    if (callback && typeof callback === "function") {
      callback(false);
    }
  };
  return { element, triggerFull, exitFull };
};

const App = () => {
  const onFulls = (isFull) => {
    console.log(isFull ? "we are full" : "we are small");
  };
  const { element, triggerFull, exitFull } = useFullscreen(onFulls);
  return (
    <div className="App" style={{ height: "1000vh" }}>
      <div ref={element}>
        <img src="https://t1.daumcdn.net/thumb/R720x0/?fname=http://t1.daumcdn.net/brunch/service/user/7mBl/image/3rkgkRbVEQ8MFZ1mayn4Rtzh2rA.jpg" />
        <button onClick={exitFull}>Exit fullscreen</button>
      </div>
      <button onClick={triggerFull}>Make fullscreen</button>
    </div>
  );
};

export default App;

#2.7 useNotification

알람이 실행되도록 만들 수 있다.

import React, { 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);
        } else {
          return;
        }
      });
    } else {
      new Notification(title, options);
    }
  };
  return fireNotif;
};

const App = () => {
  const fireNotif = useNotification("Hello~~~", {
    body: "알림 띄우기 성공!"
  });
  return (
    <div className="App" style={{ height: "1000vh" }}>
      <button onClick={fireNotif}>Hello</button>
    </div>
  );
};

export default App;

우선 윈도우 알림에만 접근해야하기 때문에 윈도우 안에 알림이 있는지 확인해야한다. 

알림 권한이 없다면 알림 권한을 요청한다. 그리고 권한이 있다면 타이틀과 옵션을 준다. 

 

#2.8 useAxios

axios는 약간의 커스터마이즈와 configuration을 허용하기 때문에 axios 인스턴스를 얻어와야한다.

=> axiosInstance = defaultAxios

import React, { StrictMode } from "react";
import ReactDOM from "react-dom";

import { useAxios } from "./useAxios";

const App = () => {
  const { loading, data, error } = useAxios({
    url: "https://yts.am/api/v2/list_movies.json"
  });
  console.log(`Loading:${loading}\nError:${error}\nData:${data}`);
  return (
    <div className="App" style={{ height: "1000vh" }}>
      <h1>hello</h1>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <App />
  </StrictMode>,
  rootElement
);

<index.js>

import defaultAxios from "axios";
import { useState, useEffect } from "react";

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

<useAxios.js>

 

#2.9 Conclusion

15개의  hook들을 배워보았다. 이제 npm modules에 publish하는 방법을 배워보자.

 

#2.10 Publishing to NPM 

패키지를 publish해보자.  

우선 npm 을 가져오기 위해 npm init을 콘솔창에 입력해준다. useTitle 대신에 @nooks/use-title을 패키지 이름으로 입력해주자. 마지막에 keyword는 react, reat hooks, title, react title로 해주고 author은 이름을 입력한다. 이후는 다 기본으로 설정하고 엔터를 누른다. 

 

npm i react react-dom

그리고 npm 사이트로 가서 회원가입을 하고 콘솔창으로 돌아와서 npm login 을 해야 올라간다.

반응형