**Hooks를 사용하는 방법
(1) 리액트 내장 Hooks 사용하기
(2) 커스텀 Hooks 만들기
=> 함수 컴포넌트에서도 가변적 상태를 지닐 수 있게 해준다
//Counter.js
import { 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;
useState 함수의 파라미터에는 상태의 기본값이 들어가며, 여기서는 0이다
이 함수는 호출 시, 배열을 반환하며, 배열의 첫 번째 인자는 상태값, 두 번째 인자는 상태설정 함수이다
상태 설정 함수에 파라미터를 넣어서 호출하면 전달받은 파라미터로 값이 바뀌고 컴포넌트가 리렌더링된다
//App.js
import Counter from './Counter';
const App = () => {
return <Counter/>;
};
이 방법을 통해 함수 컴포넌트에서 상태를 관리하기 위해 컴포넌트 코드를 굳이 클래스 형태로 바꿀 필요가 없다
하나의 useState 함수는 하나의 상태값만 관리할 수 있다
//Info.js
import { useState } from 'react';
const Info = () => {
const [name, setName] = usetState('');
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;
=> 리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다
//Info.js
import { useState } from 'react';
const Info = () => {
const [name, setName] = usetState('');
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;
컴포넌트가 처음 나타났을 때 console이 2번 출력되는데, 이는 React.StrictMode 적용 환경에서만 발생하는 현상으로, 코드에 문제없는지 확인을 위한 것이다
지금은 useEffect 컴포넌트가 화면에 나타날 때 두 번 호출된다는 정도로만 알아두면 된다
처음 렌더링될 때만 실행하고 업데이트될 때는 실행되지 않게 하려면 함수의 두 번째 파라미터를 비어있는 배열로 두면 된다
//Info.js - useEffect
useEffect ( () => {
console.log("마운트될 때만 실행");
}, []);
클래스형 컴포넌트에서는 props안에 들어있는 value값이 바뀔 때 실행된다.
componentDidUpdate ( prevProps, prevState ) {
if ( prevProps.value !== this.props.value ) {
doSomething();
}
}
useEffect에서는 두 번째 파라미터로 전달되는 배열 안에 검사하고 싶은 값을 넣어준다. 배열 안에는 useState로 관리하고 있는 상태, props로 전달받은 값 등을 넣어준다. (배열을 생략하는 상황은 거의 없다)
//Info.js - useEffect
useEffect ( () => {
console.log(name);
}, [name] );
useEffect는 기본적으로 렌더링되고 난 직후마다 실행되며, 두 번째 파라미터 배열에 무엇을 넣는지에 따라 실행 조건이 달라진다. 컴포넌트가 언마운트되기 전이나 업데이트되기 직전에 어떠한 작업 수행을 하려면 useEffect에서 뒷정리 함수(cleanup) 을 return해주어야 한다
//Info.js - useEffect
useEffect ( () => {
console.log('effect');
console.log(name);
return () => {
console.log('cleanup');
console.log(name);
};
}, [name]);
뒷정리 함수 호출 시 업데이트 직전 값을 보여준다
=> useState보다 다양한 상황에 따라 상태를 다른 값으로 업데이트하고자 할 때 사용한다
*리듀서란?
=> 현재 상태 업데이트를 위해 필요한 정보를 담은 액션(action)값을 전달받아 새로운 상태를 반환하는 함수이다
뒤에서 다룰 리덕스에서는 액션 객체에 type 필드가 반드시 필요하지만 useReducer에서 사용하는 액션 객체는 필요없다.
//Counter.js
import { 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>{value}</b>입니다.</p>
<button onClick={() => setValue(value + 1)}>+1</button>
<button onClick={() => setValue(value - 1)}>-1</button>
</div>
};
};
export default Counter;
useReducer의 첫 번째 파라미터는 리듀서 함수, 두 번째 파라미터는 리듀서의 기본값을 넣는다.
이 Hook 사용 시, 현재 가리키고 있는 상태인 state값과 액션을 발생시키는 dispatch 함수를 받아온다.
useState를 여러 번 사용하는 대신 useReducer을 사용하면, 클래스형 컴포넌트에서 input 태그에 name값을 할당하고 e.target.name을 참조하여 setState해준 것과 유사하게 작업이 가능하다
//Info.js
import { 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 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에서의 액션은 어떤 값도 사용 가능하며, 이벤트 객체가 지니고 있는 e.target 값 자체를 이용한다
이를 통해, 코드를 간결하게 유지할 수 있다
=> 함수 컴포넌트 내부에서 발생하는 연산을 최적화시킨다
//Average.js
import { 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={onClick}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값: </b> {getAverage(list)}
</div>
</div>
);
};
export default Average;
숫자 등록 뿐만 아니라 input 내용 수정 시에도 getAverage()를 호출하고, 이는 렌더링할 때마다 계산하는 낭비가 발생한다
useMemo Hook을 이용하면 렌더링 과정에서 특정 값 변경 시에만 연산을 하고, 아닌 경우에는 이전 연산 결과를 재사용한다
//Average.js
import { 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={onClick}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값: </b> {avg}
</div>
</div>
);
};
export default Average;
=> useMemo와 비슷하며, 만들어 놨던 함수 재사용이 가능하다
Average.js에서 선언한 onChange()와 onInsert()는 리렌더링될 때마다 새로 만들어진 함수를 사용한다
렌더링이 자주 발생하거나 렌더링할 컴포넌트 수가 많아지면 최적화가 필요하다
//Average.js
import { 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 혹은 list가 바뀌었을 때만 함수 생성
const avg = useMemo( () => getAverage(list), [list]);
return {
<div>
<input value={number} onChange={onChange} />
<button onClick={onClick}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값: </b> {avg}
</div>
</div>
);
};
export default Average;
useCallback의 첫 번째 파라미터는 생성하고 싶은 함수, 두 번째 파라미터는 배열이다. (어떤 값이 바뀌었을 때 함수를 새로 생성해야 하는지는 명시해야 한다)
onChange() : 빈 배열을 두어 렌더링될 때 만든 함수를 계속 재사용한다
onInsert() : 빈 배열을 두지 않아, input 내용이 바뀌거나 새로운 항목을 추가할 때는 새로 만들어진 함수를 사용한다
=> 함수 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해준다
useRef를 사용해 ref를 설정하면 useRef를 통해 만든 객체 안의 current 값이 실제 요소를 가리킨다
//Average.js
import { 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 inputE1 = useRef(null);
const onChange = useCallback( e=> {
setNumber(e.target.value);
}, []); //컴포넌트가 처음 렌더링될 때만 함수 생성
const onInsert = useCallback( ()=> {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber('');
inputE1,current.focus();
}, [number, list]); //number 혹은 list가 바뀌었을 때만 함수 생성
const avg = useMemo( () => getAverage(list), [list]);
return {
<div>
<input value={number} onChange={onChange} ref={inputE1} />
<button onClick={onClick}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값: </b> {avg}
</div>
</div>
);
};
export default Average;
=> 로컬 변수란 렌더링과 상관없이 바뀔 수 있는 값으로, 컴포넌트에서 로컬 변수를 사용해야 할 때도 useRef를 활용한다
ref 안의 값이 바뀌어도 컴포넌트는 렌더링되지 않는다(렌더링과 관련되지 않은 값을 관리할 때만 사용해야 한다)
//클래스형 컴포넌트로 작성
import {Component } from 'react';
class MyComponent extends Component {
id = 1
setId = (n) => {
this.id=n;
}
printId = () => {
console.log(this.id);
}
render() {
return {
<div>
MyComponent
</div>
);
}
}
export default MyComponent;
//함수 컴포넌트로 작성
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;
여러 컴포넌트에서 비슷한 기능을 공유하면, 이를 커스터마이징해서 사용할 수 있다
//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];
}
//Info.js
import useInputs from '/useInputs';
const Info = () => {
const [state, onChange] = userInputs ( {
name:'',
nickname:''
});
const {name, nickname } = state;
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;
커스텀 Hooks처럼, 이미 만들어져 있는 Hooks를 라이브러리 설치를 통해 사용 가능하다
**참고 링크
https://nikgraf.github.io/react-hooks/
https://github.com/rehooks/awesome-react-hooks
리액트에서 Hooks 패턴을 사용하면 클래스형 컴포넌트를 작성하지 않아도 대부분 기능 구현이 가능하다. 리액트 메뉴얼에 따르면, 기존의 클래스형 컴포넌트를 계속 지원할 예정이기 때문에, 유지/보수 시 클래스형을 함수 컴포넌트와 Hooks 사용 형태로 굳이 전환할 필요는 없다. 다만, 새로 작성하는 경우, 함수 컴포넌트와 Hooks 사용을 권장하고 있다.
Q1) Hooks를 사용하는 방법은 2가지로, ( - - - - - ) Hooks 사용하는 방법과 ( - - - ) Hooks 만들어서 사용하는 방법이 있다.
Q2) useState 함수의 파라미터에는 ( - - - - - - )이다.
Q3) 리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook은 ( - - - - - - - - - )이다.
Q4) 컴포넌트가 언마운트되기 전이나 업데이트되기 직전에 어떠한 작업 수행을 하려면 useEffect에서 ( - - - - -)를 return해주어야 한다.
Q5) 리듀서는 현재 상태 업데이트를 위해 필요한 정보를 담은 ( - - - )을 전달받아 새로운 상태를 반환하는 함수이다
Q6) useRef를 사용해 ref를 설정하면 useRef를 통해 만든 객체 안의 ( - - - - - - - )값이 실제 요소를 가리킨다
Q7) ( - - - - )란 렌더링과 상관없이 바뀔 수 있는 값이다.
Q8) 클래스형 컴포넌트로 작성한 아래 코드는 props안에 들어 있는 value 값이 바뀔 때만 특정 작업을 수행한다. 이와 같은 기능을 수행하는 코드를 useEffect를 사용하여 작성하시오.
componentDidUpdate ( prevProps, prevState ) {
if ( prevProps.value !== this.props.value ) {
doSomething();
}
}
Q9) 클래스형 컴포넌트에서 로컬 변수를 사용한 아래 코드를 함수 컴포넌트로 작성하시오.
import {Component } from 'react';
class MyComponent extends Component {
id = 1
setId = (n) => {
this.id=n;
}
printId = () => {
console.log(this.id);
}
render() {
return {
<div>
MyComponent
</div>
);
}
}
export default MyComponent;
A1) 리액트 내장 / 커스텀
A2) 상태의 기본값
A3) useEffect
A4) 뒷정리 함수(cleanup)
A5) 액션(action)값
A6) current
A7) 로컬 변수
A8 ) 예시 코드
useEffect ( () => {
console.log(name);
}, [name] );
A9 ) 예시 코드
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;
ⓒ 엘리
[리액트스타터2] 10장. 일정 관리 웹 애플리케이션 만들기 (0) | 2022.12.22 |
---|---|
[리액트스타터2] 9장. 컴포넌트 스타일링 (0) | 2022.12.01 |
[리액트스타터2] 7장. 컴포넌트의 라이프사이클 메서드 (0) | 2022.11.17 |
[리액트스타터2] 6장. 컴포넌트 반복 (0) | 2022.11.10 |
[리액트스타터2] 5장. ref (0) | 2022.11.05 |