8장 Hooks
useEffect
리액트 컴포넌트가 렌더링될 때마다 특정 작업 을 수행하도록 설장할 수 있는 Hook
componentDidMount 와 componentDidupdate 합친 형태로 보면 됨.
useEffect(callback, 변경주시변수들의 배열);
두번째 인자로 어떤 값을 전달하느냐에 따라서 실행되는 조건이 달라짐. 기본적으로 첫 렌더링되고 난 직후에는 실행된다.
- 전달하지 않으면 : 리렌더링될 때
[ ]
빈배열 전달하면 : 처음렌더링될 때- 특정 값을 배열형태로 전달하면 : 첫 렌더링 + 해당 값의 변경이 있을 때
뒷정리하기
언마운트 되기 전이나 업데이트되기 직전에 특정 함수 호출하고 싶으면 useEffect 콜백함수에서 cleanup 함수 반환해 주면 된다.
업데이트 되기 직전의 값을 보여줌.
useEffect(() => {
console.log("useEffect");
console.log(name);
return () => {
console.log("cleanup");
console.log(name);
};
}, [name]);
useReducer
액션 값을 전달받아 새로운 상태를 반환하는 함수
액션
: 업데이트를 위해 필요한 정보를 담은 값
새로운 상태 만들 때는 불변성을 지켜줘야 한다.
첫번째 파라미터로 리듀서 함수, 두번째 파라미터로 해당 리듀서의 기본값 넣어줌.
function reducer(state, action) {
return { ... }; // 불변성을 지키면서 업데이트한 새로운 상태를 반환합니다.
}
// 액션 값 예시
{
type: 'INCREMENT',
// 다른 값들이 필요하다면 추가로 들어감
}
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 Counter2 = () => {
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>
);
};
return 값으로 state 값과 dispatch 함수 받아온다. state는 현재 가리키고 있는 상태
dispatch는 액션을 발생시키는 함수이다. dispatch(action) 같은 형태로 쓰이며, 해당 액션 값으로 리듀서 함수가 호출되는 구조.
- 액션은 객체 형태
- 약간 useState를 reducer 함수가 경우에 따라 나눠어 주는 느낌.
사용은 17장 리덕스 사용하여 리액트에서 상태 관리하기
인풋 상태 관리하기
useState는 input여러개 일때 개별적으로 관리했는데, useReducer 이용해서 동적으로 관리하기
function reducer(state, action) {
return {
...state,
[action.name]: action.value,
};
}
const Info = () => {
const [state, dispatch] = useReducer(reducer, { name: "", nickName: "" });
const onChange = (e) => {
dispatch(e.target);
};
...
};
useMemo
렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행, 원하는 값이 바뀌지 않았으면 이전에 연산했던 결과를 다시 사용.
두번재 인자로 특정값을 배열 형태로 지정
리렌더링 할 때 마다, 각 요소들의 { } 안에 값들이 다시 평가되는 것 같음. 만약 { } 안에 함수가 있다면 재호출되어서 계산을 반복하게 됨.
⇒ 해당 값을 가지고 있다가, 바뀌지 않았으면 저장된 기존 값을 반환해서 2번 계산하기 않게 한다.
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>
);
};
useCallback
UseMemo의 함수 버전으로 생각하면 된다.
컴포넌트가 리렌더링될 때 마다 함수를 새로만들지 않고 기존 함수 사용
첫번째 인자로 함수, 두번째 인자로 배열
위에 코드에서 OnChange 와 OnInsert 같은 함수들이 리렌더링 될 때마다 새로 만들어진다. 이를 최적화할 수 있다.
const onChange = useCallback((e) => {
setNumber(e.target.value);
}, []);
const onInsert = useCallback(
(e) => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber("");
},
[number, list]
);
const
- 렌덩링할 때 {onChange} 가 평가되고 함수 객체가 생성된 다음, 호출 되는 것 같다. useCallback을 하면 평가되고 함수 객체가 생성되는 과정을 기존함수를 가져오는 방식으로 처리하는 것 같다. (뇌피셜임 ㅋㅋ)
⇒ 여기서 문제가 생긴다. 함수 내부 값 정확히 하자면 함수 컨텍스트 값들을 사용해야할 때에는 함수를 재평가해줘야 한다. ( [name,list] 인 이유)
useRef
함수 컴포넌트에서 ref 사용하기.
useRef 를 통해 만든 객체 안의 current 값이 실제 요소를 가르킴.
const Average = () => {
...
const inputEl = useRef(null);
...
const onInsert = useCallback(
(e) => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber("");
**inputEl.current.focus();**
},
[number, list]
);
...
return (
<div>
<input value={number} onChange={onChange} ref={**inputEl**} />
...
로컬변수 사용하기
class MyComponent extends Component {
id = 1
setId = (n) => {
this.id = n;
}
printId = () => {
console.log(this.id);
}
render() {
return (
<div>
MyComponent
</div>
);
}
}
class 형 컴포넌트에서 클래스 필드로 정의한 로컬 변수를 함수 컴포넌트에서는 useRef이용해서 로컬변수로 사용할 수 있다.
const RefSample = () => {
const id = useRef(1);
const setId = (n) => {
id.current = n;
}
const printId = () => {
console.log(id.current);
}
return (
<div>
refsample
</div>
);
};
ref안의 값이 바뀌어도 컴포넌트가 리렌더링 되지 않는다.
→ 렌더링과 관련되지 않은 값을 관리할 때만 사용해라
커스텀 Hooks 만들기
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];
}
import React from 'react';
import useInputs from './useInputs';
const Info = () => {
const [state, onChange] = useInputs({
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;