Motivation
LifeCycle 기반의 로직은, 관련 없는 로직이 섞일때가 많음. 버그 발생의 원인이 되기도 하고, 무결성이 쉽게 깨지는 문제가 있었음. 상태 관련 로직이 모든 공간에 있다보니 관리가 어려워져서 상태 관리 라이브러리들을 사용하기 시작함. 그러나 상태 관리 라이브러리는 추상화가 너무 많거나, 여러 파일들 사이에 연관을 갖다보니 컴포넌트 재사용성이 여전히 떨어진다.
Class가 혼동을 주는 문제도 발견됨. javascript의 this 작동 개념이 다른 언어들과 달라서 혼동을 주기도 함. props, state, top-down등의 데이터 흐름을 알아야 함. Class 컴포넌트들을 구별하고 각 요소를 언제 사용하는지에 대한 의견도 정리되지 않음. Hot Reloading을 지원하는 것도 불안정함.
Hook은 기존의 class와 lifeCycle 함수를 완전히 대체하는 것이 아닌, 더 나은 새로운 형식을 제공하고자 함.
useState
import React, { useState } from 'react';
const Counter = () => {
const [value, setValue] = useState(0);
return (
<div>
<p>COUNT : <b>{value}</b></p>
<button onClick={() => setValue(value + 1)}>+1</button>
</div>
);
};
exprot default Counter;
- 상태 관리 Hook
- 배열 첫번째 값은 상태값, 두번째는 원소 상태 설정 함수
- useState()에 값을 넣어서 초기값 할당
useEffect
import React, { useState, useEffect } from 'react';
const Info = () => {
const [name, setName] = useState('');
const [message, setMessage] = useState('');
useEffect(() => {
console.log({name, message);
}); // }, []); : 마운트될 때만 실행, }, [name]); : name 값이 변경될 때만 실행
const onChangeName = e => {
setName(e.target.value);
};
const onChangeMessage = e => {
setMessage(e.target.value);
};
return (
<div>
<div>
<input value={name} onChange={onChangeName} />
<input value={message} onChange={onChangeMessage} />
</div>
<div>
<p><b>이름 :</b> {name}</p>
<p>{message}</p>
</div>
</div>
);
};
exprot default Info;
- 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정
useReducer
현재상태, 업데이트를 위해 필요한 정보를 담은 액션값을 전달받아 새로운 상태를 반환하는 함수. 새로운 상태를 만들때는 불변성을 지켜줘야 한다.
import React, { useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { value: state.value + 1 };
case "DECREMENT":
return { value: state.value - 1 };
default:
state;
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { value: 0 });
return (
<div>
<p> COUNT : {state.value}</p>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-1</button>
</div>
);
};
export default Counter;
- dispatch : 액션을 발생시키는 함수. 함수 파라미터로 액션 값을 넣어 주면 리듀서 함수가 호출됨
- 업데이트 로직을 컴포넌트 밖으로 빼낼 수 있다!!
import React, { useReducer } from 'react';
function reducer(state, action) {
return {
...state,
[action.name]: action.value;
}
}
const Info = () => {
const [state, dispatch] = useReducer(reducer, {name: '', message: ''});
const {name, message} = state;
const onChange = e => {
dispatch(e.target);
}
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="message" value={message} onChange={onChange} />
</div>
<div>
<p><b>이름 :</b> {name}</p>
<p>{message}</p>
</div>
</div>
);
};
exprot default Info;
- 인풋이 여러개인 경우 reducer를 사용해 한번에 관리할 수 있음
useMemo
- 함수형 컴포넌트 내부의 연산 최적화
- 특정 값이 변경되었을 때에만 연산을 실행하게 설정
import React, {userState, useMemo} from 'react';
const getSum = numbers => {
if(number.length === 0) return 0;
return numbers.reduce((a, b) => a + b);
}
cosnt Sum = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = e => {
setNumber(e.target.value);
}
const onInsert = () => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
}
const sum = useMemo(() => getSum(list), [list]);
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
합계: {sum}
</div>
</div>
)
};
export default Sum;
- useMemo를 사용해서, list가 변경되었을 때에만 합계 연산을 수행하게 함
useCallback
- 컴포넌트가 렌더링될 때마다 함수들이 새로 생성되므로, 특정 시점에서만 생성되게 설정해서 최적화
- useMemo()에서 함수를 반환하는 것과 같은 효과
import React, {userState, useMemo, useCallback} from 'react';
const getSum = numbers => {
if(number.length === 0) return 0;
return numbers.reduce((a, b) => a + b);
}
cosnt Sum = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []); //[]: 컴포넌트가 처음 렌더링될 때만 함수 생성
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
}, [number, list]); //number 또는 list 가 변경되었을 때에만 함수 생성
const sum = useMemo(() => getSum(list), [list]);
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
합계: {sum}
</div>
</div>
)
};
export default Sum;
useRef
- 함수형 컴포넌트에서 ref 설정
import React, {userState, useMemo} from 'react';
const getSum = numbers => {
if(number.length === 0) return 0;
return numbers.reduce((a, b) => a + b);
}
cosnt Sum = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = e => {
setNumber(e.target.value);
}
const onInsert = () => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
}
const sum = useMemo(() => getSum(list), [list]);
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
합계: {sum}
</div>
</div>
)
};
export default Sum;
- useMemo를 사용해서, list가 변경되었을 때에만 합계 연산을 수행하게 함
useCallback
- 컴포넌트가 렌더링될 때마다 함수들이 새로 생성되므로, 특정 시점에서만 생성되게 설정해서 최적화
- useMemo()에서 함수를 반환하는 것과 같은 효과
import React, {userState, useMemo, useCallback, useRef} from 'react';
const getSum = numbers => {
if(number.length === 0) return 0;
return numbers.reduce((a, b) => a + b);
}
cosnt Sum = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const inputEl = useRef(null);
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []); //[]: 컴포넌트가 처음 렌더링될 때만 함수 생성
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
inputEl.current.focus();
}, [number, list]); //number 또는 list 가 변경되었을 때에만 함수 생성
const sum = useMemo(() => getSum(list), [list]);
return (
<div>
<input value={number} onChange={onChange} ref={inputEl} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
합계: {sum}
</div>
</div>
)
};
export default Sum;
Custom Hook
- 여러 컴포넌트에서 재사용되는 기능을 구현할 경우 Hook으로 작성해서 로직 재사용
import {useReducer} from 'react';
function reducer(state, action) {
return {
...state,
[action.name]: action.value
};
}
export default function useInputs(initialForm) {
const [state, dispatch] = useReducer(reducer, initialForm);
const onChange = e => {
dispatch(e.target);
};
return [state, onChange];
}
import React from 'react';
import useInputs from './useInputs';
const Info = () => {
const [state, onchange] = useInputs){
name: '',
message: ''
};
const {name, message} = state;
return (...);
}