안녕하세요 준찌(혹은 찌구리, 준조림, 준구리)입니다. 이번 주차는 페어주차였습니다 !! 이번 페어 프로그래밍 간에는 페어노트
를 활용해보았는데요. 저번 페어에서 느꼈던 프로젝트 매니징 영역에서의 문제를 해결하고자 제안해보았습니다.(이번 페어분이 문서화를 좋아하시는 분이기도 해서 ^^..)
위 템플릿을 활용하여 페어를 진행해보았는데 장점은
단점은
대부분이 장점이었네요 ㅋㅋㅋ 장점 가득한 페어 노트였습니다. 다음 페어는 없지만 레벨 3가 되면 적극적으로 활용하려 할 것 같아요!!
이번주는 신나는 데일리 조 회식 주차
이번 주 금요일 MAKER JUN과 함께하는 준조 회식을 진행해보았습니다. ㅋㅋㅋ 제가 워낙 까부는 걸 좋아해서 장소 선정 TF를 자진해서 했다는... (크루들을 위해 쓰는 시간은 아깝지 않아!! 아마도..)
방이 광안리라는 횟집에 가자고 했더니 모두들 반겨주셨다 ㅋㅋㅋ .. 대부분 맛으로는 만족, 양이 좀 부족하다는 평이 있었지만.. 그래도 장소 잡은게 어디임!! 주변에 회사들이 많아서 예약못할 줄 알았는데 괜찮은 가게를 예약하고 가게되어 행복했음 !!
헤어스타일이 아름다운 두 크루 덕분에 더 더 더 재밌게 놀았다능!! ㅠㅠ 다음날 되도 자꾸만 아놀드 헤어스타일이 떠올라서 웃프다 😭 다른 사람들이 보면 변태로 오해할 것 같아.. (피식피식 혼자 웃고다녀서)
아놀드 조금은 어려운 사람인 줄 알았는데 편견이 사라진 것 같다..!! 굉장히 순박하고 재밌는 사람인 것 같다 좀 더 친해진 것 같다는..?
우리는 상태를 업데이트 할 때 useState
를 호출하여 받아내는 setState
로 상태를 업데이트 합니다. 너무나도 당연하게 받아들이는 이 사실에 저는 궁금증이 생기더라구요 ! 상태를 직접 변경하면 어떻게 되는거지?
다음의 간단한 코드를 봅시다!
function App() {
let [state, setState] = useState({ a: 0 });
return (
<div className="App">
<div>{state.a}</div>
<button onClick={() => setState((prev) => ({ ...prev, a: prev.a + 1 }))}>
상태를 setState로 변경하는 버튼
</button>
<button
onClick={() => {
state = {
a: 10,
};
}}
>
상태를 직접 변경하는 버튼
</button>
</div>
);
}
상태를 직접 변경하는 버튼을 눌러봅시다 !!
아무런 변화도 생기지 않았습니다. 그럼 상태를 setState
로 변경하는 버튼을 클릭해볼까요?
우리가 기대했던 바와 같이 상태가 변하여 UI도 변경된 것을 확인할 수 있습니다. 하지만 여기서 기대했던 동작은 상태를 직접 변경하는 버튼
을 클릭하여 state
의 값을 변경시켰으니 상태를 setState로 변경하는 버튼
을 클릭하면 직접 변경된 state
값을 기준으로 새로운 상태를 만들어내지 않을까 였습니다.
자자 말이 길었습니다~ 정리한번 하고 갑시다잉 👻👻👻 내가 지금까지 수행한 flow는 다음과 같습니다.
상태를 직접 변경하는 버튼을 클릭했다
state 식별자가 가리키는 객체는 {a:10}
이 된다.
상태를 setState로 변경하는 버튼을 클릭한다.
이전 state 값을 기준으로 상태를 업데이트 하도록 코드를 작성하였으니 {a:11}
이 되어야한다.
하지만 state의 값은 {a:1}
이다
이 과정을 정리하면
직접 상태를 변경시키면 UI 변경을 트리거하지 않는다.
setState로 상태를 변경시키면 UI 변경을 트리거한다.
setState에 predicate
를 넣어 이전 상태를 받아 상태를 업데이트 할 때의 이전 상태는 직접 변경한 상태의 값이 아니었다.
내가 탐구하고 싶은 아이디어는 직접 변경하였을땐 UI 변경을 트리거하지 못하지만 setState로 변경하면 UI 변경을 트리거한다.
와 setState가 생각하는 이전 상태값의 기준
이다.
가설 1 :
setState
함수 내부의 동작을 보면 상태를 업데이트 시키고 UI 변경도 트리거하는 어떠한 코드가 작성되어 있겠군
가설 2 :
setState
가 기억하는 이전 상태값은 단순히 useState로 받아냈던 state 식별자가 기억하는 값이 아니다!
가설 1 :
setState
함수 내부의 동작을 보면 상태를 업데이트 시키고 UI 변경도 트리거하는 어떠한 코드가 작성되어 있겠군
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
// $FlowFixMe: Flow doesn't like mixed types
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
};
hook.queue = queue;
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
): any));
// 우리에게 전달되는 배열은 아래 배열
return [hook.memoizedState, dispatch];
}
useState가 처음 호출되면 위 mountRedcuer가 호출된다. 여기서 반환되는 배열이 결국 [state,setState]
인 것인데 state -> hook.memoizedState
dispatch -> DispatchReducerAction
과 매핑되는 것을 알 수 있다.
결국 setState
에 새로운 상태 혹은 새로운 상태를 만드는 콜백함수를 넣어 호출하는 행위는 이 DispatchReducerAction
을 실행시키는 행위라고 할 수 있다. 그럼 가설을 증명하기 위해 DispatchReducerAction
에서 렌더링 액션을 호출하는 지를 확인해봐야 겠다.
function dispatchSetState<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
if (__DEV__) {
if (typeof arguments[3] === 'function') {
console.error(
"State updates from the useState() and useReducer() Hooks don't support the " +
'second callback argument. To execute a side effect after ' +
'rendering, declare it in the component body with useEffect().',
);
}
}
const lane = requestUpdateLane(fiber);
const update: Update<S, A> = {
lane,
action,
hasEagerState: false,
eagerState: null,
next: (null: any),
};
if (isRenderPhaseUpdate(fiber)) {
// 큐에 업데이트를 푸시하는 함수겠지? 렌더페이스가 도는 중이라면 렌더페이스업데이트 작업을 큐
enqueueRenderPhaseUpdate(queue, update);
} else {
// 렌더페이스가 아니라면 업데이트 작업을 큐
enqueueUpdate(fiber, queue, update, lane);
const alternate = fiber.alternate;
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// 렌더 페이스에 들어가기전에 다음 상태를 계산한다.
// 이전 상태와 같다면 탈출
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
if (__DEV__) {
prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
}
try {
const currentState: S = (queue.lastRenderedState: any);
const eagerState = lastRenderedReducer(currentState, action);
// 상태와 리듀서를 숨긴다.
// 렌더링 단계에 들어갈 때 까지 리듀서가 변경되지 않은 경우 리듀서를 다시 호출하지 않고 상태를 사용한다?
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// 스케쥴 하지 않는다 -> 리렌더 되지 않는다?
return;
}
} catch (error) {
// Suppress the error. It will throw again in the render phase.
} finally {
if (__DEV__) {
ReactCurrentDispatcher.current = prevDispatcher;
}
}
}
}
// 실제 업데이트를 스케쥴하는 코드가 아닐까?
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitionUpdate(root, queue, lane);
}
}
markUpdateInDevTools(fiber, lane, action);
}
굉장히 복잡한 코드지만 내가 보고싶은건 Render를 트리거하는 어떤 함수를 호출하는지에 대한 여부이다. 정확히 파악하긴 힘들지만 함수 네이밍과 리액트 개발자들이 달아놓은 주석으로 유추할 수 있는 동작들이 있다.
렌더 페이스에 스케쥴한다.
이전 상태와 같다면 스케쥴하지않는다.
결국 정리하자면 setState
는 상태를 업데이트 할 뿐만 아니라 리렌더 작업을 관리하는구나 !!(렌더 페이스를 스케쥴하는 등의 동작으로 보았을 때) 그렇기 때문에 직접 변경하면 리렌더링이 되지 않는거구나 ! (내가 코드로 직접 Dispatch
가 하는 작업을 작성하지 않는 이상!)
코드가 너무 어려워서 정확한 분석이 되었다고는 할 수 없지만 함수 네이밍과 주석으로 밖에 판단하지 못했다. 나도 언젠간 저 코드들을 분석할 수 있겠지?
React 톺아보기 요 글이 도움은 되었지만 ㅠㅠ 2년전 글이라 지금 리액트 코드와는 다른 점이 있어 참고만하였습니다. 실제 소스 코드
가설 2 :
setState
가 기억하는 이전 상태값은 단순히 useState로 받아냈던 state 식별자가 기억하는 값이 아니다!
setState((prev) => ({ ...prev, a: prev.a + 1 })
위와 같이 호출할 때 prev
는 무엇을 기억하고 있을까?
mountReducer
반환 부를 보면 hook.memoizedState
을 반환하고 있다. 결국 상태를 직접 변경한다는 것은 hook.memoizedState
를 바꾼다는 것인데 setState의 콜백 인자
를 확인해보면 직접 변경한 상태값이 아닌 어떤 값을 이전 값으로 기억한다.
이전에 보았던 dispatchSetState
함수 내부의 코드를 다시 한번 살펴보면 컴포넌트에 적용된 상태값
을 이전 상태값으로 기억하고 동작하는 것을 알 수 있다.
function dispatchSetState<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
){
// ...
try {
// hook.memoizedState가 아닌 queue.lastRenderedState를 현재 상태값으로 참조한다.
const currentState: S = (queue.lastRenderedState: any);
// 리듀서에 돌려 값을 얻어낼 때에도 컴포넌트에 적용된 마지막 상태를 현재 상태로 주입.
const eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
return;
}
} catch (error) {
// Suppress the error. It will throw again in the render phase.
} finally {
if (__DEV__) {
ReactCurrentDispatcher.current = prevDispatcher;
}
}
}
내가 코드로 상태를 직접 변경하더라도 setState
를 사용할 때 기억하는 이전 상태값에 영향을 주지는 않는다는 것!!
// state를 직접 변경한 이후
// 아래와 같은 코드 형태라면 개발의도와는 다른 동작을 하게되겠지만 ㅋㅋ..
setState(state+1)
상태를 직접 변경하더라도 UI에 영향을 주지는 않는다. 하위 컴포넌트에 상태를 인자로 넘기고 있다하더라도.. 영향이 없다.. (리렌더를 트리거하지 않는다)
setState가 기억하는 이전 상태값
은 컴포넌트에 적용된 마지막 상태값이다. 직접 변경하더라도 setState가 기억하는 이전 상태값
이 되지는 않는다.
setState
에 predicate
를 주입하여 업데이트하는 경우가 아니라면 상태를 직접 변경 시 원하지 않는 동작이 수행될 수 있다.
닌자시네요