useState
컴포넌트 안에서 변경될 값을 사용하기 위한 React Hook 이다
const [상태 값 저장 변수, 상태 값 갱신 함수] = useState(상태 초기 값);
사용법
import React, { useState } from 'react';
function Counter() {
const [number, setNumber] = useState(0);
const onIncrease = () => {
setNumber(preNum => preNum + 1);
}
const onDecrease = () => {
setNumber(preNum => preNum - 1);
}
return (
<div>
<h1>{number}</h1>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
);
}
export default Counter;
setNumber( number + 1)
원래 setNumber 파라미터에는 위와 같이 업데이트 할 새로운 값을 넣어주는데,
setNumber( preNum => preNum + 1)
위와 같이 기존 값을 값을 업데이트 하는 함수를 넣어 값을 업데이트 할 수도 있다
이와 같은 함수형 업데이트는 컴포넌트를 최적화 할 때 주로 사용한다
객체값 관리하기
import React, { useState } from 'react';
function InputSample() {
const [inputs, setInputs] = useState({ name: '', nickname: ''});
const { name, nickname } = inputs;
const onChange = (e) => {
const { value, name } = e.target;
setInputs({ ...inputs, [name]: value });
};
const onReset = () => { setInputs({ name: '', nickname: '', }) };
return
<div>
<input placeholder="이름" onChange={onChange} value={name} />
<input placeholder="닉네임" onChange={onChange} value={nickname} />
<button onClick={onReset}>초기화</button>
<b> 소개: </b> {name} ({nickname})
</div>
;
}
export default InputSample;
setInputs({ ...inputs, [name]: value });
이렇게 기존 객체를 복사하여 새로운 객체에 업데이트 작업을 하는 것을 불변성 유지라 한다
불변성을 지켜줘야만 리액트가 컴포넌트의 상태가 업데이트 됐음을 감지할 수 있고 필요한 부분만을 리렌더링 할 수 있기 때문이다
(기존 상태를 직접 수정하는 경우 리렌더링이 되지 않는다)
useEffect
컴포넌트가 렌더링 될 때마다 특정 작업(Sied effect)을 실행할 수 있도록 한다
Side effect
Side effect는 component가 렌더링 된 이후에 비동기로 처리되어야 하는 부수적인 효과들을 뜻한다
useEffect(함수, [배열]);
ex)
1
useEffect(() => {
console.log("렌더링 될때마다 실행");
},[]);
2
useEffect(() => {
console.log("맨 처음 렌더링될 때 한 번만 실행");
},[]);
3
useEffect(() => {
console.log(name);
console.log("name이라는 값이 업데이트 될 때만 실행");
},[name]);
useEffect 무한루프 상황
종속성 배열에 종속성을 전달하지 않을 경우
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount((count) => count + 1);
});
return (
<div className="App">
<p> value of count: {count} </p>
</div>
);
}
- 첫 번째 렌더링에서 React는 count 값을 확인한후 useEffect 함수를 실행한다
- useEffect는 setCount 메소드를 호출하고 count Hook의 값을 업데이트한다
- React는 UI를 다시 렌더링하여 업데이트된 count 값을 표시한다
- UI가 재렌더링 되었기 때문에, useEffect는 모든 렌더 사이클에서 실행되므로 또 다시 setCount 함수를 호출한다 -> 무한반복
해결방법
useEffect(() => {
setCount((count) => count + 1);
}, []);
함수를 종속성으로 사용할 경우
function App() {
const [count, setCount] = useState(0);
function logResult() {
return 2 + 2;
}
useEffect(() => {
setCount((count) => count + 1);
}, [logResult]);
return (
<div className="App">
<p> value of count: {count} </p>
</div>
);
}
- useEffect는 디펜던시가 업데이트되었는지 확인하기 위해 얕은 비교(shallow comparison)를 실행한다
- 각 렌더 동안 React가 logResult의 참조를 재정의한다
- 각 사이클마다 useEffect 함수가 다시 트리거된다 -> 무한반복
해결방법
useCallback Hook을 사용해 참조 값이 동일하게 유지되게한다
const logResult = useCallback(() => {
return 2 + 2;
}, []); // logResult가 메모된다
useEffect(() => {
setCount((count)=> count+1);
}, [logResult]); //logResult 참조가 동일하게 유지되므로 무한 루프 오류가 없어진다
배열을 종속성으로 사용할 경우
const [count, setCount] = useState(0);
const myArray = ["one", "two", "three"];
useEffect(() => {
setCount((count) => count + 1); //이전처럼 Count 값을 증가시킨다
}, [myArray]); //배열 변수를 디펜던시로 전달
- React는 디펜던시의 참조가 변경되었는지 확인하기 위해 얕은 비교를 사용한다
- myArray에 대한 참조가 각 렌더에서 계속 변경되므로 useEffect는 setCount 을 실행한다
- myArray의 불안정한 참조 값으로 인해 React는 모든 렌더 사이클에서 useEffect를 호출한다 -> 무한반복
해결방법
useRef을 사용해 참조가 변경되지 않도록 변경 가능한 객체가 반환된다
객체(Object)를 종속성으로 사용할 경우
const [count, setCount] = useState(0);
const person = { name: "Rue", age: 17 };
useEffect(() => {
setCount((count) => count + 1);
}, [person]);
return (
<div className="App">
<p> Value of {count} </p>
</div>
);
- React는 얕은 비교를 통해 person의 참조 값이 변경되었는지 확인한다
- 렌더링할 때마다 person 객체의 참조 값이 변경되므로 React는 useEffect를 다시 실행한다
- 따라서 모든 업데이트 주기마다 setCount가 호출된다 -> 무한 반복
해결 방법
useMemo를 사용해이 디펜던시가 변경될 때 메모된 값을 계산하고 각 렌더링 중에 상태의 참조 값이 변경되지 않도록해준다
const person = useMemo(
() => ({ name: "Rue", age: 17 }),
[] //디펜던시가 없으므로 값이 변경되지 않는다.
);
useEffect(() => {
setCount((count) => count + 1);
}, [person]);
잘못된 디펜던시를 전달할 경우
const [count, setCount] = useState(0);
useEffect(() => {
setCount((count) => count + 1);
}, [count]);
return (
<div className="App">
<button onClick={() => setCount((count) => count + 1)}>+</button>
<p> Value of count{count} </p>
</div>
);
count 값이 업데이트 될 때마다, React가 useEffect를 호출 -> 무한 반복
해결방법
const [count, setCount] = useState(0);
useEffect(() => {
setCount((count) => count + 1);
}, []);
useCallback
함수를 메모이제이션(memoization)하기 위해서 사용되는 hook 함수다
첫번째 인자로 넘어온 함수를, 두번째 인자로 넘어온 배열 내의 값이 변경될 때까지 저장해놓고 재사용할 수 있게 해준다
const memoizedCallback = useCallback(함수, 배열);
ex)
어떤 React 컴포넌트 함수 안에 함수가 선언이 되어 있다면 이 함수는 해당 컴포넌트가 랜더링될 때 마다 새로운 함수가 생성된다
const add = () => x + y;
하지만 useCallback()을 사용하면 , x 또는 y 값(이 함수가 의존하는 값들) 이 바뀌면 새로운 함수가 생성되어 add 변수에 할당되고,
x와 y 값이 동일하다면 다음 랜더링 때 이 함수를 재사용한다
const add = useCallback(() => x + y, [x, y]);
사실 컴포넌트가 랜더링될 때마다 함수를 새로 선언하는 것은 성능 상 큰 문제가 되지 않는다
따라서 단순히 컴포넌트 내에서 함수를 반복해서 생성하지 않기 위해서 useCallback()을 사용하는 것은 큰 의미가 없거나 오히려 손해인 경우도 있다
useCallback 을 사용해야 할때
의존 배열로 함수를 넘길 때
userEffect 에서 fetchUser함수를써서 user값을 받아올떄 , fetchUser는 함수이기 때문에 userId 값이 바뀌든 말든 컴포넌트가 랜더링될 때 마다 새로운 참조값으로 변경된다
하지만 useEffect() 함수가 호출되어 user 상태값이 바뀌고 그러면 다시 랜더링이 되고 그럼 또 다시 useEffect() 함수가 호출되는 악순환이 반복된다
import React, { useState, useEffect } from "react";
function Profile({ userId }) {
const [user, setUser] = useState(null);
const fetchUser = () =>
fetch(`https://your-api.com/users/${userId}`)
.then((response) => response.json())
.then(({ user }) => user);
useEffect(() => {
fetchUser().then((user) => setUser(user));
}, [fetchUser]);
}
이런 상황일떄 useCallback() hook 함수를 이용하면 컴포넌트가 다시 랜더링되더라도 useEffect()에 넘어온 함수는 userId 값이 변경되지 않는 한 재호출 되지 않게 된다
import React, { useState, useEffect } from "react";
function Profile({ userId }) {
const [user, setUser] = useState(null);
const fetchUser = useCallback(
() =>
fetch(`https://your-api.com/users/${userId}`)
.then((response) => response.json())
.then(({ user }) => user),
[userId]
);
useEffect(() => {
fetchUser().then((user) => setUser(user));
}, [fetchUser]);
}
useMemo
Memoization
memoization이란 기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법이다
ex)
아래 컴포넌트는 prop으로 넘어온 x와 y 값을 compute 함수에 인자로 넘겨서 z 값을 구한 후, 그 결과값을 div 엘리먼트로 감싸 출력해준다
function MyComponent({ x, y }) {
const z = compute(x, y);
return <div>{z}</div>;
}
만약에, compute 함수가 값을 리턴하는데 시간이 몇초 이상 오래 걸린다면 컴포넌트가 재랜더링이 될때마다 이 함수가 호출이 되므로 지속적으로 UI에서 지연이 발생한다
function MyComponent({ x, y }) {
const z = useMemo(() => compute(x, y), [x, y]);
return <div>{z}</div>;
}
x와 y 값이 이 전에 랜더링했을 때와 동일할 경우, 이 전 랜더링 때 저장해두었던 결과값을 재활용합니다. 하지만, x와 y 값이 이 전에 랜더링했을 때와 달라졌을 경우,() => compute(x, y)
함수를 호출하여 결과값을 새롭게 구해 z에 할당해준다
memoization 하기 위해서 사용되는 React hook 함수쓸떄 유의할점
일반적으로 소프트웨어의 성능 최적화에는 그에 상응하는 대가가 따르기 마련이다. 따라서,
성능 최적화를 할때는 얻을 수 있는 실제 성능 이점이 지불하는 대가에 비해서 미미하지 않은지에 대해서 반드시 따지고 사용해야 한다
예를 들어 ,useMemo hook 함수를 납용하면, 컴포넌트의 복잡도가 올라가기 때문에 코드를 읽기도 어려워지고 유지보수성도 떨어지게 된다
또한 useMemo가 적용된 레퍼런스는 재활용을 위해서 가바지 컬렉션(garbage collection)에서 제외되기 때문에 메모리를 더 쓰게 된다
useRef
react에서 특정 DOM에 접근하게 해준다
const refContainer = useRef(initialValue);
ex)
import React, { useState, useEffect, useRef } from "react"
function App() {
const [name, setName] = useState("")
const inputRef = useRef("")
console.log("render")
const focus = () => { inputRef.current.focus() }
return (
<>
<input ref={inputRef} />
<button onClick={focus}>focus</button>
</>
)
}
export default App
inputRef.current.focus()
Ref객체의 .current값은 선택한 DOM을 가리키게 된다
useRef 로 리렌더링 방지
import React, { useState } from "react"
function App() {
const [name, setName] = useState("")
const [currentName, setCurrentName] = useState("")
console.log("render")
return (
<>
<input value={name} onChange={e => setName(e.target.value)} />
<button onClick={() => setCurrentName(name)}>submit</button>
<div>name : {currentName}</div>
</>
)
}
export default App
이렇게 하면 input 창에 입력되는 state 요소가 변경될때마다 불필요한 렌더링이 된다
해결방법
ref를 통해 불필요한 렌더링을 줄일 수 있다
화면을 처음 받아올 때와 제출 버튼을 눌렀을 때만 rendring이 일어난다
import React, { useState, useRef } from "react"
function App() {
const [currentName, setCurrentName] = useState("")
const inputRef = useRef("")
console.log("render")
return (
<>
<input ref={inputRef} />
<button onClick={() => setCurrentName(inputRef.current.value)}>제출</button>
<div>name : {currentName} </div>
</>
)
}
export default App
useReducer
useState 보다 좀 더 복잡한 상태 관리가 필요한 경우 reducer를 사용한다
const [state, dispatch] = useReducer(reducer, initialState);
// state : 사용할 상태
// dispatch : state의 업데이트를 일으키기 위해 사용하는 함수
// reducer : state와 action를 받아 새로운 state를 반환하는 함수
// initialState : 초기 state
ex)
useState 사용
import React, { useState } from "react";
function Counter() {
const [number, setNumber] = useState(0);
const onIncrease = () => { setNumber((prevNumber) => prevNumber + 1) };
const onDecrease = () => { setNumber((prevNumber) => prevNumber - 1) };
return (
<div>
<h1>{number}</h1>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
);
}
export default Counter;
useReducer 사용
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);
return (
<div>
<h1>{number}</h1>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-1</button>
</div>
);
}
export default Counter;
'React.js' 카테고리의 다른 글
React에서의 이벤트처리, 이벤트네이밍 정리해보기 (0) | 2023.06.12 |
---|---|
React Router v6 정리해보기 (2) | 2023.06.12 |