[React]Hooks
이 글은 [리액트를 다루는 기술](저자 김민준, 출판사 길벗) 교재를 보고 공부하며 정리한 글임.
Counter.js
import React, {useState} from 'react';
const Counter= () => {
const[value, setValue]= useState(0);
return (
<div>
<p>
현재 카운터 값은 <b>{value}</b>입니다.
</p>
<button onClick={()=>setValue(value + 1)}>+1</button>
<button onClick={() => setValue(value -1)}>-1</button>
</div>
);
};
export default Counter;
Info.js
import React, {useState}from 'react';
const Info =()=>{
const [name, setName]= useState('');
const [nickname, setNickname]=useState('');
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;
useEffect
import React, {useState, useEffect}from 'react';
const Info =()=>{
const [name, setName]= useState('');
const [nickname, setNickname]=useState('');
useEffect(() => {
console.log('렌더링이 완료되었습니다!');
console.log({
name,
nickname
});
});
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;
업데이트 될때는 실행하지 않으려면
import React, {useState, useEffect}from 'react';
const Info =()=>{
const [name, setName]= useState('');
const [nickname, setNickname]=useState('');
useEffect(() => {
console.log('마운트 될때만 실행됩니다');
},[]);
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;
특정값이 업데이트 될때만 실행하고 싶을때
useEffect의 두번째 파라미터로 전달되는 배열 안에 검사하고 싶은 값을 넣어주면 됨.
App.js
import React, {useState} from 'react';
import Info from './Info';
const App =() => {
const[visible, setVisible] = useState(false);
return (
<div>
<button
onClick={()=>{
setVisible(!visible);
}}
>
{visible ? '숨기기' : '보이기'}
</button>
<hr />
{visible && <Info />}
</div>
);
};
export default App;
Info.js
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;
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:
return state;
}
}
const Counter= () => {
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>
</div>
);
};
export default Counter;
App.js
import React from 'react';
import Counter from './Counter';
const App =() =>{
return <Counter />;
};
export default App;
인풋상태 관리하기
Info.js
import React, {useReducer}from 'react';
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>
<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>
</div>
);
};
export default Info;
App.js
import React from 'react';
import Info from './Info';
const App =() =>{
return <Info />;
};
export default App;
useMemo
함수형 컴포넌트 내부에서 발생하는 연산을 최적화 가능
Average.js
import React, {useState} from 'react';
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('');
};
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>{getAverage(list)}
</div>
</div>
);
};
export default Average;
App.js
import React from 'react';
import Average from './Average';
const App =() =>{
return <Average />;
};
export default App;
숫자를 등록할때뿐 아니라 인풋 내용 수정도리때도 getAverage 함수가 호출되는건 낭비
useMemo Hook을 사용하면 렌더링 하는 과정에서 특정값이 바뀌었을 때만 연산을 실행하고
원하는 값이 바뀌지 않았따면 이전에 연산했던 결과를 다시 사용하는 방식
Average.js
import React, {useState, useMemo} from 'react';
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;
이러면 list 배열 바뀔 때에만 getAverage가 호출됨
useCallback 은 주로 렌더링 성능을 최적화 해야할때 사용
만들어놨던 함수를 재사용할 수 있다
import React, {useState, useMemo, useCallback} from 'react';
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 = useCallback(e =>{
setNumber(e.target.value);
},[]);//컴포넌트가 처음 렌더링 될때에만 함수생성
const onInsert = useCallback(() =>{
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
}, [number, list]);//number or list가 바뀌었을 때만 함수 생성
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;
useCallback 첫번째 파라미터에는 생성하고싶은 함수를 넣고
두번째 파라미터에는 배열을 넣으면 된다
useRef Hook 은 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해줌
import React, {useState, useMemo, useCallback,useRef} from 'react';
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 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 or list가 바뀌었을 때만 함수 생성
const avg = useMemo(()=> getAverage(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>
<b>평균값:</b>{avg}
</div>
</div>
);
};
export default Average;
useRef를 이용하여 ref를 설정하면 useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킴
기존의 Info 컴포넌트에서 여러개의 인풋을 관리하기 위해 useReducer 로 작성했던 로직을 useInputs라는 Hook으로 따로 분리
useInputs.js
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];
}
이 Hook을 Info 컴포넌트에서 사용
Info.js
import React, {useReducer}from 'react';
function reducer(state, action){
return {
...state,
[action.name]:action.value
};
}
const Info =()=>{
const[state, onChange]=useInputs({
name:'',
nickname:''
});
const{name, nickname} = state;
x
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChangeNickname} />
</div>
<div>
<div>
<b>이름:</b>{name}
</div>
<div>
<b>닉네임:</b>{nickname}
</div>
</div>
</div>
);
};
export default Info;