import React, { useState, useCallback, ...} from 'react';
class형에서 setState를 쓰는것과 같다.
const [state, setState] = useState(0)
useState의 인자에는 상태의 기본값을 넣어준다. 현재는 0이 기본값으로 들어있다.
함수는 호출되면 배열을 반환하는데, 배열의 첫번째 요소는 상태값, 두번째 요소는 상태를 설정하는 함수가 된다. 이 배열을 비구조화 할당으로 상태값과 함수를 각각 나눠서 사용한다.
하나의 useState 함수는 class의 setState함수와는 달리 하나의 상태값만 관리를 할 수 있기에 여러개를 관리해야 할 경우는 useState를 여러개 사용해주면 된다.
렌더링 성능을 최적화 하기 위해 사용하는 hooks
이벤트 핸들러 함수를 필요할때만 생성 할 수 있다
const Average = () => {
const onInsert = useCallback(
e => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
},
[number, list]
); // 자식에게 props를 내려주는 함수로써 useCallback를 감싸준다
return (
<div>
<input value={number} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값:</b> {avg}
</div>
</div>
);
};
export default Average;
자식 컴포넌트에서 props로 함수를 내려줄때는 useCallback로 함수를 감싸는것이 좋다.
첫번째 인자로는 생성하고싶은 함수를 넣어주고 두번째 인자로는 deps를 넣어준다
deps가 비어있다면 컴포넌트가 렌더링 될 때 한번만 함수가 실행되며,
값이 있는 경우엔 값이 변경될때만 함수가 실행된다.
함수 내에서 기존의 상태 값을 의존하는 경우엔 꼭 deps 안에 포함시켜줘야 한다.
리액트가 렌더링 될 때 마다 특정 작업을 수행하도록 설정 할 수 있는 Hook이다.
class형에서 componentDidMount, componentDidUpdate, componentWillUnmount를 합쳐놓은 형태로 보면 된다.
useEffect는 기본적으로 렌더링 되고 난 직후마다 실행된다.
useEffect(() => {
// 이 부분은 componentDidMount, componentDidUpdate와 같다
return () => { // return은 필수가 아님
// 이 부분은 componentWillUnmout와 대응된다.
}
},[]) // useEffect의 두번째 인자에 들어가는 부분은 deps라고 부른다
// deps에는 검사하고 싶은 값을 넣는다
// deps가 비어 있는 경우 componentDidMount만,
// 인자가 비어있지 않은 경우 componentDidUpdate까지 대응된다
deps 부분에 무언가 들어있다면, 마운트 될 때와 그 값이 리렌더링 될 때 마다 실행 된다고 생각하면 된다.
const mounted = useRef(false);
useEffect(() => {
if(!mounted.current) {
mounted.curret = true;
}
// ajax
}, [바뀌는 값])
함수 컴포넌트 내부에서 발생하는 연산을 최적화 하기 위한 hooks
const getAverage = numbers => {
console.log('평균값 계산중..');
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange = e => {
setNumber(e.target.value);
};
const onInsert = e => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
};
const avg = useMemo(() => getAverage(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>
<b>평균 값:</b> {avg}
</div>
</div>
);
};
export default Average;
avg 함수가 useMemo를 사용하기 전에는 숫자를 등록 할 때 뿐 아니라 인풋 값이 수정되거나 할 때도 getAverage가 호출이 되어버린다
useMemo를 해당 함수에 적용시켜 특정 값이 바뀌었을때만(위 함수에서는 list) 함수가 호출되게 할 수 있다.
useCallback(() => {
console.log('hello world!');
}, [])
useMemo(() => {
const fn = () => {
console.log('hello world!');
};
return fn;
}, [])
// 위 두 코드는 같은 기능을 한다.
숫자,문자열,객체 같은 일반 값을 재사용하기 위해서는 useMemo를 그리고 함수를 재사용하기 위해서는 useCallback을 사용한다.
함수형 컴포넌트에서 ref를 쉽게 사용 할 수 있게 해주는 hooks
useRef는 class의 로컬변수라고 보면 된다.
const Component = () => {
const [value, setValue] = useState('')
const inputRef = useRef();
const onChangeValue = useCallback((e) => {
setValue(e.target.value)
},)
const buttonClick = useCallback(() => {
setValue('')
inputRef.current.focus();
}, [])
return (
<>
<input ref={inputRef} value={value} onChange={onChangeValue} />
<button onClick={buttonClick}></button>
</>
)
}
const timeout = useRef();
const onClickScreen = () => {
if(clickState === 'waiting') {
setClickState('ready')
setMessage('초록색이 되면 클릭하세요')
timeout.current = setTimeout(() => { // --1
setClickState('now')
setMessage('클릭하세요!!!')
startTime.current = new Date();
}, Math.floor(Math.random() * 1000) + 2000);
} else if (clickState === 'ready') {
setClickState('waiting')
setMessage('너무 빠르게 누르셧네요..')
clearTimeout(timeout.current) // --2
} else if (clickState === 'now') {
endTime.current = new Date();
setClickState('waiting')
setMessage('클릭해서 시작하세요')
})
}
}
주석으로 표기해놓은 1번과 2번만 보면 된다.
10번째 줄에서 setTimeout을 호출 했고 18번째 줄에선 clearTimeout을 호출했다.
기본적으로 서로간의 블럭이 달라서 일반적으로는 clear를 시킬 수 없다.
이에 대한 해결책으로 useRef를 이용해서 변수에 타임아웃을 할당, 밖에서도 clear 할 수 있게 만들었다.
useState보다 컴포넌트에서 더 다양한 상황에 따라 다양한 상태로 다른 값을 업데이트 해주고 싶을 때 사용하는 hooks다
현재 상태와 업데이트를 위해 필요한 정보를 담은 액션값을 전달받아 새로운 상태를 반환하는 함수이다
리듀서 함수에서 새로운 상태를 만들때는 꼭 불변성을 지켜줘야 한다
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의 첫번째 인자는 리듀서함수, 두번째는 리듀서의 기본값이다.
// state는 현재 가리키고 있는 상태, dispatch는 액션을 발생시키는 함수
return (
<div>
<p>
현재 카운터 값은 <b>{state.value} 입니다</b>
</p>
<button onClick={() => dispatch({ type: 'INCREMENT'})}>+1</button>
<button onClick={() => dispatch({ type: 'DECREMENT'})}}>-1</button>
</div>
)
}
export default Counter;
useReducer의 return값은 배열이다. 배열 안에는 state값과 dispatch함수를 받아오는데 state는 현재 가리키고 있는 상태, dispatch는 액션을 발생시키는 함수다
dispatch(action) 형태로 함수 안에 인자로 액션값을 넣어주면 리듀서 함수를 호출한다
컴포넌트 업데이트 로직을 컴포넌트 바깥으로 뺄 수 있다는 장점이 있다
useReducer를 이용해 class에서 state관리방법을 흉내 낼 수 있다.
function reducer(state, action) {
return {
...state,
[action.name]: action.value
};
}
const Info = () => {
const [state, dispatch] = useReducer(reducer, {
name: '',
nickname: ''
});
const { name, nickname } = state;
const onChange = e => {
dispatch(e.target);
}
return (
<>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChange} />
</div>
<div>
<div>
<b>이름:</b>{name}
</div>
<div>
<b>닉네임:</b>{nickname}
</div>
</div>
</>
);
};
export default Info;
input 자체를 dispatch해서 input의 name값과 value값을 state에 담는다.
import React, { createConetext, useContext } form 'react'
const NewContext = createContext('black');
const ContextSample = () => {
const theme = useContext(NewContext);
const style = {
width: '24px',
height: '24px',
background: theme
};
return <div style={style} />
}
export default ContextSample;
// 다른 컴포넌트에서 ContextSample를 부르면 24px의 검은 사각형이 생성된다.
함수형 컴포넌트에서 Context를 쉽게 사용하기 위해서 사용하는 hooks