[TIL] 내일배움캠프 React 과정 2022.12.19

Today I Learned

  • React 심화 강의 수강
  • 코딩애플에서 Promise 관련 강의 수강

 


React 심화 강의

global state에 db.json에서 가져온 데이터를 넣는 과정

// todosSlice.js
const initialState = {
    todos: [],
    isLoading: false,
    error: null,
};

initialState에 todos의 값은 빈 배열이다.

 

// db.json
{
  "todos": [
    { "id": 1, "title": "리액트 정복", "content": "리액트랑 친해지기", "isDone": false },
    { "id": 2, "title": "코딩 공부하기", "content": "성실하게! 열심히!", "isDone": true }
  ]
}

db.json에는 다음과 같이 todos의 값을 배열 안에 저장했다.

 

// TodoList.jsx
useEffect(() => {
    dispatch(__getTodos());
    }, [dispatch]
);

useEffect를 사용해서 화면이 렌더링 될 때 dispatch로 모듈 파일의 thunk 함수인 __getTodos를 실행한다.

 

// todosSlice.js
// Thunk 함수
export const __getTodos = createAsyncThunk(
    // 첫 번째 인자: action value
    "todos/getTodos",
    // 두 번째 인자: 콜백함수
    async (payload, thunkAPI) => {
        try {
            const data = await axios.get("http://localhost:3001/todos");
            return thunkAPI.fulfillWithValue(data.data);
        } catch (error) {
            return thunkAPI.rejectWithValue(error);
        }
    }
);

__getTodos라는 thunk 함수가 실행되어서 정상적으로 작동한다면 try문에서 axios.get으로 da.json의 데이터를 가져오고 그것을 data라는 변수에 담는다. 네트워크 요청이 성공한 경우에 thunkAPI.fulfillWithValue(data.data)를 통해서  [__getTodos.fulfilled]로 dispatch 된다. action을 콘솔에 찍어보면 thunkAPI.fulfillWithValue(data.data) 가 보낸 액션 객체를 볼 수 있다. 액션 객체를 보낼 때 thunkAPI.fulfillWithValue의 인자에 data.data를 담았는데 이것이 payload가 되는 것이다.

 

액션 객체란?

리덕스에서는 리듀서에 보낼 명령을 Action이라고 하는데 이것이 객체 형태로 되어 있기 때문에 액션 객체라고 부른다.

액션 객체는 반드시 type이라는 key를 가져야 한다. 

액션 객체에 payload를 추가해서 사용할 수 있는데 이렇게 액션 객체에 어떤 값을 같이 담아 보내는 것을 payload라고 한다.

 

thunkAPI. fulfillWithValue( data.data )가 보낸 액션 객체

 

export const todosSlice = createSlice({
    name: "todos",
    initialState, 
    reducers: {},
    extraReducers: {
        // 네트워크 요청이 시작되면 로딩상태를 true로 변경한다.
        [__getTodos.pending]: (state) => {
            state.isLoading = true;
        },
        // Promise가 fullfilled일 때 dispatch함
        [__getTodos.fulfilled]: (state, action) => {
            // 네트워크 요청이 끝났으니, false로 변경한다.
            state.isLoading = false;
            // Store에 있는 todos에 서버에서 가져온 todos를 넣는다.
            state.todos = action.payload;
        },
        [__getTodos.rejected]: (state, action) => {
            // 에러가 발생했지만 네트워크 요청이 끝났으니 false로 변경한다.
            state.isLoading = false;
            // catch 된 error 객체를 state.error에 넣는다.
            state.error = action.payload
        }
    },
})

[__getTodos.fulfiiled] 즉 네트워크 요청이 성공적으로 끝나면  state(initailState)의 todos에 action.payload를 담는다. action.payload는 thunkAPI.fulfillWithValue(data.data)가 인자로 보낸 data.data를 액션 객체를 통해서 받은 것이다.

 

콘솔에 찍어 봤을 때 data.data와 action.payload가 똑같이 생긴 것을 볼 수 있다.

 

 

useEffect의 의존성 배열에 dispatch가 들어간 이유는?

useEffect(() => {
    dispatch(__getTodos());
    }, [dispatch]
);

useEffect 안에 있는 의존성 배열의 의미는  “이 배열에 값을 넣으면 그 값이 바뀔 때만 useEffect를 실행할게” 라는 것인데 dispatch가 바뀔 때 useEffect를 실행해라..? 무슨 의미인지 이해가 잘 되지 않아서 그 이유를 찾아봤다.

 

dispatch의 경우 함수 형태로써 리액트에서 항상 고정값으로 유지되기 때문에 useEffect의 의존성 배열에 추가해도 별다른 동작의 변화가 없고 의존성 배열에 빈 배열을 넣은 것과 동일하게 작동한다. 그럼에도 불구하고 의존성 배열에 dispatch를 넣은 이유는 .....

 

일단 여기까지만 이해하기로 한다 ㅎ

 

참고

https://www.rinae.dev/posts/a-complete-guide-to-useeffect-ko#의존성으로-거짓말을-하면-생기는-일

https://okky.kr/articles/957064

 

 

Toolkit의 immer란?

immer란 react에서 불변성을 유지하는 코드를 작성하기 쉽게 해주는 라이브러리이다. toolkit에 기본적으로 깔려있어 추가적으로 설치하지 않아도 된다.

 

react는 기본적으로 부모 컴포넌트가 리렌더링을 하면 자식 컴포넌트도 리렌더링하게 된다.즉, 얕은 비교를 통해 새로운 값인지 아닌지를 판단한 후 새로운 값인 경우 리렌더링을 하게 되는 것이다. state가 얕은 비교를 통해 컴포넌트가 리렌더링 된다는 말은 굉장한 의미가 있는데아래의 시나리오를 보면 왜 react에서 요소를 직접 변경하면 안되는지 알 수 있다.

  1. 컴포넌트를 리렌더링 해야하는 상황이 있다고 가정하고, 타입이 배열인 state를 바꾼다.
  2. 이때 state.push(1)을 통해 state 배열에 직접 접근하여 요소를 추가한다.
  3. 우리는 push 전과 다른 값이라고 생각하지만, 리액트는 state라는 값은 새로운 참조값이 아니기 때문에 이전과 같은 값이라고 인식하고 리렌더링 하지 않는다.

즉, 위의 이유로 state를 바꾸고 DOM을 다시 만들려면 새로운 객체 or 배열을 만들어 새로운 참조값을 만들고, react에게 이 값은 이전과 다른 참조값임을 알려야 하는 것이다.

 

immer의 상세한 예시는 출처 참고 (봐도 이해가 안된다 ㅎ)

https://kyounghwan01.github.io/blog/React/immer-js/#immer-js란

 

 

builder callback 표기법과 map object 표기법

RTK에서 case reducer 액션을 처리하는 두 가지 방법은 builder callback 표기법과 map object 표기법이 있다. 두 방법 모두 동일한 역할을 하지만 타입스크립트와의 호환성을 위해서는 builder callback 표기법이 더 선호된다.

 

RTK에서도 builder callback 사용을 권장하는 듯하다.

 

Builder Callback 표기법

// 각 라인마다 빌더 메서드를 나누어 호출합니다.
const counterReducer = createReducer(initialState, (builder) => {
  builder.addCase(increment, (state) => {})
  builder.addCase(decrement, (state) => {})
})

// 또는 메서드 호출을 연결하여 연속적으로 작성합니다.
const counterReducer = createReducer(initialState, (builder) => {
  builder
    .addCase(increment, (state) => {})
    .addCase(decrement, (state) => {})
})

createReducer의 콜백 함수 인자로 주어지는 builder 객체는 addCase, addMatcher, addDefaultCase라는 메서드를 제공한다. 그리고 각 함수에서 액션을 리듀서에서 어떻게 처리할지를 정의할 수 있다.

 

createReducer(initialState, builderCallback)

  • builder.addCase(actionCreator, reducer): 액션 타입과 맵핑되는 케이스 리듀서를 추가하여 액션을 처리합니다. addMatcher 또는 addDefaultCase 메서드 보다 먼저 작성되어야 합니다.
  • builder.addMatcher(matcher, reducer): 새로 들어오는 모든 액션에 대해서 주어진 패턴과 일치하는지 확인하고 리듀서를 실행합니다.
  • builder.addDefaultCase(reducer): 그 어떤 케이스 리듀서나 매처 리듀서도 실행되지 않았다면, 기본 케이스 리듀서가 실행됩니다.

 

Map Object 표기법

const counterReducer = createReducer(0, {
  increment: (state, action) => state + action.payload,
  decrement: (state, action) => state - action.payload
})

// 위 예제처럼 작성하거나 
// 또는 'createAction'에서 생성된 액션 생성자(action creator)를
// 연산된 프로퍼티(computed property) 문법을 사용해서 바로 '키'로 사용할 수 있습니다.

const increment = createAction('increment')
const decrement = createAction('decrement')

const counterReducer = createReducer(0, {
  [increment]: (state, action) => state + action.payload,
  [decrement.type]: (state, action) => state - action.payload
})

액션 타입 문자열을 ‘키’로 사용하는 객체를 받아서 케이스 리듀서에 맵핑한다. 이는 builder callback 표기법보다 짧게 작성할 수 있다는 장점이 있기는 하지만 JavaScript를 사용하는 프로젝트에 유효한 방법이다. TypeScript를 고려한다면 대부분의 경우 builder callback 표기법을 권장한다.

 

createReducer(initialState, actionsMap, actionMatchers, defaultCaseReducer)

  • initialState: 리듀서가 최초로 호출되었을 때 사용될 상태 값이다.
  • actionsMap: 액션 타입이 케이스 리듀서에 맵핑되어 있는 객체다.
  • actionMatchers: { matcher, reducer } 형태로 정의된 매처를 배열로 담는다. 매칭된 리듀서는 순서대로 독립적으로 실행된다.
  • defaultCaseReducer: 그 어떤 케이스 리듀서나 매처 리듀서도 실행되지 않았다면, 기본 케이스 리듀서가 실행된다.

 

출처

http://blog.hwahae.co.kr/all/tech/tech-tech/6946/

 

 

최적화 Hook 공부하기

memo()란?

memo()는 훅이 아니지만 useCallback이나 useMemo를 사용하기 위해서는 반드시 알아야 할 개념이다. Memo라는 함수는 컴포넌트의 불필요한 리렌더링을 하지 않도록 해주는 함수입니다. 불필요한 리렌더링이란, 화면에서 변경되는 부분이 없음에도 불구하고 아래의 이유로 화면이 다시 렌더링 되는 것을 말한다. 불필요한 렌더링을 줄이는 것으로 리액트 프로젝트의 부하를 줄이고, 퍼포먼스 능력을 향상시킬 수 있다.

  1. 부모 컴포넌트가 렌더링 된 경우
  2. 컴포넌트의 state가 변경된 경우
  3. 부모로부터 전달받은 props 값이 변경된 경우

 

컴포넌트의 리렌더링 확인하는 방법

//App.jsx
import React, { useState } from "react";
import Button from "./components/Button";

const App = () => {
  const [value, setValue] = useState("");

  const onChangeHandler = (e) => {
    setValue(e.target.value);
  };

  return (
    <div>
      <input type="text" value={value} onChange={onChangeHandler} />
      <Button />
    </div>
  );
};

export default App;
// components/Button.js
const Button = () => {
  console.log("리렌더링되고 있어요.");
  return <button>버튼</button>;
};

export default Button;

input에 값을 입력하면 Button.js 에 있는 "리렌더링되고 있어요."라는 텍스트가 콘솔에 계속해서 찍히는 것을 볼 수 있다. 이 텍스트가 콘솔에 찍히는 것이 Button.js 컴포넌트가 리렌더링을 하고 있다는 것을 의미한다.

콘솔 창

 

불필요한 리렌더링을 막는 방법

상단의 예시에서 App.js가 리렌더링 되는 이유는 컴포넌트의 state가 변경되었기 때문이다. value라는 state가 onChange 될 때마다 setState 되고 있기 때문에 리렌더링되는 것이다. 하지만 이것은 불필요한 리렌더링이 아니다. input에 값을 입력할 때마다 입력한 값이 화면에 보여야 하는 게 당연하기 때문이다.

불필요한 리렌더링은 Button.js 에서 발생하고 있다. input value의 변화와는 아무런 관련이 없는 Button이 부모 컴포넌트가 렌더링 되었다는 이유로 계속해서 리렌더링 되고 있다.

 

import { memo } from "react";

const Button = () => {
  console.log("리렌더링되고 있어요.");
  return <button>버튼</button>;
};

export default memo(Button);

이럴 때 memo()를 사용한다.  export default memo(Button)으로 Button.js를 감싸주면 불필요한 리렌더링을 막을 수 있다.

 

 

memo()를 사용했음에도 Button.js 가 다시 리렌더링을 하게 된 이유

// src/components/Button.js
import { memo } from "react";

const Button = ({ onClick }) => {
  console.log("리렌더링되고 있어요.");
  return <button onClick={onClick}>버튼</button>;
};

export default memo(Button);

memo()를 사용했음에도 불구하고 Button.js가 다시 리렌더링을 하게 된 원인은 함수를 props로 전달받았기 때문이다. 이렇게 전달받은 props가 “부모로부터 전달받은 props 값이 변경된 경우”에 해당하도록 원인을 제공한다.

 

// src/App.jsx
import React, { useState } from "react";
import Button from "./components/Button";

const App = () => {
  // App.js가 리렌더링 될때마다 재생성됨
  const [value, setValue] = useState("");

  // App.js가 리렌더링 될때마다 재생성됨
  const onChangeHandler = (e) => {
    setValue(e.target.value);
  };

  // App.js가 리렌더링 될때마다 재생성됨
  const onClickHandler = () => {
    console.log("hello button!");
  };

  return (
    <div>
      <input type="text" value={value} onChange={onChangeHandler} />
      {/* 매번 재생성되는 함수를 props로 넘겨줌 -> Button.js 리렌더링 유발 */}
      <Button onClick={onClickHandler} />
    </div>
  );
};

export default App;

input에 값을 넣을 때 App과 Button 컴포넌트의 동작 과정

  1. input에 값을 입력
  2. onChangeHandler가 실행되고, setValue가 실행 → value라는 state가 변경됨
  3. state가 변경됨에 따라 App.js가 리렌더링 됨
  4. App.js가 리렌더링되면 App.js 안에 있는 모든 useState 및 함수들이 다시 생성됨
    • 함수란? → onChangeHandler, onClickHandler
  5. onClickHandler가 재선언되었기 때문에 Button.js 입장에서는 새로운 값으로 판단함
  6. Button.js은 onClickHandler 함수를 새로운 props로 인식하고 리렌더링함
  7. Button.js이 리렌더링되면서 console.log("리렌더링되고 있어요."); 이 실행됨

 

Button.js가 리렌더링을 하는 이유는 App.js가 리렌더링을 함에 따라 props로 전달하는 함수를 매번 재생성하고 있기 때문이다. 따라서 Button.js의 리렌더링을 막으려면 App.js가 리렌더링한다고 해도 함수를 매번 재생성하지 않게 하면 된다.

 

 

useCallback이란?

useCallback이란 컴포넌트가 리렌더링 되더라도 생성된 함수를 새로 만들지 않고 재사용하고자 할 때 사용하는 훅다.

 

import { useCallback } from "react";

const onClickHandler = useCallback(() => {
	console.log("hello button!");
}, []);

기본적인 사용방법은 useEffect와 굉장히 비슷하다. useCallback() 안에서 첫 번째 매개변수로 구현하고자 하는 함수가 들어가고,두 번째 매개변수 자리에는 의존성 배열이 들어간다. 의존성 배열의 역할은 useEffect의 의존성 배열과 비슷하다.

 

useCallback을 사용하면 함수가 생성되고 나서 재생성되지 않는다. 하지만 상황에 따라서는 이 함수를 재생성해줘야 할 때도 있는데 그러한 경우 의존성 배열에 값을 넣어주면 해당 값이 변경되었을 때 이 함수도 같이 재생성된다.

 

 

useMemo란?

useMemo는 useCallback과 똑같은 기능을 하는 훅이다. 다만 대상이 함수가 아니라 배열이나 객체와 같은 값일 때 사용하며 사용 원리와 방법은 모두 useCallback과 같다.

 

// src/App.jsx
import React, { useState } from "react";
import List from "./components/List";

const App = () => {
  const [value, setValue] = useState("");

  const onChangeHandler = (e) => {
    setValue(e.target.value);
  };

  const data = [
    {
      id: 1,
      title: "react",
    },
  ];

  return (
    <div>
      <input type="text" value={value} onChange={onChangeHandler} />
      <List data={data} />
    </div>
  );
};

export default App;
// src/components/List.jsx
import React, { memo } from "react";

const List = ({ data }) => {
  console.log("리렌더링되고 있어요.");
  return (
    <div>
      {data.map((todo) => (
        <div key={todo.id}>{todo.title}</div>
      ))}
    </div>
  );
};

export default memo(List);

상단의 App.js 에서 data라는 배열을 List라는 자식 컴포넌트에 넘겨주고 있다. List가 memo를 사용하고 있음에도 계속 리렌더링이 되는 이유는 props로 받은 배열이 App.js가 리렌더링 될 때마다 재생성되고 있기 때문이다.

 

import { useMemo } from "react";

const data = useMemo(() => {
    return [
      {
        id: 1,
        title: "react",
      },
    ];
}, []);

useCallback에서 했던 것과 마찬가지로 data라는 배열을 useMemo를 통해서 재성생되지 않도록 할 수 있다. useMemo를 사용하면 App.js가 리렌더링을 하더라도 배열이 재생성되지 않기 때문에 Button.js도 리렌더링을 하지 않는다.

 

 

주의사항

memo(), useCallback, useMemo의 무분별한 사용은 오히려 퍼포먼스 성능에 악영향이 될 수 있다. 반드시 리렌더링이 필요한 컴포넌트 또는 값 (함수, 배열, 객체)에는 memo(), useCallback, useMemo를 사용하는 것이 오히려 좋지 않다. 왜냐하면 memo(), useCallback, useMemo를 사용하는 것은 리액트에게 “렌더링 이후의 값과 전의 값을 비교해서 같으면 재생성하지 마!”라고 주문을 하는 것인데 반드시 바뀌어야 하는 값에 이 기능을 사용하면 굳이 비교하지 않아도 될 것들에도 리액트가 비교를 하기 때문이다.

 

 

memo(), useCallback, useMemo 정리하기

  • memo()를 사용하면 컴포넌트의 불필요한 리렌더링을 막을 수 있다.
    • 다만, 컴포넌트의 props가 있는 경우 useCallback과 useMemo를 적절하게 사용하여 리렌더링의 원인이 되는 것을 모두 제거해줘야 정상적으로 작동한다.
  • useCallback의 대상은 함수, useMemo의 대상은 객체나 배열과 같은 값이다.
    • 원시 타입 데이터는 useMemo를 사용하지 않아도 리렌더링하지 않으므로 useMemo를 안 써도 된다.

원시타입 데이터란 setState를 사용하지 않고 let, const 등으로 변수 선언한 데이터를 의미함.

 

 

Custom hook이란?

// input의 갯수가 늘어날때마다 state와 handler가 같이 증가한다.
const [title, setTitle] = useState("");
const onChangeTitleHandler = (e) => {
	setTitle(e.target.value);
};

// input의 갯수가 늘어날때마다 state와 handler가 같이 증가한다.
const [body, setBody] = useState("");
const onChangeBodyHandler = (e) => {
	setBody(e.target.value);
};

위의 코드는 input을 구현하고 useState로 각 input의 value를 관리하는 코드이다. 아쉬운 부분이 있다면 input의 개수가 증가하면 useState와 이벤트 핸들러도 같이 증가하고 그로 인해 코드의 중복이 생긴다는 점이다.

 

리액트에서는 위 예시처럼 반복되는 로직이나 중복되는 코드를 Custom Hook을 통해서 관리할 수 있다. 리액트에서 제공하는 useState, useEffect와 같은 내장 훅을 사용해서 훅을 만드는 것이다.

 

 

useInput이라는 custom hook 만들기

커스텀 훅을 만들 때 이름은 마음대로 해도 상관이 없으나 파일의 이름 앞에 use라는 키워드를 붙여줘야 한다.

보통 src 폴더에 보통 hooks라는 폴더를 생성해서 커스텀 훅들을 보관하는 식으로 디렉터리 구조를 설계한다.

 

// src/hooks/useInput.js
import { useState } from "react";

const useInput = () => {
  // value는 useState로 관리하고
  const [value, setValue] = useState("");

  // 핸들러 로직도 구현한다.
  const handler = (e) => {
    setValue(e.target.value);
  };

  // 이 훅은 [ ] 을 반환하는데 첫번째는 value, 두번째는 핸들러를 반환한다.
  return [value, handler];
};

export default useInput;
// src/App.jsx
import useInput from "./hooks/useInput";

const App = () => {
  // custom hook 사용하기!
  const [title, onChangeTitleHandler] = useInput();
  const [body, onChangeBodyHandler] = useInput();

  return (
    <div>
      <input
        type="text"
        name="title"
        value={title}
        onChange={onChangeTitleHandler}
      />

      <input
        type="text"
        name="title"
        value={body}
        onChange={onChangeBodyHandler}
      />
    </div>
  );
};

export default App;

기능은 커스텀 훅을 만들기 전과 동일하게 작동하지만 중복된 코드가 사라지고 전체적인 코드의 양도 감소했다.

 


비동기 처리와 Promise 이해하기

동기와 비동기

자바스크립트는 항상 동기식 처리(synchronous)를 한다. -> 한 번에 코드 한 줄씩 차례차례 실행된다!

하지만 setTimeout, addEventListener, ajax 등의 특수한 함수를 이용해서 비동기식 처리(asynchronous)를 할 수도 있다.

-> 작업이 오래 걸릴 때는 바로 실행이 가능한 코드들부터 처리한다.

 

console.log(1);
setTimeout(function(){}, 1000);
console.log(2);

상단의 코드를 읽다가 setTimeout 함수를 만나면 잠시 Web API 대기실로 옮겨서 대기시킨다. 1초의 대기 시간이 지나고 setTimeout이 완료되면 대기실에서 코드를 꺼내서 코드가 실행되게 만들어준다. 

 

 

콜백 함수를 이용한 순차적 실행

javacsript는 비동기 상황 등에서 코드를 순차적으로 실행하고 싶을 때 콜백함수를 활용한다. 

콜백 함수란? 함수 안에 들어가는 함수!

 

첫째함수(function(){
  둘째함수(function(){
    셋째함수(function(){
      어쩌구..
    });
  });
}):

콜백 함수를 이렇게 실행할 수도 있지만..

 

첫째함수().then(function(){
   그 다음에 실행할 거
}).then(function(){
   그 다음에 실행할 거
});

Promise를 생성해서 콜백 함수를 실행할 수도 있다!

 

 

Promise 기본 구조

var 프로미스 = new Promise();


프로미스.then(function(){

}).catch(function(){

});

프로미스 안의 코드가 성공하면 then(), 실패하면 catch()를 실행한다. 이를 위해 프로미스가 성공인지 실패인지를 알려줘야 하는데 Promise를 를 쉽게 정의하자면 성공&실패 판정 기계라고 할 수 있다.

 

var 프로미스 = new Promise(function(성공, 실패){
  var 어려운연산 = 1 + 1;
  성공();
});

프로미스.then(function(){
  console.log('연산이 성공했습니다')
}).catch(function(){

});

1.프로미스 내의 1+1 이라는 어려운 연산이 완료되면 성공() 판정을 내리며,

2. 성공 시 then() 내의 코드를 실행해준다. 

 

1초 대기 성공 후에 특정 코드를 실행하려면?

var 프로미스 = new Promise(function(성공, 실패){
  setTimeout(function(){
    성공();
  }, 1000);
});

프로미스.then(function(){
  console.log('1초 대기 성공했습니다')
}).catch(function(){
  console.log('실패했습니다')
});

 

Promise의 특징

1. new Promise()로 생성된 변수를 콘솔 창에 출력해보시면 현재 상태를 알 수 있다. 

성공/실패 판정 전에는 <pending>이라고 나오며

성공 후엔  <resolved>

실패 후엔 <rejected>로 나온다.

이렇게 프로미스 오브젝트들은 3개의 상태가 있다.

 

2. Promise는 동기를 비동기로 만들어주는 코드가 아니며 비동기적 실행과 전혀 상관이 없다. (자바스크립트는 평상시엔 동기적으로 실행이 되며 비동기 실행을 지원하는 특수한 함수들 덕분에 가끔 비동기적 실행이 될 뿐이다.) 

 

 

<img> 이미지 로딩 성공 시 특정 코드를 실행하고 싶을 때

let imgLoading = new Promise((성공, 실패) => {
    let img = document.querySelector("#test")
    img.addEventListener('load', function() {
        성공();
    })
    img.addEventListener('error', function() {
        실패();
    })

})

imgLoading.then(() => {
    console.log("성공했어요")
}).catch(() => {
    console.log("실패했어요");
})

 

async 이해하기

async function 어려운연산 (){
  1 + 1 
}

async 키워드를 쓰면 Promise 오브젝트가 저절로 생성되어서 이 함수 자체가 Promise가 되어버린다. 주의할 점은 async는 function 선언 앞에만 붙일 수 있다는 것이다.

 

async function 더하기(){
  1 + 1 
}

더하기().then(function(){
  console.log('더하기 성공했어요')
});

그래서 Promise 만들 때 했던 것처럼 then을 붙여서 더하기() 함수가 성공한 뒤에 뭔가를 실행시킬 수 있다. 

 

async function 더하기(){
  return 1 + 1 
}

더하기().then(function(결과){
  console.log(결과) // 2
});

함수 안에서 연산한 결과를 then 안에서 사용하고 싶다면 이렇게 작성하면 된다.

 

 

await 이해하기

async 키워드를 쓴 함수 안에서는 await를 사용할 수 있다. await는 그냥 프로미스.then() 대체품으로 생각하시면 되는데 then보다 문법이 훨씬 간단하다.

 

async function 더하기(){
  var 어려운연산 = new Promise((성공, 실패)=>{
    var 결과 = 1 + 1;
    성공(결과);
  });
  var 결과 = await 어려운연산;
  console.log(결과); // 2
}
더하기();

'어려운연산 Promise를 기다린 다음에 완료되면 결과를 변수에 담아주세요'라는 뜻이다.

 

async function 더하기(){
  var 어려운연산 = new Promise((성공, 실패)=>{
    실패();
  });
  var 결과 = await 어려운연산;
  console.log(결과);
}
더하기();

await는 실패하면 에러가 나고 코드가 멈춘다. 따라서 어려운연산이라는 Promise가 실패할 경우 await 어려운연산이라는 코드는 에러가 나고 코드 실행을 멈추고, await 하단에 있는 코드들도 실행되지 않는다.

 

async function 더하기(){
  var 어려운연산 = new Promise((성공, 실패)=>{
    실패();
  });
  try {  var 결과 = await 어려운연산 }
  catch { 어려운연산 Promise가 실패할 경우 실행할 코드 }
}

 Promise가 실패했을 경우에도 코드 실행을 멈추고 싶지 않다면 try, catch문을 사용해야 한다. try {} 안의 코드가 에러가 나고 멈출 경우 대신 catch {} 내부의 코드를 실행해준다.

 

 

optional chaining 연산자

console.log(user?.name);

'?. 왼쪽에 있는 게 null 혹은 undefined 인 경우 마침표 찍지 말고 undefined 남겨주세요~'라는 뜻이다.

 

var user = {
    name : 'kim',
    age : { value : 20 }
}

console.log(user.age1?.value1)

optional chaining 연산자는 "중첩된 object 자료에서 에러 없이 안전하게 데이터를 꺼낼 때" 자주 사용한다. 위의 코드에서 user에 age1이라는 값은 없지만 에러가 발생하지 않고 콘솔에 undefined가 찍힌다. 만약 optional chaining 연산자를 사용하지 않고 에러가 발생한다면 에러가 난 부분 하단에 있는 코드들은 실행되지 않는 문제가 발생할 것이다.

 

var user = {
    name : 'kim'
}

console.log(user?.name1)

위와 같이 중첩되지 않은 object 자료의 경우에는 해당하는 자료가 없더라도 자동으로 undefined가 남기 때문에 optional chaining 연산자를 사용하지 않아도 된다.

 

 

nullish coalescing operator

var user;
console.log(user ?? '로딩중')

?? 왼쪽이 null 또는 undefined 일 경우 오른쪽을 보여달라는 뜻이다. 

 


Programmers 문제 풀기

양꼬치

function solution(n, k) {
    return 12000*n + 2000*k - Math.floor((12000*n)/120000)*2000
}

너무 지저분하게 푼 거 같다ㅋㅋ Math.floor 안에 그냥 깔끔하게 (n/10)으로 적으면 되는 것을...

다른 사람들의 풀이를 보니 Math.floor 말고 parseInt로도 풀 수 있었다.

 

소수점 버리기

console.log( Math.floor(8.52) ) // 8

자바스크립트의 floor()는 소수점을 버림하여 정수를 반환하는 함수다.

출처 https://redcow77.tistory.com/430

 


회고

오후에 리액트 심화 강의를 다 듣고 남은 시간에는 코딩애플에서 promise와 optional chaining 연산자 관련 강의를 들었다. 하지만 아직도 Promise와 async, await를 완벽하게 이해하진 못했다.😓 optional chaining 연산자는 예전부터 코드에서 볼 때마다 궁금했었는데 구글링을 해봐도 도대체 뭔지 찾지 못했었다. 그런데 마침 코딩애플 강의 목록에 '?. / ?? 연산자 (optional chaining)'가 보이길래 이게 바로 그거구나!하고 강의를 들었다. 이름도 몰랐던 이 녀석은 optional chaining 연산자라고 부르는 거였다 ㅋㅋ

 

그리고 오늘은 수준별 수업반을 신청하는 날이었다. 나는 고민하다가 일단 어려우면 후발대로 내려가자...하는 마음으로 선발대를 선택했다. 선발대에 들어가는 게 걱정되는 부분은 AWS, MySQL, 웹소켓, Express 등 완전 새로운 내용을 배우는 거 같은데 내배캠 정규 과정 따라가기에도 시간이 부족한데 과연 여기서 추가적으로 뭘 더 배울 수 있을까 하는 점이었다. 하지만 일단은 선발대가 어떤 방식일지 경험해보기로 했다.