리액트를 깊게 공부한 것이 아니라 잘은 모르겠으나 지금까지 알고 있는 내용을 정리하면 이렇다. 리액트는 지금 함수형 컴포넌트를 밀어주고 있는데 과거에는 클래스형 컴포넌트가 주류였다. 왜냐하면 함수형 컴포넌트에서 할 수 있는 것들이 제한적이기 때문이다. 예를 들면 컴포넌트 라이프사이클 메서드는 클래스형 컴포넌트에서만 사용이 가능하다.
후킹 - 위키백과 를 참고하면 Hook이 의미하는 바를 이해할 수 있다. 리액트에서는 props,state, lifecycle 등 컴포넌트와 관련된 내용을 함수형 컴포넌트에서 다루는 API를 Hook이라고 부르는 것 같다. 일단은 요정도만 이해하고 넘어가려고 한다.
이해를 위해서 자바/스프링 대입법을 사용하고 추론에 의해 작성된 내용이 많으니 틀린 내용이 있다면 지적 부탁드립니다.
useState
함수형 컴포넌트에서 상태를 관리할 때 사용한다. 리액트에서는 컴포넌트의 속성(props)과 더불어 상태라는 개념이 존재한다. 이게 참 자바/스프링을 공부하던 사람 입장에선 낯선 개념이다. 왜 그러냐하면 리액트의 컨셉 자체가 커스텀 태그(컴포넌트)를 만드는 것이다. 태그에는 속성이라는게 존재한다. 예를 들어 input 태그에는 value, name, classname 등 다양한 속성이 존재한다.
컴포넌트도 결국 태그기 때문에 속성이 존재한다. 이와 더불어 상태라는 것이 존재한다. 하지만 클래스 기반 언어인 자바에서는 컴포넌트에서 말하는 속성(props)이라는 개념은 아주 낯설다. 내가 아는 선에서 자바 진영에서는 리액트 진영의 속성이란 개념은 없다. 클래스는 상태(필드)와 행위(메서드)로 구성된다.
또 용어 사용의 차이가 존재한다. 자바 진영의 클래스를 설명할 때 상태라는 말이 사용되기는 한다. 예를 들면 객체지향의 사실과 오해에서는 상태라는 말을 주로 사용한다. 하지만 이게 주로 통용되는 용어는 아닌 것 같다. (물론 내가 아직 몰라서 그럴수도 있다.) 주로 필드, 프로퍼티, 멤버 변수 등의 용어를 사용한다.
이러다 보니 혼동이 올 수 밖에 없다. 첫째 자바에서는 리액트에서 말하는 속성이라는 개념이 존재하지 않는다.
둘째 두 진영에서 용어 사용의 차이가 존재한다. 리액트에서는 속성과 상태는 다른 개념이다. 그래서 속성을 상태라 부르지도, 상태를 속성이라 부르지도 않는다. 하지만 자바에서는 속성(property)이라는 용어는 상태(state)라는 용어와 동일하게 사용할 수도 있다.
서론이 길었는데 굳이 위 내용을 넣은 이유는 내 나름대로 헷갈리지 않기 위함이다. useState는 자바 진영의 게터/세터의 집합이다. 내부까지 들어가면 잘 모르겠는데 눈 앞에 보이는 내용으로 추론하면 상태에 접근(get)할 때는 상태에 직접 접근한다. 메서드를 사용하지 않는다는 의미이다. 자바로 치면 getXxx()가 아니라 this.name 이런식으로 접근하는 것 같다.
반면 설정(set)은 자바 프로퍼티 접근법과 유사하게 세터를 사용한다.
const [name, setName] = useState('');
사실 이러한 형태의 변수 선언과 할당도 자바를 공부한 사람 입장에서는 혼동이 일어날 수 밖에 없는 내용이다. 위 코드를 자바로 나타내면 아래와 같이 나타낼 수 있지 않을까 싶지 않다.
public class UseState {
public String name;
public void setName(String name) {
this.name = name;
}
public UseState() {
this.name = "";
}
}
구성 자체도 차이가 존재하기 때문에 헷갈리지만 더 혼동을 일으키는건 아래 두 가지 때문이 아닐까 싶다.
1. 배열에 변수를 담는건 자바 입장에선 생각보다 낯선 일이다.
2. 함수를 변수에 담는다는 개념 자체가 낯설다. 자바에서 이게 가능한가...?
정리해보면 자바 입장에서는 헷갈린다. 캬캬캬 useState는 게터/세터와 역할이 유사하다.
useEffect
useEffect는 리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook입니다. 스프링 대입법으로 치면 useEffect는 리스너와 하는 역할이 유사해 보인다. 클래스형 컴포넌트의 componetDidMount와 componentDidUpdate를 합친 형태로 봐도 무방하다고 한다.
// ... onChange 메서드
useEffect(() => {
console.log('렌더링이 완료됐습니다.')
console.log({
name,
nickname
});
})
onChange에 의해 랜더링 될 때 마다 useEffect가 동작한다.
만약 마운트될 때만 실행하고, 업데이트될 때는 실행하지 않으려면 useEffect 함수의 두 번째 파라마터로 비어 있는 배열을 넣어주면 된다.
마운트 될 때만 useEffect 실행
useEffect(() => {
console.log('렌더링이 완료됐습니다.')
console.log({name,nickname});
}, [])
특정 값이 업데이트될 때만 실행하기
useEffect(() => {
console.log('렌더링이 완료됐습니다.')
console.log({name,nickname});
}, [name])
이 경우 name state가 업데이트 될 때만 useEffect가 호출됩니다.
useReducer
useReducer는 useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트해 주고 싶을 때 사용하는 Hook입니다.
리듀서는 현재 상태, 그리고 업데이트를 위해 필요한 정보를 담은 액션 값을 전달받아 새로운 상태로 반환하는 함수입니다. 리듀서 함수에서 새로운 상태를 만들 때는 반드시 불변성을 지켜 주어야 합니다.
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return {value: state.value + 1 };
case 'DECREMENT':
return {value: state.value - 1 };
default:
return state;
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, {value:0});
};
useReducer 첫 번째 파라미터는 리듀서 함수를 넣고 두 번째 파라미터는 해당 리듀서의 기본 값을 넣습니다. useReducer는 state 값과 dispatch 함수를 받아올 수 있습니다.
state는 현재 상태
dispatch는 액션을 발생시키는 함수입니다. dispatch(action)과 같은 형태로 함수 안에 파라미터로 액션 값을 넣어주면 리듀서 함수가 호출되는 구조입니다.
const [state, dispatch] = useReducer(reducer, {value:0});
return (
<div>
<p>
현재 카운터 값은 <b>{state.value}</b>입니다.
</p>
<button onClick={() => dispatch({type: 'INCREMENT'})}>+1</button>
<button onClick={() => dispatch({type: 'DECREMENT'})}>-1</button>
<button onClick={() => dispatch({type: ''})}>초기화</button>
</div>
);
이런식으로 dispatch 안에 action.type을 설정해서 함수를 호출할 수 있습니다. useReducer를 사용했을 때 가장 큰 장점은 컴포넌트 업데이트 로직을 컴포넌트 바깥으로 빼낼 수 있다는 것입니다.
인풋 상태 관리하기
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
const onChangeName = (e) => {
setName(e.target.value);
}
const onChangeNickname = (e) => {
setNickname(e.target.value);
}
이름, 닉네임 등 인풋 창이 여러개라면 관리해야 하는 상태들도 많아지게 됩니다. useReducer를 이용하면 이를 조금더 효과적으로 유지할 수 있습니다.
const [state, dispatch] = useReducer(reducer, {
name : '',
nickname : ''
});
const { name, nickname } = state;
const onChange = (e) => {
dispatch(e.target);
}
인풋창이 추가되어 관리해야되는 상태가 추가된다면 추가되는 코드량도 useReducer가 적고 밀집도가 높기 때문에 유지보수하기도 편할 것입니다.
'Front-end' 카테고리의 다른 글
css 정리 (2) 포지션 (0) | 2023.06.28 |
---|---|
css 정리 - (1) inline & block level, box sizing (0) | 2023.06.28 |
리액트 - 컴포넌트 반복 (0) | 2023.06.14 |
리액트 - ref (0) | 2023.06.14 |
리액트 - 이벤트 핸들링 (0) | 2023.06.14 |