본문 바로가기

인강/노마드코더

[노마드코더] React Custom Hooks

✅ 비구조화 할당, 구조 분해

: event를 다른 함수로 분리할 수 있다. Ex) 이벤트를 분리한 파일, 다른 entity에 연결하여 처리할 수 있음.

const useInput = (initialValue) => {
  const [value, setValue] = useState(initialValue);

  return { value };
};

const App = () => {
  const name = useInput("Mr.");

  return (
    <div className="App">
      <h1>Use Hooks</h1>
      <input placeholder="What's your name?" {...name} />
      <input placeholder="What's your name?" value={name.value} />
    </div>
  );
};

=> useInput이 반환하는 모든 것을 ...name을 통해 풀어준다.(packing/unpacking)

 

💯💯💯 객체의 비구조화 할당

const { newValue } = e;
const newValue = e.newValue;

// e의 속성인 target: {value}가 존재하고
// target의 속성인 value를 변수 value에 할당한다.
const { target: { value }, } = e;
const value = e.target.value;

=> e객체의 속성인 newValue를 추출하여, 변수 newValue에 할당하는 것.

 

💯💯💯 배열의 비구조화 할당

const [newValue] = e;

=> e배열의 첫번째 요소를 추출하여, 변수 newValue에 할당하는 것.


✅ useState는 배열을 return 한다.

const item = useState("Start")[0];
const setItem = useState("Finish")[1];

=> 배열의 값을 하나만 사용하고 싶은 경우 다음과 같이 useState를 custom한다.


 

✅ useInput(초기값, 유효성 검사 function) → useState를 커스텀

const useInput = (initialValue, validator) => {
  const [value, setValue] = useState(initialValue);
  const onChange = (event) => {
    const {
      target: { value },
    } = event;
    
    let willUpdate = true;
    if (typeof validator === "function") {
      willUpdate = validator(value);
    }
    if (willUpdate) {
      setValue(value);
    }
  };

  return { value, onChange };
};

const App = () => {
  const maxLen = (value) => {
    value.length <= 10;
  };
  const name = useInput("Mr.", maxLen);

  return (
    <div className="App">
      <h1>Use Hooks</h1>
      <input placeholder="What's your name?" {...name} />
    </div>
  );
};

=> useInput의 두 번째 매개변수는 함수로, validator라는 함수명을 지어둔다.

 

 

✅ useTabs(초기값, 배열) → useState를 커스텀

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) => {
  if (!allTabs || !Array.isArray(allTabs)) {
    return;
  }
  const [currentIndex, setCurrentIndex] = useState(initialTab);
  
  return {
    currentItem: allTabs[currentIndex],
    changeItem: setCurrentIndex,
  };
};

const App = () => {
  const { currentItem, changeItem } = useTabs(0, content);
  
  return (
    <div className="App">
      <h1>Use Hooks</h1>
      {content.map((section, index) => (
        <button onClick={() => changeItem(index)}>{section.tab}</button>
      ))}
      <div>{currentItem.content}</div>
    </div>
  );
};

=> useTabs가 첫 번째 매개변수로 초기값을 입력받고 두 번째 매개변수로 배열을 입력받아, 객체를 return한다.

객체 첫 번째 속성은 currentItem이고, 두 번째 속성은 changeItem이다.

 

💯💯💯

const [ 상태, 상태변경함수 ] = useState(초기값);

currentItem: 배열의 현재 index, changeItem: 배열의 index를 변경하는 함수.


 

✅ useEffect는 ComponentDidMount, ComponentWillUnMount, ComponentDidUpdate로 이루어진다.

1. ComponentDidMount

2. ComponentDidUpdate

3. ComponentWillUnMount


 

 

✅ useTitle(초기값) → useEffect를 커스텀

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"), 5000);

  return (
    <div className="App">
      <h1>Use Hooks</h1>
    </div>
  );
};

=> 상태변경함수를 return 함.

 

 

✅ useClick(함수) & useHover

  useEffect(() => {
    if (typeof onClick !== "function") return;

    if (element.current) {
      element.current.addEventListener("click", onClick);
    }
    return () => {
      if (element.current) {
        element.current.removeEventListener("click", onClick);
      }
    };
  }, []);

=> 컴포넌트가 unMount될 때, useEffect에서 함수를 return한다. (cleanup)

💯💯💯

이벤트를 추가한 뒤 동일한 이벤트핸들러로 삭제를 해줘야 한다.

 

 

✅ useConfirm & usePreventLeave

: useState와 useEffect를 사용하지 않는다.

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

  const confirmAction = () => {
    if (window.confirm(message)) {
      onConfirm();
    } else {
      onCancel();
    }
  };

  return confirmAction;
};

=> !onConfirm, onCancel을 검사하는 이유?? 더 공부하기~~ 함수형 프로그래밍에 대한 이해!!!

kill everything

 

 

 useConfirm & usePreventLeave

: useState와 useEffect를 사용하지 않는다.

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">
      <h1>Use Hooks</h1>
      <button onClick={enablePrevent}>Protect</button>
      <button onClick={disablePrevent}>Unprotect</button>
    </div>
  );
};

 

 

✅ useBeforeLeave

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);
    };
  }, []);
};

 

 

✅ useFadeIn & useNetwork

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 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);
    };
  }, []);

  return status;
};

 

 

✅ useScroll & useFullscreen

const useScroll = () => {
  const [state, setState] = useState({
    x: 0,
    y: 0,
  });

  const onScroll = () => {
    setState({ x: window.scrollX, y: window.scrollY });
  };

  useEffect(() => {
    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  return {
    x: state.x,
    y: state.y,
  };
};

 

 

✅ useNotification

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

 

✅ useAxios

axios: http request이다. axios client를 받는다.(axios Instance)

Instance: 사용자가 http 통신할 때 커스텀하여 request를 보내고 싶을 때 사용하는 것.

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

 


 

✅ 추가적으로...

useContext, useReducer, useCallback, useMemo

=> useContext, useReducer를 활용한 번역 라이브러리

=> useCallback, useMemo를 활용한 컴포넌트 최적화