NomadCoder - React Hooks

박영빈·2023년 5월 16일

멋쟁이사자처럼

목록 보기
4/5

5주차 과제 - React Hook

0. Introduction

  • react hook에는 useTitle, useInput, usePageLeave, useClick, useFadeIn, useFullScreen, useHover, useNetwork, useScroll 등 다양하게 있다.
  • use 옆에 적혀있는 행동과 관련해서 유저가 동작하면 이벤트를 발생시킨다.
  • 이 hook은 VSCode에서 개발하지 않을 것임
    • hook이 동작하는지 확인하고 NPM directory에 등록할 것임
    • Code SandBox에서 제작한다.


번외편. React Hook YouTube

  • 강의 듣기 전에 듣고 오래서 들었다.
  • react hook을 통해 functional component에서 state를 갖게 해준다.
  • 앱을 react hook으로 만들면 class compoenet, did mount, render를 안 해도 된다.
  • 이를 통해 functional programming 스타일이 된다.
    • 니꼬는 이걸 좋아한다고 한다.
  • 우리가 그 동안 사용했던 useState, useEffect 이런 것이 다 hook이었다.

1. UseState

  • useState는 항상 2가지를 리턴한다.
    • value, setValue / 이름은 상관 없음
  • hook을 사용함으로써 class component 형태로 작성하여 this나 render를 걱정하는 방식에서 바뀌었다.

useInput

import { useState } from "react";
import "./styles.css";

const useInput = (initialValue, validator) => {
  const [value, setValue] = useState(initialValue);
  const onChange = (event) => {
    let willUpdate = true;
    if(typeof(validator) === "function"){
      willUpdate = validator(event.target.value);
    }
    if (willUpdate)
      setValue(event.target.value);
  };
  return { value, onChange };
};
// validator는 유호성 검증, 예를 들어 길이 제한이나 특정 문자 입력 못하게게
export default function App() {
  // const maxLen = (value) => value.length <= 10;
  const forbiddenChr = (value) => !value.includes('@');
  const name = useInput("", forbiddenChr);
  return (
    <div className="App">
      <h1>Hello</h1>
      <input placeholder="Name" {...name} />
    </div>
  );
}
  • useInput을 작성하였다.
  • 이 hook을 통해 input을 깔끔하게 사용하고 유효성 검증까지 실행 가능하다.
  • useInput.js 파일에 export const useInput … 함수만 저장!

useTab

import { useState } from "react";
import "./styles.css";

const content = [
  {
    tab: "Section 1",
    content: "I'm the content of the Section 1"
  },
  {
    tab: "Section 2",
    content: "I'm the content of the Section 2"
  }
];

const useTabs = (initialTab, allTabs) => {
  const [currentIndex, setCurrentIndex] = useState(initialTab);
  if (!allTabs || !Array.isArray(allTabs)) {
    return;
  }
  return {
    currentItem: allTabs[currentIndex],
    changeItem:setCurrentIndex
    // 얘는 그냥 함수를 리턴한거, changeItem === setCurrentIndex
    // click 하면 setCurrentIndex가 호출되어 currentIndex가 변경됨
    // 이 후 currentItem도 갱신
  };
};

export default function App() {
  const { currentItem, changeItem } = useTabs(0, content);
  return (
    <div className="App">
      <h1>Hello</h1>
      {content.map((section, index) => (
        <button onClick={()=> changeItem(index)}>{section.tab}</button>
      ))}
      <div>{currentItem.content}</div>
    </div>
  );
}
  • Tab별로 담고 있는 정보를 활용하는 예제
  • 초기값으로 tab의 인덱스를 넣어주고 useTabs는 해당 인덱스의 Tab 정보를 넘겨준다.
  • 헷갈렸던 것 중 하나는 changeItem(index) 이 부분인데, 주석으로 작성 해두었다.



2. UseEffect

  • useEffect는 useEffect(함수, [관찰하는 변수])로 사용한다.
  • dependency의 변수가 변하면 함수가 매 번 실행된다.

useTitle

import { useEffect, useState } from "react";
import "./styles.css";

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

export default function App() {
  const titleUpdater = useTitle("Loadiing...");
  // setTimeout(() => titleUpdater("Home"), 3000);
  // 3초 뒤 title을 Home으로 변경
  return (
    <div className="App">
      <div>Hi</div>

    </div>
  );
}
  • title → 탭 상단에 표기되는 문구
  • 처음엔 Loading으로 지정한 뒤에 setTimeout을 이용해 titleUpdater를 호출하면 setTitle이 호출되고, title이 변한 것을 useEffect에서 감지해서 updateTitle을 호출하면 현재 페이지의 title 태그를 querySelector로 가져와서 내부 텍스트를 변경해준다.

useClick

export default function App() {
  const potato = useRef();
  // reference는 기본적으로 component를 선택할 수 있는 방법
  // document.getElementByID()와 동일

  // setTimeout(()=>console.log(potato.current), 3000);
  // <input placeholder="la"></input> 출력, 특정 요소 선택 가능

  // setTimeout(() => potato.current.focus(), 3000);
  // 3초 뒤 input창 focus

  return (
    <div className="App">
      <div>Hi</div>
      <input ref={potato} placeholder="la" />
    </div>
  );
}
  • useClick 들어가기 전에 useRef에 대해 학습
  • document.getElementByID()와 동일한 역할을 함
  • ref를 만들고 만든 ref를 지정해서 다른 곳에서 사용 가능
import { useEffect, useRef, useState } from "react";
import "./styles.css";

const useClick = (onClick) => {
  
  const element = useRef();
  useEffect(() => {
    if (element.current) {
      element.current.addEventListener("click", onClick);
    }
    return () => {
      if(element.current){
        element.current.removeEventListener("click", onClick);
      }
    }
  }, []);
  // useEffect에서 반환하는 함수는 cleanup 함수인데, component가 unmount될 때 실행된다.
  if (typeof onClick !== "function"){
    return;
  }
  return element;
};

// title은 useClick이 반환한 element라는 ref를 갖는다.
// 해당 ref는 h1 태그에 부여됐고, click 이벤트를 감시하며 click시 인자로 받은 sayHello를 실행행
export default function App() {
  const sayHello = () => {
    console.log("say Hello");
  };
  const title = useClick(sayHello);
  return (
    <div className="App">
      <h1 ref={title}>Hi</h1>
    </div>
  );
}
  • useClick
  • ref를 부여하고 eventlistener를 지정하는 것과 useEffect에서 함수를 return하는 개념을 학습했다.
  • useEffect에서 반환하는 함수는 cleanup함수로, element가 unmount 될 때, 실행된다.
  • 음 이게 중요하다고 하는데 헷갈린다. 이후에 계속 복습한다고 하니 일단 넘어가자.

useConfirm & usePreventLeave

  • 얘네는 실제로 hook은 아님, useState와 useEffect를 사용하지 않는다.
export const useConfirm = (message, onConfirm, onCancel) => {
  if (onConfirm && typeof onConfirm !== "function") {
    return;
  }
  if (onCancel && typeof onCancel !== "function") {
    return;
  }
  // 이걸 계속 작성하는 것이 함수형 프로그래밍을 이해하는데 도움이 됨
  const confirmAction = () => {
    if (confirm(message)) {
      onConfirm();
      // confirm은 확인 알림 창 띄우는 함수, ok 누르면 true 리턴턴
    } else {
      onCancel();
    }
    // 취소 누르면 onCancel으로 받은 abort가 실행 됨.
  };
  return confirmAction;
};
  • 특정 element 동작 시 이전에 확인 창 띄우는 기능
  • button의 onClick에다가 넣으면 message를 반영한 확인 창을 띄우고 ok시 onConfirm함수를, cancel시 onCancel함수를 실행한다.
import { useEffect, useRef, useState } from "react";
import "./styles.css";

const usePreventLeave = () => {
  const listener = (event) => {
    event.preventDefault();
    event.returnValue = ""; // 이걸 꼭 써줘야 함
  };
  const enablePrevent = () => window.addEventListener("beforeunload", listener);
  const disablePrevent = () =>
    window.removeEventListener("beforunload", listener);
  // beforeunload -> 사이트를 나갈 때 발생하는 이벤트
  return { enablePrevent, disablePrevent };
};

export default function App() {
  const { enablePrevent, disablePrevent } = usePreventLeave();
  return (
    <div className="App">
      <h1>Hi</h1>
      <button onClick={enablePrevent}>Protect</button>
      <button onClick={disablePrevent}>Unprotect</button>
    </div>
  );
}
  • 얘는 창을 떠날 때 확인 창을 띄우는 기능을 한다.
  • beforeunload 이벤트가 있다는 것을 기억해두자.
  • preventDefault를 이용해 창을 떠날 때 listener가 동작하면서 경고한다.

useBeforeLeave

import { useEffect, useRef, useState } from "react";
import "./styles.css";

const useBeforeLeave = (onBefore) =>{
  if(typeof onBefore !== "function"){
    return;
  }
  const handle = (event) =>{
    // console.log(event.clientY);
    if(event.clientY <= 0)
      onBefore();
  };
  useEffect(() => {
    document.addEventListener("mouseleave", handle);
    return ()=> document.removeEventListener("mouseleave", handle);
    // 계속해서 event가 쌓이는 것을 막기 위해 cleanup
  }, []);
}

export default function App() {
  const begForLife = () =>{
    console.log("Please dont leave");
  }
  useBeforeLeave(begForLife);
  return (
    <div className="App">
      <h1>Hi</h1>
    </div>
  );
}
  • mouseleave 이벤트를 이용해서 마우스가 특정 위치를 벗어날 때 실행한다.

useFadeIn & useNetwork

import { useEffect, useRef, useState } from "react";
import "./styles.css";

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}`;
      current.style.opacity = 1;
    }
  }, []);
  return { ref: element, style: { opacity: 0 } };
};

export default function App() {
  const fadeInH1 = useFadeIn(1, 2);
  const fadeInP = useFadeIn(5, 10);
  return (
    <div className="App">
      <h1 {...fadeInH1}>Hi</h1>
      <p {...fadeInP}>hello world</p>
    </div>
  );
}
  • useFadeIn → 서서히 나타나게 하는 hook인데,,,
  • react 최신버전에서는 동작하지 않는다고 한다.
import { useEffect, useRef, useState } from "react";
import "./styles.css";

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);

    return () => {
      window.removeEventListener("online", handleChange);
      window.removeEventListener("offline", handleChange);
    };
  }, []);
};

export default function App() {
  const online = useNetwork();
  return (
    <div className="App">
      <h1>{online ? "onLine" : "offLine"}</h1>
    </div>
  );
}
  • useNetwork

useScroll & useFullscreen

import { useEffect, useRef, useState } from "react";
import "./styles.css";

export default function App() {
  const { y } = useScroll();

  return (
    <div className="App" style={{ height: "1000vh" }}>
      <h1 style={{ position: "fixed", color: y > 100 ? "red" : "blue" }}>
        hello
      </h1>
    </div>
  );
}
  • 현재 scroll의 좌표를 받아서 일정 기준에 따라 텍스트의 색상을 바꾸었다.
import { useEffect, useRef, useState } from "react";
import "./styles.css";

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 };
};

export default function 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://www.hongik.ac.kr/front/images/local/header_logo.png" />
        <button onClick={exitFull}>Exit FullScreen</button>
      </div>
      <button onClick={triggerFull}>Make FullScreen</button>
    </div>
  );
}
  • useFullscreen → 버튼을 눌러서 전체화면으로 전환하게 해줌
  • 추가로 현재 상태를 알리는 state도 추가하였음!

useNotification

import { useEffect, useRef, useState } from "react";
import "./styles.css";

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;
}

export default function App() {
  const triggerNotif = useNotification("Can I steal your kimchi?");
  return (
    <div className="App" style={{ height: "1000vh" }}>
      <h1>Hello</h1>
      <button onClick={triggerNotif}>Hello</button>
    </div>
  );
}
  • useNotification : chrome상에서 알람을 띄운다.
  • 권한 문제가 있으므로 우선 허가 요청을 하고 허용한다면 알람을 띄운다.

useAxios

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

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};
};

export default useAxios;
import { useEffect, useRef, useState } from "react";
import "./styles.css";
import useAxios from "./useAxios";

export default function App() {
  const { loading, data, error, refetch } = useAxios({
    url:
      "https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json"
  });
  console.log(
    `Loading : ${loading}, Data : ${JSON.stringify(data)}, Error : ${error}`
  );

  return (
    <div className="App" style={{ height: "1000vh" }}>
      <h1>{data && data.status}</h1>
      <h2>{loading && "Loading..."}</h2>
      <button onClick={refetch}>Refetch</button>
    </div>
  );
}
  • Axios를 이용해서 api를 호출해온다.
  • 신기하네

Publishing

npm init

blahblah

여기는 나중에 필요하면 다시 찾아서 보자.

profile
안녕하세요<br>반가워요<br>안녕히가세요

0개의 댓글