1. 인증과 인가

인증(Authentication): 자신의 신원을 시스템에 증명하는 것. 어떤 실체가 정말 그 실체가 맞는지 확인하는 과정.

인가(Authorization): 신원이 확인되어 인증 받은 사람이 출입문에 들어가도록 허락/허가하는 과정. 

 

2. 메시지 인증

메시지 인증: 송신자가 보낸, 수신자가 받기로 한 그 메시기가 맞는지 확인하는 과정.

메시지 무결성 확인: 수신된 메시지가 전송 도중 불법적으로 변경되지 않았는지 확인하는 것.

3. 메시지 인증 코드(MAC, Message Authentication Code)

MAC 동작 원리

MAC(Message Authentication Code): 메시지에 추가로 붙는 태그. 메시지 인증에 필요하다. MAC은 해당 데이터의 고유한  송신자는 메시지를 보낼 때 MAC을 함께 전송. 수신자는 받은 메시지의 변조 여부를 MAC을 통해 식별 가능. 참고로 메시지는 암호화 되지 않아 누구나 볼 수 있다. 

 

4. 메시지 인증 방법

4.1. 메시지 인증 방법의 3가지 특징

1. 비밀 키 사용

2. 기밀성 제공 안함 -> 메시지는 MAC과 독립적. 누구나 읽을 수 있다.

3. 작은 크기. -> MAC 크기는 메시지 크기와 무관.

4.2.메시지 인증 방법 종류

1. HMAC(Hash-based Message Authentication Code)

HMAC 로 MAC 생성 예

 

- 정의: 일방향 해시 함수를 이용해서 MAC을 구현하는 방법을  HMAC(Hash-based Message Authentication)이라고 한다.

- 특징 : 일방향이기 때문에 역방향 함수는 없으며, 생성된 MAC의 길이는 생성 시 바탕이된 메시지의 길이와 전혀 상관이 없다. 메시지는 암호화 되지 않기 때문에 누구나 읽을 수 있다는 특징도 있다. 사용하는 일 방향 해시 함수는 단 한 종류로 정해두고 있는 것이 아니며, 강한 일 방향 해시 함수라면 뭐든지 HMAC에 이용할 수 있다. 대칭  키, 공개 키, 비밀 값 등을 사용해 메시지 다이제스트를 사용하고 검증한다.

 

 

1) 대칭 키 사용
2) 공개 키 암호 사용
3) 비밀 값 사용

 

 

2. CMAC(Cipher-based Message Authentication Code)

CMAC 진행 과정

 

- 정의: CMAC(Cipher-based Message Authentication Code)은 블록 암호 기반. CBC 모드 메시지 적용. 트리플 DESAES와 같은 블록 암호 사용해 메시지 인증 코드(MAC)를 구현한다. 블록 암호의 키를 메시지 인증 코드의 공유 키로 사용하고, CBC 모드를 써서 메시지 전체를 암호화 한다. 메시지 인증 코드(MAC)은 복호화 할 필요가 없으므로 마지막 블록을 제외하고 모두 폐기해 마지막 블록만 MAC값으로 사용한다.

 

5. HMAC vs CMAC

 HMAC(Hash-based Message Authentication)은 해시함수로, CMAC(Cipher-based Message Authentication Code)는 DES나 AES와 같은 암호 알고리즘을 사용해 메시지 인증 수단, 즉 MAC을 구현한다고 했다. 그렇다면 과연 둘 중 어떤 방법이 더 효율적일까? 우선 해시함수와 암호 알고리즘부터 비교해보자. 

 

해시 함수는 데이터 무결성을 검증하고 데이터를 고유하게 식별하기 위해 사용되며, 이러한 면에서 암호학적인 기술의 하나로 간주될 수 있다. 그러나 해시 함수는 "암호화"와는 다르다. 일반적으로 암호화는 평문 데이터를 암호화하여 암호문으로 변환하는 과정을 의미한다. 이러한 암호화 과정은 일반적으로 해독이 가능한 형태로, 즉 특정 키를 사용하여 원래 데이터를 복원할 수 있는 형태로 이루어진다. 즉, 암호화란 복호화가 가능해야한다. 반면에 해시 함수는 일방향 함수이므로, 해시 값으로 변환된 데이터는 원래의 데이터를 복원하기가 매우 어렵거나 불가능하다. 즉, 해시 값은 복호화가 웬만해서는 불가하다.

 

암호기술이 첨가된 해시 함수는 대칭 암호 알고리즘인 DES와 비교했을 때 몇 가지 장점이 있다. 첫째, 일반적으로 대칭 암호 알고리즘인 DES보다 소프트웨적으로 속도가 빠르다. 둘째, 암호기술이 포함된 해시 함수에 대한 코드들을 쉽게 구할 수 있다. 해시 함수(1997년)가 블록 암호 기술(2006년)보다 먼저 나왔기 때문이다.  셋째, 수출에 보다 자유롭다. 대칭 암호 알고리즘이나 MAC 에서 사용하는 대칭 암호 알고리즘까지 수출 규제를 받고 있는 데 반해, 암호적 해시 함수에 대해서는 미국이나 다른 나라들이 수출 규제를 하고 있지 않다. 

 

따라서 해시함수를 바탕으로 하는 HMAC이 더 널리 쓰이는 편이다. 하지만 예외적인 몇 사항 -ex)이미 블록 암호를 사용하는 프로그램에 적용경우- 에서는 CMAC을 사용하는 편이 나을 수 도 있다. 

 

6. HMAC 설계 목표

- 수정하지 않고 쓸 수 있는 해시 함수들을 만든다. 소프트웨어에서 잘 돌아가고 코드를 무료로 제공하고 널리 쓰일 수 있도록 한다.

- 더 빠르고 안전한 해시 함수가 있거나 필요하다면 기존의 해시 함수를 쉽게 교환할 수 있도록 한다.

- 심각하게 기능저하를 유발하지 않고 해시 함수의 원래 성능을 유지하도록 한다.

- 키를 보다 쉽게 다루고자 한다.

- 내장된 해시 함수가 충분히 강하다면 인증 메커니즘의 강도에 대한 암호해독의 정도를 확실히 파악할 수 있도록 한다.

 


Reference

1.

https://withbabybird.tistory.com/6

 

해시함수의 특징 및 정의

안녕하세요. 어미새입니다. 이전 블록체인 포스팅에서 합의 문제, 합의 알고리즘에 대해서 알아봤습니다. 블록체인에서 합의 알고리즘이란 어떤 방식으로 블록을 생성해 낼 것이며, 어떤 블록

withbabybird.tistory.com

 

2. 

https://m.blog.naver.com/wnrjsxo/221719726759

 

메시지 인증 코드(Message Authentication Code, MAC)

● 메시지 인증 코드(Message Authentication Code, MAC) 해시 알고리즘으로 수정 또는 변경을 검출...

blog.naver.com

 

3.

https://crypto.stackexchange.com/questions/15721/use-cases-for-cmac-vs-hmac

 

Use cases for CMAC vs. HMAC?

Both can be used to verify the integrity of a message. Assuming you have the needed primitives available to you (i.e. the code space of needing both a cipher and a hash function isn't prohibitive),...

crypto.stackexchange.com

 

목차

1. 대칭키 암호화와 비대칭키 암호화

2. 대칭키 암호의 분류

3. 블록화 암호의 분류

4. 블록화 암호의 운영 모드


1. 대칭키 암호화와 비대칭키 암호화

블록화 암호대칭키의 한 종류이다. 따라서 대칭키와 관련된 간략한 배경지식이 필요하다. 이에 대한 지식이 생소할 독자를 위해 대칭키와 이와 함께 등장하는 공개키 암호화 개념에 대해 다음 글에 간략히 정리해 두었다.

2. 대칭키 암호의 분류

1) 스트림 암호

 

평문과 같은 길이의 키 스트림을 생성하여, 평문과 키를 비트 단위로 XOR 하여 암호문을 얻는 대칭키 암호 방식.

 

ex) 메세지(평문)   1   0   1   1   0   1   0   1

      암호화키          1   1   0   0   1   1   0   0

      -----------------------------------

       XOR              0   1   1   1   1   0   0   1  (암호문)

 

ex) 암호문             0   1   1   1   1   0   0   1

      복호화키          1   1   0   0   1   1   0   0

      -----------------------------------

       XOR              1   0   1   1   0   1   0   1  (평문)

 

 

 

2) 블록화 암호

 

평문을 고정된 크기의 블록으로 나누어, 각 블록마다 암호화 과정을 수행하여 블록 단위로 암호문을 얻는 대칭키 암호 방식.

 

블록 암호화 알고리즘은 복호화가 가능파이스텔(Feistel) 구조와, 복호화가 불가능SPN 구조로 또 세분화된다.

 

또는 각 블록에 블록 암호화를 적용하는 방식에 따라 다양한 운영 모드를 가진다. 다양한 운영 모드가 있지만 대표적인 5가지는 다음과 같다: ECB, CBC, CFB, OFB, CTR

 

 

 

1.3. 블록화 암호의 분류

 

 

파이스텔 구조

 

SPN

 

 

1) 파이스텔 구조

 

하나의 입력 블록을 분할하여 좌우 두 개의 블록으로 구분한 뒤 라운드 진행

 

 

 

 

 

 

2) SPN(Substitution Permutation Network)

 

하나의 입력 블록을 여러 개의 소블록으로 나눈 후 라운드 진행.

 

 

 

 

 

 

 

 

 

 

1.4. 블록화 암호의 운영 모드

https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation,

* IV: Initialization Vector(IV)는 블록 암호화에서 사용되는 보조 입력 값으로, 이름에서 알 수 있듯 블록 암호화에서 사용되는 키와 함께 암호화 프로세스를 초기화한다. IV의 작업을 통해 암호화에 사용되는 블록들의 패턴이 반복되지 않도록 하여 최종적으로는 보안성이 향상된다.

 

 

 

1) 전자 코드 북(ECB, Electronic CodeBook) 모드

전자 코드 북(ECB, Electronic Codebook) 모드에서는 평문 블록이 고정된 암호키를 사용하 여 암호화 한다. 같은 평문 블록이라면 항상 같은 암호문 블록을 생성한다. 장점은 단순하 기 때문에 암호화/복호화 시 빠르고 병렬처리가 가능하다는 점이다. 그러나 동일한 평문 블 록은 동일한 암호문을 생성하기 때문에 패턴 분석이 쉬워 안전하지 않다는 단점이 있다. 중요하지 않은 단순한 인증에서만 사용된다.

 

 

2) 암호 블록 연결(CBC, Cipher Block Chaining) 모드

암호 블록 연결(CBC, Cipher Block Chaining) 모드에서는 각 평문 블록이 이전 블록의 암호 문과 XOR 연산을 수행한 후 암호화된다. , 이전 블록의 암호문이 다음 블록의 암호화에 사용되므로, 동일한 평문 블록에 대해서도 다른 암호문이 생성되어 보다 안전하다. 또한 무 작위 초기화 백터(IV, Initialization Vector)를 사용하여 보안성이 강화된다. 다만, 각 블록이 이전 블록의 결과에 의존하기 때문에 순차적으로 처리되어 병렬처리가 가능한 ECB에 비해 다소 느리다. 대부분의 암호화 용도에 적합하다.

 

 

3) 암호 피드백(CFB, Cipher FeedBack) 모드

암호 피드백(CFB, Cipher FeedBack) 모드에서는 블록 암호화 기능을 스트림 암호화처럼 사 용한다. 먼저 초기화 백터(IV)가 블록 암호화 함수를 통해 암호문 블록을 생성한다. 그 후 이러한 암호문 블록과 평문의 각 비트를 XOR연산한다. , 블록 단위가 아닌 비트 단위로 XOR 처리되어 암호화되므로 스트림 암호화와 유사하다. 이렇게 생성된 암호문은 다음 블 록의 암호화에 암호문 블록으로써 사용된다. 이렇게 순차적인 암호화가 이루어지기 떄문에 병렬화가 어려워 성능이 떨어진다는 단점이 있지만, 블록 암호의 기능을 사용하여 스트림 암호화로 변환하거나, 특정 앱에 맞춰 데이터를 조작할 때 쓰일 수 있다는 장점이 있다.

 

 

4) 출력 피드백(OFB, Output FeedBack) 모드

출력 피드백(OFB, Output FeedBack) 모드에서는 CBF와 동일하게 스트림 암호화 방식으로 암호화가 이루어진다. 다른 점은 암호화 시 생성된 이전 암호문 블록이 사용되는 CFB와 달 리, 초기에 생성된 암호문 블록이 계속 사용된다. 이렇게 이전 암호문 블록이 현재 암호화 에 연결되지 않기 때문에, 오류 전파 방지가 가능하다는 장점이 있다. , 어느 한 블록에서 오류가 생겨도 다음 블록으로 전파되지 않는다. 또한 각 블록이 독립적이기 때문에 앞의 두 모드와 달리 병렬 처리가 가능해 빠르다는 것 역시 또다른 장점이다. 하지만 동일한 초 기화 백터(IV)를 사용하고, 동일한 평문 블록에 대해 같은 암호문 블록이 생성되어 패턴 분 석 및 유출이 가능하기에 보안이 취약하다는 단점이 존재한다.

 

 

 

5) 카운터(CTR, Counter) 모드

카운터(CTR, Counter) 모드에서 역시 CFB, OFB와 마찬가지로 암호문 블록을 스트림 암호처 럼 사용한다. 그러나 초기화 백터(IV)가 아닌 카운터(Counter)으로 암호화에 필요한 암호문 블록을 생성한다는 것이 특징이다. 먼저 IV가 카운터를 생성한다. 그 다음 카운터 값을 통 해 생성한 암호문 블록을 생성한다. 이렇게 생성된 암호문 블록을 통해 평문을 암호화 한 뒤, 다음 평문 블록을 암호화 하기 전 카운터 값이 1씩 증가하게 된다. 이러한 카운터 블록 의 장점은 각 평문 블록이 독립적으로 처리되기 때문에 병렬 처리가 가능, , 성능이 순차 처리에 비해 향상된다는 장점이 있다. 하지만 무결성 보장을 위해 카운터 값이 무작위로 선택하도록 하는 등 추가적인 조치가 필요하다는 단점이 있다. SSL/TLS 프로토콜 등 빠른 처리가 중요한 네트워크의 트래픽을 암호화하는데 사용된다.

 


참고 문헌

 

정보보안 - 블록 암호화 기법의 종류와 특징 : ESB, CBC, CFB, OFB, CTR

블록 암호화 알고리즘 구조 블록 암호화 방법은 사전에 공유한 암호키를 사용해서 고정된 길이의 입력 블록을 고정된 길이의 출력 블록으로 변환하는 알고리즘이다. 즉, 블록 암호화는 암호화

ohaengsa.tistory.com

 

3.

https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation

 

Block cipher mode of operation - Wikipedia

From Wikipedia, the free encyclopedia Cryptography algorithm "Mode of operation" redirects here. For "method of operation", see Modus operandi. Six common block cipher modes of operation for encrypting In cryptography, a block cipher mode of operation is a

en.wikipedia.org

 

https://velog.io/@yukina1418/%EC%B7%A8%EC%A4%80%EC%83%9D%EC%97%90%EA%B2%8C-%EB%8F%84%EB%A9%94%EC%9D%B8%EC%9D%B4-%EC%A4%91%EC%9A%94%ED%95%9C%EA%B0%80%EC%9A%94

https://ykss.netlify.app/translation/navigating_the_future_of_frontend/?utm_source=substack&utm_medium=email

https://brunch.co.kr/@susubaa/72

04-automatic-static-optimization

원본 저장소:

next.js/docs/03-pages/01-building-your-application/02-rendering/04-automatic-static-optimization.mdx at canary · vercel/next.js

Next.js kr 저장소: https://github.com/Nextjs-kr/Nextjs.kr

내 저장소: https://github.com/jlee0505/Nextjs.kr

참고 저장소들:

https://github.com/haileyport/Nextjs.kr

https://velog.io/@jlee0505/goorm-x-SeSAC-x-%EC%84%9C%EC%9A%B8%EA%B2%BD%EC%A0%9C%EC%A7%84%ED%9D%A5%EC%9B%90-%EC%83%88%EC%8B%B9%ED%86%A4-%EC%9A%B0%EC%88%98%EC%83%81-%ED%9B%84%EA%B8%B0-%EC%96%B4%EC%84%9C%EC%99%80-%ED%95%B4%EC%BB%A4%ED%86%A4%EC%9D%80-%EC%B2%98%EC%9D%8C%EC%9D%B4%EC%A7%80

문제 마주하기

5/23/23 Tue

문제 상황

  • 지도 생성 기능 앱을 만드려고 함.
  • 지도 생성, 마크 표시 등 모든 기능들이 현재 유저 위치를 받는 것으로 부터 시작함.
  • but, 현재 유저 위치를 받는 getCurrentPosition 내장 함수는 “비동기적”으로 수행됨.
  • 모든 코드를 App 파일에 순차적으로 때려넣었을 때는 순서대로 진행됐지만, 훅으로 분리하니 getCurrentPosition 함수가 값을 받아오는 동안 벌써 다른 함수들이 실행되는 문제가 발생함.
  • 당연히 값이 없으니 undefined 값으로 실행되고 에러가 남.

고민점

  • 어떻게 훅으로 분리할 때 비동기함수를 동기적으로 처리할 수 있을까?

접근

1: useMap 안의 setState
But, 초기값 { lat: 0, lon: 0 } 이 맨처음에 렌더링 되는 게 지저분해보임. 비동기 함수를 좀 더 깔끔히 다루는 방법이 있을 것 같음.

// useMap.tsx

const useMap = () => {
  const [currentPosition, setCurrentPosition] = useState({ lat: 0, lon: 0 });

  const getUserPosition = async () => {
    if (!navigator.geolocation)
      throw new Error("위치 접근 권한을 허용해주세요");

    navigator.geolocation.getCurrentPosition((position) => {
	    const lat = position.coords.latitude;
	    const lon = position.coords.longitude;
	
	    setCurrentPosition({ lat, lon });
	  });
  };

  //...
}
// App.tsx
function App() {
  const { currentPosition, getUserPosition, createMap } = useMap();

  if (Object.keys(currentPosition).length !== 0) {
    createMap(currentPosition.lat, currentPosition.lon);
  }

  useEffect(() => {
    getUserPosition();
  }, []);

  return (
    <div id="app">
      <div id="map" className="map-container"></div>
    </div>
  );
}

 

2. promise, async/await → return new Promise를 통해 해결.

const getUserPosition = () => {
    if (!navigator.geolocation)
      throw new Error("위치 접근 권한을 허용해주세요");

    return new Promise((resolve, reject) => {
      navigator.geolocation.getCurrentPosition((position) => {
        const lat = position.coords.latitude;
        const lon = position.coords.longitude;

        resolve({ lat, lon });
      });
    });
  };
function App() {
  const { getUserPosition, createMap, displayMarker } = useMap();

  useEffect(() => {
    getUserPosition().then((res) => {
      const { lat, lon } = res;
      const map = createMap(lat, lon);
      displayMarker(lat, lon, "내 위치", map);
    });
  }, []);

  return (
    <div id="app">
      <div id="map" className="map-container"></div>
    </div>
  );
}

 


useHttp 훅 만들기

5/24/23 Wed

 

추가적으로, 비동기함수 중 fetch처럼 상태, 에러 등을 관리할 필요가 있다면 상태관리를 위해 훅으로 빼보자.

찾아보니 그 훅은 비슷비슷한 것 같다.

  1. data, status(loading), error : 이 3가지 상태를 관리한다.
  2. useCallback과 useReducer, 그리고 dispatch를 사용한다.

상태를 다루는 방법에는 알다시피 2가지가 있다: useState, useReducer 가 그것이다. 그런데 useHttp훅은 알다시피 다뤄야할 상태가 3가지이다. 따라서 복수 상태를 다루기 더 편리한 useReducer를 사용하는 것이 편할 것이다.(물론 useState를 사용할 수 도 있다.)

2가지 방법으로 모두 훅을 만들어 사용해 보았다.

 

1. useState를 이용한 커스텀훅

// useFetchMoviesUsingUseState.tsx

import { useCallback, useEffect, useState } from "react";

const useFetchMoviesUsingUseState = (url: string) => {
  const [movies, setMovies] = useState([]);
  const [loading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchMoviesHandler = useCallback(async () => {
    setIsLoading(true);
    setError(null);

    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error("Something went wrong!");
      }

      const data = await response.json();

      const transformedMovies = data.results.map((movieData: any) => {
        return {
          id: movieData.episode_id,
          title: movieData.title,
          openingText: movieData.opening_crawl,
          releaseDate: movieData.release_data,
        };
      });
      setMovies(transformedMovies);
    } catch (error: any) {
      setError(error.message);
    }

    setIsLoading(false);
  }, []);

  useEffect(() => {
    fetchMoviesHandler();
  }, [fetchMoviesHandler]);

  return { movies, loading, error };
};

export default useFetchMoviesUsingUseState;

2. useReducer를 이용한 커스텀훅

// useFetchMoviesUsingUseReducer.tsx

import { useCallback, useReducer } from "react";

const reducer = (state, action) => {
  switch (action.type) {
    case "SEND":
      return {
        data: undefined,
        error: null,
        status: "pending",
      };
    case "SUCCESS":
      return {
        data: action.payload,
        error: null,
        status: "completed",
      };
    case "ERROR":
      return {
        data: undefined,
        error: action.errorMessage,
        status: "completed",
      };
    default:
      return state;
  }
};
const initialState = {
  data: undefined,
  error: null,
  status: "ready",
};

function useFetchMoviesUsingUseReducer(requestFunction) {
  const [state, dispatch] = useReducer(reducer, initialState);
  const loading = state.status === "pending";

  const sendRequest = useCallback(
    async (...requestData) => {
      dispatch({ type: "SEND" });
      try {
        const responseData = await requestFunction(...requestData);
        dispatch({ type: "SUCCESS", payload: responseData });
        return responseData;
      } catch (error) {
        dispatch({ type: "ERROR", errorMessage: error.message });
        return;
      }
    },
    [requestFunction]
  );

  return {
    sendRequest,
    loading,
    ...state,
  };
}

export default useFetchMoviesUsingUseReducer;

확실히 여러가지 상태를 관리할 때에는useState보단 useReducer를 사용하는 편이 가독성이 올라간다고 생각은 하는데, 여전히 작성할 때 코드가 늘어난다는 점에서 (reducer를 따로 작성해줘야 했다) 더 편리한지는 모르겠더라.

 

🔖 학습 참조글
https://itchallenger.tistory.com/258
https://velog.io/@sae1013/Reactcustom-hook으로-httpRequest-구현하기
https://www.udemy.com/course/react-the-complete-guide-incl-redux/learn/lecture/25599794#overview

react-query 이용하기

5/27/23 Sat

 

이렇게 서버측 데이터를 불러오고 관리하는 커스텀훅을 만들다보니 하나 알게된 사실: 한 라이브러리를 쓰면 이렇게 커스텀훅을 만들지 않아도 이러한 훅을 쓴 것처럼 data, isLoading 등 상태관리를 쉽게 할 수 있게 된 것을 알게됐다. 그 라이브러리의 이름은 react-query. 그렇게 react-query에 대해서도 공부해보게 되었다.

 

react-query란?

여러 종류의 데이터(state 등) 중에서도 서버에서 받아온 데이터 상태의 관리를 도와주는 라이브러리이다. 이 글은 react-query를 상세히 다루기 위한 글이 아니어서 이에 대한 학습 및 설명글은 따로 뺐다. 아주 간단히 말하자면  처음 사용시 queryProvider로 감싸주기만 하면 그 하위에선 커스텀훅을 사용했던 것처럼 useQuery훅을 통해 어디서든 서버측 데이터와 데이터의 상태를 이용할 수 있었다. 이렇게 위에서 작성했던 모든 커스텀hook과 api함수들이 불필요해지는 편리함을 맛보았다.

 

react-query 사용시 장점

기대했던대로 수많은 보일러플레이트를 없앨 수 있을 뿐 아니라 staleTime, cacheTime등을 활용해 캐싱 기능을 더 수월하게 다룰 수 있다는 점이 인상깊었다. 또 혼자 공부하다보니 놓쳤는데 커스텀훅으로 상태관리를 하면 말그대로 ‘커스텀’이기 때문에 개발자마다 다르게 작성된다. 하지만 react-query를 사용하면 통일된 방식으로 서버측 데이터 관리가 가능해진다. 이렇게 react-query를 공부하고 사용하며 느낀 장점을 정리하면 아래와 같다:

  • 귀찮은 boilerplate x — isLoading 등 상태, 캐싱 기능
  • 팀원들끼리 통일된 방식으로 상태 관리 가능 — 누구는 isLoading, 누구는 pending 변수명을 다르게 사용하는 것을 막을 수 있다.
// index.tsx

const queryClient = new QueryClient();
console.log(queryClient);
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>
);
// App.tsx

const { data, status, error } = useQuery(["movies"], fetchMovies, {
    refetchOnWindowFocus: false,
    retry: 0,
    staleTime: 5000,
    cacheTime: Infinity,
    onSuccess: (data) => console.log(data),
    onError: (e) => console.log(e.message),
  });

  let content = <p>No Found Movies.</p>;

  if (status === "success") {
    content = (
      <div>
        {data.results.map((movie, idx) => (
          <div key={idx}>{movie.title}</div>
        ))}
      </div>
    );
  }

  if (status === "loading") {
    content = <p>Loading...</p>;
  }

  if (status === "error") {
    content = <p>{error.message}</p>;
  }

  return <div>{content}</div>;
}

 

🔖 학습 참조글
https://tech.kakaopay.com/post/react-query-1/
https://tanstack.com/query/latest/docs/react/overview?from=reactQueryV3&original=https://tanstack.com/query/v3/docs/overview
https://velog.io/@jay/10-minute-react-query-concept#:~:text=리엑트 쿼리는 데이터,데이터를 다시 불러온다
https://2ham-s.tistory.com/407
https://kyounghwan01.github.io/blog/React/react-query/basic/
https://www.youtube.com/watch?v=novnyCaa7To

 


Sum Up

어찌어찌 하다보니 비동기함수로 시작해, 비동기 함수중에서도 fetch를 집중적으로 다루게 되었다. 나아가 fetch해온 데이터(서버측 데이터)를 프론트측에서 리액트로 관리하는 방법에 대해 찾아보게 된 시간이었다.

 

네이티브(useState/useReducer) 그리고 툴(react-query)

useState+useEffect 혹은 useReducer+useCallback를 사용해 관리할 수 있다.(이경우 훅을 따로 분리해 관리하면 깔끔하다.) 그러나 이 경우 수많은 보일러 플레이트가 필요하고, 특히 캐싱에 관련해서도 수많은 작업을 진행해줘야 한다. 또한 규명된 상태관리 폼이 존재하지 않아 팀원들끼리 공통된 상태관리가 힘들 수 있다.(다른 상태명, 로직….) 이러한 점을 보다 쉽게 해주는 것이 라이브러리겠지. redux의 saga 미들웨어와 mobx의 __을 이용할 수 있겠지만, 이들은 서버상태 전용 라이브러리가 아니다. 서버 상태 전용 라이브러리 react-query는 보일러 플레이트를 줄이며, 캐싱, 클라이언트 측과 서버 측 상태의 동기화에 특화된 인터페이스를 제공해 서버측에서 넘어온 데이터 관리를 간단하게 한다. 또한 공통된 서버 상태 관리로 팀작업을 원활히 한다.

 

마무리하며&다음에는...

fetch를 가지고 작업하다 서버측 데이터 상태관리까지 넘어가게 되었는데 많은 사람들이 fetch 대신 axios를 사용하고 있었다. fetch 에서 넘어오는 데이터의 에러 처리를 간단하게 해준다는 장점이 있어서 쓴다고 하는데, 한 번 가볍게 다뤄보는 것도 좋겠다.

목차

1. Redux 선택 이유

2. Redux와 contextAPI 구조적 차이

3. Redux 사용 후기(contextAPI와의 비교)

4. 마무리


1. Redux 선택 이유

아직 후기를 쓰진 못했지만 지난 미션에서는 상태관리로 Context API를 사용했었다. Context API를 사용하며 처음 전역상태관리의 개념, Context API와 다른 전역상태관리 라이브러리의 차이점에 대해 공부했었는데, 이 과정에서 전역상태관리 개념의 최초에 redux가 있다는 걸 알게 되었다. 

 

redux에서 처음 나왔다고 알려져있는 reducer와 dispatch의 개념은 언제나 헷갈리는 개념이었다. 또 React에서 상태를 관리하는 방법에는, 요즘 뜨는 recoil, 네이티브 contextAPI, 기타 mobx... 정말 다양하다고 알려져 있지만, 앞으로 어떤 회사에 가서 어떤 상태관리 라이브러리를 사용하게 되든 근본인 redux를 배워두면 컨셉을 이해하는데 유용하지 않을까 생각했다. 

 

그래서 사실 이번 미션의 필수 요구 사항은 오직 React(contextAPI)로만 상태관리를 하는 것이었음에도 불구하고  redux 를 택하게 되었다. 반항심리는 아니었고 근-본은 과연 어떤지, 네이티브 전역상태관리 기능인 contextAPI와는 어떤 차이가 있는지 직접 써보며 확인하고 싶었다.

 

2.  Redux와 ContextAPI의 차이

아래는 한 블로그에서 하나의 도표로 정리한 Redux vs ContextAPI의 차이점으로 분석하며 내 경험과 비교해보았다.(🔗 Redux vs Context API: When to use them, Tapajyoti Bose)

  Redux  Context API
1. 설치 추가 설치 필요, 최종 번들 크기 증가 React와 함께 제공되는 기본 제공 도구
2. 설정 React 애플리케이션과 통합하려면 광범위한 설정이 필요 최소한의 설정 필요
3. 데이터 정적 데이터동적 데이터 모두에 적합 자주 새로 고치거나 업데이트하지 않는 정적 데이터를 위해 특별히 설계
4. 확장성 초기 설정 후 새로운 데이터/액션을 쉽게 추가할 수 있어 쉽게 확장 가능 새로운 컨텍스트를 추가하려면 처음부터 새로 만들어야 함
5. 디버깅 개발 도구에서도 컴포넌트 구조 디버깅을 쉽게 해주는 놀랍도록 강력한 Redux 개발 도구  고도로 중첩된 React에서는 디버깅이 어려울 수 있음.
6. 코드 구성 UI 로직과 상태 관리 로직을 분리하여 코드 구성 개선 UI 로직과 상태 관리 로직이 동일한 구성 요소에 있음

1. 설치

Redux는 npm i redux redux-react @reduxjs/toolkit  으로 redux포함 3가지 라이브러리를 설치해야한다. 이는 최종 프로그램 번들 사이즈의 향상으로 이어진다. 반면 Context API는 다른 설치 없이 사용 가능하다.

 

2. 설정

Redux는 reducer, slice, store, Provider로 감싸기 등 프로그램 사용을 위해 초기 설정해줘야하는 게 많다. 반면 Context API는 context생성 Provider로 감싸주기로 초기 설정이 간단하다.

 

3. 데이터

Redux는 정적 및 동적 데이터 모두에 적합하다고 하는데, 그 이유는 Redux가 불변성을 유지하고 예측 가능한 방식으로 상태를 관리하도록 설계되었기 때문이다(Thinking in Redux, redux). 

 

반면, Context API는 정적 데이터를 위해 것으로 예를 들면, 테마나 인증 정보, 사용자 언어와 같은 수준의 잘 변하지 않는 데이터를 전역적으로 공유할 때 유용하다고 한다.(when to use context, react) 따라서 Redux와 달리 복잡한 상태관리를 위한 툴은 아니라고 할 수 있겠다.  

 

4. 확장성

Redux는 새로운 데이터나 액션을 추가할 때, 서브store 개념인 slice나 새로운 액션인 reducer를 새로 추가하면 되는 반면, context는 서브context 개념이 따로 없어 수정이 어렵다는 말로 들린다. 

 

하지만  Context API가 보통 가장 상단이 되는 컴포넌트를 감싸는 식으로 사용되는 것과 달리, 컴포넌트와 상태를 별도의 파일로 분리해 보다 확장성 있게 사용하는 방법도 있다고 한다(How to use context effectively, Kent C. Dodds) 따라서 새로운 데이터나 액션을 추가하기 위해 컨텍스트를 처음부터 새로 짜야만한다는 이 부분에 대해서는 의구심이 든다. 

 

5. 디버깅

Redux에서는 리덕스 개발자 도구를 처음 개발하고 배포한 개발자 중 한 명인 Zalmoxisus라는 개발자가 만든 크롬 확장 Redux DevTools이 존재한다. 아래와 같은 디버깅을 위한 다양한 기능을 제공하고, 이를 이용해 훨씬 쉽게 디버깅을 할 수 있다:

  • 시간여행(time-travel) 기능: 과거 상태로 돌아가서 상태 변경 과정을 디버깅할 수 있습니다.
  • 상태 스냅샷 기능: 현재 상태를 스냅샷으로 저장하여 나중에 다시 볼 수 있습니다.
  • 액션 필터링 기능: 특정 액션만 필터링하여 디버깅할 수 있습니다.

반면 Context API는 Provider Hell을 야기할 수 있고(🔗 Context API가 존재하지만 여전히 사람들이 redux와 전역 상태관리 라이브러리를 쓰는 이유, nanalog) 이러한 고도의 중첩의 경우 디버깅이 상당히 복잡하고 짜증나는 작업이 될 수 있다.

 

6. 코드 구성

Redux는 UI 로직과 상태 관리 로직을 분리하여 보다 클린한 코드 구성이 가능하하지만, context API는 UI를 업데이트하는 로직과 상태 관리 로직이 동일한 구성 요소에 있다고 한다. 

 

하지만 이 역시 context에서 reducer 함수나 custom hook의 사용을 통해 충분히 분리할 수 있을 거라 생각해 공감이 가진 않는다.

// 1. useReducer 사용해 UI로직과 상태관리 로직 분리하기

import React, { useReducer } from "react";

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

export const CounterContext = React.createContext();

function CounterProvider(props) {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <CounterContext.Provider value={{ state, dispatch }}>
      {props.children}
    </CounterContext.Provider>
  );
}

export default CounterProvider;
// 2. custom hook(useCounter) 사용해 UI로직과 상태관리 로직 분리하기

import React, { createContext, useContext, useState } from "react";

const CounterContext = createContext();

function CounterProvider(props) {
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);

  const value = { count, increment, decrement };

  return <CounterContext.Provider value={value} {...props} />;
}

function useCounter() {
  const context = useContext(CounterContext);
  if (context === undefined) {
    throw new Error("useCounter must be used within a CounterProvider");
  }
  return context;
}

export { CounterProvider, useCounter };
// 이렇게 하면, useCounter hook을 사용하여 UI를 업데이트하는 로직과 상태 관리 로직을 분리할 수 있다.

import React from "react";
import { CounterProvider, useCounter } from "./CounterContext";

function Counter() {
  const { count, increment, decrement } = useCounter();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}

function App() {
  return (
    <CounterProvider>
      <Counter />
    </CounterProvider>
  );
}

export default App;

 

3. 마무리하며

 

처음 상태관리 라이브러리로 redux를 직접 사용해보며, 시중에 나와있는 redux vs contextAPI 글을 분석해봤다. 4번과 6번 특징에 대해서는 동의하지 않지만, 그외의 특징에는 동의한다. 특히

 

1. 초기 세팅이 너무 많다는 점에 동의한다.

이를 위해 reduxjs/toolkit이 등장했다고 해도 여전히 contextAPI보다는 초기 설정 너무 많아 번거로움.

 

2. 디버깅이 쉽다는 점에 동의한다.

redux 개발자 도구를 사용해서 전역 관리 상태를 가시적으로 볼 수 있는 점이 너무너무 편리했다. redux 자체보다는 redux 개발자 도구을 쓸 수 있다는 점이 좋았다. 

 

3. 단순한 상태라면 redux를 사용할 필요는 없다.

redux는 초기 설정이 번거롭지만, 이후 데이터 확장성이 좋고 보다 복잡하고 많은 상태를 관리할 수 있다는 장점이 있다고 한다. 하지만 굳이 cartSlice 하나의 상태로도 충분했던 이번 미션에서 ContextAPI로도 충분했으리라는 생각이 든다. 

 

'Extracurricular > 교육' 카테고리의 다른 글

[NEXTSTEP] CDD with React 온보딩 미션 회고  (0) 2023.02.12

 

자바스크립트에서 비동기를 제어하는 4가지 방법


자바스크립트에서 비동기를 제어하는 방법에는 총 "4가지"가 있다고 알려져 있다.

1) 콜백함수

2) 프로미스

3) async/await

4) 제너레이터

 

각각의 사용 방법 차이를 예제 코드와 함께 살펴보면 아래와 같다:

1) 콜백함수

function asyncFunction(callback) {
  setTimeout(() => {
    callback('Hello, world!');
  }, 1000);
}

asyncFunction((result) => {
  console.log(result);
});

2) 프로미스

function asyncFunction() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Hello, world!");
    }, 1000);
  });
}

asyncFunction()
  .then((result) => console.log(result))
  .catch((err) => console.log(err));

3) async/await

function asyncFunction() {
  return new Promise((resolve, resject) => {
    setTimeout(() => {
      resolve("Hello, world!");
    }, 1000);
  });
}

async function main() {
  try {
    const result = await asyncFunction();
    console.log(result);
  } catch (err) {
    console.log(err);
  }
}

main();

4) 제너레이터

function* asyncFunction() {
  yield new Promise((resolve) => {
    setTimeout(() => {
      resolve('Hello, world!');
    }, 1000);
  });
}

const gen = asyncFunction();
const promise = gen.next().value;

promise
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(error);
  });

 


문제 💡
아래와 같은 코드에서 콘솔에 찍히는 결과는 어떻게 나올까?


문제

console.log("start");

// 일반
console.log("1");

// Callback
setTimeout(() => {
  console.log("2");
}, 0);

// Promise
const promise = new Promise((resolve, reject) => {
  resolve("3");
});
promise.then((result) => console.log(result));

// async/await
const promise2 = new Promise((resolve) => {
  resolve("4");
});
async function test() {
  try {
    const res = await promise2;
    console.log(res);
  } catch (e) {
    console.log(e);
  }
}
test();

// Generator
function* generator() {
  yield "5";
}
const gen = generator();
console.log(gen.next().value);

console.log("end");

 

정답

start → 1(일반) → 5(제너레이터)→ end(일반) → 3(promise) → 4(async/await) → 2(callback)


해설 ✍️
이벤트 루프에 대해 알아보자

 


왜 정답이 이렇게 나왔는지 알려면 이벤트 루프(event loop)의 개념에 대해 알아야 한다.

이벤트 루프란 자바스크립트의 동시성을 관리하는 핵심 메커니즘이다. 

그 메커니즘을 간단하게 한 번, 자세히 한 번 이렇게 두 번으로 나누어 보며 이해해보자.

 

1) 자바스크립트 비동기 메소드의 브라우저 상 기본 동작 원리

자바스크립트에서 비동기 메소드는 기본적으로 아래와 같이 작동한다.

 

실행 콜스택(call stack)에서 이루어진다.

➡️ 시간이 오래 걸리는(비동기) 메소드의 경우 작업의 효율성을 위해 큐(queue)로 이동해 대기한다.

➡️ 다른 작업이 다 끝나면 다시 콜스택으로 호출 해 실행한다. 

https://blog.learncodeonline.in/javascript-event-loop,

 

 

2) 큐(queue)의 종류: 매크로 테스크 큐(Macrotask Queue)와 마이크로 테스크 큐(Microtask Queue)

좀 더 자세히 살펴보면, 사실 비동기 메소드들이 대기하는 큐(queue)는 작업 성질에 따라 여러 종류가 있다. 

마이크로 테스크 큐의 작업이 매크로 테스크 큐의 작업보다 앞서 처리되는 것을 확인할 수 있다.

 

대표적인 2가지는 매크로테스크 큐(Macrotask Queue, 일반적인 큐는 보통 이 매크로테스크 큐를 가리킨다) 마이크로테스크 큐(Microtask Queue)이다.

 

이러한 큐는 종류에 따라 실행 순서가 다르다.

예를 들어, 마이크로테스크 큐에 들어간 api는 매크로테스크 큐의 api보다 먼저 처리된다.

즉, 1️⃣ call stack ➡️ 2️⃣ Microtask Queue ➡️ 3️⃣ Macrotask Queue 순서로 처리된다.

 

api 종류에 따라 다른 큐에 배치되며 위에서 언급된 비동기 메소드들의 경우,

  • Promise, async/await ➡️ 마이크로 테스크 큐
  • setTimeout ➡️ 매크로 테스크 큐
  • Generator 의 경우, 비동기 기능을 가지지만 큐로 이동되지 않고 첫 콜 스택에서 바로 처리된다.

결론


위 내용을 요약하면 아래와 같다.

이벤트 루프란

큐에는 여러 종류가 있으며, 각 큐는 실행 순서가 있다.
마이크로 큐는 매크로 큐의 작업보다 우선적으로 실행된다.

자바스크립트에서 쓰이는 비동기 처리의 4가지 방법은 각각의 큐/스택을 거쳐 실행된다.
1) 콜백함수: call stack ➡️ Macrotask Queue ➡️ call stack 
2) 프로미스: call stack ➡️ Microtask Queue ➡️ call stack 
3) async/await: 프로미스 기반이므로 프로미스와 작동원리 같음.(즉, 마이크로 큐)
4) 제너레이터: call stack 에서 바로 처리. (비동기지만 Queue로 이동하지 않고 call stack에서 바로 처리됩니다.)

 

따라서 위 문제의 코드 동작 결과는 아래와 같다:

 1️⃣ call stack (start  1  5  end) ➡️ 2️⃣ Microtask Queue (3  4) ➡️ 3️⃣ Macrotask Queue (2)

 

 


Reference


 

1.

What the heck is the event loop | Philip Roberts | JSConf EU

 

 

2.

Microtasks and (Macro)tasks in Event Loop

https://medium.com/@saravanaeswari22/microtasks-and-macro-tasks-in-event-loop-7b408b2949e0

 

Microtasks and (Macro)tasks in Event Loop

JavaScript has a concurrency model based on an event loop, which is responsible for executing the code, collecting and processing events…

medium.com

 

 


Last Updated on 2024/07/02

 

 

 


Summary


이 글은 미션을 수행하며 학습했던 컴포넌트 분리 기준에 대한 내용을 정리한 것입니다. 컴포넌트 분리에 대한 절대적인 기준이 없어 여러 선행자분들의 의견을 참고하고자 아래 3가지 강의를 참고해 공통적인 부분을 거르고, 이후 개인적인 의견을 간단히 남기고자 하였습니다.

 

첫 두 강의의 공통점은, "컴포넌트를 쪼개는 절대적 기준 같은 것은 없다. 하지만 상대적으로 잘 쪼개는 컴포넌트 분리 기준은 존재한다."는 것이었는데요, 두 분 모두 '유지보수를 쉽게 하는'것에 기준을 두고 컴포넌트 분리를 설명하신 것이 인상 깊었습니다. 유지보수를 쉽게 하려면 어떻게 해야할까요? 데이터를 미래에 변할 수 있는 것과 상대적으로 잘 변하지 않을 데이터를 구분하는 것이었습니다.

 

조금 더 구체적으로는, 첫 강의자인 원지혁님은 컴포넌트의 구성을 스타일, 로직, 전역상태, 리모드 데이터 스키마(데이터)로 분리하셨고, 유지보수를 위해 변화가 가장 잘 일어나는 데이터 모델에 따라 컴포넌트 분리를 해주는 것을 추천하셨습니다. 두 번째 강의의 한재엽님은 변하는 것에 앞서 설명된 데이터를 데이터계산과 상호작용으로 나누셨습니다. 결국 둘 다 데이터 관리부분인데 처음부터 제공되는 데이터냐, 사용자의 액션이 있어야 계산되는 데이터냐에 따라 한 번 더 나누신듯하다. 변하는 부분을 데이터, 변하지 않는 부분을 UI로 잡고 분리한다는 점에서 원지혁님과 의견이 같았습니다. 두 강의자분 다 컴포넌트 분리의 기준을 유지보수로 잡고 계시고, 상대적으로 잘 변하는 것, 즉 컴포넌트가 다루는 데이터를 기준으로 컴포넌트를 분리하는 것을 제안하신다는 데에서 동일했습니다.

 

결론적으로, 저도 유지보수를 쉽게하는 컴포넌트 분리 기준으로 데이터 모델을 잡는 것에 설득되었습니다. 이 데이터를 변경가능성이 높은 데이터인지, 그렇지 않은 데이터인지 - 즉, 도메인 관련인지 아닌지로 나누는 방법이 좋다는 것도 이해하였습니다. 더하여 데이터는 훅스, UI관련은 컴포넌트, 상태관리는 store 등으로 나누는 기준에 동의합니다. 앞으로 리액트를 활용한 개발에 있어서 컴포넌트 분리는 이러한 기준으로 하고자 합니다. (하지만 컴포넌트 분리에 절대적인 기준이 없는 만큼 이 글을 보시는 분들의 다양한 의견에도 열려있습니다.)

 


강의

당근마켓, 원지혁 - 컴포넌트, 다시 생각하기


요약

  • 바라보기: React 컴포넌트의존성 - React 컴포넌트를 만드는데 필요한 것들은 어떤 것들이 있을까?
    • 기능적 분류(Type): props 와 hooks
    • 특징적 분류(Feature):
      • 스타일(컴포넌트의 스타일, src/components/Something.css)
      • 로직(UI조작에 필요한 커스텀 로직, src/components/Something.useInfinityScroll.ts),
      • 전역 상태(현재 UI를 표현하기 위해 유저의 액션을 통해 초래된 상태, src/store.ts)
      • 리모트 데이터 스키마(API 서버에서 내려주는 데이터의 모양, https://examples.com/api/v2/articles/json)
    • React 컴포넌트의 숨은 의존성
      • 한 컴포넌트에 정보를 추가한다고 하자. 그 컴포넌트에 새로운 props 를 추가하면 된다.
      • 하지만, 그게 끝이 아니다. 숨은 의존성이 존재한다. 타겟 컴포넌트와 루트 컴포넌트 사이의 props들의 추가 수정이 필연적일 수 밖에 없다. props drilling 없이 따로 store를 둔다해도 페이지 기반 라우팅을 한다면 결국 root 컴포넌트에 의존할 수 밖에 없을 것이다. 즉, 해당 컴포넌트에 새로운 정보(의존성)를 추가하기 위해서는 root 컴포넌트 수정도 필연적이다.
  • 함께 두기(co-locate): 이러한 의존성들을 어떻게 정리할 수 있을까?
    • 원칙1 - 비슷한 관심사라면 가까운 곳에(Keep Locality)
      • 한 컴포넌트에 필요한 스타일과 로직은 함께 두기 쉽다.
      • 전역상태는 여러 컴포넌트가 공유하고 있기 때문에 특정 컴포넌트와 함께 두기 어렵다.
      • 리모트 데이터 스키마 함께두기는 좀 더 복잡하므로 원칙2로 따로 분류해보자.
    • 원칙2 - 데이터를 ID 기반으로 정리하기 (Abstraction by Normalization)
      • 데이터 정규화(Normalization) 이라고도 한다.
        • yarn add mormalizer : 정규화를 도와주는 라이브러리
        • 하지만 여전히 숨은 의존성 문제는 존재한다: 사용 컴포넌트의 상위 컴포넌트에서 id 값을 정확히 받아와야 한다.
      • 글로벌 아이디(Global Id): 모델명을 따로 넘길 필요 없이 ID값만 가지고 특정 데이터를 유일하게 식별할 수 있도록 하는 체계.(상위 컴포넌트에서 ID값을 받아올 필요가 없다.)
        • 예시: 첫번째는 필요한 데이터를 바깥(./store.ts)에서 받아오고 있고, 두번째에서는 GlobalId를 이용해서 id 데이터를 같은 컴포넌트 안에 둘 수 있다! (16:02)
        • 도우미: 전역 객체 식별 (Global Object Identification, GOI): (17:02)
  • 이름 짓기(Naming): 프롭스 네이밍(Props Naming)에 대해서 생각해보자.
    • 원칙3 - 의존한다면 그대로 드러내기(Make Explicit)
      • 프로필 컴포넌트 - User 와 User가 의존하고 있는 Image, 이렇게 크게 2가지 데이터에 의존하고 있다. 2번째는 User와 Image의 의존성까지 보여주는 이름짓기를 활용해 더 직관적으로 의존성을 알 수 있다!
      • 하지만 위와 같이 한 컴포넌트에서 여러 모델의 정보(User 컴포넌트 내에 user 뿐 아니라 image 정보도 받아오고 있다.)를 표현하는 것은 일종의 관심사 분리가 제대로 안되었다는 일종의 신호이기도 하기에 아래와 같이 수정해주면 더욱 좋다.
    • 재사용하기(Reuse): 개발할 때 편리하기 위한 것보다 변경할 때 편리하기 위해 = 유지보수에 편하기 위해!
      • 변화하는 부분을 미리 예측하고 컴포넌트를 나누는게 중요하다.
        • 대부분의 변화는 리모트 데이터 스키마가 변화하는 방향을 따라서 움직인다.
        • 그렇다면 어떻게 변하는 것들과 변하지 않는 것들을 분리할 수 있을까?
    • 원칙4 - 데이터 모델 기준으로 컴포넌트 분리하기, Separating Components by Data model
      • 같은 모델을 의존하는 컴포넌트: 재사용
      • 다른 모델을 의존하는 같은 컴포넌트: 분리
  • 결론(Conclusion)
    • 원칙 요약
      • 원칙1 - 비슷한 관심사라면 가까운 곳에
      • 원칙2 - 데이터를 Id 기반으로 정리하기
      • 원칙3 - 의존한다면 그대로 드러내기
      • 원칙4 - 모델 기준으로 컴포넌트 분리하기
    • 추가
      • 이러한 위 4가지 원칙을 강제해서 리액트 클라이언트 개발을 더 편하게 해주는 GraphQL 데이터 레이어 프레임워크 Relay 를 소개해주시면서 강의를 마치셨다.

강의

토스슬래시, 한재엽: Effective Component 지속 가능한 성장과 컴포넌트


요약

  • 무엇이 변경될지 알았다면… 변경은 예측불가하다. 그래서 변경은 예측하지 말고 대응해야 한다.
  • 변경에 대응하기: 변경에 유연하게 대응하도록 컴포넌트를 나누기
    • 만들다보니 페이지가 커지고, 이 커진 코드를 ‘적당히’ 나누기 → ‘적당히’: 변경에 유연하게 대응하도록
    • 어떻게 하면 변경에 유연하게 대응하도록 컴포넌트를 분리할 수 있을까?
      1. Headless 기반의 추상화하기: 변하는 것 vs 상대적으로 변하지 않는 것
      2. 한 가지 역할만 하기: 또는 한가지 역할만 하는 컴포넌트의 조합으로 구성하기
      3. 도메인 분리하기: 도메인을 포함하는 컴포넌트와 그렇지 않은 컴포넌트 분리하기
  • Headless 기반의 추상화하기
    • 컴포넌트는 크게 3가지 역할을 한다:
      • 데이터 관리: 외부에서 받은 데이터, 상태과 같은 내부 데이터를 어떻게 관리하는지
      • UI 관리: 그러고 이러한 데이터를 어떻게 유저에게 보여줄지(UI)
      • 상호작용: UI를 기반으로 어떻게 사용자와 상호작용할지
    • 데이터와 UI의 분리. 컴포넌트를 구성하는데 필요한 데이터를 계산해야 하는데 이 역할을 use~훅스에 위임한다. 이러한 use~훅스는 UI를 관심사에서 제외하고 오직 데이터를 모듈화 하는데에만 집중할 수 있다.
    • UI와 상호작용 분리: 상호작용 부분도 역시 훅스로 만들어 관리한다.
  • 한 가지 역할만 하기(Composition)
  • 도메인 분리하기
    • UI패턴 공통화
  • 결론: 컴포넌트 분리시 고민해야 하는 것
    • 의도가 무엇인가? 분리하면 어떻게 좋아지는가?
    • 이 컴포넌트의 기능은 무엇인가?: 어떤 데이터를 다루는가?
    • 어떻게 표현되어야 하는가?

강의

당근마켓 원지혁 : 그래서 DECLARATIVE가 뭔데?


Declarative UI 패턴을 통해 자바스크립트를 마치 마크업짜듯 작성할 수 있게 되었다 → HTML, CSS / JavaScript 가 아니라 “관심사(정보)”를 기준으로 분리하게 되었다.

정보는 트리구조가 아니다! (함수형-순서형이 아니다!) 정보는 그래프 형태이다!

 

단일 진실 공급원 Single source of Truth

특정 상태가 변경되었을 때, 다른 부수효과를 최소로 하기 위해 단일 상태로 만들자. → ID 하나만 가지고 찾자. (Normalization) React가 UI에 대한 HOW를 제공한다면, GraphQL은 데이터에 대한 HOW를 제공한다.

 

선언적으로 데이터 요청하기 Declarative Data Fetching

리액트 컴포넌트는 데이터에 의존한다. 리액트는 어떤 정보(WHAT)가 필요한지만 요청한다. GraphQL 같은 라이브러리는 데이터를 어떻게(HOW)를 담당해 알아서 데이터를 처리해 넘겨준다. 결국 **선언적(Declarative)**란 공통된 HOW를 잘 분석해서 꺼내고 이를 통해 내 코드에 WHAT을 잘 뭉치는 과정. 이를 위해 HOW와 WHAT을 잘 가르는 안목이 필요하다.

 

문제

https://www.acmicpc.net/problem/11053

 

수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오.

예를 들어, 수열 A = {10, 20, 10, 30, 20, 50} 인 경우에 가장 긴 증가하는 부분 수열은 A = {10, 20, 10, 30, 20, 50} 이고, 길이는 4이다. 

 

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000)이 주어진다.

둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (1 ≤ Ai ≤ 1,000)

풀이

해설

sequence = [ 10, 20, 10, 30, 20, 50 ]

dp[i] i=0 i=1 i=2 i=3 i=4 i=5
  1 2 2 3 3 4

 

+ Recent posts