React Hooks
1. Hooks
: 기존의 함수형 컴포넌트에서는 할 수 없었던 다양한 작업을 할 수 있게 해줌
2. useState(상태초기값)
: 가장 기본적인 Hook
: 함수형 컴포넌트에서도 상태를 관리할 수 있음
import { useState } from "react";
export const Counter = () => {
//useState hook 사용 => useState(초기값)
//첫번째 원소는 상태값, 두번째 원소는 상태를 설정하는 함수
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>
);
};
2-1. useState를 여러번 사용하기
import { useState } from "react";
const Info = () => {
//useState(상태 초기값);
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;
3. useEffect(function, dep)
: 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook
: 클래스형 컴포넌트의 componentDidMount와 componentDidUpdate를 합친 형태로 보아도 무방 (LifeCycle)
3-1. Rendering 될 때마다 실행하고 싶을 때
: 두번째 파라미터에 아무것도 넣지 않음
import { useEffect, useState } from "react";
const Info = () => {
//useState(상태 초기값);
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
//useEffect 사용!
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;
3-2. 마운트 될 때만 실행하고 싶을 때
: useEffect에서 설정한 함수가 컴포넌트가 화면에 가장 처음 rendering 될 때만 실행되고 업데이트 할 경우에는 실행할 필요가 없는 경우
: useEffect의 두번째 파라미터로 비어있는 배열을 넣어줌
import { useEffect, useState } from "react";
const Info = () => {
//useState(상태 초기값);
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
//useEffect 이용!
// useEffect(() => {
// console.log('렌더링이 완료되었습니다.');
// console.log({
// name,
// nickname
// });
// });
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;
3-3. 특정 값이 업데이트 될 때만 실행하고 싶을 때
: useEffect의 두번째 파라미터로 전달되는 배열에 검사하고 싶은 값을 넣어줌
import { useEffect, useState } from "react";
const Info = () => {
//useState(상태 초기값);
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
//useEffect 이용!
// useEffect(() => {
// console.log('렌더링이 완료되었습니다.');
// console.log({
// name,
// nickname
// });
// });
// useEffect(() => {
// console.log('마운트 될 때만 실행됩니다.');
// }, [])
//특정값 업데이트시에만 실행
useEffect(() => {
console.log(name);
}, [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;
3-4. 뒷정리 하기 (cleanup 함수)
: 컴포넌트가 언마운트되기 전이나, 업데이트 되기 직전에 어떠한 작업을 수행하고 싶다면 useEffect에 뒷정리(cleanup) 함수를 반환해줘야 함
import { useEffect, useState } from "react";
const Info = () => {
//useState(상태 초기값);
const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
//useEffect 이용!
// useEffect(() => {
// console.log('렌더링이 완료되었습니다.');
// console.log({
// name,
// nickname
// });
// });
// useEffect(() => {
// console.log('마운트 될 때만 실행됩니다.');
// }, [])
// //특정값 업데이트시에만 실행
// useEffect(() => {
// console.log(name);
// }, [name])
//뒷정리함수
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;
import logo from './logo.svg';
import './App.css';
import { Counter } from './Counter';
import Info from './Info';
import { useState } from 'react';
const App = () => {
const [visible, setVisible] = useState(false);
return (
<div>
<button
onClick={() => {
setVisible(!visible);
}}
>
{/* visible이 true면 숨기기가 보이고, false면 보이기가 보임 */}
{visible ? '숨기기' : '보이기'}
</button>
<hr />
{/* visible이 true일 때만 Info 컴포넌트가 보임 */}
{visible && <Info />}
</div>
);
}
export default App;
4. useContext
: 함수형 컴포넌트에서 Context를 쉽게 사용 가능
import { createContext, useContext } from "react";
const ThemeContext = createContext('black');
const ContextSample = () => {
const theme = useContext(ThemeContext);
const style = {
width: '24px',
height: '24px',
background: theme
};
return <div style={style} />;
};
export default ContextSample;
5. useReducer(리듀서함수, 리듀서의 기본값)
: 컴포넌트에서 useState보다 더 다양한 상황에 따라, 다양한 상태를 다른 값으로 업데이트해주고 싶을 때 사용
: Reducer는 현재 상태와, 업데이트를 위해 필요한 정보를 담은 액션(action)값을 전달 받아 새로운 상태를 반환하는 함수
: 새로운 상태를 만들 때는 불변성을 반드시 지켜야 함
function reducer(state, action) {
return {...};
}
↓ 액션값의 주된 형태
{
type: 'INCREMENT',
//다른 값들도 필요하다면, 추가적으로 들어감
}
5-1. 카운터 구현하기
import { useReducer, useState } 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 = () => {
//useState 대신 useReducer 사용
//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>
</div>
);
};
export default Counter;
6. useMemo
: 함수형 컴포넌트 내부에서 발생하는 연산 최적화
: 렌더링 하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고, 원하는 값이 바뀐 것이 아니라면 이전에 연산했던 결과를 다시 사용
import { useMemo, 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('');
};
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;
7. useCallback(함수, 배열)
: useMemo와 비슷
: 렌더링 성능 최적화 → 컴포넌트의 렌더링이 자주 발생하거나, 렌더링 해야 할 컴포너트의 개수가 많아질 때
: 이벤트 핸들러 함수를 필요시에만 생성할 수 있음
import { useCallback, useMemo, 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('');
//useCallback 사용
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []); //컴포넌트가 처음 렌더링 될 때만 함수 생성
const onInsert = useCallback(
e => {
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()과 useMemo()
useCallback(() => {
console.log('hello world!');
}, []);
useMemo(() => {
const fn = () => {
console.log('hello world!);
};
return fn;
}, []);
→ 결국 useCallback은 useMemo에서 함수를 반환하는 상황에서 더 편하게 사용할 수 있는 Hook!
객체처럼 일반 값을 재사용하기 위해서는 useMemo, 함수를 재사용하기 위해서는 useCallback을 사용
8. useRef
: 함수형 컴포넌트에서 엘리먼트에 접근할 수 있도록 해주는 Hook
: useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킴
import { useCallback, useMemo, useRef, 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('');
//useRef 사용
const inputEl = useRef(null);
const onChange = useCallback(e => {
setNumber(e.target.value);
}, []);
const onInsert = useCallback(
e => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
//useRef 호출
inputEl.current.focus();
},
[number, 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;
8-1. 로컬 변수 사용하기
: 컴포넌트 로컬 변수를 사용할 때도 useRef를 활용
: 로컬변수 - 렌더링이라은 관계 없이 바뀔 수 있는 값
import { useRef } from "react"
const RefSample = () => {
const id = useRef(1);
const setId = (n) => {
id.current = n;
}
const printId = () => {
console.log(id.current);
}
return (
<div>
refsample
</div>
);
};
export default RefSample;