이번 포스팅에서는 함수형 컴포넌트 사용시 유용한 리액트 훅과 구조분해 할당에 대해서 다뤄보겠습니다. 👀
클래스 컴포넌트는 멤버변수 및 함수를 클래스 최초 생성시 한 번만 선언하고, 이후에 재선언하지 않는다.
(state나 props에 따라 render 함수만 호출됨)
반면, 함수 컴포넌트는 결국 함수이기 때문에 컴포넌트 내 코드블럭 전체가 계속 반복적으로 호출된다. 즉 로컬변수, 콜백함수 등 매번 새로 생성하고 새로운 참조값을 전달하게 된다.
이를 방지하기 위해선 어떻게 해야할까? 바로 리액트 훅을 사용하면 된다. state는 useState
로, 멤버함수는 useCallback
으로, ref는 useRef
를 이용하는 등 값을 메모리에 저장해 코드블록이 반복적으로 호출되지 않게 즉, 재사용이 가능하도록 만들 수 있다.
리액트 훅은 16.8 버전에 새로 추가된 것으로, 함수 컴포넌트에서 React state와 Lifecycle methods를 연동(hook into)할 수 있게 해주는 함수 이다. Hook은 class안에서는 동작하지 않으며, class 없이 React를 사용할 수 있게 해주는 함수이다.
Hook에는 내장 Hook과 커스텀 Hook이 있는데 여기선 내장 Hook만 다뤄보겠다.
아래 예시를 통해 함수 컴포넌트에 state을 추가하는 방법을 알아보자.
import React, { useState } from 'react';
// useState을 import 해야 함
function Example() {
// "count"라는 새 상태 변수를 선언
const [count, setCount] = useState(0);
const handleIncrement=()=>{
setCount(count+1);
};
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleIncrement}>
Click me
</button>
</div>
);
}
새로운 상태 변수를 선언할 때는 아래와 같이 하고,
const [상태변수이름,set상태변수이름]=useState(상태변수초기값);
상태를 업데이트 할 때 set상태변수이름()
을 클래스 컴포넌트의 this.setState()
사용하듯 쓰면 된다.
🔍특징
this.state
와 달리 setState
Hook의 state 초기값은 객체일 필요가 없다.this.setState
와는 달리 이전 state와 새로운 state를 합치지 않는다. 즉, state 변수를 업데이트할 때 업데이트된 필드를 객체에 병합하는 class의 this.setState와 달리 useState는 그 값을 대체한다.📍__예시
function ExampleWithManyStates() {
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
}
Effect Hook, 즉 useEffect는 함수 컴포넌트 내에서 side effects를 수행할 수 있게 해준다. 이는 클래스 컴포넌트의 componentDidMount
, componentDidUpdate
, componentWillUnmount
의 기능들을 하나의 API로 통합한 것이다.
🔍 Side Effects란? ( = Effects )
React 컴포넌트 안에서 데이터를 가져오거나 구독하고, DOM을 직접 조작하는 등의 동작들을 말한다. 이는 다른 컴포넌트에 영향을 주기도 하고, 렌더링 과정에서는 구현할 수 없는 작업들이다.
📍__예시 1
마운트 될 때, 그리고 state나 props가 업데이트 될 때마다 호출하는 방법
→ componentDidMount
+componentDidUpdate
처럼 사용가능
useEffect(()=>{
// do something...
})
📍__예시 2
마운트 될 때만 호출하는 방법 → componentDidMount
처럼 사용가능
useEffect(()=>{
// do something...
},[]);
// 🌟빈 배열을 두 번째 인자로 전달
📍__예시 3
마운트 될 때, 그리고 두 번째 인자(=디펜던시)로 전달받은 state가 업데이트 될 때마다 호출하는 방법
→ componentDidMount
+componentDidUpdate
처럼 사용가능
useEffect(()=>{
// do something...with some states or props
},[count]);
// 🌟여러가지 전달 가능 [state1, state2, ...]
보통 useEffect
내에서 쓰이는 state
나 props
는 []
안에 전달하는 것이 좋다.
📍__예시 4
언마운트될 때, 콜백함수를 자동으로 호출하는 방법
→ componentWillUnmount
처럼 사용가능
useEffect(()=>{
// do something...
return ()=> {
// do something...
};
});
return
에 콜백 함수를 등록해 놓으면 React가 알아서 언마운트시 호출해준다.
📍__참고
하나의 컴포넌트 내에서 여러 개의 effect를 사용 가능하다.
const [count, setCount] = useState(0);
useEffect(() => {
// do something...
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
// do something...
return () => {
// do something...
};
});
앞서 말했듯이, 함수형 컴포넌트에서는 리렌더링될 때 선언된 멤버변수 및 함수가 재선언된다. 이러한 특성으로, 콜백함수를 props로 받는 자식 컴포넌트가 있을 경우 새로운 참조값이 전달되게 돼 자식 컴포넌트 또한 리렌더링이 일어나게 된다. 이를 막기 위해 쓰는 것이 useCallback
이다.
useCallback
은 한 번 만들어진 함수를 캐시하는 것이라 메모리가 소모된다. 따라서 남발하면 안되므로 자식 컴포넌트에 props로 전달되어지는 함수에만 사용 하고, 그외에 일반 div나 button 등과 같은 말단 요소에 전달되어지는 콜백일 경우 성능에 문제가 없으므로 사용하지 않는 것이 좋다.
📍__예시
const Example=({data})=>{ //이 부분이 생소하다면 3.destructurting 부분을 보자.
const [count,setCount]=useState(0);
...
const handleIncrement=useCallback(()=>{
setCount(count+1);
data.add();
},[data]);
return(
<Cart onIncrement={handleIncrement} count={count} />
);
}
export default Example;
useCallback()
안에서 컴포넌트가 받아온 props를 쓸 경우(여기선, data) 디펜던시를 전달하지 않으면 이전의 props(업데이트X)를 계속 사용하게 된다. 따라서, 이러한 경우 두 번째 인자로 의존성 배열(디펜던시)를 꼭 전달해 업데이트 되도록 하자.
함수형 컴포넌트는 호출될 때마다 React.createRef
가 있을 경우 계속 새로운 참조값을 만들어 할당하게 되므로, useRef
를 사용해 방지하도록 하자. ( 용도는 React.creatRef
와 비슷함 )
const Example=()=>{
const inputRef=useRef();
...
return(
<input ref={inputRef} type="text"/>
);
}
export default Example;
React.createRef
때와 같이 inputRef.current.
으로 접근해 사용하면 된다.
Hook은 그냥 JavaScript 함수이지만, 두 가지 규칙을 준수해야 한다.
이로써 React Hook에 대해 어느정도 다뤘다. 더 자세한 것은 공식문서를 참고하면서 필요할 때마다 공부하도록 하자.
구조 분해 할당 구문은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식이다. 자세한 내용은 이곳에서 확인해보고, 여기서는 함수형 컴포넌트에서 어떻게 쓰면 좋을 지에 대해서 다루겠다.
먼저 예시가 될 함수 컴포넌트를 생성해보면, 아래와 같다.
import React from 'react';
const Example = (props) => {
return(
<h1>Hello, world!</h1>
);
};
export default Example;
❗ 참고
function
으로도 생성이 가능하다.function Example(props){ return( <h1>Hello, world!</h1> ); };
위의 컴포넌트가 아래와 같은 props를 부모로부터 전달 받았다고 가정해보자. 그러면 Example 컴포넌트 내에서 props.onAdd
와 같이 접근해 사용할 수 있을 거다.
<Example onAdd={handleAdd} user={user.name}/>
하지만 매번 props.onAdd
, props.user
....처럼 쓰는 것이 번거롭다면 props 대신 ({onAdd, user})
로 작성하면 props.
를 때고 그냥 onAdd
, user
이런식으로 접근이 가능해진다.
import React from 'react';
const Example = ({onAdd, user}) => {
return(
<h1>Hello, {user}!</h1>
);
};
export default Example;
만약 받아온 props
를 다른 이름으로 사용하고 싶다면 ({user:userName,handleAdd})
와 같이 :
를 이용해 새 이름을 정의해 쓸 수 있다. 아래 코드에선 user가 userName으로 변경되어 사용되고 있다.
(handleAdd
는 재정의 하지 않았으므로 그대로 사용됨)
import React from 'react';
const Example = ({onAdd, user:userName}) => {
const handleAdd=()=>{
console.log(`${userName} added!`);
onAdd();
};
return(
<div>
<h1>Hello, {userName}!</h1>
<button onClick={handleAdd}>Add</button>
</div>
);
};
export default Example;