
VSC 대신 codesandbox를 사용할 것이다.
React Hooks - 노마드 코더 영상
functional component에서 state를 가질 수 있게 해준다.
리액트 훅을 사용한다면 class component, did mount, render 등을 하지 않아도 된다.
functional programming(함수형 프로그래밍)이 된다.
recompose library로 부터 시작되었다.
hooks은 react의 state machine에 연결하는 기본적인 방법이다.
useState
useState는 항상 2개의 value를 가진 Array를 return한다.
// functional component
const App = () => {
const [item, setItem] = useState(1);
const incrementItem = () => setItem(item + 1);
const decrementItem = () => setItem(item - 1);
}
// class component
// hook을 사용할 수 없다. -> state를 사용할 수 없다.
// 코드 길이가 엄청 길어진다.
class App extends React.Component{
state = {
item: 1
}
render() {
const {item} = this.state;
return (
<div>...</div>
)
}
incrementItem = () => {
this.setState(state => {
return {
item: state.item + 1
};
});
}
decrementItem = () => {
this.setState(state => {
return {
item: state.item - 1
};
});
}
}
React는 다른 function에서 event처리를 할 수 있다.
event를 분리된 파일, 다른 entity에 hooking해서 처리할 수 있다.
export const useInput = (initialValue, validator) => {
const [value, setValue] = useState(initialValue)
const onChange = (e) => {
const {
target: {value}
} = e
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>
// name의 value, onChange가 shadowing 된다.
<input {...name} />
</div>
);
};
setState는 모든 걸 새로고침 해준다. rerender해준다.
import React, { useState } from "react";
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
});
}
const UseTabsApp = () => {
const { currentItem, changeItem } = useTabs(0, content);
return (
<div>
{content.map((section, index) => (
<button onClick{()=>changeItem(index)}> {section.tab}</button>
))}
{currentItem.content}
</div>
)
}
export default UseTabsApp;
useEffect는 함수와 deps 2개의 인자를 가진 함수이다.
useEffect()는 componentDidMount(), componentDidUpdate(), componentWillUnmount() 의 역할을 한다.
useEffect를 이용한 hook을 작성할 것이다.
title을 업데이트하는 useTitle이라는 hook을 작성해볼 것!
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 ();
};
references? componenet의 어떤 부분을 선택할 수 있는 방법
const element = useRef();
<div ref={element}></div>
const useClick = (onClick) => {
const element = useRef();
useEffect(() => {
if(typeof onClick !== "function") {
return;
}
if(element.current){
element.current.addEventListener("click", onClick);
}
return () => {
if(element.current){
element.current.removeEventListener("click", onClick);
}
}
},[])
return typeof onClick !== "function" ? undefined: element;
};
useState, useEffect를 사용하지 않기 때문에 실제로 hook은 아닌 useConfirm, usePreventLeave를 만들어 볼 것이다.
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...");
const abort = () => console.log("aborted");
const confirmDelete = useConfirm("Are u sure?", deleteWorld);
return (
<button onClick={confirmDelete}>Delete</button>
);
}
beforeunload는 window가 닫히기 전에 function이 실행되는 것을 허락한다.
const usePreventLeave = () => {
const listener = (event) => {
event.preventDefault();
event.returnValue = "";
};
const enablePrevent = () => window.addEventListener("beforeunload");
const disablePrevent = () => window.removeEventListener("beforeunload", listener);
return { enablePrevent, disablePrevent }
}
const App = () => {
const { enablePrevent, disablePrevent } = usePreventLeave();
return (
<button onClick={enablePrevent}>Protect</button>
<button onClick={disablePrevent}>unprotect</button>
);
}
마우스가 브라우저에서 위로 벗어날 때!
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 begForLife = () => console.log("plz don't leave");
useBeforeLeave(begForLife);
return (
...
);
}
useFadeIn
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();
const fadeInP = useFadeIn();
return (
<div>
<h1 {...fadeInH1}>hello</h1>
</div>
);
}
useNetwork
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);
// cleanup everything when componentWillUnmount
() => {
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>
<h1>{onLine ? "Online": "Offline"}</h1>
</div>
);
}
const useScroll = () => {
const [state, setState] = useState({
x: 0,
y: 0
})
const onSccroll = () => {
setState({ y: window.scrollY, x: window.scrollX });
}
useEffect(() => {
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, []);
return state
}
const App = () => {
const {y} = useScroll();
return (
<div style={{ height: "1000vh" }}>
<h1 style={{ position: "fixed", color: y > 100 ? "red": "blue"}}>{onLine ? "Online": "Offline"}</h1>
</div>
);
}
useFullscreen 브라우저에 따라 fullscreen 실행함수명이 다르다.
const useFullscreen = (callback) => {
const element = useRef();
const runCb = (isFull) => {
if (callback && typeof callback === "function") {
callback(isFull);
}
};
const triggerFull = () => {
if (element.current) {
if (element.current.requestFullscreen) { //chrome
element.current.requestFullscreen();
} else if (element.current.webkitRequestFullscreen) { //opera
element.current.webkitRequestFullscreen();
} else if (element.current.msRequestFullscreen) { //Microsoft
element.current.msRequestFullscreen();
} else if (element.current.mozRequestFullScreen) { //firefox
element.current.mozRequestFullScreen();
}
runCb(true);
}
};
const exitFull = () => {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitRequestFullscreen) {
document.webkitRequestFullscreen();
} else if (document.msRequestFullscreen) {
document.msRequestFullscreen();
} else if (document.mozRequestFullScreen) {
document.mozRequestFullScreen();
}
runCb(false);
};
return {element, triggerFull, exitFull};
};
const App = () => {
const onFullS = (isFull) => {
console.log(isFull ? "we are full": "we are small")
}
const { element, triggerFull, exitFull } = useFullscreen();
return (
<div style={{ height: "1000vh" }}>
<div ref={element}>
<img src="..."></img>
<button onClick={trigerFull}>make fullscreen</button>
</div>
<button onClick={exitFull}>exit fullscreen</button>
</div>
);
}
https://developer.mozilla.org/en-US/docs/Web/API/notification 참고해서 만들어나가면 된다.
const useNotification = (title, options) => {
if (!("Notification" in window)) {
return;
}
const fireNotif = () => {
if (Notification.permission !== "granted") { // permission이 부여되지 않은 경우
// promise를 준다.
Notification.requestPermission().then((permission) => {
if (permission === "granted") {
new Notification(title, options);
} else return;
});
}
};
return fireNotif;
}
const App = () => {
const triggerNotif = useNotification(
"Hello, Can I hack your Computer?",
{body: "If you agree, I will hack 😈"}
);
return (
<div className="App" style={{ height: "1000vh" }}>
<button onClick={triggerNotif}>Hello</button>
</div>
);
};
axios는 http request를 만드는 것이다.
axios는 instance 만드는 것을 허용하고, configure할 수 있고, 헤더를 보낼 수 있다.
// useAxios.js
import dafaultAxios from "axios";
import {useState, useEffect} from "react";
const useAxios = (opts, axiosInstance = dafaultAxios) => {
const [state, setState] = useState({
loading: true,
error: null,
data: null
});
const [trigger, setTrigger] = useState(0);
const refetch = () => {
setState({
...state,
loading: true
});
setTrigger(Date.now());
// trigger로 Date.now()를 사용한다!
};
useEffect(() => {
axiosInstance(opts)
.then((data) => {
setState({
...state,
loading: false,
data
});
})
.catch(error => {
setState({...state, loading: false, error});
});
}, [trigger]); // trigger가 바뀔때마다 useEffect가 실행된다.
if (!opts.url) {
return;
}
return {...state, refetch};
};
export default useAxios;
// App.js
import ReactDOM from "react-dom";
import useAxios from "./useAxios/useAxios.js";
const App = () => {
const {loading, data, error, refetch} = useAxios({
url:"https://yts.mx/api/v2/list_movies.json"
});
console.log(`loading: ${loading}\ndata: ${JSON.stringify(data)}\n`)
return (
<div className="App" style={{ height: "1000vh" }}>
<h1>{data && data.status}</h1>
<h2>{loading && "Loading"}</h2>
<button onClick={refetch}>Refetch</button>
</div>
);
};