새싹 프론트엔드 실무 과정 8주차 Hooks
11. Hooks - 1
useState()
- 가장 기본적인 Hook
- 함수형 컴포넌트가 가변적인 상태를 지닐 수 있도록 해 줌
import React, { useState } from "react";
const AddName = () => {
const [names, setNames] = useState(["간", "리액트"]);
const [input, setInput] = useState("");
function InputChange(e) {
setInput(e.target.value);
}
function uploadInput() {
setNames([input]);
}
return (
<div>
<input type="text" onChange={InputChange} />
<button onClick={uploadInput}>추가</button>
<div>
{names.map((name, idx) => (
<p key={idx}>{name}</p>
))}
</div>
</div>
);
};
export default AddName;
기존에 있던 두 개의 값에 추가를 했으면 좋겠으니, 기존값 유지 + 새로운 값 추가
➡ setState()의 콜백 함수에 prevState 값을 전달해서 유지
function uploadInput() {
setNames((prevState) => [input, ...prevState]);
}
function uploadInput() {
setNames((prevState) => [...prevState, input]);
}
⭐ setState() 함수 동작 과정
useState() 성능 최적화
useState() 함수의 인자에 초기값을 지정한 경우
- state 값이 업데이트 될 때마다 초기값이 계속해서 호출됨
- 만약 초기값에 복잡한 계산식이 있다면 성능 저하 문제 발생
useEffect()
- 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하 도록 설정해주는 Hook
- 최초에 한번 실행하게 하고 싶은 작업을 작성할 때 주로 사용
Mount : 화면에 첫 렌더링
Updaste : 다시 렌더링
Unmount : 화면에서 제거
useEffect()가 받을 수 있는 데이터
매개변수1 : 콜백함수 (필수)
매개변수2 : 빈 배열 [ ] (옵션)
매개변수3 : [value]
case 1)
- useEffect()의 매개변수에 콜백함수만 있는 경우 :
컴포넌트가 렌더링 될 때마다 콜백함수 실행 됨.
네트워크 통신을 통해 데이터를 가져올 때 매번 데이터를 가져옴
➡ 사용하는 의미❌
case 2)
- useEffect()의 매개변수에 콜백함수, 빈배열이 있는 경우 :
컴포넌트가 처음 렌더링 될 때 실행, value 값이 변경 되었을 때 실행
빈 배열이 있는 경우 최초에 한 번만 콜백함수를 실행
case 3) useEffect()의 매개변수에 콜백함수, 배열[value]이 있는 경우 :
- 1. 컴포넌트가 처음 렌더링 될 때
- 2. value값이 변경되었을 때 실행
import React, { useState } from "react";
const AddName = () => {
const [names, setNames] = useState(() => heavyWork());
const [input, setInput] = useState("");
function InputChange(e) {
setInput(e.target.value);
}
function uploadInput() {
setNames((prevState) => [input, ...prevState]);
}
function heavyWork() {
for (let i = 0; i < 1000; i++) {
console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
return ["간", "리액트"];
}
}
return (
<div>
<input type="text" onChange={InputChange} />
<button onClick={uploadInput}>추가</button>
<div>
{names.map((name, idx) => (
<p key={idx}>{name}</p>
))}
</div>
</div>
);
};
export default AddName;
정말 엄청나다..
import React, { useState, useEffect } from "react";
const UseEffect = () => {
const [names, setNames] = useState(() => heavyWork());
const [input, setInput] = useState("");
function InputChange(e) {
setInput(e.target.value);
}
function uploadInput() {
setNames((prevState) => [input, ...prevState]);
}
function heavyWork() {
for (let i = 0; i < 1000; i++) {
console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
}
return ["간", "리액트"];
}
useEffect(() => {
console.log("렌더링이 완료되었습니다.");
console.log({ names });
}, [names]);
return (
<div>
<input type="text" onChange={InputChange} />
<button onClick={uploadInput}>추가</button>
<div>
{names.map((name, idx) => (
<p key={idx}>{name}</p>
))}
</div>
</div>
);
};
export default UseEffect;
뒷정리하기 - cleanup
useEffect() 함수 내부에서 return 함수를 반환하면 됨
useEffect(() => {
console.log("렌더링이 완료되었습니다.");
console.log({ names });
return () => {
console.log("cleanup");
console.log({ names });
};
}, [names]);
useRef()
- 컴포넌트 내부에서 사용되는 변수를 저장하는 Hook
특징
- 컴포넌트가 재렌더링되어도 저장된 변수 값을 유지
- 불필요한 렌더링을 방지할 수 있음
- 특정 DOM 요소에 접근 가능
import React, { useRef } from "react";
const UseRefComponent1 = () => {
const ref = useRef("안녕하세요");
console.log("변경 전 ref 값 : ", ref.current);
ref.current = "Hello";
console.log("변경 후 ref 값 : ", ref.current);
return <div></div>;
};
export default UseRefComponent1;
⭐ State vs useRef()
useState() 렌더링과 관련 없는 값을 저장하기에는 적합치 않다.
useRef() 리랜더링을 하지 않음. 렌더링과 관련이 없는 값을 저장하기에 적합하다.
⭐ useRef() vs 일반 변수
일반변수는 렌더링시 값이 초기화되지만,
useRef의 값은 컴포넌트의 생애주기를 통해 유지가 되어 컴포넌트가 렌더링이 되어도 값을 유지한다.
✔ 화면이 unmount되면 ref값이 초기화
useRef()로 DOM에 접근하기
inputRef.current.focus();
깜빡깜빡
12. Hooks - 2
useReducer()
재사용이 가능해짐
⭐ useReducer() vs useState()
➡ 사용하는 방법은 다르지만 결과는 같음!
✔ useState()
- 컴포넌트에서 관리하는 값이 한 개
- 값이 단순한 숫자, 문자열, 불리언 등의 값인 경우
✔ useReducer()
- 컴포넌트에서 관리하는 값이 여러 개
- 구조가 복잡한 경우
사용방법 :
const [state, dispatch] = useReducer(reducer, initialState);
dispatch() 사용 방법 : dispatch({ key : value })
ex) dispatch({메뉴 : 햄버거})➡ 액션 (객체)
일반적으로 따로 파일을 생성! 따로 파일을 빼놔야, 다른 파일에서 import해서 사용 가능
컴포넌트가 아니기 때문에, 첫 글자 대문자 안해도 됨!
➡ countReducer.js
const countReducer = (state, action) => {
switch (action.type) {
case "INCREMENT":
return action.icon;
case "DECREMENT":
return action.icon;
default:
return state;
}
};
export default countReducer;
➡ Counter.js
function numUp() {
dispatch({ type: "INCREMENT", icon: "🌷" });
}
function numDown() {
dispatch({ type: "DECREMENT", icon: "🌼" });
}
첫 글자 대문자! 컴포넌트는 return값이 있음!
➡ 여러개의 값을 넣을 수 있음
⭐ useReducer() vs useState()
useState()
- 컴포넌트에서 관리하는 값이 한 개
- 값이 단순한 숫자, 문자열, 불리언 등의 값인 경우
useReducer()
- 컴포넌트에서 관리하는 값이 여러 개
- 구조가 복잡한 경우
- 로직을 분리 가능, 재사용 가능
useMemo()
- 동일한 계산을 하는 함수를 포함하고 있는 컴포넌트가 반복적으로 렌더링이 될 때, 해당 함수의 값을 메모리에 저장해 놓고 재사용할 수 있게 함
메모이제이션(Memoization) : 전문용어
- 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거
- 장점 : 값을 재사용, 별도의 계산❌ ➡ 프로그램 실행 속도를 빠르게 하는 기술
useRef()랑 같은 구조
첫 번째 매개변수 : 콜백함수
- 메모이제이션 할 값을 계산해서 반환해주는 함수
두 번째 매개변수 : 의존성 배열
- 배열 안의 값이 업데이트 될 때만 콜백함수를 재호출
의존성 배열이 빈 배열인 경우
: 컴포넌트가 마운트되었을 때만 콜백함수 호출
의존성 배열이 없는 경우
: 컴포넌트가 렌더링 될 때마다 콜백함수 호출 ➡ useMemo() 사용하는 의미❌
const useMemoComponent = ({ a, b }) => {
const result = useMemo(() => compute(a, b), [a, b]);
return <div>{result}</div>;
};
➡ 최초 한번, a 또는 b의 값이 바뀔 때 실행
화면이 랜더링 될 때마다 계속 실행 중, 값을 저장
쉬운 계산만 눌렀는데, 둘다,,
const hardSum = useMemo(() => hardCalculate(hardNumber), [hardNumber]);
useMemo 사용 시 주의점
목적 : 값을 재사용하기 위해 별도의 메모리를 할당하여 값을 저장
➡ 불필요한 값까지 메모이제이션하면 메모리 용량이 늘어나 성능 저하 발생
useCallBack()
- 함수를 재 사용하기 위한 Hook
const UseCallBackComponent1 = () => {
const name1 = () => "gan";
const name2 = () => "gan";
console.log("name1 : ", name1);
console.log("name2 : ", name2);
return <div>{name1 === name2 ? "같다" : "다르다"}</div>;
};
⭐ 함수는 객체이기 때문에 주소를 가지고 있음.
===으로 비교하면 서로 다른 메모리에 저장되어 있었기 때문에 '다르다'
코드를 다시 실행할 때 마다 clickHandler을 다시 실행
useEffect(() => {
console.log("clickHandler() 변경");
}, [clickHandler]);
// 하나 만들어 놓고, 함수 이름을 집어 넣기
// 랜더링 후 함수가 변경되지 않았다면 새로 만들지마!
const [count, setCount] = useState(0);
const clickHandler = useCallback(() => {
console.log("count : ", count);
}, []);
// 빈 배열[] : 최초 랜더링 됐을 때만
여전히 count : 0,,
useState(0)에서 0으로 저장했던 게, count에서 0으로 저장되어있음
➡ count바뀔 때 만 변경해줘~
const clickHandler = useCallback(() => {
console.log("count : ", count);
}, [count]);
React.memo()
컴포넌트를 통채로 저장
- 리액트에서 제공하는 고차 컴포넌트
- props의 변화가 있는지를 체크
➡ App.js
import React, { useState } from "react";
import ChildComponent from "./2022-12-06/ChildComponent";
const App = () => {
const [count, setCount] = useState(0);
const updateHandler = () => {
console.log("update");
};
return (
<div>
<input type="number" onChange={(e) => setCount(e.target.value)} />
<ChildComponent update={updateHandler} />
</div>
);
};
export default App;
➡ ChildComponent.js
import React from "react";
const ChildComponent = (props) => {
const { update } = props;
console.log("child component 렌더링");
return <div></div>;
};
export default ChildComponent;
부모가 랜더링 되는데 자식이 자꾸 실행 되는 이유는?
함께 랜더링 중..
const updateHandler = useCallback(() => {
console.log("update");
}, []);
자식도 부모로 부터 props 전달 받을 때 마다 매번 새로운 객체로 바뀜
props 내부의 값이 바뀌지 않았으면 랜더링 하지마!
➡ ChildComponent.js
컴포넌트를 통채로 메모리에 저장
3가지 메모
value를 메모리에 저장 : useCallback()
일반 함수 : useMemo()
컴포넌트를 저장리액트의 메모 함수 : React.memo()
새싹DT 기업연계형 프론트엔드 실무 프로젝트 과정 8주차 블로그 포스팅