component가 마운드 됐을 때 (처음 나타났을 때), 언마운트 됐을 때 (사라질 때), 업데이트 될 때 (특정 props가 바뀔 때) 특정 작업을 처리하는 방법
useEffect(() => {
console.log('컴포넌트가 화면에 나타남');
return () => {
console.log('컴포넌트가 화면에서 사라짐');
};
}, []);
첫번째 파라미터에는 함수, 두번째 파라미터는 의존 값이 들어있는 배열 (debs
)
debs
배열이 비어있으면, 컴포넌트가 처음 나타났을 때에만 useEffect
에 등록한 함수가 호출됩니다.
useEffect
에서 함수를 반환할 수 있는데 이를 cleanup
함수라고 부른다.
clean up 함수는 useEffect에 대한 뒷정리르 해준다고 생각하면 된다.
debs가 비어있는 경우에는 컴포넌트가 사라질 때 cleanup 함수가 호출된다
props
로 받은 값을 컴포넌트에 로컬 상태로 설정debs
배열에 값을 넣으면, 컴포넌트가 처음 마운트될 때에도 호출이 되고, 지정한 값이 바뀔 때에도 호출이 된다. 언마운트시에도 호출되고, 값이 바뀌기 직전에도 호출.
useEffect(() => {
console.log('user 값이 설정됨');
console.log(user)
return () => {
console.log('user가 바뀌기 전');
console.log(user);
};
}, [user]);
useEffect
안에서 사용하는 상태나 props가 있다면 useEffect
의 deps에 넣어줘야 한다. debs 내에서 지정한 값이 변화했을 때에만 effector가 다시 실행되도록 한 것.
memoized → 이전에 계산한 값을 재사용한다는 의미이다.
import './App.css';
import React, {useRef, useState, useMemo }from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
function counterActiveUsers(users) {
console.log('counting active users...');
return users.filter(user => user.active).length;
}
function App() {
...
const count = useMemo(() => counterActiveUsers(users), [users]);
return (
<div className="App">
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
< UserList users = {users} onRemove={onRemove} onToggle={onToggle}/>
<div>number of Active users : {count}</div>
</div>
);
}
export default App;
그냥
const count = counterActiveUsers(users);
를 해버리면 component가 바뀔때마다 countActiveUsers가 실행되지만,
useMemo
를 쓰면 역시 debs 배열 내의 내용이 바뀌지 않았을 땐 이전에 연산한 값을 재사용하게 한다.
useMemo
와 비슷
useMemo
가 특정 결과값을 재사용한다면,useCallback
은 특정 함수를 재사용하고 싶을 때 사용한다.
그냥 함수를 선언하면 컴포넌트가 리렌더링할때마다 새로 만들어진다.
const onCreate = useCallback(() => {
const user = {
id: nextId.current,
username,
email
};
setUsers([...users, user]);
setInputs({
username: '',
email: ''
})
nextId.current += 1;
}, [users, username, email]);
이런 식으로.
함수 안에서 사용하는 state / props는 항상 debs
에 포함시켜야 한다.
컴포넌트의 props 가 바뀌지 않았을땐 리렌더링을 방지하는 것
export default React.momo(CreateUser);
를 이용해주면 된다.
또는,
const User = React.memo(function User(){...});
컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있다.
function reducer(state, action) {
return nextState;
}
현재 상태와 액션 객체를 parameter로 가져와서 새로운 상태를 반환하는 것이 useReducer.
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'Increment'});
라고 사용한다.
useReducer
에 넣는 첫번째 파라미터는 reducer 함수이고, 두번쨰 파라미터는 초기 상태.
import React, { useReducer } from 'react'
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return state +1;
case 'DECREMENT':
return state -1;
default:
return state;
}
}
function Counter() {
const [number, dispatch] = useReducer(reducer, 0);
const onIncrease = () => {
dispatch({ type : 'INCREMENT'});
};
const onDecrease = () => {
dispatch({ type : 'DECREMENT'});
};
return (
<div>
<h1>{number}</h1>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
);
}
export default Counter;
useReducer
를 사용하여 모든 code를 변환
import './App.css';
import React, {useRef, useState, useMemo, useCallback, useReducer }from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
function counterActiveUsers(users) {
console.log('counting active users...');
return users.filter(user => user.active).length;
}
function reducer(state, action) {
switch (action.type) {
case 'CHANGE_INPUT':
return {
...state,
inputs: {
...state.inputs,
[action.name]: action.value
}
}
case 'CREATE_USER':
return {
inputs: initialState.inputs,
users: state.users.concat(action.user)
}
case 'REMOVE_USER':
return {
...state,
users : state.users.filter(user => user.id != action.userId)
}
case 'TOGGLE_USER':
return {
...state,
users : state.users.map(user => user.id === action.userId? {...user, active : ! user.active } : user)
}
default:
return state;
}
}
const initialState = {
inputs : {
username: '',
email: ''
},
users : [
{
id: 1,
username: 'velopert',
email: 'public.velopert@gmail.com',
active : true,
},
{
id: 2,
username: 'tester',
email: 'tester@gmail.com',
active : false,
},
{
id: 3,
username: 'liza',
email: 'liz@gmail.com',
active : false,
},
]
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
const nextId = useRef(4);
const { users } =state;
const { username, email } = state.inputs;
const onChange = useCallback(e => {
const { name, value } = e.target;
dispatch({
type: 'CHANGE_INPUT',
name,
value
});
}, [])
const onCreate = useCallback(e => {
dispatch({
type : 'CREATE_USER',
user : {
id: nextId.current,
username,
email
}
})
nextId.current +=1;
}, [username, email]);
const onRemove = useCallback(e => {
dispatch({
type : 'REMOVE_USER',
userId : e,
})
}, [])
const onToggle = useCallback(e => {
dispatch({
type: 'TOGGLE_USER',
userId : e,
})
},[])
const count = useMemo(() => counterActiveUsers(users), [users]);
return (
<div className="App">
<CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate}/>
< UserList users = {users} onRemove={onRemove} onToggle={onToggle}/>
<div>number of Active users : {count}</div>
</div>
);
}
export default App;
import { useState, useCallback } from 'react'
function useInputs(initialForm) {
const [form, setForm] = useState(initialForm);
const onChange = useCallback(e => {
const { name, value } = e.target;
setForm(form => ({...form, [name] : value}));
}, []);
const reset = useCallback(e => setForm(initialForm), [initialForm]);
return [form, onChange, reset];
}
export default useInputs;
const [{username, email}, onChange, reset] = useInputs({
username : '',
email: ''
});
useInput 을 useReducer
로 다시 구현한 것
import {useReducer, useCallback} from 'react'
function reducer(state, action) {
switch (action.type) {
case 'CHANGE':
return {
...state,
[action.name] : action.value
};
case 'RESET':
return {
...action.initialState
};
default :
return state;
}
}
function useNewInputs(initialState) {
const [state, dispatch] = useReducer(reducer, initialState);
const onChange = useCallback(e => {
const { name, value } = e.target;
dispatch({
type : 'CHANGE',
name,
value,
})
}, []);
const reset = useCallback(e => {
dispatch({
type : 'RESET',
initialState,
})
},[initialState]);
return [state, onChange, reset]
}
export default useNewInputs