훅(Hook)이란?
State 를 이용하여 렌더링에 필요한 데이터를 관리하게 되는 것을 살펴 보았다.
클래스 컴포넌트에서는 생성자에서 state를 정의 하고 setState 함수를 통해 state 를 업데이트 하게 된다.
하지만 기존 함수 컴포넌트는 이러한 state를 정의해서 사용하거나 컴포넌트 생명주기에 맞춰 실행 되도록 할 수 없기 때문에 나온것이 바로 훅(Hook) 이다.
Hook 이란 갈고리 라는 뜻을 의미하는데 보통 프로그래밍에서 '원래 존재하는 어떤 기능에 갈고리를 거는 것 처럼 끼어 들어가 같이 수행하는 것' 을 의미한다.
마찬가지로 리액트에서도 'state와 생명주기 기능에 갈고리를 걸어 원하는 시점에 정해진 함수를 실행 되도록 만든 것'이다.
이러한 훅의 이름은 모두 use 로 시작한다.
다음으로 훅들에 대해 하나씩 알아 보자.
useState
useState는 state를 사용하기 위한 훅이다.
다음의 예를 통해 확인해 보자.
import React,{useState} from 'react';
function Counter(props){
var count = 0;
return (
<div>
<p> 총 {count} 번 클릭</p>
<button onClick={()=>count++}>클릭</button>
</div>
)
}
ReactDOM.render(
<Counter />,
document.getElementById('root')
)
이렇게 했을 때 위와 같은 화면에서 클릭 버튼을 클릭하게 되면 count 는 증가 하지만 state 가 변경 되지 않아 재랜더링 되지 않는다.
이럴때 useState()를 사용하게 된다.
useState는 다음과 같이 사용한다.
const[변수명,set함수명]=useState(초깃값);
따라서 다음과 같이 수정을 해 보자.
import React,{useState} from 'react';
function Counter(props){
const [count,setCount]=useState(0);
return (
<div>
<p> 총 {count} 번 클릭</p>
<button onClick={()=>setCount(count+1)}>클릭</button>
</div>
)
}
ReactDOM.render(
<Counter />,
document.getElementById('root')
)
구름 IDE 에서 실행 할때 Type Error 가 뜬다.
react 버젼이 낮아서 에러가 뜰 수 있다.
react를 다음 명령어로 업데이트 하자.
터미널에서 다음을 입력해서 설치 해 준다.
npm add react@next react-dom@next
그렇게 하면 다음과 같이 정상적으로 수행 되는 것을 확인 할 수 있다.
클릭 할 때마다 바로 갱신이 된다.
useEffect
사이드이펙트를 수행하기 위한 훅이다.
여기서 사이드이펙트는 사전적인 의미로는 부작용 이라는 뜻을 가지고 있다.
즉 개발자가 의도치 않은 코드가 실행되면 버그가 발생하는데 이때 사이드이펙트가 발생했다고 한다.
useEffect 는 리액트의 함수 컴포넌트에서 사이드이펙트를 실행 할 수 있도록 해 주는 훅으로 사용법은 다음과 같다.
useEffect(이펙트함수,의존성배열);
의존성 배열안의 변수 중에서 하나라도 값이 변경 되었을 때 이펙트 함수가 실행 된다.
이펙트 함수는 컴포넌트가 렌더링 된 후와 업데이트로 인한 재렌더링 이후에 실행 되는데 마운트와 언마운트시 한번만 수행되게 하고 싶으면 의존성 배열에 빈배열([]) 을 넣으면 된다.
예제코드를 살펴 보자.
function Counter(props){
const [count,setCount]=useState(0);
useEffect(()=>{
document.title=`총 ${count}번 클릭했습니다.`
}
);
return (
<div>
<p> 총 {count} 번 클릭</p>
<button onClick={()=>setCount(count+1)}>클릭</button>
</div>
)
}
ReactDOM.render(
<Counter />,
document.getElementById('root')
)
document.title 은 브라우저에서 제공하는 API를 사용해서 document의 title을 업데이트 한다.
위 코드처럼 의존성 배열 없이 useEffect()를 사용하면 리액트는 DOM이 변경된 이후에 해당 이펙트 함수를 실행하라는 의미로 받아 들여 진다.
여기서는 useEffect() 함수를 이용하여 componentDidMount(),componenetDidUpdate() 와 같은 생명주기 함수의 기능을 수행하게 된다
useMemo
useMemo는 Memoized value를 리턴하는 훅이다.
리액트에서 컴포넌트가 리렌더링 될 때 변화가 없는 부분도 리렌더링 된다면 자원이 낭비되고 성능에도 문제가 생기게 됩니다. 이러한 상황에서 useMemo를 사용 시 의존성 배열의 값에 아무런 변화가 없다면 이전에 연산된 값인 memorized value를 리턴하고 불필요한 연산을 없애 성능을 최적화할 수 있습니다.
사용법을 살펴 보면 다음과 같습니다.
const getNumber = (number) => {
console.log("숫자가 변동되었습니다.");
return number;
};
const getText = (text) => {
console.log("글자가 변동되었습니다.");
return text;
};
const ShowState = ({ number, text }) => {
const showNumber = getNumber(number);
const showText = getText(text);
return (
<div className="info-wrapper">
{showNumber} <br />
{showText}
</div>
);
};
const App = () => {
const [number, setNumber] = useState(0);
const [text, setText] = useState("");
const increaseNumber = () => {
setNumber((prevState) => prevState + 1);
};
const decreaseNumber = () => {
setNumber((prevState) => prevState - 1);
};
const onChangeTextHandler = (e) => {
setText(e.target.value);
};
return (
<div className="App" style={{ margin: "50px" }}>
<div>
<button onClick={increaseNumber}>+</button>
<button onClick={decreaseNumber}>-</button>
<input
type="text"
placeholder="Text..."
onChange={onChangeTextHandler}
/>
</div>
<ShowState number={number} text={text} />
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById('root')
)
숫자나 글자가 변경되었을 때마다 둘 다 출력 되는 것을 확인 할 수 있다.
이것은 불필요한 자원을 낭비하고 있다는 증거가 된다.
useMemo 사용법
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo를 사용해서 memoizedValue라는 memorization 될 값을 선언하고 의존성 배열에 a와 b값을 넣어 a, b의 변경 여부를 확인하며, 변경 시 computeExpensiveValue를 이용하여 연산된 값을 memoizedValue에 대입합니다
다음과 같이 useMemo 를 적용해 보자.
import React,{useState,useEffect,useMemo} from 'react';
import ReactDOM from 'react-dom';
import './index.css';
//import App from './App';
import * as serviceWorker from './serviceWorker';
const getNumber = (number) => {
console.log("숫자가 변동되었습니다.");
return number;
};
const getText = (text) => {
console.log("글자가 변동되었습니다.");
return text;
};
const ShowState = ({ number, text }) => {
const showNumber = useMemo(() => getNumber(number), [number]);
const showText = useMemo(() => getText(text), [text]);
return (
<div className="info-wrapper">
{showNumber} <br />
{showText}
</div>
);
};
const App = () => {
const [number, setNumber] = useState(0);
const [text, setText] = useState("");
const increaseNumber = () => {
setNumber((prevState) => prevState + 1);
};
const decreaseNumber = () => {
setNumber((prevState) => prevState - 1);
};
const onChangeTextHandler = (e) => {
setText(e.target.value);
};
return (
<div className="App" style={{ margin: "50px" }}>
<div>
<button onClick={increaseNumber}>+</button>
<button onClick={decreaseNumber}>-</button>
<input
type="text"
placeholder="Text..."
onChange={onChangeTextHandler}
/>
</div>
<ShowState number={number} text={text} />
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById('root')
)
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
숫자가 변경되면 숫자만 변경되는 이벤트가 발생 되는 것을 확인 할 수 있습니다.
useCallback
useCallback는 useMemo()와 유사한 역할을 수행한다. 다만 값이 아닌 함수를 반환하는 부분이 다르다.
useMemo는 특정 결괏값을 재사용할 때 사용하는 반면, useCallback은 특정 함수를 새로 만들지 않고 재사용하고 싶을 때 사용하는 함수입니다.
사용법
const memoizedCallback = useCallback(function, deps);
침실,주방,욕실의 불을 끄고 켜는 프로그램을 확인해 봅니다.
import React,{useState,useEffect,useMemo} from 'react';
import ReactDOM from 'react-dom';
import './index.css';
//import App from './App';
import * as serviceWorker from './serviceWorker';
function Light({ room, on, toggle }) {
console.log({ room, on });
return (
<div>
<button onClick={toggle}>
{room}
{on ? "💡" : "⬛"}
</button>
</div>
);
}
function SmartHome() {
const [masterOn, setMasterOn] = useState(false);
const [kitchenOn, setKitchenOn] = useState(false);
const [bathOn, setBathOn] = useState(false);
const toggleMaster = () => {
setMasterOn(!masterOn);
};
const toggleKitchen = () => {
setKitchenOn(!kitchenOn);
};
const toggleBath = () => {
setBathOn(!bathOn);
};
return (
<div>
<Light room="침실" on={masterOn} toggle={toggleMaster}></Light>
<Light room="주방" on={kitchenOn} toggle={toggleKitchen}></Light>
<Light room="욕실" on={bathOn} toggle={toggleBath}></Light>
</div>
);
}
const App = () => {
return (
<div style={{ position: "absolute", top: "50%", left: "50%" }}>
<SmartHome />
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById('root')
)
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
참고 : (https://cocoon1787.tistory.com/798?category=973781)
침실만 켜도 세개의 이벤트가 발생합니다.
function Light({ room, on, toggle }) {
console.log({ room, on });
return (
<div>
<button onClick={toggle}>
{room}
{on ? "💡" : "⬛"}
</button>
</div>
);
}
Light = React.memo(Light);
function SmartHome() {
const [masterOn, setMasterOn] = useState(false);
const [kitchenOn, setKitchenOn] = useState(false);
const [bathOn, setBathOn] = useState(false);
const toggleMaster = useCallback(() => {
setMasterOn(!masterOn);
}, [masterOn]);
const toggleKitchen = useCallback(() => {
setKitchenOn(!kitchenOn);
}, [kitchenOn]);
const toggleBath = useCallback(() => {
setBathOn(!bathOn);
}, [bathOn]);
return (
<div>
<Light room="침실" on={masterOn} toggle={toggleMaster}></Light>
<Light room="주방" on={kitchenOn} toggle={toggleKitchen}></Light>
<Light room="욕실" on={bathOn} toggle={toggleBath}></Light>
</div>
);
}
const App = () => {
return (
<div style={{ position: "absolute", top: "50%", left: "50%" }}>
<SmartHome />
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById('root')
)
이때 Light 는 React.memo 타입으로 감싸 주어야 props 값이 변경되니 않는 한 다시 호출되지 않습니다.
실행해 보면 주방을 켰을 때 주방의 이벤트만 발생하는 것을 알 수 있습니다.
훅의 규칙
훅은 단순한 자바스크립트 함수이지만 다음 두가지 지켜야 할 규칙이 있다.
첫번째규칙 : 훅은 무조건 최상위 레벨에서만 호출해야 한다.(따라서 반복문,중첩된 함수안에서 호출하면 안된다.)
두번째규칙 : 리액트 함수 컴포넌트에서만 훅을 호출해야 한다.(일반적인 자바스크립트 함수에서 훅을 호출하면 안된다.)
참고
소플의 처음 만난 리액트
'웹프로그래밍 > react' 카테고리의 다른 글
2. visual studio code 설치 후 React 소스코드 작성하기 (0) | 2024.10.07 |
---|---|
1. Create React App로 리액트 시작하기 (0) | 2024.10.07 |
5. State와 생명주기 (0) | 2022.06.14 |
4. 컴포넌트와 Props (0) | 2022.06.14 |
3. 엘리먼트란 (0) | 2022.06.13 |