wafflestudio / seminar-2021

2021 Rookies 세미나
47 stars 110 forks source link

response를 받아서 값을 할당할 때 문제 #547

Closed subir-sh closed 3 years ago

subir-sh commented 3 years ago

요약

아마도 비동기/동기 순서 문제인 것 같습니다.





상황

response를 받아서 어떤 함수들의 인자에 값을 넘겨줄 때 문제가 생깁니다.





문제 내용

안녕하세요,

const [token, setToken] = useState(null);

requester.post("v1/auth/login", {
      username: content.id,
      password: content.password
    })
.then((res) => {
      setToken(res.data.access_token);
      localStorage.setItem("loginToken", token);
    })

이렇게 하고 token을 하면 제대로 나오는데, localStorage.getItem을 하면 null이 나옵니다. 그런데,

.then((res) => {
      const temp = res.data.access_token;
      setToken(temp);
      localStorage.setItem("loginToken", temp);
    })

이렇게 하면 잘 작동합니다.

res를 받은 후에는 동기처럼 처리하는 것으로 이해했는데, 그게 아닌가요? 뭔가 더 나은 해결 방법이 있을 것 같아 질문드립니다.

woohm402 commented 3 years ago

일단 localStorage.get을 할 이유가 없을 것 같습니다. #531 에서도 말씀드렸듯 다 state에 저장하는 게 좋기 때문에, localStorage에 자주 접근할 필요가 딱히 없습니다!


아무튼 질문에 답변을 드리자면, 비동기 관련 문제는 아닙니다. create-react-app 하고 이 코드 한번 실행해 보시기 바랍니다.

const App = () => {
  const [count, setCount] = useState(0);

  console.log('a', count);

  const handleClick = () => {
    console.log('b', count);
    setCount(count + 1);
    console.log('c', count);
  }

  return <button onClick={handleClick}>count: {count}</button>;
}

실행해 보시고, 두 번 이상 클릭해보신 다음 콘솔창 캡쳐해서 댓글로 올려보시겠어요?

subir-sh commented 3 years ago

아 state로 저장하기는 했는데, 새로고침하면 state가 날라가서 다시 토큰 불러오려고 getitem을 쓰고 있습니다.

코드 실행해보니까

a 0
// 한번 클릭
b 0
c 0
a 1
// 두번 클릭
b 1
c 1
a 2

이렇게 나옵니다. b와 c의 값이 같은데, 먼저 쓰여있든 아니든 블럭 안에서 함수가 실행되는 순서가 정해져 있는 것인가요??

woohm402 commented 3 years ago

아하 그런거였군요 👍

넵 잘 이해하셨습니다. 그러니까 이게 어떻게 되냐면, 렌더가 된다는 건 그 함수 (컴포넌트) 가 실행된다고 생각하시면 돼요

렌더는 우리가 평소 보던 코드처럼

let state = 1;

const func = () => {
  console.log(state); // 1
  state = state + 1;
  console.log(state); // 2
}

이렇게 실행되지 않습니다. 매 렌더마다 필드 및 함수가 새롭게 다시 생성되고, 생성된 아이들은 자기가 생성된 시점의 값들을 이용합니다.

말이 좀 어려운데, 그러니까 무슨 말이냐: 위 코드를 예로 설명하자면

  1. setState를 이용하면 state가 변경되면서 렌더가 다시 되겠죠?
  2. 하지만 지금 실행중인 handleClick 에서는 변경된 state가 아닌 기존의 값을 참조합니다.
  3. 변경되는 state는 다음 렌더할 때 생성된 handleClick이 참조하고 있겠군요

때문에 지금과 같이 한 함수 블럭 내에서 (정확히는 하나의 렌더 스코프 안에서) setState를 이용해서 값을 바꾼 다음 바뀐 값을 사용하는 패턴은 불가능합니다! 대신 setState에 넣어준 값이 있을 테니 그 값을 가져다 쓰면 되겠네요 (가령 temp 변수 써서 구현하신 방식처럼요! 그 코드가 정확합니다)

woohm402 commented 3 years ago

말 나온 김에 좀만 덧붙이자면 setState는 비동기로 작동하기 때문에, 현재 실행 중인 동기 작업이 다 끝난 다음에야 실행됩니다!

https://stackoverflow.com/questions/36085726/why-is-setstate-in-reactjs-async-instead-of-sync

동기 작업이란 걸 강조한 이유 왜 ***동기 작업***이 다 끝나고 나서 setState가 실행된다고 했냐면, 가령 아래 코드는 `a0 b0 c0 a1`이 아닌 `a0 b0 a1 c0`을 출력합니다. setState가 실행된 다음 현재 블럭의 console.log가 실행되었다는 뜻이겠네요! 이 부분은 자바스크립트 이벤트 루프를 이해하시면 도움이 되실 거예요 :+1: ```js const App = () => { const [count, setCount] = useState(0); console.log("a", count); const handleClick = async () => { console.log("b", count); setCount(count + 1); setTimeout(() => { console.log("c", count); }, 0); }; return ; } ```

혹시 이 말땜에 더 헷갈리신다면 그냥 무시하시면 됩니다 😅