const [state, setState] = useState(초기값)
function App() {
const [number, setNumber] = useState(0);
return (
<>
<div>Number State: {number}</div>
<button
onClick={() => {
//기존 업데이트
//setNumber(number + 1);
//함수형 업데이트
setNumber((currentNumber) => {
return currentNumber + 1;
});
}}
>
누르면 UP
</button>
</>
);
}
function App() {
const [number, setNumber] = useState(0);
return (
<>
<div>Number State: {number}</div>
<button
onClick={() => {
//기존 업데이트 -> 배치형으로 처리되고,
//한꺼번에 변경된 내용들을 모아서 한번만 반영
//세번을 써도 한꺼번에 모으기 때문에 똑같은 것이 3번 반복이라 같은 것으로 생각해서 업데이트를 치지 않는다.
//setNumber(number + 1);
//setNumber(number + 1);
//setNumber(number + 1);
//함수형 업데이트
//명령들을 모아서 순차적으로 한번씩 실행시킨다
//괄호 안, 즉 인자 부분에 현재 상태의 state를 넣고 화살표 후에는 바뀐 state를 반환한다. -> 최신값 유지
setNumber((currentNumber) => currentNumber + 1);
setNumber((currentNumber) => currentNumber + 1);
setNumber((currentNumber) => currentNumber + 1);
}}
>
누르면 UP
</button>
</>
);
}
렌더링이 잦다 -> 성능에 이슈가 있다.
그렇게 때문에 한꺼번에 요청 사항을 모아서 한번만 처리하는게 렌더링을 줄일 수 있는 좋은 방법이다. 이래서 배치 업데이트라는 방식을 사용하는 것이다.
렌더링
될 때, 특정한 작업을 수행해야 할 때 설정하는 훅function App() {
const [value, setValue] = useState("");
useEffect(() => {
console.log("hihi");
});
return (
<div>
<input
text="text"
value={value}
onChange={(event) => {
setValue(event.target.value);
}}
/>
</div>
);
}
useEffect를 통해서 한번만 콘솔이 찍혔으면 좋겠다.
이때 알아두어야 할 개념이 의존성 배열
이다.
//[] 빈배열을 추가하면 화면이 처음 로딩될 때만 동작
useEffect(() => {
console.log("hihi");
}, []);
//value값이 바뀔때만 동작.
useEffect(() => {
console.log(`hihi ${value}`);
}, [value]);
Clean-up함수란,
useEffect()에서 파라미터로 넣은 함수의 return 함수이다. return을 쓰면 contents가 사라질때 동작한다.
function App() {
const ref = useRef("초기값");
console.log("ref", ref);
return <div></div>;
ref
{current: "초기값"}
current: "초기값"
}
설정된 ref 값은 컴포넌트가 계속해서 렌더링되어도 unmount 전까지 값을 유지한다.
💡 state는 리렌더링이 꼭 필요한 값을 다룰때 쓰면 된다.
💡 ref는 리렌더링을 발생시키지 않는 값을 저장할 때 사용한다.
function App() {
const [count, setCount] = useState(0);
const countRef = useRef(0);
const plusStateCountButtonHandler = () => {
setCount(count + 1);
};
const plusRefCountButtonHandler = () => {
countRef.current++;
console.log("countRef", countRef);
/*
렌더링은 일어나고 있지 않아서 값이 바뀌지는 않지만
console로 찍어보면 카운트가 증가하고 있다는 것을 알 수 있다.
*/
};
return (
<>
<div
style={{
border: "1px solid black",
margin: "10px",
padding: "10px"
}}
>
state 영역입니다. {count} <br />
<button onClick={plusStateCountButtonHandler}>state 증가</button>
</div>
<div
style={{
border: "1px solid black",
margin: "10px",
padding: "10px"
}}
>
ref 영역입니다. {countRef.current}
<br />
<button onClick={plusRefCountButtonHandler}>Ref 증가</button>
</div>
</>
);
}
function App() {
const [id, setId] = useState("");
const idValueHandler = (event) => {
setId(event.target.value);
};
const idRef = useRef("");
const pwRef = useRef("");
//화면이 렌더링 될때, 어떤 작업을 하고 싶다. : useEffect
useEffect(() => {
//console.log("렌더링");
idRef.current.focus();
//pwRef.current.focus();
}, []);
useEffect(() => {
if (id.length >= 10) {
pwRef.current.focus();
}
}, [id]);
return (
<>
<div>
아이디 :{" "}
<input type="text" ref={idRef} value={id} onChange={idValueHandler} />
</div>
<div>
패스워드 : <input type="password" ref={pwRef} />
</div>
</>
);
}
리액트에서 리렌더링이 빈번하게 일어나는 것은 좋은 것이 아니다.(cost(=비용)이 많이 든다.) 불필요한 리렌더링을 줄이기 위해 최적화(Optimization)
를 해주어야한다.
대표적인 방법은 memo(React.memo): 컴포넌트를 캐싱
, useCallback: 함수를 캐싱
, useMemo: 값을 캐싱
3가지가 있다.
리-렌더링의 발생 조건 중 3번째 경우. 즉, 부모 컴포넌트가 리렌더링되면 자식 컴포넌트는 모두 리렌더링 된다.
React.memo
이다.React.memo를 이용해서 컴포넌트를 메모리
에 저장해두고 필요할 때 갖다 쓸 수 있다. 이렇게 하면 부모 컴포넌트의 state의 변경으로 인해 props가 변경이 일어나지 않는 한 컴포넌트는 리렌더링 되지 않는다.
이것은 컴포넌드 memoization
라고 한다.
export default content
👇🏼 //아래처럼 메모에 저장해준다.
export default React.memo(content)
React.memo는 컴포넌트를 메모이제이션했다면, useCallback은 인자로 들어오는 함수 자체를 기억(메모이제이션)한다.
메모이제이션을 했음에도 불구하고 리렌더링 현상이 일어나는데 그것은 함수형 컴포넌트로 만들었기 때문에 새로운 메모리공간에 저장을 하고 바뀌었다고 판단해 리렌더링이 된다.
//이것을 useCallback으로 감싸주면 된다.
const initCount = () => {
setCount(0);
}
const initCount = useCallback(() => {
setCount(0);
), [count]}
/*
[]는 의존성 배열에 count를 안넣어주면 계속 0인상태로 스냅샷을 찍어서
보관해두기 때문에 뭘 해도 0 임.
*/
여기서 말하는 memo는 memoization
을 뜻한다. 기억한다.
동일한 값을 반환하는 함수를 계속 호출해야 하면 필요없는 렌더링을 한다고 볼 수있다. 맨 처음 해당 값을 반환할때 그 값을 특별한 곳(메모리)에 저장한다. 이렇게 하면 필요할때마다 다시 함수를 호출해서 계산하는게 아니라 이미 저장한 값
을 단순히 꺼내와서 쓸 수 있다. 이러한 기법을 캐싱을 한다.
라고 표현한다. (함수를 리턴한 값, 값 자체를 캐싱한다.)
//as-is
const value = 반환할_함수();
//to-be
const value = useMemo(() => {
return 반환할_함수()
}, [dependencyArray]);
dependencyArray의 값이 변경될 때만 반환할_함수()가 호출된다. 그 외의 경우에는 memoization해놨던 값을 가져오기만 하면 된다.
남발하면 별도의 메모리 확보를 너무나 많이 하게 되기 때문에 오히려 성능이 악화될 수 있다. 필요할때만 쓰자!