- 함수컴포넌트에서도 상태관리할수있다 : useState
- 렌더링 직후 작업을 설정하는 : useEffect
- 리액트 내장 Hooks 사용하기 → 커스텀 Hooks 만들기
useState
- 가장 기본적인 훅
- 함수컴포넌트에서도 가변적인 상태를 가질 수 있게 해줌
import React, {useState} from 'react';
const Counter = () => {
const [value, setValue] = useState(0);
const plusBtn = () => {
setValue(value +1)
}
const minusBtn = () => {
setValue(value -1)
}
return (
<div>
<p>
현재 카운터 값은 <b>{value}</b>입니다.
</p>
<button
onClick={plusBtn}
> +1 </button>
<button
onClick={minusBtn}
> -1 </button>
</div>
);
};
export default Counter;
import Counter from './Counter';
const App = () => {
return <Counter />
}
export default App;
useState를 여러번 사용하기
- 하나의 useState함수는 하나의 상태값만 관리할 수 있따.
- 컴포넌트에서 관리해야 할 상태가 여러개라면 useState를 여러번 사용하면 됨.
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={onChangName} />
<input
value={nickName}
onChange={onChangNickName} />
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임:</b> {nickName}
</div>
</div>
</div>
)
}
export default Info;
import Info from './Info';
const App = () => {
return <Info />;
};
export default App;
useEffect
- useEffect는 리액트컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다.
- componentDidMount 와 componentDidUpdate 를 합친형태
componentDidMount
: 컴포넌트를 만들고 첫렌더링을 다 마친 후 실행 됨, 이 안에서 다른 자바스크립트라이브러리 또는 프레임워크의 함수를 호출하거나 이벤트등록, setTimeout, setInterval, 네트워크요청 같은 비동기작업 처리
componentDidUpdate
(prevProps, prevState, snapshot){} : 리렌더링 완료 후 실행, DOM관련 처리가능, prevProps 또는 prevState 사용해서 컴포넌트가 이전에 가졌던 데이터에 접근가능, getSnapshotBeforeUpdate에서 반환한 값이 있다면 여기서 snapshot값을 전달 받을 수 있다.
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={onChangName} />
<input
value={nickName}
onChange={onChangNickName} />
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임:</b> {nickName}
</div>
</div>
</div>
)
}
마운트 될 때만 실행하고 싶을 때
- useEffect에서 설정한 함수를 컴포넌트가 화면에 맨 처음 렌더링 될 때만 실행하고, 업데이트 될 때는 실행하지 않으려면??
- 함수의 두번째 파라미터에 빈 배열을 넣어주면 된다.
import React, {useState, useEffect} from 'react';
const Info = () => {
(...)
useEffect(() => {
console.log('마운트 될 때만 실행됩니다.');
}, [])
(...)
}
특정 값이 업데이트 될 때만 실행하고 싶을 때
- useEffect를 사용할 때, 특정 값이 변경 될 때만 호출
- 클래스형이라면?
componentDidUpdate(prevProp, prevState) {
if(prevProps.value !== this.props.value) {
doSomething();
}
}
: props 안에 들어있는 value값이 바뀔 때만 특정작업 수행한다, 이걸 useEffect에서 해야한다면??
- useEffect의 두번째 파라미터로 전달되는 배열 안에 검사하고 싶은 값ㅇ르 넣어주면 된다.
import React, {useState, useEffect} from 'react';
const Info = () => {
(...)
useEffect(() => {
console.log(name);
}, [name])
(...)
}
- 배열 안에는 useState를 통해 관리하고 있는 상태를 넣어줘도 되고, props로 전달받은 값을 넣어줘도 된다.
cleanup함수 (뒷정리)
- useEffect는 기본적으로 렌더링 된 직후마다 실행되고,
- 두번째 파라미터 배열에 뭘 넣는지에 따라 실행되는 조건이 달라진다.
- 컴포넌트가 언마운트 되기 전이나 업데이트되기 직전에 어떤 작업을 수행하고 싶다면, useEffect에서 cleanup함수를 반환해줘야함.
useEffect (() => {
console.log('effect');
console.log(name);
return () => {
console.log('cleanup');
console.log(name);
}
}, [name])
import React, {useState} from 'react';
import Info from './Info';
const App = () => {
const [visible, setVisible] = useState(false);
const handleClickVisible = () => {
setVisible(!visible);
}
return (
<div>
<button
onClick={handleClickVisible}
>
{visible ? '숨기기' : '보이기'}
</button>
{visible && <Info />}
</div>
);
};
export default App;
* 업데이트할때마다 발생하는 effect 와 cleanup
* 컴포넌트가 나타날 때 콘솔에 : effect
* 사라질 때 : cleanup
* 렌더링 될 때마다 뒷정리함수가 계속 나타난다. 클린업함수가 호출될 때는 업데이트되기 직전 값을 보여준다.
* 오직 언마운트 될 때만 클린업함수를 호출하고 싶다면 useEffect함수의 두번째 파라미터에 비어있는 배열을 넣으면 된다.
* (언마운트 : 페이지에서 컴포넌트가 사라짐)
useEffect(() => {
console.lof('effect');
return () => {
console.log('unmount');
}
}, []);
useReducer
- useReducer는 useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용하는 Hook
- Reducer는 현재상태, 그리고 업데이트를 위해 필요한 정보를 담은 action값을 전달받아 새로운 상태를 반환하는 함수
- 리듀서함수에서 새로운 상태를 만들 떄는 반드시 불변성을 지켜줘야한다.
function reducer(state, action) {
return {...}
}
{
type: 'INCREMENT',
}
useCallback
- 주로 렌더링 성능을 최적화해야 하는 상황에서 사용한다. 만들어놨던 함수를 재사용할 수 있다.
- useCallback의 첫번째 파라미터 : 생성하고 싶은 함수를 넣고
- 두번째 파라미터 : 배열
- 함수 내부에서 상태 값에 의존해야 할 때는 그 값을 반드시 두번째 파라미터 안에 포함
- 예를들면, onChange : 기존 값을 조회하지 않고 바로 설정만 해서 배열이 비어도 상관없으나,
- onInsert : 기존의 number와 list를 조회해서 nextList를 생성하기 때문에 배열 안에 number와 list를 꼭 넣어주어야한다.
import {useState, useMemo, useCallback} from 'react';
const getAverage = number => {
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]);
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;
useRef
- useRef Hook은 함수컴포넌트에서 ref를 쉽게 사용할 수 있도록한다.
import {useRef} from 'react';
(...)
const Average = () => {
(...)
const inputEl = useRef(null);
(...)
return (
<input value={number} onChange={onChange} ref={inputEl} />
)
}
로컬변수 사용하기
- 컴포넌트 로컬변수를 사용해야 할 때도 useRef 활용 가능.
- 로컬변수 : 렌더링과 상관없이 바뀔 수 있는 값을 의미한다.
import React, {useState} 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
- 리액트에서 Hooks패턴을 사용하면 클래스형 컴포넌트를 작성하지 않고도 대부분의 기능구현가능
* App컴포넌트에서 todos배열에 새 객체를 추가하는 onInsert함수를 만들기
- 이 함수에서는 새로운 객체를 만들 때마다 id값에 1씩 더해주어야 한다.
- id값은 useRef를 사용하여 관리.
- useState가 아닌 useRef를 사용하여 컴포넌트에서 사용할 변수를 만드는 이유는???
- id값은 렌더링되는 정보가 아니기 때문이다.
- 이 값은 화면에 보이지도 않고, 이 값이 바뀐다고 해서 컴포넌트가 리렌더링 될 필요도 없음
- onInsert함수는 컴포넌트의 성능을 아낄 수 있도록 useCallback으로 감싸준다.
- props로 전달해야 할 함수를 만들 때는 useCallback을 사용해서 함수를 감싸는 것이 좋다.
import {useState, useRef, useCallback} from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
text: '리액트의기초알아보기',
checked: true
},
{
id: 2,
text: '컴포넌트스타일링',
checked: true
},
{
id: 3,
text: '일정관리앱만들기',
checked: false
}
]);
const nextId = useRef(4);
const onInsert = useCallback(
text => {
const tode = {
id: nextId.current,
text,
checked: false
};
setTodos(todos.concat(todo));
nextId.current += 1;
}, [todos]);
return(
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} />
</TodoTemplate>
)
};
export default App;
import {useState, useCallback} from 'react';
import {MdAdd} from 'react-icons/md';
import './TodoInsert.scss';
const TodoInsert = ( {onInsert} ) => {
const [value, setValue] = useState('');
const onChange = useCallback(e => {
setValue(e.target.value);
}, []);
const onSubmit = useCallback(e => {
onInsert(value);
setValue('');
e.preventDefault();
}, [onInsert, value])
return (
<form
className="TodoInsert"
onSumit={onSubmit}
>
<input
placeholder='할일입력'
value={value}
onChange={onChange}
/>
<button
type="submit"
>
<MdAdd/>
</button>
</form>
);
};
export default TodoInsert;
const onClick = useCallback(() => {
onInsert(value);
setValue('');
}, [onInsert, value])
return (
<form className="TodoInsert">
<input
placeholder='할일입력'
value={value}
onChange={onChange}
/>
<button
onClick={onClick}
>
<MdAdd/>
</button>
</form>
);
};
- onClick이벤트로 가능한데 form과 onSubmit이벤트를 사용한 이유 ???
- onSubmit 이벤트 쓰면 Enter 때문에 onKeyPress 따로 안써도 됨
- 지우기 기능 : 배열의 불변성을 지키면서 배열원소를 제거해야할 경우, 배열 내장 함수인 filrer 사용
- filter함수 : 기존 배열은 그대로 둔 상태에서 특정 조건 만족하는 원소들만 따로 추출하여 새로운 배열을 만들어준다.
const array = [1,2,3,4,5,6,7,8,9,10];
const biggerThanFive = array.filter(number => number > 5);
- filter함수에는 조건을 확인해주는 함수를 파라미터로 넣어줘야한다. 파라미터로 넣는 함수는 true/false 값을 반환해야하며, true 를 반환하는 경우만 새로운 배열에 포함된다.
* filter함수를 사용하여 onRemove함수를 작성하기
* App컴포넌트에 id를 파라미터로 받아와서 `같은 id를 가진 항목을` todos 배열에서 지우는 함수. 이 함수를 만들고 나서 TodoList의 props로 설정
import {useState, useRef, useCallback} from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
const App = () => {
(...)
const onRemove = useCallback(id => {
setTodos(todos.filter(todo => todo.id !== id));
}, [todos]);
return(
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove} />
</TodoTemplate>
)
};
export default App;
- TodoListItem에서 onRemove함수를 사용하려면, TodoList컴포넌트를 거쳐야 한다.
- props 로 받아온 onRemove함수를 TodoListItem에 그대로 전달하자,
import TodoListItem from './TodoListItem';
import './TodoList.scss';
const TodoList = ({todos, onRemove}) => {
return (
<div>
{todos.map(todo => (
<TodoListItem
todo={todo}
key={todo.id}
onRemove={onRemove}
/>
))}
</div>
)
}
- '삭제'버튼을 누르면 TodoListItem에서 onRemove 함수에 현재 자신이 가진 id를 넣어서 삭제함수를 호출
import {MdCheckBoxOutlineBlank, MdCheckBox, MdRemoveCircleOutline} from 'react-icons/md';
import cn from 'classnames';
import './TodoListItem.scss';
const TodoListItem = ({todo, onRemove}) => {
return (
<div className="TodoListItem">
<div className={cn('checkbox', {checked})}>
{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div classname="text"> {text} </div>
</div>
<div
className="remove"
onClick={() => onRemove(id)}>
<MdRemoveCircleOutline/>
</div>
</div>
)
};
export default TodoListItem
const App = () => {
(...)
const onToggle = useCallback(id => {
setTodos(
todos.map(todo =>
todo.id === id ? {...todo, checked: !todo.checked} : todo,
)
}, [todo])
return(
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
</TodoTemplate>
)
};
import TodoListItem from './TodoListItem';
import './TodoList.scss';
const TodoList = ({todos, onRemove, onToggle}) => {
return (
<div className="TodoList">
{todos.map(todo => (
<TodoListItem
todo={todo}
key={todo.id}
onRemove={onRemove}
onToggle={onToggle}
/>
))}
</div>
)
}