드디어 오늘부터 hooks를 배우고 알게 된 내용들을 하나씩 기록해보려 한다.
여기에 기록될 내용들은 노마드코더의 실전형 리액트 Hooks 무료강의, velopert님의 리액트의 Hooks 완벽 정복하기, React Hooks 공식문서를 바탕으로 나만의 Hooks를 만들어보며 배우게 된 내용들을 적을 예정이다.
먼저 리액트 hooks 공식문서의 Motivation 을 보면
컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어렵습니다.
복잡한 컴포넌트들은 이해하기 어렵습니다.
Class은 사람과 기계를 혼동시킵니다.
라는 클래스 컴포넌트의 문제때문에 class 없이 React 기능들을 사용할 수 있도록 하는 방법을 권장하는것 같다. 나는 위 같은 문제를 겪어 본 것도 있고 아직 경험하지 못한 것도 있지만 클래스 컴포넌트 방식과 함수형 컴포넌트 방식의 장단점을 알아야 나중에 내가 개발할 서비스와 개발환경에 적절히 쓸 수 있을거라 생각했다.
Hooks의 기원부터 살펴보면 Hooks는 recompose 라는 라이브러리에서 시작되었다고 한다.
위 링크에 들어가보면 recompose의 사용 방법이 지금의 hooks와 매우 유사한데 이것을 React팀이 인수하여 지금의 Hooks가 릴리즈 되었다고 한다.
useState는 함수 컴포넌트 안에서 state를 사용할 수 있게 해준다.
useState로 넘겨주는 인자로 state의 초기 값을 설정해줄 수 있다.
state는 클래스와 달리 객체일 필요는 없고, 숫자 타입과 문자 타입을 가질 수 있다.
useState는 state 변수, 이 변수를 갱신할 수 있는 함수 두가지를 반환한다.
// 기본적으로 아래와 같이 사용한다.
const [count, setCount] = useState(0);
const App = () => {
const [count, setCount] = useState(1);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return (
<div className="App">
<h1>Hello! {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
위와 같이 count를 증가/감소 할 수 있는 함수 컴포넌트를 만들어 봤다.
처음으로 hooks를 사용해 본 느낌은 this를 생각하지 않아도 된다는 것과 코드 라인이 줄어든다는 것이다. 굉장히 보기에 간단하다.
class App extends Component {
constructor(props) {
super(props)
this.state = {
count: 1
}
}
incrementCount = () => {
const { count } = this.state;
this.setState({ count: count + 1 });
};
decrementCount = () => {
const { count } = this.state;
this.setState({ count: count - 1 });
};
render() {
const { count } = this.state;
return (
<div className="App">
<h1>Hello Component! {count}</h1>
<button onClick={this.incrementCount}>incrementCount</button>
<button onClick={this.decrementCount}>decrementCount</button>
</div>
);
}
}
위는 함수 컴포넌트로 작성했던 코드를 클래스 컴포넌트 방식으로 작성한 경우다. 로직이 간단하여 이것도 코드량이 많은 편은 아니지만 state의 초기화, this를 사용해 state객체 접근, 이에 따른 디스트럭쳐링 등 생각해야 할 부분들이 많다.
useEffect는 컴포넌트가 렌더링 될때와 업데이트 될때
즉, 클래스형 컴포넌트의 componentDidMount, componentDidUpdate와 같은 역할을 한다.
useEffect의 첫번째 인자는 function, 두번째 인자는 dependency다.
dependency란, 두번째 인자로 받은 값이 변할 때 useEffect가 실행되는 것이다.
// 기본적으로 아래와 같이 사용할 경우 컴포넌트 초기 렌더시에 "Hello"가 콘솔에 찍힌다.
const App = () => {
useEffect(() => {
console.log("Hello!");
});
return (
<div className="App">
</div>
);
componentDidUpdate(prevProps, prevState) {
if (prevProps.value !== this.props.value) {
doSomething();
}
}
만약 어떤 특정 값이 변경이 될 때만 호출되게 하고 싶을 때 클래스형 컴포넌트라면 위와 같이 작성 했을 것 이다.
const App = () => {
const [count, setCount] = useState(0);
const [number, setNumber] = useState(100);
useEffect(() => {
console.log("Hello");
}, [count]);
return (
<div className="App">
<button onClick={() => setCount(count + 1)}>count증가</button>
<button onClick={() => setNumber(count + 1)}>number증가</button>
</div>
)
};
위와 같이 작성할 때 useEffect는 초기 렌더시, 배열 형태로 dependency로 넘겨준 count가 변경될 때만 실행된다.
때문에 number가 변경 될때는 useEffect가 실행되지 않는다. 만약 빈 배열을 넘겨준다면 useEffect는 초기에만 실행되고 어떠한 값을 변경 하더라도 실행되지 않을 것 이다.
만약 컴포넌트가 언마운트 되기 전이나, 업데이트 되기 직전에 어떠한 작업을 수행하고 싶다면 useEffect에서 뒷정리 함수(cleanup)를 반환해줘야 한다.
(아래 코드는 velopert님 블로그 예제 참조)
import React, { useState } from 'react';
import Counter from "./Counter";
import Info from "./Info";
const App = () => {
const [visible, setVisible] = useState(false);
return (
<div>
<button
onClick={() => {
setVisible(!visible);
}}
>
{visible ? "숨기기" : "보이기"}
</button>
<br />
{visible && <Info />}
</div>
);
};
export default App;
import React, { useState, useEffect } from "react";
const Info = () => {
const [name, setName] = useState("");
const [nickname, setNickname] = useState("");
useEffect(() => {
console.log("effect");
console.log(name);
return () => {
console.log("cleanup");
console.log(name);
};
});
const onChangeName = e => {
setName(e.target.value);
};
const onChangeNickname = e => {
setNickname(e.target.value);
};
return (
<div>
<div>
<input value={name} onChange={onChangeName} />
<input value={nickname} onChange={onChangeNickname} />
</div>
<div>
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임: </b>{nickname}
</div>
</div>
</div>
);
};
export default Info;
위 코드로 실행 시 Info 컴포넌트가 보일 때 "effect", 사라질 때 "cleanup" 로그가 찍힌다.
또한, name에 value를 입력 시(렌더링이 될 때마다) effect와 cleanup이 모두 찍히는데 뒷정리 함수는 업데이트 되기 직전의 값을 보여준다.
여기서도 뒷정리 함수가 언마운트시에만 호출되게 하려면 빈 배열[]을 전달해주면 된다.
hooks를 처음 써보았는데 확실히 간단하고 명료한 느낌이다. 하지만 이게 정말 클래스보단 좋은가? 하는 질문에는 많은 코드를 작성해보고 여러 lifecycle 방식 대용으로 써보고 redux까지 붙여 큰 로직을 구현해봐야 알 수 있을것 같다. 아직까지 클래스 컴포넌트 방식이 익숙하기도 하고...
그렇다고 '클래스 방식이 나쁘다, 함수형 컴포넌트가 백번 좋다'. 가 아닌 각각의 장단점이 있다고 생각한다. 그 각각의 장단점을 정확히 알고 실무에서 쓸 수 있도록 성장하기 위해 지금 배움을 계속 이어나가야 할 것 같다.