[TIL, React] React Hooks 복습하기
리액트를 다루는 기술 8장을 복습하며 글을 쓴다.
1. UseState, SetState
상태 값 관리
Counter.js
import React, { 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;
Counter.js
import React, { 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로 state값의 초기값을 설정한다.
useState는 반드시 객체가 아니어도 상관 없다.
useState는 여러번 사용될 수 있다.
함수를 호출하면 배열이 반환되는데, 배열의 첫 번째 원소는 현재 상태이고, 두 번째 상태는 원소의 상태를 바꿔주는 함수이다. -
state의 값은 세터함수, setState를 사용해 바꿔준다.
이 때, 불변성을 지키기 위해 배열이나 객체 값을 업데이트 할 대는,
객체의 사본을 만들고 그 사본에 값을 업데이트 한 후, 그 사본의 상태를 setState또는 useState를 통해 전달받은 세터함수를 통해 업데이트 한다. -
객체에 대한 사본을 만들 때는 spread 연산자 [...] 를 이용해 처리한다.
-
배열에 대한 사본을 만들 때는 concat 같은 배열의 내장함수를 이용해 처리한다.
-
state는 부모 -> 자식으로 단 방향적 흐름을 갖는데, 자식컴포넌트에서 부모의 state 값을 바꾸고 싶은 경우에는 props에 setState를 내려줘서 처리한다.
2. UseEffect
컴포넌트가 랜더링 될 때 특정 작업 실행
const Info = () => {
const [name, setName] = useState("");
const [nickname, setNickname] = useState("");
useEffect(() => {
console.log("랜더링 완료.");
console.log("name, nickname -->", name, nickname);
});
const onChangeName = (e) => {
setName(e.target.value);
};
const onChangeNickname = (e) => {
setNickname(e.target.value);
};
return (
<>
<div>
<input value={name} onChange={onChangeName} />
<input value={nickname} onChange={onChangeNickname} />
</div>
<div>
<div>
<b>이름 :</b> {name}
</div>
<div>
<b>별명 :</b> {nickname}
</div>
</div>
</>
);
};
const Info = () => {
const [name, setName] = useState("");
const [nickname, setNickname] = useState("");
useEffect(() => {
console.log("랜더링 완료.");
console.log("name, nickname -->", name, nickname);
});
const onChangeName = (e) => {
setName(e.target.value);
};
const onChangeNickname = (e) => {
setNickname(e.target.value);
};
return (
<>
<div>
<input value={name} onChange={onChangeName} />
<input value={nickname} onChange={onChangeNickname} />
</div>
<div>
<div>
<b>이름 :</b> {name}
</div>
<div>
<b>별명 :</b> {nickname}
</div>
</div>
</>
);
};
핵심 개념
-
useEffect는 리액트 컴포넌트가 랜더링 될 때마다 특정 작업을 수행시키는 hook.
-
실행 시점은 랜더링되고 난 직후 마다, 의존값으로 설정한 값이 바뀌고 난 직후 마다 싱행된다.
-
컴포넌트가 마운트 될 때(최초 랜더링 시)만 실행하고 싶을 때는, 함수의 두 번째 파라미터로 빈 배열을 넣으면 된다.
useEffect(() => {
console.log("랜더링 완료.");
console.log("name, nickname -->", name, nickname);
},[]);
-
특정 값이 업데이트 될 때에만 실행하고 싶을 때는, 함수의 두 번째 파라미터로 전달되는 배열안에 검사하고 싶은 값을 넣어주면 된다.
-
뒷정리하기, 컴포넌트가 언마운트 되기 전이나 의존 값이 업데이트 되기 직전에 어떤 작업을 수행하고 싶다면 useEffect에서 뒷정리(clean up) 함수를 반환해 주어야 한다.
useEffect(() => {
console.log("랜더링 완료.");
console.log("name, nickname -->", name, nickname);
return () =>{
console.log('clean up');
console.log(name);
};
},[name]);
- 오직 언마운트 될 때만 뒷정리 함수를 호출하고 싶다면 useEffect 함수의 두 번째 파라미터에 빈 배열을 넣어주면 된다.
그리고, 뒷정리 함수는 함수가 호출 될 때 업데이트 되기 직전의 값을 보여준다.
useEffect(() => {
console.log("effect");
return () =>{
console.log('unmount');
};
},[]);
* 프로젝트에 useEffect를 사용해보니,
원하는 dependency를 설정해서 useEffect를 사용하는것 보다,
필요한 곳에 해당 로직을 넣는게 나은 것 같다.
useEffect를 사용하면 코드리뷰를 할 때
사용한 의도가 무엇인지, 어떻게 작동하게 되는지, 구구절절 설명해야 했다.
해당 컴포넌트에 들어간 모든 로직을 이해해야 해당 useEffect를 이해할 수 있기 때문에
협업할 때 지장이 생겼다.
생각보다 dependency 설정도 까다로웠고,
랜더링때마다 호출되다 보니 sideEffect가 필연적 발생하게 되는 것 같다.
그래서 앞으로는 useEffect를 최대한 지양해서 쓰려고 한다.
단, 렌더링 할 때마다 로그를 찍는 디버깅용으로는 자주 사용할 것 같다.
3. useReducer
const reducer = (state, action) => {
// action.type에 따라 다른 작업을 수행함.
switch (action.type) {
case "INCRESEMENT":
return { value: state.value + 1 };
case "DECRESEMENT":
return { value: state.value - 1 };
default:
// 아무것도 해당되지 않을 때 기존 상태 반환
return state;
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { value: 0 });
return (
<div>
<p>
현재 카운터 값은 <b>{state.value}</b>입니다.
</p>
<button onClick={() => dispatch({ type: "INCRESEMENT" })}> +1 </button>
<button onClick={() => dispatch({ type: "DECRESEMENT" })}> -1 </button>
</div>
);
};
const reducer = (state, action) => {
// action.type에 따라 다른 작업을 수행함.
switch (action.type) {
case "INCRESEMENT":
return { value: state.value + 1 };
case "DECRESEMENT":
return { value: state.value - 1 };
default:
// 아무것도 해당되지 않을 때 기존 상태 반환
return state;
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { value: 0 });
return (
<div>
<p>
현재 카운터 값은 <b>{state.value}</b>입니다.
</p>
<button onClick={() => dispatch({ type: "INCRESEMENT" })}> +1 </button>
<button onClick={() => dispatch({ type: "DECRESEMENT" })}> -1 </button>
</div>
);
};
핵심개념
-
useReducer는 useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용하는 Hook.
개인적으로 state가 지역 변수라면, reducer는 전역 변수 제어라고 이해했다. -
리듀서는 현재 상태, 그리고 업데이트를 위해 필요한 정보를 담은 액션(action) 값을 전달받아 새로운 상태를 반환하는 함수다. 새로운 상태를 만들 때는 반드시 불변성을 지켜주어야 한다.
-
useReducer의 첫 번째 파라미터에는 리듀서 함수를 넣고, 두 번째 파라미터에는 해당 리듀서의 기본값을 넣어준다.
-
이 hook을 사용하면 state값과 dispatch함수를 받아온다. state는 현재 가리키고 있는 상태고, dispatch는 액션을 발생시키는 함수다.
-
dispatch(action) 과 같은 형태로, 함수 안에 파라미터로 액션 값을 넣어주면 리듀서 함수가 호출되는 구조다.
-
useReducer를 사용했을 때의 가장 큰 장점은 컴포넌트 업데이트 로직을 컴포넌트 바깥으로 뺄 수 있다는 것이다.
리듀서로 input 상태 관리하기
const 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>
<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>
</>
);
};
- useReducer에서의 액션은 그 어떤 값도 사용 가능하다. 그래서 이번에는 이벤트 객체가 지니고 있는 e.target의 값 자체를 액션값으로 사용했다.
4. useMemo
import React, { useMemo, 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("");
};
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>
);
};
export default Average;
import React, { useMemo, 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("");
};
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>
);
};
export default Average;
핵심요약
- useMemo는 렌더링 하는 과정에서 특정 값이 바뀌었을 때만 실행하도록 연산 실행 시점에 대해 조건을 준다. 만약 dependency 로 설정한 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용하는 방식이다.
5. useCallback
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(
(e) => {
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={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값 : </b> {avg}
</div>
</div>
);
};
export default Average;
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(
(e) => {
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={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값 : </b> {avg}
</div>
</div>
);
};
export default Average;
핵심요약
-
useCallback은 useMemo와 상당히 비슷한 함수로, 주로 렌더링 성능을 최적화해야 하는 상황에서 사용한다. 이 Hook을 사용하면 만들어 놨던 함수를 재사용 할 수 있다.
-
컴포넌트의 렌더링이 자주 발생하거나 렌더링해야 할 컴포넌트의 개수가 많아지면 최적화 해주는 것이 좋다.
-
useCallback의 첫 번째 파라미터에는 생성하고 싶은 함수를 넣고, 두 번째 파라미터에는 배열을 넣으면 된다.
-
이 배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해야 하는지 명시해야 한다.
-
onChange 처럼 비어있는 배열을 넣게 되면 컴포넌트가 렌더링될 때 만들었던 함수를 계속해서 재사용하게 된다. dependency를 설정하게 되면 해당 dependency의 변경에 따라 새로 만들어진 함수를 사용하게 된다.
-
상태 값에 의존해야 할 때는 그 값을 반드시 두 번째 파라미터 안에 포함시켜 주어야 한다.
-
useCallback 은 결국 useMemo 에서 함수를 반환하는 상황에서 더 편하게 사용 할 수 있는 Hook 이다. 숫자, 문자열, 객체 처럼 일반 값을 재사용하기 위해서는 useMemo 를, 그리고 함수를 재사용 하기 위해서는 useCallback 을 사용할 것.
6. useRef
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 inputEl = useRef(null);
const onChange = useCallback((e) => {
setNumber(e.target.value);
}); // 컴포넌트가 처음 렌더링 될 때만 함수 생성
const onInsert = useCallback(
(e) => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber("");
inputEl.current.focus();
},
[number, list]
); // number 혹은 list 가 바뀌었을 때만 함수 생성
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value={number} onChange={onChange} ref={inputEl} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
<div>
<b>평균값 : </b> {avg}
</div>
</div>
);
};
export default Average;
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 inputEl = useRef(null);
const onChange = useCallback((e) => {
setNumber(e.target.value);
}); // 컴포넌트가 처음 렌더링 될 때만 함수 생성
const onInsert = useCallback(
(e) => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber("");
inputEl.current.focus();
},
[number, list]
); // number 혹은 list 가 바뀌었을 때만 함수 생성
const avg = useMemo(() => getAverage(list), [list]);
return (
<div>
<input value={number} onChange={onChange} ref={inputEl} />
<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 Hook은 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있게 한다.
-
useRef를 사용하여 ref를 설정하면 useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리키게 된다.
useRef 로컬 변수 사용하기
import React, { 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;
컴포넌트 로컬 변수를 사용해야 할 때도 useRef를 활용할 수 있다.
여기서 로컬 변수란 렌더링과 상관없이 바뀔 수 있는 값을 의미한다.
이렇게 ref안의 값이 바뀌어도 컴포넌트가 렌더링 되지 않는다는 점은 주의해야 한다.
렌더링과 관련되지 않은 값을 관리할 때만 이러한 방식으로 코드를 작성한다.
Author And Source
이 문제에 관하여([TIL, React] React Hooks 복습하기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jazzyfact95/TIL-React-Hooks-복습하기저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)