나만의 훅을 만들어 보자.
파일 하나를 생성하자. useWindowWidth.js파일을 만들자.
export default function useWindowWidth() {
const [width] = useState(window.innderWidth);
return width;
}
import { useEffect, useState } from "react";
import useWindowWidth from "../hooks/useWindowWidth";
// state = { count: 0 }
export default function Example5() {
const [count, setCount] = useState(0);
const [name, setName] = useState("Mark");
const width = useWindowWidth();
// 2개의 인자를 받는다. 하나는 함수 이 아이는 시점이 없다.
useEffect(() => {
console.log("componentDidMount & componentDidUpdate");
return () => {
console.log(`clean up`);
};
}); // 시점을 지정하지 않으면 무조건 랜더 된 직후를 의미 한다.
// 2번째 인자로 빈 배열을 넣으면 componentDidMount만 실행한다. 즉 함수는 실행되는 아이(한일?)를 의미하고 2번째 인자인 배열은 시점을 이야기 하는 것이다.(언제의 뜻?)
useEffect(() => {
console.log("componentDidMount");
return () => {
console.log("componentWillUnmount");
}; // 함수를 리턴을 한다. 함수를 반환하면 해당 함수는 다음 랜더를 하기 전에 실행한다.
}, []); // 시점이 빈 배열이면 최초에 렌더 된 직후를 의미(의존성?)
useEffect(() => {
console.log("[count]", count);
return () => {
console.log("[count] - cleanup", count);
};
}, [count]);
return (
<div>
<h2>
{name} - {width}
</h2>
<p>You clicked {state.count} times</p>
<button onClick={this.click}>Click me</button>
</div>
);
function click() {
// setState => 두 번째 배열 요소
// setState({ count: state.count + 1 }); // 이렇게 도 사용할 수 있고 함수로도 사용가능하다.
setState(count + 1);
}
}
이렇게 하면 name 옆에 현재 가로값이 나올 것이다. 위에서 하고 싶었던 거는 가로값을 줄었다 늘렸다 하면 가로값이 화면에서 나오게 하는 훅을 만들고 싶은 거다.
그래서 다른 컴포넌트를 만들어서 해보자.
import { useState } from "react";
import useWindowWidth from "../hooks/useWindowWidth";
// state = { count: 0 }
export default function Example5() {
const [count, setCount] = useState(0);
const [name, setName] = useState("Mark");
const width = useWindowWidth();
return (
<div>
<h2>
{name} - {width}
</h2>
<p>You clicked {state.count} times</p>
<button onClick={this.click}>Click me</button>
</div>
);
function click() {
// setState => 두 번째 배열 요소
// setState({ count: state.count + 1 }); // 이렇게 도 사용할 수 있고 함수로도 사용가능하다.
setState(count + 1);
}
}
커스텀 훅이 왜 나왔나? 상태에 대한 로직을 재사용하기 어렵다. 재사용을 위해서 따로 때어내는 행위를 하는 것이다. useWindowWidth는 이렇게 사용할 수 있다.
import { useEffect, useState } from "react";
import useWindowWidth from "../hooks/useWindowWidth";
// state = { count: 0 }
export default function Example5() {
const [count, setCount] = useState(0);
const [name, setName] = useState("Mark");
const [width, setWidth] = useState(window.innderWidth);
// 우리가 화면 크기를 줄이거나 늘릴때 알아차리는건 window객체이다. 즉 render 직후에 이벤트를 달아줘야한다.
useEffect(() => {
const resize = () => {
setWidth(window.innerWidth);
};
window.addEventListener("resize", resize); // reference를 넣어야하기 때문에 함수를 만들어서 참조값을 갖고 있는 식별자를 넣는다.
return () => {
window.removeEventListener("resize", resize);
};
}, []);
return (
<div>
<h2>
{name} - {width}
</h2>
<p>You clicked {state.count} times</p>
<button onClick={this.click}>Click me</button>
</div>
);
function click() {
// setState => 두 번째 배열 요소
// setState({ count: state.count + 1 }); // 이렇게 도 사용할 수 있고 함수로도 사용가능하다.
setState(count + 1);
}
}
이렇게 만들 수 있다.
const [width, setWidth] = useState(window.innderWidth);
// 우리가 화면 크기를 줄이거나 늘릴때 알아차리는건 window객체이다. 즉 render 직후에 이벤트를 달아줘야한다.
useEffect(() => {
const resize = () => {
setWidth(window.innerWidth);
};
window.addEventListener("resize", resize); // reference를 넣어야하기 때문에 함수를 만들어서 참조값을 갖고 있는 식별자를 넣는다.
return () => {
window.removeEventListener("resize", resize);
};
}, []);
이 부분을 우리가 만든 useWindowWidth.js에 그대로 합치자
import { useEffect, useState } from "react";
export default function useWindowWidth() {
const [width, setWidth] = useState(window.innderWidth);
// 우리가 화면 크기를 줄이거나 늘릴때 알아차리는건 window객체이다. 즉 render 직후에 이벤트를 달아줘야한다.
useEffect(() => {
const resize = () => {
setWidth(window.innerWidth);
};
window.addEventListener("resize", resize); // reference를 넣어야하기 때문에 함수를 만들어서 참조값을 갖고 있는 식별자를 넣는다.
return () => {
window.removeEventListener("resize", ㄱㄷ냨ㄷ);
};
}, []);
return width;
}
이렇게 옮기고 Example6.jsx에서
import { useState } from "react";
import useWindowWidth from "../hooks/useWindowWidth";
// state = { count: 0 }
export default function Example5() {
const [count, setCount] = useState(0);
const [name, setName] = useState("Mark");
const width = useWindowWidth();
return (
<div>
<h2>
{name} - {width}
</h2>
<p>You clicked {state.count} times</p>
<button onClick={this.click}>Click me</button>
</div>
);
function click() {
// setState => 두 번째 배열 요소
// setState({ count: state.count + 1 }); // 이렇게 도 사용할 수 있고 함수로도 사용가능하다.
setState(count + 1);
}
}
이렇게 간단한 윈도우의 가로값을 가져오는 훅을 만들었다.
훅은 다른 훅에서 사용하거나 함수 컴포넌트에서만 사용할 수 있다.
custom훅은 useState와 useEffect로 이루어 진다.
하나 더 만들어 보자.
hoc로 만든 withHasMounted와 훅으로 만든 useHasMounted를 만들어서 비교해 보자.
처음에는 HOC를 만들자.
withHasMounted.js를 만들고 useHasMounted.js도 하나 파일을 만들자.
import React from "react";
export default function withHasMounted(Component) {
class C extends React.Component {
state = {
hasMounted: false,
};
render() {
return <Component {...this.props} hasMounted={this.state.hasMounted} />;
}
componentDidMount() {
this.setState({ hasMounted: true });
}
}
return C;
}
Example6.jsx에서 써보자.
import { useState } from "react";
import useWindowWidth from "../hooks/useWindowWidth";
import withHasMounted from "../hocks/withHasMounted";
function Example6(props) {
const [count, setCount] = useState(0);
const [name, setName] = useState("Mark");
const width = useWindowWidth();
return (
<div>
<h2>
{name} - {width} - {props.hasMounted.toString()}
</h2>
<p>You clicked {state.count} times</p>
<button onClick={this.click}>Click me</button>
</div>
);
function click() {
setState(count + 1);
}
}
// state = { count: 0 }
export default withHasMounted(Example6);
이렇게 만든 아이를 훅으로 만들자.
export default function useHasMounted() {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
return hasMounted;
}
이렇게 하면 훅이 만들어 진다.
import { useState } from "react";
import useWindowWidth from "../hooks/useWindowWidth";
import withHasMounted from "../hocks/withHasMounted";
function Example6(props) {
const [count, setCount] = useState(0);
const [name, setName] = useState("Mark");
const width = useWindowWidth();
const hasMounted = useHasMounted();
return (
<div>
<h2>
{name} - {width} - {props.hasMounted.toString()}
</h2>
<p>You clicked {state.count} times</p>
<button onClick={this.click}>Click me</button>
</div>
);
function click() {
setState(count + 1);
}
}
// state = { count: 0 }
export default withHasMounted(Example6);
이렇게 보면 차이점이 드러난다. hoc를 사용할 때마다 컴포넌트를 감싸서 하나의 컴포넌트를 만든다. 즉 많이 사용하면 컴포넌트가 계속 랩핑된다. 그래서 새로운 용어가 나왔다. 예전에 콜백 핼이라고 불렸던 것 처럼 랩핑 핼이 나왔다.
이와 다르게 훅은 다르게 랩핑을 하는 일이 없이 하나의 state를 사용하는데 로직만 따로 분리를 해서 간편하게 useHasMounted에 옮겨 놓았다. 그래야 HOC를 떠나서 hooks로 오는 것이다.
왜 hoc보다 더 hooks를 더 선호하냐?
HOC를 사용하면 변화되는 데이터를 프롭스로 관리하는데 그 프롭스를 위에서 계속 컴포넌트를 랩핑하는 과정이 생긴다.
그 과정에서의 복잡도도 증가하고 불필요한 랩핑으로 인해서 디버깅도 힘들다.