비동기적이란 말은 setState를 하고 바로 state가 변경이 되는게 아니란 뜻이다.
먼저 밑의 num의 값을 예상해보자
import { useState } from "react";
export default function App() {
const [num, setNum] = useState(0);
const handleClick = () => {
setNum(num + 1);
setNum(num + 1);
setNum(num + 1);
};
return (
<>
<h1>{num}</h1>
<button onClick={handleClick}>+</button>
</>
);
}
3번 setState 했으니까 3아니야?하지만
실제로는 1이다
비동기함수라서 제쳐두고 그 다음 코드를 실행한다
비동기 겪어보기
const handleClick = () => {
setNum(num + 1); // 0+1 = 1
setNum(num + 1); // 0+1 = 1
setNum(num + 1); // 0+1 = 1
};
React는 DOM 대신
Virtual DOM
을 조작해서 렌더링 성능을 높이지만, 데이터가 조작될 때마다 리렌더링 되는걸 방지하기위해 state변경사항을 바로 반영하지 않고대기열
에 넣은 뒤 한번에(batch) 처리한다. 이것이 바로setState의 비동기적 특성
리액트 내부원리가 궁금해서 만들어봤다
// somewhere in react data
const mockReactStore = {
state: null,
}
function useState(state) {
// init state
if(!mockReactStore.state) {
fakeReactStore.state = state;
}
// state has changed
if(mockReactStore.state !== state) {
fakeReactStore.state = state;
}
const setState = (newState) => {
fakeReactStore.state = newState;
render();
}
return [fakeReactStore.state, setState]
}
보완할 점
state의 변화를 배열에 저장해서 언제 업데이트할지
가장 좋은 방법은 setState를 호출하자마자 state에 접근하지 않는 것 이지만 이렇게 할 수 밖에 없는 경우에는 밑의 방법이 있따.
state1(count)이 바뀌고 난뒤 useEffect안에서 state2(age)를 바꿔준다
useEffect(() => {
// console.log("a", count);
if (count > 0 && count < 3) {
setAge(age + 1);
}
}, [count]);
const handleClick = () => {
setCount(count + 1);
};
const handleClick = () => {
setNum((prev) => prev + 1); // 0+1=1
setNum((prev) => prev + 1); // 1+1=2
setNum((prev) => prev + 1); // 2+1=3
};
함수를 setState에 전달해주면
3이 나온다!
- useState는
비동기
적으로 동작하는 훅이다.- 비동기적으로 동작하는 이유는
성능 최적화
때문이다.- 리액트는 성능을 최적화하기 위해 setState를 batch 처리한다.
- useState를 동기적으로 처리하려면 setState 인자로 함수를 집어넣거나 useEffect의 의존성 배열을 활용하면 된다.
import { useEffect, useState } from "react";
export default function App() {
const [num, setNum] = useState(0);
useEffect(() => {
setNum(1);
console.log(num);
}, [num]);
return <h1>{num}</h1>;
}
setState 하자마자 바로 바뀌는게 아니라 두번의 리렌더링을 거친뒤? 바뀐다
React.StrictMode
때문이다.
밑의 코드를 제거해주면 2번씩 호출되지 않는다.
ReactDOM.render(
<App />,
document.getElementById('root')
);
Strict mode는 개발모드에서만 활성화된다.
동기적으로 바로 업데이트하면 props, state사이의 일관성을 해칠 수 있고, 디버깅이 어렵기떄문이다.
useEffect는 컴포넌트가 렌더링이 끝나고 난뒤 실행된다
state가 업데이트된뒤
컴포넌트가 unmount될때
didMount, didUpdate, willUnmount
컴포넌트 안의 state에 접근이 가능해서
직접적으로 돔을 가져와서 변화를 그리는게 아니라, 상태의 변화를 감지해서 비교한뒤 렌더링을 하는 아이디어
setState는 비동기적으로, 컴포넌트를 즉각적으로 갱신하지 않는다
하지만 상황에 따라 동기적이기도하다 참조
const [inputs, setInputs] = useState({
name: "",
password: ""
});
const { name, password } = inputs;
const onChange = (e) => {
const { name, value } = e.target;
// 객체를 바로 변형시키면 안되고, 새로운 객체를 만들어서 넣어야함 ex) inputs[name] = ...
// spread 펼쳐 복사해서 원래의 것을 복사해오고, 새로운 것을 붙여 새로운 객체를 할당함
// *********
setInputs({
...inputs,
[name]: value // Obj[key] = value
});
};
// *********
const onReset = () => {
setInputs({
name: "",
password: ""
});
};
불변성: immutable, 기존객체를 수정하지 않는다
.
기존의 것과 새로운것 을 비교
해야하기 때문에주소
는 변하지 않아서 변화를 인식 하지 못하고 ,리렌더링이 안 일어남Ref 객체의 .current
값은 우리가 원하는 특정DOM
을 가르키게 됩니다.
리셋버튼을 눌렀을때 인풋이 포커스되게하기
const inputRef = useRef();
const onReset = () => ref.current.focus(); // HTMLElement.focus()
<input ref={inputRef} />
<button onClick={onReset}></button>
업데이트된 상태를 바로 조회가능하다
밑에서는 배열의 키를 ref로 사용
관계를 명확
하게 할수 있어서.리렌더링이 효율적
const nextId = useRef(4);
const onCreate = () => {
const newUser = {
id: nextId.current,
username,
email
};
// 1. spread
setUsers([...users, newUser]);
// 2. concat
setUsers(users.concat(newUser));
nextId.current += 1;
// 초기화
setInputs({
username: '',
email: ''
})
};
concat 함수는 기존의 배열을 수정하지 않고, 새로운 원소가 추가된 새로운 배열을 만들어줍니다.
function onRemove(id) {
// 1. splice
users.splice(id - 1, 1); // splice는 원래배열을 바꾸고, 삭제된걸 리턴한다.
setUsers([...users]); // 그냥 users를 넣으면 리렌더링이 안 일어난다.
// 2. filter
// shallow copy 리턴
setUsers(users.filter((user) => user.id !== id));
}
function onToggle(id) {
setUsers(
users.map((user) =>
user.id === id ? { ...user, active: !user.active } : user));
}
useEffect은 컴포넌트 생기고나서
cleanUp은 컴포넌트 사라질때
주로, 마운트 시에 하는 작업들은 다음과 같은 사항들이 있습니다.
그리고 언마운트 시에 하는 작업들은 다음과 같은 사항이 있습니다.
deps에 값이 있으면 처음에 마운트될때, 특정값 변할때, 언마운트 될때 호출
useEffect안에서 사용하는 state나 prop을 deps에 넣는다
deps 파라미터가 없으면 컴포넌트가 렌더링될때마다 실행된다
색깔바꾸기는 ref를 사용해서 색깔이 바뀌어도 리렌더링이 일어나지 않는다
렌더링을 한번했을 때 두번씩 실행되는건 리액트 StrictMode
virtual Dom을 새로 렌더할 필요없다.
useMemo [] 상관없는게 리렌더링되면 전에 저장된 값을 재사용한다.
useCallback [] 상관없는게 리렌더링되면 함수를 재사용한다.
컴포넌트의 props 가 바뀌지 않았다면, 리렌더링을 방지
React.memo로 감싸기만 하면 됨
export default React.memo(CreateUser);
setState할때 콜백함수에서 최신 state를 참조할수 있기 때문에, deps에 state를 넣지 않아도 된다.
setState(state => newState)
이렇게 해주면, 특정 항목을 수정하게 될 때, 해당 항목만 리렌더링 될거예요.
하지만 컴포넌트의 성능을 실제로 개선할수있는 상황에서만 사용하기 (불필요한 비교 & 버그발생)
이 훅을 사용하면 컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있습니다. 상태 업데이트 로직을 컴포넌트 바깥에 작성 할 수도 있고, 심지어 다른 파일에 작성 후 불러와서 사용 할 수도 있지요.
reducer 는 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수입니다.
function reducer(state, action) {
const nextState = ....
return nextState
}
https://react.vlpt.us/basic/01-concept.html
https://medium.com/@baphemot/understanding-reactjs-setstate-a4640451865b