React Hooks 활용 (useState 부터 useReducer까지)
2020, Oct 19
React Hooks 이란?
- 함수형 컴포넌트에서도 클래스형 컴포넌트의 기능을 사용할 수 있게 하는 기능
- 클래스형 컴포넌트의 한계를 극복하기 위해 사용 (생명 주기 메서드, 로직 재사용)
useState
- 함수형 컴포넌트에서 상태값 관리
useState를 사용한 턱걸이 갯수 카운터
//useState import
import React, {useState} from 'react';
const App =()=> {
//count라는 상태값 관리 변수 선언, 초기값 0
const [count, setCount] = useState(0);
//count 변수 1 증가 함수
const plus =()=>{
setCount(count+1);
}
//count 변수 1 감소 함수
const minus =()=>{
setCount(count-1);
}
return (
<div>
<h1>{count}</h1>
<button onClick={minus}>-</button>
<button onClick={plus}>+</button>
</div>
);
}
import React, {useState} from 'react';
const App =()=> {
const [count, setCount] = useState(0);
const [name, setName] = useState('미입력');
const [age, setAge] = useState('미입력');
const plus =()=>{
setCount(count+1);
}
const minus =()=>{
setCount(count-1);
}
return (
<div>
이름 : <input type="text" onChange={e=>setName(e.target.value)}/>
나이 : <input type="text" onChange={e=>setAge(e.target.value)}/>
<h1>이름 : {name}</h1>
<h1>나이 : {age}</h1>
<h1>턱걸이 갯수 카운터 : {count}</h1>
<button onClick={minus}>-</button>
<button onClick={plus}>+</button>
</div>
);
}
export default App;
useState를 사용한 턱걸이 갯수 카운터(이름, 나이 입력값 추가)
import React, {useState} from 'react';
const App =()=> {
const [count, setCount] = useState(0);
//const [name, setName] = useState('미입력');
//const [age, setAge] = useState('미입력');
//useState 하나로 여러개의 상태값 관리
const [state, setState] = useState({name:'미입력', age:'미입력'});
const plus =()=>{
setCount(count+1);
}
const minus =()=>{
setCount(count-1);
}
return (
<div>
//스프레드 연산자 사용해서 값 세팅
이름 : <input type="text" onChange={e=>setState({...state, name: e.target.value})}/>
나이 : <input type="text" onChange={e=>setState({...state, age: e.target.value})}/>
<h1>이름 : {state.name}</h1>
<h1>나이 : {state.age}</h1>
<h1>턱걸이 갯수 카운터 : {count}</h1>
<button onClick={minus}>-</button>
<button onClick={plus}>+</button>
</div>
);
}
export default App;
useEffect
- 함수형 컴포넌트 생명주기 관리
- useEffect를 사용하면 생명주기 메서드를 한곳으로 모을 수 있어서 가독성이 좋음
useEffect를 사용한 JSON 리스트 데이터 출력
//useEffect import
import React, {useState, useEffect} from 'react';
const App =()=> {
//상태관리 변수 list 선언 및 초기화
const [list, setList] = useState([]);
//생명주기 함수 useEffect 사용
useEffect(()=>{
//해당 url에서 json 데이터를 가져옴
fetch('https://jsonplaceholder.typicode.com/posts')
.then((data)=>{
return data.json();
})
.then(commits=>{
//각 리스트를 li태그에 감싸서 list 상태 변수에 저장
const commitList = commits.map((data)=>{
return <li key={data.id}>{data.title} : {data.body}</li>;
});
setList(commitList)
})
.catch(err=>console.log(err));
},[]); //api 통신을 최초에 1회만 하도록 함.
//다른 변수를 넣는다면 변수값 변경에 따라 api 호출됨
return (
<div>
{//list 상태변수 출력}
{list}
</div>
);
}
export default App;
Custom Hook
- 훅의 이름은 가독성을 위해 use로 시작하는 것이 좋음
- 리액트가 제공하는 훅을 이용해서 새로운 훅 생성 가능
창 너비, 높이를 관리하는 커스텀 훅 생성
App.js
import React, {useState, useEffect} from 'react';
import {useWindow} from './hooks'
const App =()=> {
const [list, setList] = useState([]);
const {width,height} = useWindow();
useEffect(()=>{
fetch('https://jsonplaceholder.typicode.com/posts')
.then((data)=>{
return data.json();
})
.then(commits=>{
const commitList = commits.map((data)=>{
return <li key={data.id}>{data.title} : {data.body}</li>;
});
setList(commitList)
})
.catch(err=>console.log(err));
},[]);
return (
<div>
<h1>width:{width}, height:{height}</h1>
{list}
</div>
);
}
export default App;
hooks.js
import {useState, useEffect} from 'react';
export function useWindow(){
const [width, setWidth] = useState(window.innerWidth);
const [height, setHeight] = useState(window.innerHeight);
useEffect(()=>{
const onWidthResize = () => setWidth(window.innerWidth);
const onHeightResize = () => setHeight(window.innerHeight);
window.addEventListener('resize', onWidthResize);
window.addEventListener('resize', onHeightResize);
return () => {
window.removeEventListener('resize', onWidthResize);
window.removeEventListener('resize', onHeightResize);
}
},[])
return {width,height};
}
useContext
- Consumer 컴포넌트 없이 부모 컴포넌트로부터 전달되어 콘텍스트 데이터 사용 가능
App.js
import React from 'react';
import {UserProvider} from "./User";
import Display from './Display';
const App = () => {
return (
<UserProvider>
<Display/>
</UserProvider>
);
};
export default App;
User.js
import React, {useState} from 'react';
const UserContext = React.createContext(null);
const UserProvider = ({children}) => {
const [name, setName] = useState('hamletshu');
const user = {
name, setName
};
return (
<UserContext.Provider value={user}>
{children}
</UserContext.Provider>
)
};
export {
UserProvider,
UserContext
};
Display.js
import React, {useContext} from 'react';
import {UserContext} from "./User";
const Display = () => {
const {name, setName} = useContext(UserContext);
return (
<>
<div>Name : {name}</div>
</>
)
};
export default Display;
useRef
- DOM 요소 접근시 사용
App.js
import React, {useRef, useState} from 'react';
const App = () => {
const input = useRef(null);
const [output, setOutput] = useState('');
const inputText=()=>{
setOutput(input.current.value);
}
return (
<>
<input ref={input} type="text"></input>
<button onClick={inputText}>입력</button>
<div>{output}</div>
</>
);
};
export default App;
useMemo
- 이전 값을 기억해서 성능을 최적화 하는 용도로 사용
- 반환값 재활용
- 첫번째 매개변수는 함수
computeExpensiveValue(a, b)
: 반환한 값 기억 - 두번째 매개변수는 배열
[a, b]
: 배열의 값이 변경되지 않으면 반환된 값을 재사용
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useCallback
- 이전 값을 기억해서 성능을 최적화 하는 용도로 사용
- 불필요한 렌더링 발생을 줄여 렌더링 성능 향상 가능
- 콜백의 의존성이 바뀌었을 때만 변경됨
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useReducer
- 컴포넌트 상태값 관리 (Redux의 Reducer 처럼 관리)
contextAPI
를 사용하면 이벤트 함수를 손쉽게 전달 가능
App.js
import React, {useReducer} from 'react';
import UseReduxTest from './UseReduxTest';
export const AppDispatch = React.createContext(null);
const App = () => {
const initialState = {count: 0};
//useReducer에 이벤트 함수들을 등록
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
//useReducer(함수, 초기값);
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<div>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</div>
<AppDispatch.Provider value={dispatch}>
<UseReduxTest/>
</AppDispatch.Provider>
</>
);
};
export default App;
UseReducerTest.js
import React, {useContext} from 'react';
import {AppDispatch} from './App';
const UseReducerTest = () => {
//useContext를 사용하여 useReducer에 등록한 이벤트 함수들을 가져옴
const dispatch = useContext(AppDispatch);
return (
<>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
};
export default UseReducerTest;
- 아래의 버튼도 동일하게 작동하는 것을 확인할 수 있다.