본문으로 건너뛰기

· 약 1분
Dongmin Yu

Migrating React Native library to Turbo Modules

Migrating your React Native library to use Turbo Modules, which is part of the new architecture of React Native, can provide several benefits. Turbo Modules are the next iteration on Native Modules and provide a few extra benefits such as strongly typed interfaces that are consistent across platforms, the ability to write your code in C++, either exclusively or integrated with another native platform language, reducing the need to duplicate implementations across platforms, lazy loading of modules, allowing for faster app startup and more efficient communication between native and JavaScript code than the bridge.

References

  1. Turbo Native Modules · React Native
  2. Turbo Modules as Legacy Native Modules · React Native
  3. Migrating a React Native Library to the New Architecture

· 약 21분
Dongmin Yu

코드 예제

if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
     chrome_path = os.path.join(sys._MEIPASS, 'chromedriver.exe')
 else:
      chrome_path = ChromeDriverManager().install()
      chrome_service = Service(chrome_path)

코드 설명

이 코드는 PyInstaller로 생성된 번들 실행 파일에서 스크립트가 실행되는지 여부를 확인합니다. 만약 그렇다면 번들에 포함된 Chrome 드라이버의 경로를 설정합니다. 그렇지 않으면 ChromeDriverManager를 사용하여 Chrome 드라이버를 설치하고 경로를 설정합니다. 그런 다음 지정된 경로를 사용하여 Chrome 서비스 객체를 생성합니다. webdriver.Chrome()을 사용하여 드라이버 인스턴스를 만들 때 이 코드가 없으면 에러가 발생할 수 있습니다. 이는 Chrome 드라이버의 경로가 올바르게 설정되지 않았기 때문일 수 있습니다. 이 코드는 Chrome 드라이버의 경로를 올바르게 설정하여 이러한 문제를 해결하는 데 도움이 됩니다. getattr(sys, 'frozen', False)sys 모듈의 frozen 속성을 가져오는 코드입니다. frozen 속성은 스크립트가 PyInstaller로 생성된 번들 실행 파일에서 실행되는 경우에만 존재합니다. 따라서 이 코드는 스크립트가 번들 실행 파일에서 실행되는지 여부를 확인하는 데 사용됩니다. Falsefrozen 속성이 존재하지 않을 경우 반환되는 기본값입니다. hasattr(sys, '_MEIPASS')sys 모듈에 _MEIPASS 속성이 있는지 여부를 확인하는 코드입니다. _MEIPASS 속성은 PyInstaller로 생성된 번들 실행 파일에서 스크립트가 실행되는 경우에만 존재하며, 번들의 임시 디렉토리 경로를 나타냅니다. 따라서 이 코드도 스크립트가 번들 실행 파일에서 실행되는지 여부를 확인하는 데 사용됩니다. _MEIPASS는 PyInstaller의 내부 변수로, 약어가 아닙니다. 이 변수는 PyInstaller로 생성된 번들 실행 파일에서 스크립트가 실행되는 경우에만 존재하며, 번들의 임시 디렉토리 경로를 나타냅니다. 이 경로는 PyInstaller가 번들을 실행할 때 생성하는 임시 디렉토리로, 번들에 포함된 모든 리소스 파일이 이 디렉토리에 추출됩니다. 셀레니움(Selenium) 웹 드라이버의 생성자에 파라미터가 없는 경우, 셀레니움은 시스템 환경 변수 PATH에서 Chrome 드라이버의 실행 파일을 찾습니다. 따라서 Chrome 드라이버의 실행 파일이 PATH에 포함된 디렉토리에 있어야 합니다. 서비스 객체를 생성자의 service 파라미터로 전달하는 경우, 셀레니움은 서비스 객체가 가리키는 경로에서 Chrome 드라이버의 실행 파일을 찾습니다. 이 경우 PATH에 포함된 디렉토리에 Chrome 드라이버의 실행 파일이 없어도 됩니다. 서비스 객체를 사용하면 Chrome 드라이버의 경로를 명시적으로 지정할 수 있습니다. executable_path 변수를 사용하여 Chrome 드라이버의 경로를 지정하는 것과 서비스 객체를 사용하여 Chrome 드라이버의 경로를 지정하는 것은 기능적으로 동일합니다. 둘 다 Chrome 드라이버의 경로를 명시적으로 지정할 수 있습니다. 그러나 서비스 객체를 사용하면 추가적인 옵션을 설정할 수 있습니다. 예를 들어, 서비스 객체의 service_args 속성을 사용하여 Chrome 드라이버의 로그 파일 경로나 로그 수준을 지정할 수 있습니다. 따라서 서비스 객체를 사용하는 것이 더 유연하고 확장 가능한 방법입니다. service_args 속성은 서비스 객체를 생성할 때 전달할 수 있는 선택적 파라미터입니다. 이 속성은 Chrome 드라이버의 명령줄 인수를 지정하는 데 사용됩니다. 예를 들어, Chrome 드라이버의 로그 파일 경로와 로그 수준을 지정하려면 다음과 같이 service_args 속성을 사용할 수 있습니다.

from selenium.webdriver.chrome.service import Service
chrome_service = Service(executable_path='/path/to/chromedriver',
                         service_args=['--log-path=/path/to/logfile', '--verbose'])

위 코드에서 --log-path 인수는 Chrome 드라이버의 로그 파일 경로를 지정하고, --verbose 인수는 로그 수준을 지정합니다. 이 외에도 다양한 명령줄 인수를 사용하여 Chrome 드라이버의 동작을 제어할 수 있습니다. Chrome 드라이버의 실행 파일에 --help 커맨드를 사용하면 사용 가능한 모든 명령줄 인수의 목록을 확인할 수 있습니다. 예를 들어, Windows에서는 명령 프롬프트에서 다음과 같이 입력할 수 있습니다.

chromedriver.exe --help

Linux나 macOS에서는 터미널에서 다음과 같이 입력할 수 있습니다.

./chromedriver --help

위 명령을 실행하면 Chrome 드라이버가 지원하는 모든 명령줄 인수와 각 인수의 설명이 출력됩니다. 이 정보를 참조하여 service_args 속성에 전달할 인수를 선택할 수 있습니다. WebDriverManager는 Chrome 드라이버의 설치와 관리를 자동화하는 라이브러리입니다. 이 라이브러리는 시스템에 설치된 Chrome 브라우저의 버전과 호환되는 Chrome 드라이버의 버전을 자동으로 감지하고 설치합니다. WebDriverManager를 사용하면 Chrome 드라이버의 버전을 수동으로 관리할 필요가 없습니다. WebDriverManager가 자동으로 Chrome 브라우저의 버전과 일치하는 Chrome 드라이버의 버전을 설치하므로, Chrome 브라우저와 Chrome 드라이버의 버전이 항상 일치합니다. 만약 시스템에 이미 설치된 Chrome 드라이버의 버전이 Chrome 브라우저의 버전과 일치하지 않는 경우, WebDriverManager는 새로운 버전의 Chrome 드라이버를 다운로드하여 설치합니다. 따라서 WebDriverManager를 사용하면 Chrome 드라이버의 버전 관리가 편리해집니다.

셀레니움 (Selenium)

셀레니움(Selenium)은 여러 가지 프로그래밍 언어를 지원합니다. 이 중에서 가장 인기 있는 언어는 Java입니다. 그러나 Python, C#, Ruby, JavaScript 등도 셀레니움을 사용하는 데 널리 사용되는 언어들입니다. 각 언어의 성능은 다양한 요인에 따라 달라질 수 있습니다. 따라서 특정 언어가 다른 언어보다 더 좋은 성능을 보장하는 것은 아닙니다. 셀레니움을 사용할 때 중요한 것은 개발팀이 사용하는 언어와의 호환성, 개발자 커뮤니티의 지원 정도, 개발자의 경험 등이 있습니다.

References

(1) What are the different programming languages supported by Selenium? - QAFox (2) Selecting a Programming Language for Selenium Automation Testing (3) Best Language for Selenium Testing - Tutorsbot (4) Selecting a Programming Language to build Selenium Test Automation Suite - Cigniti (5) Puppeteer vs Selenium: Core Differences | BrowserStack (6) Puppeteer vs Selenium - A Comparison Between 2 Automated Testing Libraries (7) Cheerio vs Puppeteer for Web Scraping in 2023: In-Depth Guide

Puppeteer와 Selenium은 모두 웹 자동화 도구로, 웹 페이지의 테스트와 크롤링 등에 사용됩니다. 하지만 이들은 몇 가지 차이점이 있습니다. Puppeteer는 Google에서 개발한 Node.js 라이브러리로, Chrome과 Chromium 브라우저를 제어하는 데 사용됩니다. Puppeteer는 Chrome DevTools Protocol을 사용하여 브라우저를 제어하며, 기본적으로 headless 모드로 실행됩니다. 이는 화면에 브라우저 창을 띄우지 않고 백그라운드에서 실행되는 것을 의미합니다.

Puppeteer

Puppeteer는 스크린샷 캡처, PDF 생성, 웹 스크래핑 등 다양한 기능을 제공합니다. 하지만 Puppeteer는 Chrome과 Chromium 브라우저만 지원하며, JavaScript 언어만 사용할 수 있습니다. 반면 Selenium은 여러 가지 브라우저와 프로그래밍 언어를 지원하는 웹 자동화 도구입니다. Selenium은 Java, Python, C#, Ruby 등 다양한 언어로 테스트 스크립트를 작성할 수 있으며, Chrome, Firefox, Safari 등 다양한 브라우저에서 테스트를 실행할 수 있습니다. Selenium은 큰 커뮤니티 지원과 다양한 라이브러리 및 확장 기능을 제공합니다. Puppeteer와 Selenium의 선택은 사용자의 요구사항과 환경에 따라 달라질 수 있습니다. Puppeteer는 Chrome 브라우저에서 빠른 성능을 제공하며, JavaScript 언어로 간단하게 사용할 수 있습니다. 반면 Selenium은 여러 가지 브라우저와 언어를 지원하며, 큰 커뮤니티 지원을 받습니다.

Selenium

Selenium은 웹 애플리케이션의 자동화 테스트를 위한 도구로, 모바일 애플리케이션의 테스트에는 직접 사용할 수 없습니다. 하지만 Appium이라는 모바일 애플리케이션 자동화 테스트 도구가 있습니다. Appium은 Selenium과 유사한 API를 제공하며, iOS와 Android 모바일 애플리케이션의 자동화 테스트를 지원합니다.

Appium

Appium은 Selenium WebDriver와 호환되며, Selenium과 유사한 방식으로 사용할 수 있습니다. 따라서 Selenium을 사용해본 경험이 있다면 Appium을 쉽게 사용할 수 있습니다. Appium은 Java, Python, Ruby, JavaScript 등 다양한 언어로 테스트 스크립트를 작성할 수 있습니다. Appium은 Dan Cuellar에 의해 2011년에 개발되었습니다. 처음에는 "iOSAuto"라는 이름으로 C# 프로그래밍 언어로 작성되었습니다. 2012년 8월에 Apache 2 라이선스로 오픈소스화되었고, 2013년 1월에 Sauce Labs가 Appium의 개발을 지원하면서 Node.js로 코드가 재작성되었습니다. Appium은 모바일 애플리케이션의 자동화 테스트를 위한 오픈소스 도구로, iOS와 Android 모바일 애플리케이션의 테스트를 지원합니다. Appium은 Selenium과 유사한 API를 제공하며, Java, Python, Ruby 등 다양한 언어로 테스트 스크립트를 작성할 수 있습니다.

References

(1) Appium - Wikipedia (2) Appium: Mobile App Automation Made Awesome. (3) What is Appium and How does it work? An Overview and Its Use Cases

크로스플랫폼 프레임워크로 만들어진 모바일 앱의 자동화 테스트

크로스플랫폼 프레임워크로 만들어진 모바일 앱의 경우에도, 안드로이드와 iOS 각각의 테스트 스크립트를 작성해야 합니다. 이는 안드로이드와 iOS가 각각 다른 운영 체제이기 때문입니다. 따라서 Appium을 사용하여 안드로이드와 iOS 앱을 테스트할 때에는 각각의 테스트 스크립트를 작성해야 합니다. 하지만 Appium은 Selenium과 유사한 API를 제공하므로, Selenium을 사용해본 경험이 있다면 Appium을 쉽게 사용할 수 있습니다. 또한 Appium은 Page Object Model과 같은 디자인 패턴을 사용하여 테스트 코드의 재사용성을 높일 수 있습니다. 이를 통해 안드로이드와 iOS 앱의 테스트 코드를 최대한 비슷하게 작성할 수 있습니다. Appium을 사용하려면 먼저 Appium 서버를 설치해야 합니다. Appium 서버는 Node.js로 작성되었으므로 Node.js와 npm이 설치되어 있어야 합니다. Appium 서버를 설치하려면 다음 명령을 실행합니다.

npm install -g appium

Appium 서버를 설치한 후에는 Appium 클라이언트 라이브러리를 사용하여 테스트 스크립트를 작성할 수 있습니다. Appium 클라이언트 라이브러리는 Java, Python, Ruby 등 다양한 언어로 제공됩니다. 예를 들어, Python으로 테스트 스크립트를 작성하려면 다음 명령으로 Appium의 Python 클라이언트 라이브러리를 설치할 수 있습니다.

pip install Appium-Python-Client

Appium 클라이언트 라이브러리를 설치한 후에는 Selenium과 유사한 방식으로 테스트 스크립트를 작성할 수 있습니다. 예를 들어, 다음은 Python으로 작성된 간단한 Appium 테스트 스크립트입니다.

from appium import webdriver
desired_caps = {
    "platformName": "Android",
    "platformVersion": "9.0",
    "deviceName": "Android Emulator",
    "app": "/path/to/your/app.apk"
}
driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps)
# 여기에 테스트 코드를 작성합니다.
driver.quit()

위 코드에서 desired_caps 변수는 테스트할 모바일 장치와 앱의 정보를 지정합니다. webdriver.Remote 메서드를 사용하여 Appium 서버에 연결하고, 이후에는 Selenium과 유사한 방식으로 테스트 코드를 작성할 수 있습니다. Appium 서버는 Node.js로 작성되었기 때문에 Appium 서버를 실행하려면 Node.js가 설치되어 있어야 합니다. 하지만 Appium 클라이언트 라이브러리는 여러 가지 언어로 제공되므로, 테스트 스크립트를 작성할 때에는 Node.js가 아닌 다른 언어를 사용할 수 있습니다. 리액트 네이티브 애플리케이션의 UI 테스트를 위해서는 Detox라는 도구를 사용할 수 있습니다. Detox는 리액트 네이티브 애플리케이션의 엔드-투-엔드 테스트를 위한 도구로, iOS와 Android 모바일 애플리케이션의 테스트를 지원합니다. Detox는 빠른 테스트 실행 속도와 안정적인 테스트 결과를 제공합니다. Detox 테스트가 실패하는 경우에는 여러 가지 원인이 있을 수 있습니다. 먼저 Detox의 문서와 트러블슈팅 가이드를 참조하여 일반적인 문제와 해결 방법을 확인해보세요. 또한 Detox 테스트의 로그를 확인하여 실패 원인을 파악할 수 있습니다. E2E 테스트의 규모를 줄이는 것 외에도, 다음과 같은 방법으로 테스트의 안정성을 높일 수 있습니다.

  • 테스트 전에 앱의 상태를 초기화하여 테스트가 항상 동일한 상태에서 시작하도록 합니다.
  • 네트워크 지연이나 애니메이션 등으로 인한 지연을 고려하여 적절한 대기 시간을 설정합니다.
  • 테스트 중 발생하는 예외 상황을 처리하는 코드를 작성합니다. 또한 Detox는 Jest와 같은 테스트 프레임워크와 함께 사용할 수 있습니다. Jest의 기능을 활용하여 테스트 코드의 구조화와 재사용성을 높일 수 있습니다. Jest는 JavaScript 테스트 프레임워크로, Detox와 함께 사용하여 리액트 네이티브 애플리케이션의 E2E 테스트를 작성할 수 있습니다. Jest를 사용하면 테스트 코드의 구조화와 재사용성을 높일 수 있습니다. 다음은 Jest와 Detox를 함께 사용하는 간단한 예제 코드입니다.
describe("Login", () => {
  beforeAll(async () => {
    await device.reloadReactNative();
  });
  it("should show login screen", async () => {
    await expect(element(by.id("loginScreen"))).toBeVisible();
  });
  it("should login successfully", async () => {
    await element(by.id("emailInput")).typeText("user@example.com");
    await element(by.id("passwordInput")).typeText("password");
    await element(by.id("loginButton")).tap();
    await expect(element(by.id("homeScreen"))).toBeVisible();
  });
});

위 코드에서 describe 함수는 Jest에서 제공하는 함수로, 관련된 테스트 케이스들을 그룹화하는 데 사용됩니다. beforeAll 함수는 테스트 그룹의 모든 테스트 케이스가 실행되기 전에 한 번만 실행되는 함수로, 이 예제에서는 앱의 상태를 초기화하는 데 사용됩니다. it 함수는 각각의 테스트 케이스를 정의하는 데 사용됩니다. 위 예제에서처럼 Jest의 기능을 활용하면 Detox 테스트 코드를 구조화하고 재사용성을 높일 수 있습니다.

· 약 20분
Dongmin Yu

WebGL: 웹 그래픽 라이브러리(Web Graphics Library)

WebGL은 웹 그래픽 라이브러리(Web Graphics Library)의 약자로, 웹 브라우저에서 상호작용 가능한 3D와 2D 그래픽을 표현하기 위한 JavaScript API입니다. WebGL은 HTML5 <canvas> 요소에서 사용할 수 있는, OpenGL ES 2.0을 대부분 충족하는 API를 제공합니다. WebGL은 플러그인을 사용하지 않고 웹 브라우저에서 상호작용 가능한 3D와 2D 그래픽을 표현하기 위한 JavaScript API입니다.

References

  1. WebGL: 2D and 3D graphics for the web - Web APIs | MDN - Mozilla
  2. WebGL - Web API | MDN
  3. WebGL - Wikipedia

WebGL과 OpenGL

WebGL 1.0은 대부분의 주요 브라우저의 안정적인 릴리즈에서 데스크탑 및 모바일 플랫폼 모두에서 지원됩니다. Chrome, Firefox, Internet Explorer, Opera 및 Safari는 모두 데스크탑 및 모바일 브라우저에서 WebGL 지원이 좋은 것으로 알려져 있습니다. 그러나 사용자의 장치도 WebGL 기능을 지원해야 합니다.

OpenGL ES는 OpenGL for Embedded Systems의 약자로, OpenGL 컴퓨터 그래픽 렌더링 응용 프로그래밍 인터페이스(API)의 하위 집합입니다. 이 API는 비디오 게임에서 사용하는 것과 같은 2D 및 3D 컴퓨터 그래픽을 렌더링하기 위해 설계되었으며 일반적으로 그래픽 처리 장치(GPU)를 사용하여 하드웨어 가속됩니다. 스마트폰, 태블릿 컴퓨터, 비디오 게임 콘솔 및 PDA와 같은 임베디드 시스템용으로 설계되었습니다. OpenGL ES 2.0은 OpenGL ES의 버전 중 하나로, 이전 버전인 OpenGL ES 1.X와 달리 프로그래밍 가능한 셰이딩을 지원합니다. 이는 모바일 그래픽 하드웨어의 최신 세대에서 프로그래막 가능한 셰이더를 노출하는 최초의 이식 가능한 모바일 그래픽 API였습니다.

References

  1. OpenGL ES - Wikipedia
  2. OpenGL ES Overview - The Khronos Group Inc
  3. Khronos OpenGL ES Registry - The Khronos Group Inc

OpenGL ES 2.0

OpenGL ES 2.0은 OpenGL ES의 버전 중 하나로, 이전 버전인 OpenGL ES 1.X와 달리 프로그래밍 가능한 셰이딩을 지원합니다. 이는 모바일 그래픽 하드웨어의 최신 세대에서 프로그래막 가능한 셰이더를 노출하는 최초의 이식 가능한 모바일 그래픽 API였습니다. OpenGL ES 2.0 API에는 다양한 함수와 명령이 정의되어 있습니다. 이러한 함수와 명령은 프로그래머가 고품질의 그래픽 이미지, 특히 3차원 객체의 컬러 이미지를 생성하는 데 필요한 객체와 작업을 지정할 수 있도록 합니다. OpenGL ES 2.0 API에 대한 자세한 정보는 Khronos Group의 웹사이트에서 찾을 수 있습니다.

References

  1. OpenGL ES Overview - The Khronos Group Inc
  2. OpenGL ES 2.0 API Quick Reference Card - Khronos Group
  3. Khronos OpenGL ES Registry - The Khronos Group Inc

OpenGL ES 2.0 API의 주요 함수와 명령

OpenGL ES 2.0 API에는 다양한 함수와 명령이 정의되어 있습니다. 이러한 함수와 명령은 프로그래머가 고품질의 그래픽 이미지, 특히 3차원 객체의 컬러 이미지를 생성하는 데 필요한 객체와 작업을 지정할 수 있도록 합니다. OpenGL ES 2.0 API에 대한 자세한 정보는 Khronos Group의 웹사이트에서 찾을 수 있습니다.

  • glGenBuffers(), glBindBuffer(), glBufferData(), glBufferSubData(): 버퍼 객체 생성 및 데이터 저장
  • glCreateShader(), glShaderSource(), glCompileShader(): 셰이더 생성 및 컴파일
  • glCreateProgram(), glAttachShader(), glLinkProgram(), glUseProgram(): 셰이더 프로그램 생성 및 사용
  • glVertexAttribPointer(), glEnableVertexAttribArray(): 정점 속성 지정
  • glDrawArrays(), glDrawElements(): 기하학적 도형 렌더링

셰이더 프로그램

셰이더 프로그램은 그래픽 파이프라인의 특정 부분에서 실행되는 작은 프로그램입니다. OpenGL ES 2.0에서는 두 가지 유형의 셰이더가 지원됩니다: 버텍스 셰이더와 프래그먼트 셰이더. 버텍스 셰이더는 모든 정점에 대해 실행되며, 정점의 위치와 다른 속성을 계산하는 데 사용됩니다. 프래그먼트 셰이더는 화면에 그려지는 각 픽셀에 대해 실행되며, 픽셀의 최종 색상을 계산하는 데 사용됩니다. 셰이더 프로그램은 버텍스 셰이더와 프래그먼트 셰이더를 결합하여 생성됩니다. 먼저 glCreateShader() 함수를 사용하여 각 셰이더 객체를 생성한 다음, glShaderSource() 함수를 사용하여 셰이더 소스 코드를 지정하고 glCompileShader() 함수를 사용하여 셰이더를 컴파일합니다. 그런 다음 glCreateProgram() 함수를 사용하여 셰이더 프로그램 객체를 생성하고, glAttachShader() 함수를 사용하여 버텍스 셰이더와 프래그먼트 셰이더를 프로그램에 첨부한 다음, glLinkProgram() 함수를 사용하여 프로그램을 링크합니다. 마지막으로 glUseProgram() 함수를 사용하여 링크된 프로그램을 현재 렌더링 상태의 일부로 지정합니다. 셰이더 프로그램을 사용하면 개발자가 그래픽 파이프라인의 중요한 부분을 직접 제어할 수 있어 더욱 유연하고 강력한 그래픽 효과를 만들 수 있습니다. 셰이더 소스 코드는 OpenGL ES 셰이딩 언어(GLSL ES)로 작성됩니다. GLSL ES는 C 언어와 유사한 구문을 가진 고수준 프로그래밍 언어로, 그래픽 처리 장치(GPU)에서 실행되도록 설계되었습니다. 셰이더 소스 코드는 일반적으로 문자열 형태로 작성되며, glShaderSource() 함수를 사용하여 셰이더 객체에 전달됩니다. 각 셰이더 유형(버텍스 셰이더 또는 프래그먼트 셰이더)에 대해 다른 소스 코드가 필요합니다. GLSL ES에는 벡터, 행렬, 텍스처 등과 같은 그래픽 처리에 유용한 데이터 형식과 함수가 포함되어 있습니다. 또한 조건문, 반복문, 함수 정의 등과 같은 일반적인 프로그래밍 구조도 지원됩니다.

간단한 버텍스 셰이더 예제

attribute vec4 a_position;
uniform mat4 u_mvpMatrix;
void main() {
    gl_Position = u_mvpMatrix * a_position;
}

이 버텍스 셰이더는 정점 위치(a_position)를 모델-뷰-투영 행렬(u_mvpMatrix)로 변환하여 최종 정점 위치(gl_Position)를 계산합니다.

precision mediump float;
uniform vec4 u_color;
void main() {
    gl_FragColor = u_color;
}

이 프래그먼트 셰이더는 모든 픽셀의 색상을 동일한 색상(u_color)으로 설정합니다.

Node.js에서 OpenGL을 활용하는 방법

셰이더 소스 코드 작성에 대한 자세한 정보는 OpenGL ES 셰이딩 언어 사양서에서 찾을 수 있습니다. Node.js에서 OpenGL을 활용할 수 있는 몇 가지 라이브러리가 있습니다. 이 중 일부는 다음과 같습니다:

  • node-webgl: 데스크톱 OpenGL에 대한 WebGL 바인딩
  • headless-gl: Node.js에서 windowless WebGL을 제공하는 라이브러리
  • node-glfw: Node.js에서 GLFW 바인딩
  • node-occ: OpenCascade와 NodeJS로 BREP Solids를 구축하는 3D 모델링 라이브러리

References

  1. GitHub - mikeseven/node-webgl: WebGL bindings to desktop OpenGL
  2. 10 Best Node.js Graphics Libraries in 2023 | Openbase
  3. Getting started with WebGL - Web APIs | MDN - Mozilla

WebGL을 기반으로 풍부한 반응형 UI를 제공하는 라이브러리

  • Three.js: WebGL을 사용하여 3D 그래픽을 쉽게 생성할 수 있는 JavaScript 라이브러리
  • Babylon.js: 강력한 3D 엔진으로, WebGL을 사용하여 3D 그래픽을 생성하는 데 사용됩니다
  • A-Frame: 웹에서 가상 현실(VR) 경험을 쉽게 만들 수 있는 웹 프레임워크
  • PlayCanvas: WebGL 기반의 3D 게임 엔진으로, 강력한 툴과 API를 제공합니다

References

  1. List of WebGL frameworks - Wikipedia
  2. 10 Best JavaScript WebGL Libraries in 2023 | Openbase
  3. GitHub - bradtraversy/design-resources-for-developers: Curated list of design and UI ...

Three.js

Three.js는 WebGL을 사용하여 3D 그래픽을 쉽게 생성할 수 있는 JavaScript 라이브러리입니다. 이 라이브러리는 다양한 3D 객체, 재질, 조명, 애니메이션, 카메라 등을 제공하여 개발자가 쉽게 3D 씬을 구성하고 렌더링할 수 있도록 합니다. Three.js는 다양한 형식의 3D 모델을 로드하고, 물리 엔진과 통합하며, 다양한 효과와 포스트 프로세싱 기능을 제공합니다. 이 라이브러리는 웹 브라우저에서 실행되므로, 플러그인이나 추가 설치 없이도 사용자가 쉽게 접근할 수 있습니다. Three.js는 꾸준히 업데이트되고 있으며, 활발한 커뮤니티와 많은 예제 및 문서가 제공됩니다. 이 라이브러리를 사용하면 개발자가 WebGL의 복잡한 세부 사항을 걱정하지 않고도 강력한 3D 그래픽을 쉽게 만들 수 있습니다. Three.js의 공식 웹사이트에는 다양한 예제가 제공됩니다. 이 예제들은 Three.js의 다양한 기능을 보여주며, 개발자가 이 라이브러리를 사용하는 방법을 배울 수 있도록 도와줍니다. 다음은 간단한 Three.js 예제입니다:

// 씬 생성
const scene = new THREE.Scene();
// 카메라 생성
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000,
);
// 렌더러 생성

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 큐브 생성

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 카메라 위치 조정
camera.position.z = 5;
// 애니메이션 루프
function animate() {
  requestAnimationFrame(animate); // 큐브 회전
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01; // 렌더링
  renderer.render(scene, camera);
}
animate();

이 예제는 Three.js를 사용하여 간단한 큐브를 생성하고 회전시키는 방법을 보여줍니다. 먼저 씬, 카메라, 렌더러를 생성하고, 큐브의 기하학과 재질을 정의하여 큐브 메시를 생성합니다. 그런 다음 큐브를 씬에 추가하고, 애니메이션 루프에서 큐브를 회전시키며 렌더링합니다. Three.js의 공식 웹사이트에서는 이보다 훨씬 더 다양하고 복잡한 예제들을 볼 수 있습니다. Three.js를 React 또는 React Native에서 사용하기 위한 몇 가지 래퍼(wrapper) 컴포넌트가 있습니다. 이러한 래퍼 컴포넌트는 Three.js의 기능을 React 컴포넌트로 캡슐화하여 개발자가 쉽게 사용할 수 있도록 합니다. React에서 Three.js를 사용하기 위한 래퍼 컴포넌트 중 일부는 다음과 같습니다:

  • react-three-fiber: Three.js의 선언적 리액트 렌더러
  • threejs-react-wrapper: Three.js 코드를 React 컴포넌트 내에서 실행할 수 있는 간단한 래퍼

React Native에서 Three.js를 사용하기 위한 래퍼 컴포넌트 중 하나는 다음과 같습니다:

  • expo-three: Expo와 함께 사용할 수 있는 Three.js 래퍼

References

  1. Using Three.js in React - Medium
  2. GitHub - felixturner/threejs-react-wrapper
  3. How to develop 3D games with React Native using Three.js

Expo Three JS 모듈

expo-three는 Expo와 함께 사용할 수 있는 Three.js 래퍼입니다. 이 라이브러리는 Expo 앱에서 Three.js의 기능을 쉽게 사용할 수 있도록 지원합니다. expo-three를 사용하면 개발자가 Expo 앱에서 Three.js의 다양한 기능을 사용하여 3D 그래픽을 생성하고 렌더링할 수 있습니다. 이 라이브러리는 Three.js의 기본 객체와 메소드를 지원하며, 추가적으로 Expo의 기능과 통합되어 있습니다. expo-three는 Expo 앱에서 Three.js를 사용하는 데 필요한 추가 설정이나 설치 없이 쉽게 사용할 수 있습니다. 이 라이브러리의 문서와 예제는 GitHub에서 찾을 수 있습니다. expo-three의 GitHub 저장소에는 다양한 예제가 제공됩니다. 이 예제들은 expo-three의 다양한 기능을 보여주며, 개발자가 이 라이브러리를 사용하는 방법을 배울 수 있도록 도와줍니다. 다음은 간단한 expo-three 예제입니다:

import React from "react";
import { View } from "react-native";
import { GLView } from "expo-gl";
import ExpoTHREE, { THREE } from "expo-three";
export default function App() {
  return (
    <View style={{ flex: 1 }}>
           {" "}
      <GLView
        style={{ flex: 1 }}
        onContextCreate={async (gl) => {
          const scene = new THREE.Scene();
          const camera = new THREE.PerspectiveCamera(
            75,

            gl.drawingBufferWidth / gl.drawingBufferHeight,

            0.1,

            1000,
          );
          const renderer = new ExpoTHREE.Renderer({ gl });
          renderer.setSize(gl.drawingBufferWidth, gl.drawingBufferHeight);
          const geometry = new THREE.BoxGeometry(1, 1, 1);
          const material = new THREE.MeshBasicMaterial({
            color: 0x00ff00,
          });
          const cube = new THREE.Mesh(geometry, material);
          scene.add(cube);
          camera.position.z = 5;
          const animate = () => {
            requestAnimationFrame(animate);
            cube.rotation.x += 0.01;
            cube.rotation.y += 0.01;
            renderer.render(scene, camera);
            gl.endFrameEXP();
          };
          animate();
        }}
      />
         {" "}
    </View>
  );
}

이 예제는 expo-three를 사용하여 간단한 큐브를 생성하고 회전시키는 방법을 보여줍니다. 먼저 씬, 카메라, 렌더러를 생성하고, 큐브의 기하학과 재질을 정의하여 큐브 메시를 생성합니다. 그런 다음 큐브를 씬에 추가하고, 애니메이션 루프에서 큐브를 회전시키며 렌더링합니다. expo-three의 GitHub 저장소에서는 이보다 훨씬 더 다양하고 복잡한 예제들을 볼 수 있습니다.

Three.js의 3D 모델을 공유하는 오픈소스 커뮤니티

Three.js의 3D 모델을 공유하는 오픈소스 커뮤니티가 몇 가지 있습니다. 이러한 커뮤니티에서는 개발자들이 Three.js로 만든 3D 모델을 공유하고, 서로의 작업을 검토하고 피드백을 제공합니다. Three.js의 공식 웹사이트에서는 다양한 예제와 모델이 제공됩니다. 이 예제들은 Three.js의 다양한 기능을 보여주며, 개발자가 이 라이브러리를 사용하는 방법을 배울 수 있도록 도와줍니다. 또한 GitHub에서도 Three.js로 만든 3D 모델을 찾아볼 수 있습니다. 여러 개발자들이 자신의 작업물을 GitHub 저장소에 공유하고 있으며, 이러한 저장소에서는 다양한 종류의 3D 모델을 찾아볼 수 있습니다.

References

  1. Three.js – JavaScript 3D Library
  2. GitHub - mrdoob/three.js: JavaScript 3D Library.
  3. 10 Stunning Open-Source Examples of Three.js In Action - Speckyboy Design Magazine
  4. GitHub - Fasani/three-js-resources: A collection of resources for building 3D projects ...

Three.js의 공식 웹사이트에는 다양한 예제 모델이 제공됩니다. 이 예제들은 Three.js의 다양한 기능을 보여주며, 개발자가 이 라이브러리를 사용하는 방법을 배울 수 있도록 도와줍니다. 예를 들어, Three.js의 공식 웹사이트에서는 다음과 같은 예제 모델들을 볼 수 있습니다:

  • Interactive Cubes: 마우스로 상호작용하는 큐브 모델
  • Clipping with Cap: 클리핑 효과가 적용된 모델
  • Morph Targets: 모핑 타겟 애니메이션이 적용된 모델
  • Lines: 다양한 선 스타일이 적용된 모델

Three.js의 공식 웹사이트에서는 이보다 훨씬 더 다양하고 복잡한 예제 모델들을 볼 수 있습니다. 이러한 예제들은 Three.js의 다양한 기능과 가능성을 보여주며, 개발자가 이 라이브러리를 사용하여 자신만의 3D 모델을 만드는 데 도움이 됩니다.

· 약 19분
Dongmin Yu

Postman과 Newman 라이브러리

Postman은 Node.js 기반의 런타임을 포함하고 있어, Request와 Collection에 동적으로 동작을 추가할 수 있습니다. 이를 통해 동적 매개변수를 사용하거나 요청 간에 데이터를 전달하는 것이 가능합니다. 이런 동적 작업을 수행하는 다음 2가지 이벤트 흐름에 대해 자바스크립트 코드를 작성할 수 있습니다. Pre-request Script : Request 헤더에 key를 추가시키거나, URL 매개변수에 문자열을 추가하고자 할 때 사용 (요청이 서버로 가기 전에 실행) Test Script : .test 함수를 사용하고, .response, .expect 객체 등에 접근이 가능함 (요청이 서버로 간 이후 응답이 반환된 후에 실행). Postman에서 작성한 스크립트나 API 테스트를 Node.js 프로젝트에 반영하는 방법으로는 Newman 라이브러리를 사용하는 방법이 있습니다. Newman은 Postman의 CLI 버전으로 Postman Collection을 Node.js 모듈로 실행할 수 있습니다.

References

(1) POSTMAN - Run Postman Collection Using JavaScript Project | As Node.js module | Newman ... (2) Scripting in Postman | Postman Learning Center (3) Node.js 및 React 앱 만들기 - Visual Studio (Windows) (4) Create API with node JS and test with postman | Node Js Tutorial | Learning Points ...

Newman에 대한 설명과 사용 예제

Newman은 Postman의 CLI 버전으로 Postman Collection을 Node.js 모듈로 실행할 수 있습니다. 이를 통해 Postman에서 작성한 스크립트나 API 테스트를 Node.js 프로젝트에서 실행할 수 있습니다. Newman을 사용하려면 먼저 Node.js가 설치되어 있어야 합니다. 그 다음 npm을 통해 Newman을 전역으로 설치합니다. 그러나 Newman 자체는 Axios 리퀘스트를 보내는 기능을 제공하지 않습니다. Express와 같은 환경을 구축하지 않고 Axios 리퀘스트를 보내려면 Node.js의 내장 모듈인 http 또는 https 모듈을 사용하거나 Axios와 같은 서드파티 라이브러리를 사용해야 합니다.

npm install -g newman

Newman을 실행하는 가장 쉬운 방법은 Collection과 함께 실행하는 것입니다. 파일 시스템에서 Collection 파일을 실행할 수 있습니다. 또한 Collection을 공유하여 URL로 전달할 수도 있습니다.

newman run mycollection.json

Collection이 환경 변수를 사용하는 경우, Postman에서 템플릿을 내보내고 -e 플래그와 함께 실행합니다.

newman run https://www.postman.com/collections/cb208e7e64056f5294e5 -e dev_environment.json

Newman은 라이브러리로 구축되어 다양한 방식으로 확장 및 사용할 수 있습니다. Node.js 코드에서 다음과 같이 사용할 수 있습니다.

var newman = require("newman");
// `options` 객체를 전달하고 콜백을 기다리면서 newman.run 호출
newman.run(
  {
    collection: require("./sample-collection.json"),
    reporters: "cli",
  },
  function (err) {
    if (err) {
      throw err;
    }
    console.log("collection run complete!");
  },
);

References

(1) Installing and running Newman | Postman Learning Center (2) newman - npm (3) Newman University Library: Library Homepage: Home

Swagger UI와 Postman

Swagger UI는 OpenAPI (이전에는 Swagger) 사양을 기반으로 한 오픈 소스 프로젝트로, RESTful API를 설계, 빌드, 문서화 및 사용하는 데 도움이 되는 도구 모음입니다. Swagger UI를 사용하면 API를 시각적으로 탐색하고 상호 작용할 수 있으며, API 문서를 자동으로 생성할 수 있습니다. Postman은 API 개발 환경으로, API를 개발하고 테스트하는 데 필요한 모든 기능을 제공합니다. Postman을 사용하면 API 요청을 보내고 응답을 확인할 수 있으며, 테스트 스크립트를 작성하고 자동화된 테스트를 실행할 수 있습니다. 또한 Postman은 API 문서화, 모니터링 및 공유 기능도 제공합니다.

두 도구 모두 API 개발에 유용하지만, Swagger UI는 API 문서화와 시각화에 중점을 두고 있으며, Postman은 API 개발 및 테스트에 중점을 두고 있습니다. Swagger UI와 Postman은 모두 API 개발에 유용한 도구이지만, 각각의 목적과 기능이 다릅니다. 따라서 어떤 것이 더 나은지는 사용자의 필요에 따라 달라집니다. Swagger UI는 API 문서화와 시각화에 중점을 두고 있습니다. 따라서 API를 설계하고 문서화하는 데 중점을 두는 경우 Swagger UI가 더 유용할 수 있습니다. Postman은 API 개발 및 테스트에 중점을 두고 있습니다. 따라서 API를 개발하고 테스트하는 데 중점을 두는 경우 Postman이 더 유용할 수 있습니다. 또한 Swagger UI와 Postman은 함께 사용할 수도 있습니다. 예를 들어 Swagger UI를 사용하여 API를 설계하고 문서화하고, Postman을 사용하여 API를 테스트하는 것이 가능합니다. Swagger UI와 Postman은 함께 사용하여 API 개발 프로세스를 강화할 수 있습니다. 예를 들어, Swagger UI를 사용하여 API를 설계하고 문서화할 수 있습니다. Swagger UI는 OpenAPI 사양을 기반으로 하며, 이 사양을 사용하여 API의 구조와 기능을 정의할 수 있습니다. 또한 Swagger UI는 이 사양을 기반으로 자동으로 API 문서를 생성할 수 있습니다. 이렇게 생성된 API 문서는 Postman에서 가져올 수 있습니다. Postman에서는 가져온 API 문서를 기반으로 API 요청을 보내고 응답을 확인할 수 있습니다. 또한 Postman에서는 테스트 스크립트를 작성하여 자동화된 테스트를 실행할 수 있습니다. 이렇게 Swagger UI와 Postman을 함께 사용하면 API 설계 및 문서화와 API 개발 및 테스트를 원활하게 진행할 수 있습니다.

Newman과 pm은 Postman

Newman은 Postman의 커맨드 라인 도구로, Postman 컬렉션을 실행하는 데 사용됩니다. Newman을 사용하면 Postman 컬렉션을 지정된 환경에서 실행하고 테스트 결과를 생성할 수 있습니다. Newman은 지속적인 통합 및 지속적인 전달 (CI/CD) 파이프라인에 통합하여 API 테스트를 자동화하는 데 유용합니다. pm은 Postman 스크립트에서 사용할 수 있는 내장 라이브러리입니다. pm을 사용하면 요청 및 응답 데이터에 액세스하고 조작할 수 있으며, 환경 및 전역 변수를 설정하고 관리할 수 있습니다. 또한 pm을 사용하여 테스트 스크립트를 작성하고 실행할 수 있습니다. 따라서 Newman과 pm은 서로 다른 목적으로 사용되는 도구입니다. Newman은 Postman 컬렉션을 실행하는 데 사용되며, pm은 Postman 스크립트를 작성하는 데 사용됩니다.

Newman을 사용하여 Postman 컬렉션을 "실행"한다는 것은 Postman 컬렉션에 포함된 모든 요청을 순차적으로 실행하고 테스트 스크립트를 실행하는 것을 의미합니다. Postman 컬렉션은 여러 개의 API 요청과 테스트 스크립트로 구성됩니다. Newman을 사용하여 Postman 컬렉션을 실행하면 Newman은 컬렉션에 포함된 각 요청을 순차적으로 실행하고 응답을 확인합니다. 또한 Newman은 컬렉션에 포함된 테스트 스크립트를 실행하여 API가 예상대로 작동하는지 확인합니다. Newman은 Postman의 Publish 기능과는 다릅니다. Publish 기능은 Postman 컬렉션의 문서화된 버전을 웹에서 공유하는 데 사용됩니다. 반면 Newman은 Postman 컬렉션을 실행하는 데 사용되며, API 엔드포인트를 간편하게 리스팅하거나 관리하는 것이 아니라 API 테스트를 자동화하는 데 목적이 있습니다.

Newman은 백엔드와 프론트엔드 모두에게 유용한 도구입니다. Newman은 Postman 컬렉션을 실행하는 커맨드 라인 도구로, API 테스트를 자동화하는 데 사용됩니다. 백엔드 개발자는 Newman을 사용하여 API가 예상대로 작동하는지 확인할 수 있습니다. Newman을 사용하면 API 요청을 보내고 응답을 확인하며, 테스트 스크립트를 실행하여 API가 예상대로 작동하는지 확인할 수 있습니다. 또한 Newman은 지속적인 통합 및 지속적인 전달 (CI/CD) 파이프라인에 통합하여 API 테스트를 자동화할 수 있습니다. 프론트엔드 개발자도 Newman을 사용하여 API가 예상대로 작동하는지 확인할 수 있습니다. 프론트엔드 개발자는 Newman을 사용하여 백엔드 API가 예상대로 작동하는지 확인하고, 프론트엔드 코드가 백엔드 API와 올바르게 상호 작용하는지 확인할 수 있습니다. Newman은 백엔드와 프론트엔드 모두에게 유용한 도구입니다.

Postman에서 API 문서 생성하는 방법

Postman은 OpenAPI 사양을 기반으로 한 API 문서를 생성하는 기능을 제공합니다. Postman 컬렉션에서 API 문서를 생성하려면 다음 단계를 따르십시오.

  1. Postman에서 컬렉션을 선택하고 "..." 버튼을 클릭한 다음 "Export"를 선택합니다.
  2. "Collection v2.1"을 선택하고 "Export" 버튼을 클릭합니다.
  3. 파일을 저장하고 Postman API Builder로 이동합니다.
  4. "APIs" 탭에서 "+" 버튼을 클릭하고 "Import an API"를 선택합니다.
  5. "Choose Files" 버튼을 클릭하고 앞서 저장한 파일을 선택합니다.

이제 Postman API Builder에서 OpenAPI 사양을 기반으로 한 API 문서를 생성할 수 있습니다. 생성된 API 문서는 Swagger와 같은 다른 도구에서도 사용할 수 있습니다.

Swagger와 Open API 3.0

또한 일부 백엔드 프레임워크 (예: Node.js의 Express)에서는 Swagger와 같은 도구를 사용하여 코드 주석을 기반으로 자동으로 API 문서를 생성하는 기능도 제공합니다. 이러한 기능을 사용하면 코드 주석에 기반하여 자동으로 API 문서를 생성할 수 있습니다. OpenAPI 2.0 (이전에는 Swagger로 알려졌음)은 OpenAPI 3.0과 몇 가지 중요한 차이점이 있습니다. OpenAPI 3.0은 2017년에 출시되었으며, 몇 가지 주요한 변경 사항이 있습니다.

  • 문서 구조가 재구성되어 API 재사용성이 증가하도록 개발되었습니다.
  • JSON 스키마 지원이 확장되었습니다.
  • 새로운 콜백 및 링크 기능이 추가되었습니다.
  • 서버 변수 및 다중 서버 지원이 추가되었습니다.

OpenAPI 3.1은 OpenAPI 3.0의 마이너 업데이트로, JSON 스키마와의 호환성을 개선하는 데 중점을 두고 있습니다. OpenAPI 3.1은 JSON 스키마의 최신 초안과 100% 호환됩니다. OpenAPI 2.x에서 생성된 문서는 OpenAPI 3.x와 완벽하게 호환되지 않습니다. 그러나 OpenAPI 2.x 문서를 OpenAPI 3.x로 마이그레이션하는 것은 가능합니다. 이를 위해서는 일부 변경 사항을 수동으로 적용해야 할 수도 있습니다.

References

(1) OpenAPI 란? (feat. Swagger) - 호롤리한 하루 (2) What's the Difference Between OpenAPI Types 2.0, 3.0, and 3.1? - Stoplight (3) Migrating from OpenAPI 3.0 to 3.1.0 - OpenAPI Initiative (4) OpenAPI Specification 3.1.0 Released - OpenAPI Initiative

Open API의 관리 주체

OpenAPI 사양은 OpenAPI Initiative (OAI)이라는 비영리 조직에 의해 관리됩니다. OAI는 API 개발 및 사용을 지원하는 목적으로 설립된 커뮤니티 주도 조직으로, 여러 기업과 개인이 참여하고 있습니다. SwaggerHub는 SmartBear라는 회사에서 개발한 서비스입니다. SmartBear는 Swagger UI를 개발한 회사로, OpenAPI Initiative의 창립 회원 중 하나입니다. SwaggerHub는 OpenAPI 사양을 지원하는 API 디자인 및 문서화 플랫폼으로, Swagger UI와 같은 도구와 함께 사용할 수 있습니다. OpenAPI 사양을 기반으로 코드를 생성하는 도구는 다양한 백엔드 언어와 프레임워크에서 제공됩니다. 이러한 도구를 사용하면 OpenAPI 문서에서 API 엔드포인트와 데이터 모델을 자동으로 생성할 수 있습니다. 예를 들어, Swagger Codegen은 OpenAPI 사양을 기반으로 서버 스텁 및 클라이언트 SDK를 생성하는 오픈 소스 도구입니다. Swagger Codegen은 다양한 언어와 프레임워크를 지원하며, Java, Python, Ruby, Node.js, Go 등의 언어로 코드를 생성할 수 있습니다. 또한 일부 백엔드 프레임워크 (예: Node.js의 Express)에서는 Swagger와 같은 도구를 사용하여 코드 주석을 기반으로 자동으로 API 문서를 생성하는 기능도 제공합니다. 이러한 기능을 사용하면 코드 주석에 기반하여 자동으로 API 문서를 생성할 수 있습니다. 이 외에도 OpenAPI Generator, NSwag 등의 도구가 있으며, 각각의 도구는 다양한 언어와 프레임워크를 지원합니다.

RAML

OpenAPI와 유사한 목적을 가진 다른 API 설명 언어로는 RAML (RESTful API Modeling Language)과 API Blueprint가 있습니다. RAML은 YAML을 기반으로 한 API 설명 언어로, RESTful API를 모델링하는 데 사용됩니다. RAML은 API의 리소스, 메서드, 매개 변수, 응답 등을 정의할 수 있으며, API 문서를 자동으로 생성할 수 있습니다. API Blueprint는 Markdown을 기반으로 한 API 설명 언어로, RESTful API를 문서화하는 데 사용됩니다. API Blueprint는 API의 리소스, 메서드, 매개 변수, 응답 등을 정의할 수 있으며, API 문서를 자동으로 생성할 수 있습니다. 이러한 도구들은 OpenAPI와 유사한 목적을 가지고 있지만, 각각의 도구는 고유한 문법과 기능을 가지고 있습니다. 따라서 사용자의 필요에 따라 적절한 도구를 선택하여 사용할 수 있습니다.

· 약 27분
Dongmin Yu

What AI Can do in Cybersecurity

인공지능은 사이버 보안에서 점점 더 중요한 역할을 하고 있습니다. 그러나 해커들도 이점을 얻을 수 있습니다. 조직은 최신 AI 기반 도구를 활용하여 위협을 더 잘 감지하고 시스템과 데이터 자원을 보호할 수 있습니다. 그러나 사이버 범죄자들도 더 정교한 공격을 할 수 있는 기술을 사용할 수 있습니다. AI 기반 보안 제품의 글로벌 시장은 2030년까지 1338억 달러에 이를 것으로 추정되며, 작년에는 149억 달러였습니다. 해커들도 이점을 얻고 있습니다: AI가 생성한 피싱 이메일은 수동으로 작성된 피싱 이메일보다 열리는 비율이 높습니다. AI는 사이버 보안에서 많은 이점과 응용 분야를 제공합니다. 빠르게 진화하는 사이버 공격과 빠르게 증가하는 장치들이 오늘날 발생하고 있기 때문에, AI와 머신러닝은 사이버 범죄자들과 계속해서 발전하고, 위협 감지를 자동화하며, 전통적인 소프트웨어 기반 또는 수동 기술보다 효과적으로 응답할 수 있도록 도와줍니다. AI는 새로운 위협 감지에 사용될 수 있습니다. 전통적인 소프트웨어 시스템은 매주 생성되는 새로운 악성 코드의 양에 따라 유지하기 어렵기 때문에 AI가 정말 도움이 될 수 있는 영역입니다. 복잡한 알고리즘을 사용하여 AI 시스템은 악성 코드 감지, 패턴 인식을 실행하고, 시스템에 들어가기 전에 맬웨어나 랜섬웨어 공격의 아주 작은 행위도 감지하는데 훈련되어 있습니다. AI는 자연어 처리를 통해 우수한 예측 지능을 제공하며 스스로 사이버 위협에 대한 기사, 뉴스 및 연구를 스크랩하여 데이터를 정리합니다. 이를 통해 새로운 이상 현상, 사이버 공격 및 예방 전략에 대한 정보를 제공할 수 있습니다. 물론 AI의 발전과 함께 보안 분야에서도 계속해서 새로운 방법들이 연구되고 개발되고 있습니다. 현재까지의 연구 결과와 의견들은 AI가 사이버 보안에서 중요한 역할을 하고 있으며 앞으로도 계속해서 중요한 역할을 할 것으로 예상됩니다.

References

(1) AI has bigger role in cybersecurity, but hackers may benefit the most (2) Artificial Intelligence in Cybersecurity | IEEE CS - IEEE Computer Society (3) The Impact of AI on Cybersecurity | IEEE Computer Society

AI는 사이버 보안에서 미래에도 중요한 역할을 할 것으로 예상됩니다. AI는 조직이 공격자들이 움직일 수 있는 것보다 빠르게 응답할 뿐만 아니라 이들의 움직임을 예측하고 미리 행동할 수 있도록 지원하는 힘의 배가 될 수 있습니다. AI 기술과 도구는 초기 단계의 채택에 있습니다. 글로벌 시장은 2021년부터 2025년 사이에 190억 달러가 증가할 것으로 예상됩니다. AI의 적응적 학습 및 새로운 패턴 감지 능력은 검출, 격리 및 응답을 가속화하여 SOC 분석가의 부담을 줄이고 더 적극적으로 행동할 수 있도록 도와줍니다. 추가로, 조직이 결국 AI 주도의 사이버 범죄의 발전에 대비할 수 있도록 도와줍니다. AI는 사이버 보안 분야에서 계속해서 중요한 역할을 할 것으로 예상되며, 새로운 위협에 대응하고 조직의 보안을 강화하는데 도움이 될 것입니다.

References

(1) The future of cybersecurity and AI | Deloitte Insights (2) Artificial Intelligence: The Future Of Cybersecurity? - Forbes (3) The Role of Artificial Intelligence in Cybersecurity (4) Understanding the role of AI in Cyber Security: current applications and future trends

패스워드는 오랫동안 불안전하다고 알려져 왔습니다. 악의적인 행위자들이 패스워드 기반의 보안 프로토콜을 자주 우회하기 때문입니다. 그러나 보안 문제만이 패스워드 기반 인증의 유일한 단점은 아닙니다. 관리하는데 비용이 많이 들고 부담스럽습니다. 사용자들은 자주 패스워드를 잊어버리기 때문에 패스워드를 재설정해야 하며, 이로 인해 UX가 나빠집니다. MFA (Multi-Factor Authentication)와 생체 인증과 같은 새로운 기술들이 이들을 대체할 수 있습니다. 예를 들어, Microsoft는 최근에 Microsoft 계정에서 패스워드를 완전히 제거할 수 있는 기능을 출시했습니다. Microsoft Authenticator 앱, Windows Hello, 보안 키 또는 전화 또는 이메일로 전송된 인증 코드를 사용하여 Microsoft Outlook, Microsoft OneDrive, Microsoft Family Safety 등 좋아하는 앱과 서비스에 로그인할 수 있습니다. 생체 인증은 MFA의 미래입니다. 패스워드를 훔치기는 쉽습니다. 그러나 MFA에 사용되는 전화기나 다른 장치를 훔치기는 쉽지 않습니다. 스마트폰 외에도 MFA에 사용되는 장치에는 보안 키가 포함됩니다. 보안 키는 사용자가 컴퓨터의 USB 포트에 연결할 수 있는 휴대용 장치입니다. 따라서 패스워드 기반의 인증 방식이 완벽하게 사라지고 대체될 가능성이 있습니다. MFA와 생체 인증과 같은 새로운 기술들이 이들을 대체할 수 있습니다.

References

(1) Biometric Authentication: The Key to a Passwordless Future? | Blog - Juniper Research (2) The passwordless future with Microsoft - Microsoft Security Blog (3) MFA and Biometrics as a Remedy for the Weakness of Passwords (4) The Future of Multi-Factor Biometric Authentication | PHONEXIA

MFA나 보안 키, 생체 인증과 같은 기술들은 관리나 개발 비용이 만만치 않습니다. 그러나 이러한 기술들은 사용자 경험을 향상시키기 위해 계속 발전하고 있습니다. 예를 들어, Microsoft는 최근에 패스워드 없이 Microsoft 계정에 로그인할 수 있는 기능을 출시했습니다. 이를 통해 사용자는 Microsoft Authenticator 앱, Windows Hello, 보안 키 또는 전화 또는 이메일로 전송된 인증 코드를 사용하여 Microsoft Outlook, Microsoft OneDrive, Microsoft Family Safety 등 좋아하는 앱과 서비스에 로그인할 수 있습니다. 또한, 일부 시스템에서는 관리자가 패스워드 없는 인증을 구현하고 사용자에게 단일 로그인 기능을 제공할 수 있습니다. 사용자 경험을 향상시키기 위한 새로운 아이디어들이 계속 제안되고 있습니다. 예를 들어, 사용자 인증 및 액세스 관리 솔루션의 Top 10 목록에서는 MFA 옵션, 적응형 인증, 유연성, 가시성 수준, 사용자 경험 및 가격 등의 주요 기능을 비교합니다. 따라서 MFA나 보안 키, 생체 인증과 같은 기술들은 사용자 경험을 향상시키기 위해 계속 발전하고 있습니다.

References

(1) 6 Steps to Improve User Experience: Embracing Your End-Users - OutSystems (2) The Top 10 User Authentication And Access Management Solutions In 2023 - Expert Insights (3) Authentication and Digital Media: Improving the User Experience - Parks Associates

사용자 경험을 향상시키기 위한 새로운 아이디어들은 계속 제안되고 있습니다. 예를 들어, OutSystems의 블로그 게시물에서는 사용자 경험을 향상시키기 위한 6가지 팁을 제공합니다.

  1. 사용자 중심의 제품 개발: 제품을 인간을 위해, 특히 그 제품을 사용할 인간을 위해 개발해야 합니다.
  2. 사용자와의 대화: 사용자와의 대화를 통해 그들이 원하는 것이 무엇인지 파악하고 그들의 기대를 충족시키세요.
  3. 사용자 테스트: 사용자 테스트를 통해 제품이 얼마나 직관적인지, 사용하기 쉬운지, 그리고 사용자가 제품을 사용하여 원하는 작업을 수행하는 데 얼마나 쉬운지 확인하세요.
  4. 디자인: 디자인은 제품의 맛을 더하는 양념이 아니라 레시피의 시작부터 필요한 밀가루입니다.
  5. 성능: 성능은 사용자 경험에 중요한 요소입니다. 제품이 빠르게 로드되고 반응성이 좋아야 합니다.
  6. 지속적인 개선: 사용자 경험은 한 번에 완성되는 것이 아닙니다. 지속적으로 개선해야 합니다. (2) 6 Steps to Improve User Experience: Embracing Your End-Users - OutSystems (3) Authentication and Digital Media: Improving the User Experience - Parks Associates 인공지능과 코드 생성 기능이 빠르게 성장함에 따라 프로그래밍의 미래에 대한 다양한 의견이 있습니다. 일부 사람들은 인공지능이 프로그래밍을 더 쉽게 접근할 수 있는 범용적인 기초 능력으로 만들어 줄 것이라고 생각합니다. 예를 들어, GitHub Copilot와 같은 도구는 개발자들이 코드를 자동으로 생성하고 완성하는 데 도움을 줍니다. 다른 사람들은 인공지능이 프로그래밍을 더 어렵게 만들 수도 있다고 생각합니다. 예를 들어, 인공지능이 생성한 코드는 읽기 어렵고 이해하기 어려울 수 있습니다. 그러나 인공지능과 코드 생성 기능의 발전은 프로그래밍의 미래에 큰 영향을 미칠 것으로 예상됩니다. 예를 들어, GitHub Copilot X는 채팅 및 음성 인터페이스를 지원하고 pull request를 지원하며 문서에서 질문에 답변하고 OpenAI의 GPT-4를 채택하여 개발자 경험을 개인화하는 등의 기능을 제공합니다. 따라서 인공지능과 코드 생성 기능의 발전은 프로그래밍의 미래에 큰 영향을 미칠 것으로 예상됩니다.

References

(1) GitHub Copilot X: The AI-powered developer experience (2) 5 AI Tools That Can Generate Code To Help Programmers - Forbes (3) What is the Future of Coding? (4) GitHub Copilot: AI-based Code Generation and the Future of Software Engineering ...

GitHub Copilot의 런칭 시기에, 일부 사람들은 코드가 기계학습에 이용되는 것에 대해 우려를 표했습니다. GitHub Copilot는 클라우드 기반 서비스이며 GitHub Copilot 서버와 지속적인 통신이 필요합니다. 이러한 불투명한 아키텍처는 개별 키 입력의 텔레메트리 및 데이터 마이닝에 대한 우려를 촉발했습니다. GitHub Copilot의 개발자들은 이러한 우려에 대해 다양한 조치를 취하고 있습니다. 예를 들어, GitHub Copilot의 FAQ에서는 "GitHub Copilot는 코드 합성기이며 검색 엔진이 아닙니다"라고 언급하고 있습니다. 그리고 "제안된 코드의 대부분은 고유하게 생성되며 이전에 본 적이 없습니다"라고 언급하고 있습니다. 따라서 GitHub Copilot와 같은 프로그래밍 AI들은 사용자의 개인 정보 보호와 관련된 우려에 대해 다양한 조치를 취하고 있습니다.

References

(1) GitHub Copilot - Wikipedia (2) GitHub Copilot auto-coder snags emerge, from seemingly spilled secrets to bad code ... (3) Is Github Copilot safe privacy wise? · community · Discussion #7163

코드 제너레이터는 개발자들이 제공하는 코드의 포인트, 특히 가독성이나 함의성에 대해서 많은 힌트를 받고 있습니다. 그러나 이러한 현상에 불안감과 불쾌함을 가진 사람들에게 대안적으로 제안되는 엄격한 라이센스나 robots.txt와 같은 기계에 의해 수집되는 것에 대한 명시적 동의 및 거부 수단이 있습니다. 예를 들어, GitHub Copilot의 경우 사용자가 자신의 코드를 공유하고 싶지 않다면 해당 코드를 비공개 저장소에 저장하거나 robots.txt 파일을 사용하여 검색 엔진이 해당 코드를 수집하지 못하도록 할 수 있습니다. 또한, 일부 오픈 소스 라이센스는 코드가 재사용될 때 원작자의 동의가 필요하도록 규정하고 있습니다. 오픈 소스 라이센스는 크게 두 가지로 분류됩니다: 퍼미시브 라이센스와 카피레프트 라이센스. 퍼미시브 라이센스는 MIT 라이센스와 BSD 라이센스와 같은 라이센스로, 사용자가 소스 코드를 자유롭게 사용, 수정 및 재배포할 수 있도록 허용합니다. 이러한 라이센스는 일반적으로 소스 코드의 사용에 대한 제한이 적습니다. 카피레프트 라이센스는 GNU 일반 공개 라이센스(GPL)와 같은 라이센스로, 사용자가 소스 코드를 자유롭게 사용, 수정 및 재배포할 수 있도록 허용합니다. 그러나 이러한 라이센스는 수정된 소스 코드를 재배포할 때 원본 소스 코드와 동일한 라이센스 조건을 적용해야 한다는 조건을 부과합니다. 따라서 타인이 공유한 코드를 사용한 내 코드에서 해당 코드의 라이센스를 명시하는 것만으로 완벽한 명시적인 법적 방어조치가 이뤄질 수 있습니다. 하지만 이러한 방어조치가 완벽하게 작동하려면 사용자가 해당 코드의 라이센스 조건을 준수해야 합니다.

코드의 표절 검사는 연구 논문의 표절 검사와 유사한 방식으로 이루어질 수 있습니다. 코드에서 사용된 텍스트나 개념의 자체적 작성 비율은 중요한 요소가 될 수 있습니다. 예를 들어, 일부 오픈 소스 라이센스는 코드가 재사용될 때 원작자의 동의가 필요하도록 규정하고 있습니다. 따라서 타인이 작성한 코드를 사용할 때는 해당 코드의 라이센스 조건을 준수해야 합니다. 그러나 코드의 표절 검사는 연구 논문의 표절 검사와는 다른 점이 있습니다. 예를 들어, 코드는 일반적으로 기능적인 목적을 가지고 있으며, 이러한 기능을 구현하는 방법은 다양할 수 있습니다. 따라서 코드의 표절 검사는 연구 논문의 표절 검사와는 다른 방식으로 이루어질 수 있습니다. 결국 중요한 것은 코드가 어떻게 작성되었는지가 아니라 코드가 어떤 기능을 수행하는지와 해당 코드가 어떤 라이센스 조건을 준수하는지입니다. POSA와 같은 서비스들은 개발자가 자신의 프로젝트에서 사용한 의존성 패키지들의 라이센스를 체크하고 분류하여 개발자에게 알려주는 일을 합니다. 이러한 서비스들은 개발자가 자신의 프로젝트에서 사용한 의존성 패키지들이 어떤 라이센스 조건을 가지고 있는지 파악하는 데 도움을 줍니다.

의존성 패키지들의 라이센스 조건을 파악하는 것은 중요합니다. 일부 라이센스는 코드가 재사용될 때 원작자의 동의가 필요하도록 규정하고 있습니다. 따라서 개발자가 자신의 프로젝트에서 사용한 의존성 패키지들이 어떤 라이센스 조건을 가지고 있는지 파악하는 것은 법적 문제를 예방하는 데 중요합니다. 따라서 POSA와 같은 서비스들이 제공하는 수치들은 개발자가 자신의 프로젝트에서 사용한 의존성 패키지들이 어떤 라이센스 조건을 가지고 있는지 파악하는 데 중요한 의미를 가집니다. POSA와 같은 서비스들이 제공하는 수치나 추상적인 엄밀함의 정도는 개발자의 결정을 제한하는 것이 아니라 개발자가 자신의 프로젝트에서 사용한 의존성 패키지들이 어떤 라이센스 조건을 가지고 있는지 파악하는 데 도움을 줍니다. 일부 라이센스는 코드가 재사용될 때 원작자의 동의가 필요하도록 규정하고 있습니다. 따라서 개발자가 타인의 코드에서 일부를 인용하거나 응용한 프로젝트를 개발할 때는 해당 코드의 라이센스 조건을 준수해야 합니다. POSA와 같은 서비스들은 개발자가 자신의 프로젝트에서 사용한 의존성 패키지들이 어떤 라이센스 조건을 가지고 있는지 파악하는 데 도움을 줍니다. 이러한 정보는 개발자가 자신의 프로젝트에서 사용한 의존성 패키지들이 어떤 라이센스 조건을 가지고 있는지 파악하고 이에 따라 적절한 결정을 내리는 데 중요합니다. 따라서 POSA와 같은 서비스들이 제공하는 수치나 추상적인 엄밀함의 정도는 개발자의 결정을 제한하는 것이 아니라 개발자가 자신의 프로젝트에서 사용한 의존성 패키지들이 어떤 라이센스 조건을 가지고 있는지 파악하는 데 도움을 줍니다.

프로그래밍 코드에서 일반 연구자들이 사용하는 사이테이션과 멘션과 같은 기술적 방법으로 내가 만든 코드가 이후에 재사용되거나 설치되는 등의 백링크 추적은 구조 상 구현하기 어려울 수 있습니다. 코드는 일반적으로 기능적인 목적을 가지고 있으며, 이러한 기능을 구현하는 방법은 다양할 수 있습니다. 따라서 코드의 재사용 여부를 추적하는 것은 어려울 수 있습니다. 그러나 일부 오픈 소스 라이센스는 코드가 재사용될 때 원작자의 동의가 필요하도록 규정하고 있습니다. 따라서 개발자가 타인의 코드를 사용할 때는 해당 코드의 라이센스 조건을 준수해야 합니다. 결국 중요한 것은 코드가 어떻게 작성되었는지가 아니라 코드가 어떤 기능을 수행하는지와 해당 코드가 어떤 라이센스 조건을 준수하는지입니다.

코드의 재사용 여부를 추적하는 것이 어려운 이유는 다음과 같습니다:

  1. 코드의 복잡성: 코드는 일반적으로 매우 복잡하며 다양한 구성 요소와 상호 작용합니다. 따라서 코드의 재사용 여부를 추적하는 것은 어려울 수 있습니다.
  2. 코드의 변경 가능성: 코드는 지속적으로 변경될 수 있습니다. 따라서 코드의 재사용 여부를 추적하는 것은 어려울 수 있습니다.
  3. 코드의 분산: 코드는 일반적으로 여러 위치에 분산되어 있습니다. 따라서 코드의 재사용 여부를 추적하는 것은 어려울 수 있습니다.
  4. 기술적 제약: 현재 기술 수준에서는 코드의 재사용 여부를 완벽하게 추적하는 것이 어려울 수 있습니다.

코드의 재사용 여부를 추적하는 것이 중요한 이유는 다음과 같습니다:

  1. 법적 문제 예방: 일부 라이센스는 코드가 재사용될 때 원작자의 동의가 필요하도록 규정하고 있습니다. 따라서 코드의 재사용 여부를 추적하는 것은 법적 문제를 예방하는 데 중요합니다.
  2. 코드 품질 향상: 코드의 재사용 여부를 추적하는 것은 코드 품질을 향상시키는 데 도움이 될 수 있습니다. 예를 들어, 재사용 가능한 코드는 일반적으로 높은 품질의 코드입니다.
  3. 개발 비용 절감: 코드의 재사용 여부를 추적하는 것은 개발 비용을 절감하는 데 도움이 될 수 있습니다. 예를 들어, 재사용 가능한 코드를 사용하면 개발 시간을 단축하고 개발 비용을 절감할 수 있습니다.

· 약 32분
Dongmin Yu

**매우 흥미로운 제안인 레코드 & 튜플이 **TC39**에서 2단계에 도달했습니다.

레코드와 튜플은 불변 데이터 구조를 자바스크립트로 가져옵니다.

하지만 React매우 흥미로운 동등성(equality) 속성을 간과해서는 안 됩니다.

React 버그의 많은 범주는 불안정한 객체 아이덴티티와 관련이 있습니다:

  • 성능: 피할 수 있는 재렌더링
  • 동작: 쓸모없는 이펙트 재실행, 무한 루프
  • API 표면: 안정적인 객체 아이덴티티가 중요한 경우 표현할 수 없음

레코드와 튜플의 기초를 설명하고, 이를 통해 어떻게 현실의 React 문제를 해결할 수 있는지 설명하겠습니다.

레코드와 튜플 101

이 글은 React의 레코드와 튜플에 관한 글입니다. 여기서는 기본적인 내용만 다루겠습니다.

레코드와 튜플은 # 접두사가 붙은 일반적인 객체와 배열처럼 보입니다.

const record = #{a: 1, b: 2};
record.a;
// 1

const updatedRecord = #{...record, b: 3};
// #{a: 1, b: 3};
const tuple = #[1, 5, 2, 3, 4];
tuple[1];
// 5

const filteredTuple = tuple.filter(num => num > 2)
// #[5, 3, 4];

They are deeply immutable by default.

const record = #{a: 1, b: 2};
record.b = 3;
// throws TypeError

이들은 **"복합 프리미티브"**로 볼 수 있으며 값으로 비교할 수 있습니다.

매우 중요: 두 개의 완전히 동일한 레코드는 ===와 함께 true항상 반환합니다.

const record = JSON.parseImmutable("{a: 1, b: [2, 3]}");
// #{a: 1, b: #[2, 3]}
JSON.stringify(record);
// '{a: 1, b: [2, 3]}'

어떻게든 레코드의 아이덴티티는 일반 JS 프리미티브와 마찬가지로 실제 값이라고 생각할 수 있습니다.

이 프로퍼티는 앞으로 살펴볼 것처럼 React에 심각한 영향을 미칩니다.

JSON과 상호 운용이 가능합니다:

const record1 = #{
  a: {
    regular: 'object'
  },
};
// throws TypeError, because a record can't contain an object

const record2 = #{
  b: new Date(),
};
// throws TypeError, because a record can't contain a Date

const record3 = #{
  c: new MyClass(),
};
// throws TypeError, because a record can't contain a class

const record4 = #{
  d: function () {
    alert('forbidden');
  },
};
// throws TypeError, because a record can't contain a function

다른 레코드와 튜플 또는 프리미티브 값만 포함할 수 있습니다.

참고: 심볼을 WeakMap 키(별도 제안)로 사용하고 레코드의 심볼을 참조하여 레코드에 이러한 변경 가능한 값을 추가할 수 있습니다.

더 알고 싶으신가요? 제안을 직접 읽어보시거나 Axel Rauschmayer의 기사를 읽어보세요.

React를 위한 레코드와 튜플

React 개발자들은 이제 **불변성(immutability)**에 익숙해졌습니다.

어떤 상태를 불변의 방식으로 업데이트할 때마다 새로운 객체 아이덴티티를 생성합니다.

안타깝게도 이 불변성 모델은 React 애플리케이션에 완전히 새로운 종류의 버그와 성능 문제를 일으켰습니다. 때로는 컴포넌트가 올바르고 성능 좋은 방식으로 작동하지만, 이는 소품이 시간이 지나도 최대한 아이덴티티를 보존한다는 가정 하에서만 가능합니다.

저는 레코드와 튜플을 객체 아이덴티티를 보다 "안정적으로"** 만드는 편리한 방법으로 생각하고 싶습니다.

실제 사용 사례를 통해 이 제안이 React 코드에 어떤 영향을 미치는지 살펴봅시다.

참고: React를 실행할 수 있는 레코드 & 튜플 플레이그라운드가 있습니다.

불변성

불변성을 적용하는 것은 재귀적인 Object.freeze() 호출로 달성할 수 있습니다.

하지만 실제로는 업데이트할 때마다 Object.freeze()를 적용하는 것이 편리하지 않기 때문에 불변성 모델을 너무 엄격하게 적용하지 않고 사용하는 경우가 많습니다. 하지만 상태를 직접 변경하는 것은 초보 React 개발자들이 흔히 저지르는 실수입니다.

레코드 및 튜플 제안은 불변성을 강제하고 일반적인 상태 변경 실수를 방지합니다:

const Hello = ({ profile }) => {
  // prop mutation: throws TypeError
  profile.name = 'Sebastien updated';
  return <p>Hello {profile.name}</p>;
};
function App() {
  const [profile, setProfile] = React.useState(#{
    name: 'Sebastien',
  });
  // state mutation: throws TypeError
  profile.name = 'Sebastien updated';
  return <Hello profile={profile} />;
}

불변 업데이트

React에서 불변 상태 업데이트를 수행하는 방법에는 여러 가지가 있습니다: vanilla JS, Lodash set, ImmerJS, ImmutableJS...

레코드와 튜플은 ES6 객체와 배열에서 사용하는 것과 동일한 종류의 불변 업데이트 패턴을 지원합니다:

const initialState = #{
  user: #{
    firstName: "Sebastien",
    lastName: "Lorber"
  }
  company: #{
    name: "Lambda Scale",
  }
};
const updatedState = {
  ...initialState,
  company: {
    ...initialState.company,
    name: 'Freelance',
  },
};

지금까지는 중첩된 속성을 처리하는 단순성과 일반 JS 코드와의 상호 운용성 덕분에 ImmerJS가 불변성 업데이트 전쟁에서 승리했습니다.

Immer가 레코드 및 튜플과 어떻게 작동할 수 있는지는 아직 명확하지 않지만, 제안서 작성자들이 연구하고 있는 부분입니다.

별개로 Michael Weststrate는 관련된 제안이 [레코드 및 튜플에 ImmerJS를 **불필요하게 만들 수 있다]고 강조했습니다:

const initialState = #{
  counters: #[
    #{ name: "Counter 1", value: 1 },
    #{ name: "Counter 2", value: 0 },
    #{ name: "Counter 3", value: 123 },
  ],
  metadata: #{
    lastUpdate: 1584382969000,
  },
};
// Vanilla JS updates
// using deep-path-properties-for-record proposal
const updatedState = #{
  ...initialState,
  counters[0].value: 2,
  counters[1].value: 1,
  metadata.lastUpdate: 1584383011300,
};

useMemo

useMemo()는 비용이 많이 드는 계산을 메모하는 것 외에도 트리의 더 깊은 곳에서 쓸모없는 계산, 재렌더링 또는 이펙트 실행을 트리거할 수 있는 새 객체 ID 생성을 피하는 데에도 유용합니다.

여러 필터가 있는 UI가 있고 백엔드에서 일부 데이터를 가져오고자 하는 사용 사례를 고려해 보겠습니다.

기존 React 코드베이스에는 다음과 같은 코드가 포함될 수 있습니다:

// Don't change apiFilters object identity,
// unless one of the filter changes
// Not doing this is likely to trigger a new fetch
// on each render
const apiFilters = useMemo(
  () => ({ userFilter, companyFilter }),
  [userFilter, companyFilter],
);
const { apiData, loading } = useApiData(apiFilters);

레코드 및 튜플을 사용하면 간단히 이렇게 됩니다:

const {apiData,loading} = useApiData(#{ userFilter, companyFilter })

useEffect

API 필터 사용 사례를 계속 살펴보겠습니다:

const apiFilters = { userFilter, companyFilter };
useEffect(() => {
  fetchApiData(apiFilters).then(setApiDataInState);
}, [apiFilters]);

안타깝게도 이 컴포넌트가 리렌더링할 때마다 apiFilters 객체의 ID가 변경되기 때문에 가져오기 효과가 재실행됩니다. setApiDataInState는 리렌더링을 트리거하고, 결국 무한한 불러오기/렌더링 루프가 발생하게 됩니다.

이 실수는 React 개발자들 사이에서 매우 흔한 실수이며, useEffect 무한 루프에 대한 구글 검색 결과만 수천 개가 넘습니다.

Kent C Dodds는 개발 과정에서 무한 루프를 끊는 도구를 만들기도 했습니다.

매우 일반적인 해결책: 이펙트의 콜백에 직접 apiFilters를 생성합니다:

useEffect(() => {
  const apiFilters = { userFilter, companyFilter };
  fetchApiData(apiFilters).then(setApiDataInState);
}, [userFilter, companyFilter]);

트위터에서 찾을 수 있는 또 다른 창의적인 솔루션(성능은 좋지 않음)입니다:

const apiFiltersString = JSON.stringify({
  userFilter,
  companyFilter,
});
useEffect(() => {
  fetchApiData(JSON.parse(apiFiltersString)).then(setApiDataInState);
}, [apiFiltersString]);

그 중 가장 제 마음에 드는 기능입니다:

// We already saw this somewhere, right? :p
const apiFilters = useMemo(
  () => ({ userFilter, companyFilter }),
  [userFilter, companyFilter],
);
useEffect(() => {
  fetchApiData(apiFilters).then(setApiDataInState);
}, [apiFilters]);

이 문제를 해결하는 멋진 방법이 많이 있지만 필터 수가 증가함에 따라 모두 귀찮아지는 경향이 있습니다.

use-deep-compare-effect(Kent C Dodds)가 덜 성가시겠지만, 리렌더링할 때마다 딥 이퀄리티를 실행하는 데는 지불하고 싶지 않은 비용이 듭니다.

레코드 및 튜플보다 훨씬 장황하고 관용적이지 않습니다:

const apiFilters = #{ userFilter, companyFilter };
useEffect(() => {
  fetchApiData(apiFilters).then(setApiDataInState);
}, [apiFilters]);

Props와 React.memo

프로포즈에서 객체 아이덴티티를 보존하는 것은 React 퍼포먼스에도 매우 유용합니다.

또 다른 매우 흔한 성능 실수는 렌더링에서 새로운 객체 아이덴티티를 생성하는 것입니다.

const Parent = () => {
  useRerenderEverySeconds();
  return (
    <ExpensiveChild
      // someData props object is created "on the fly"
      someData={{ attr1: "abc", attr2: "def" }}
    />
  );
};
const ExpensiveChild = React.memo(({ someData }) => {
  return <div>{expensiveRender(someData)}</div>;
});

대부분의 경우 이것은 문제가 되지 않습니다. React는 충분히 빠르니까요.

하지만 때때로 앱을 최적화하려는 경우, 이 새로운 객체 생성은 React.memo()를 쓸모없게 만듭니다. 최악의 경우, 실제로 애플리케이션을 조금 느리게 만듭니다(항상 거짓을 반환하는 추가적인 얕은 동등성 검사를 실행해야 하기 때문입니다).

클라이언트 코드베이스에서 자주 볼 수 있는 또 다른 패턴입니다:

const currentUser = { name: "Sebastien" };
const currentCompany = { name: "Lambda Scale" };
const AppProvider = () => {
  useRerenderEverySeconds();
  return (
    <MyAppContext.Provider
      // the value prop object is created "on the fly"
      value={{ currentUser, currentCompany }}
    />
  );
};

currentUser 또는 currentCompany업데이트되지 않는다는 사실에도 불구하고, 이 공급자가 리렌더링할 때마다 컨텍스트 값이 변경되어 모든 컨텍스트 구독자의 리렌더링을 트리거합니다.

이러한 모든 문제는 메모화를 통해 해결할 수 있습니다:

const someData = useMemo(() => ({ attr1: "abc", attr2: "def" }), []);
<ExpensiveChild someData={someData} />;
const contextValue = useMemo(
  () => ({ currentUser, currentCompany }),
  [currentUser, currentCompany],
);
<MyAppContext.Provider value={contextValue} />;

레코드 및 튜플을 사용하면 성능이 뛰어난 코드를 작성하는 것이 관용적입니다:

<ExpensiveChild someData={#{ attr1: 'abc', attr2: 'def' }} />;
<MyAppContext.Provider value={#{ currentUser, currentCompany }} />;

불러오기 및 다시 불러오기

React에서 데이터를 불러오는 방법은 여러 가지가 있습니다: useEffect, HOC, Render props, Redux, SWR, React-Query, Apollo, Relay, Urql, ...

대부분의 경우, 우리는 요청을 백엔드에 전달하고 JSON 데이터를 반환받습니다.

이 섹션을 설명하기 위해 제가 만든 아주 간단한 불러오기 라이브러리인 react-async-hook을 사용하겠지만, 다른 라이브러리에도 적용될 수 있습니다.

API 데이터를 가져오는 고전적인 비동기 함수를 고려해 봅시다:

const fetchUserAndCompany = async () => {
  const response = await fetch(`https://myBackend.com/userAndCompany`);
  return response.json();
};

이 앱은 데이터를 가져오고, 시간이 지나도 이 데이터가 오래된 데이터가 아닌 '최신' 상태로 유지되도록 합니다:

const App = ({ id }) => {
  const { result, refetch } = useAsync(fetchUserAndCompany, []);
  // We try very hard to not display stale data to the user!
  useInterval(refetch, 10000);
  useOnReconnect(refetch);
  useOnNavigate(refetch);
  if (!result) {
    return null;
  }
  return (
    <div>
      <User user={result.user} />
      <Company company={result.company} />
    </div>
  );
};
const User = React.memo(({ user }) => {
  return <div>{user.name}</div>;
});
const Company = React.memo(({ company }) => {
  return <div>{company.name}</div>;
});

문제: 성능상의 이유로 React.memo를 사용했지만, 다시 불러올 때마다 가져온 데이터가 이전과 동일(완전히 동일한 페이로드)임에도 불구하고 새로운 아이덴티티를 가진 새로운 JS 객체가 생성되고 모든 것이 리렌더링됩니다.

이 시나리오를 상상해 봅시다:

  • "Stale-While-Revalidate" 패턴(캐시된/부실 데이터를 먼저 표시한 다음 백그라운드에서 데이터를 새로 고침)을 사용합니다.
  • 페이지가 복잡하고 렌더링 집약적이며 많은 백엔드 데이터가 표시됩니다.

캐시된 데이터로 인해 처음 렌더링하는 데 이미 많은 비용이 드는 페이지로 이동합니다. 1초 후 새로 고쳐진 데이터가 다시 표시됩니다. 캐시된 데이터와 거의 동일함에도 불구하고 모든 것이 리렌더링됩니다. 동시성 모드와 타임 슬라이싱이 없으면 일부 사용자는 수백 밀리초 동안 UI가 멈추는 현상을 경험할 수도 있습니다.

이제 가져오기 함수를 레코드를 반환하도록 변환해 보겠습니다:

const fetchUserAndCompany = async () => {
  const response = await fetch(`https://myBackend.com/userAndCompany`);
  return JSON.parseImmutable(await response.text());
};

우연히도 JSON은 레코드 및 튜플과 호환되며, JSON.parseImmutable을 사용하여 모든 백엔드 응답을 레코드로 변환할 수 있어야 합니다.

참고: 제안 작성자 중 한 명인 Robin Ricard는 새로운 response.immutableJson() 함수를 추진하고 있습니다.

레코드 및 튜플을 사용하면 백엔드에서 동일한 데이터를 반환하면 아무것도 리렌더링하지 않습니다!

또한 응답의 한 부분만 변경된 경우에도 응답의 다른 중첩 객체는 여전히 아이덴티티를 유지합니다. 즉, user.name만 변경된 경우 User 컴포넌트는 리렌더링되지만 Company 컴포넌트는 렌더링되지 않습니다!

"Stale-While-Revalidate"와 같은 패턴이 점점 더 대중화되고 있고 SWR, React-Query, Apollo, Relay와 같은 라이브러리에서 기본으로 제공된다는 점을 고려할 때 이 모든 것이 성능에 미치는 영향을 상상해 보겠습니다.

쿼리 문자열 읽기

검색 UI에서는 쿼리 문자열에 필터의 상태를 보존하는 것이 좋습니다. 그러면 사용자는 링크를 복사/붙여넣거나 페이지를 새로고침하거나 북마크할 수 있습니다.

필터가 1~2개라면 간단하지만, 검색 UI가 복잡해지면(필터가 10개 이상, AND/OR 로직으로 쿼리를 작성하는 기능 등) 쿼리 문자열을 관리하기 위해 좋은 추상화를 사용하는 것이 좋습니다.

저는 개인적으로 qs를 좋아합니다. 중첩 객체를 제대로 처리하는 몇 안 되는 라이브러리 중 하나이기 때문입니다.

const queryStringObject = {
  filters: {
    userName: "Sebastien",
  },
  displayMode: "list",
};
const queryString = qs.stringify(queryStringObject);
const queryStringObject2 = qs.parse(queryString);
assert.deepEqual(queryStringObject, queryStringObject2);
assert(queryStringObject !== queryStringObject2);

queryStringObjectqueryStringObject2는 매우 동일하지만, qs.parse가 새 객체를 생성하기 때문에 더 이상 동일하지 않습니다.

쿼리 문자열 구문 분석을 후크에 통합하고 useMemo() 또는 use-memo-value와 같은 라이브러리를 사용하여 쿼리 문자열 객체를 "안정화"할 수 있습니다.

const useQueryStringObject = () => {
  // Provided by your routing library, like React-Router
  const { search } = useLocation();
  return useMemo(() => qs.parse(search), [search]);
};

이제 트리의 더 깊은 곳을 상상해 보세요:

const { filters } = useQueryStringObject();
useEffect(() => {
  fetchUsers(filters).then(setUsers);
}, [filters]);

이것은 약간 지저분하지만 같은 문제가 반복해서 발생합니다.

queryStringObject 아이덴티티를 보존하기 위해 useMemo()를 사용했음에도 불구하고 원치 않는 fetchUsers 호출이 발생하게 됩니다.

사용자가 (다시 불러오기를 트리거하지 않고 렌더링 로직만 변경해야 하는) displayMode를 업데이트하면 쿼리 문자열이 변경되어 쿼리 문자열이 다시 구문 분석되고 filter 속성에 대한 새로운 객체 ID가 생성되어 원치 않는 useEffect 실행으로 이어집니다.

다시 말하지만, 레코드 및 튜플은 이러한 일이 발생하지 않도록 방지합니다.

// This is a non-performant, but working solution.
// Lib authors should provide a method such as qs.parseRecord(search)
const parseQueryStringAsRecord = (search) => {
  const queryStringObject = qs.parse(search);
  // Note: the Record(obj) conversion function is not recursive
  // There's a recursive conversion method here:
  // https://tc39.es/proposal-record-tuple/cookbook/index.html
  return JSON.parseImmutable(JSON.stringify(queryStringObject));
};
const useQueryStringRecord = () => {
  const { search } = useLocation();
  return useMemo(() => parseQueryStringAsRecord(search), [search]);
};

이제 사용자가 displayMode를 업데이트하더라도 filters 객체는 그 아이덴티티를 유지하며 쓸모없는 리페치를 트리거하지 않습니다.

참고: 레코드 및 튜플 제안이 받아들여지면 qs와 같은 라이브러리에서 qs.parseRecord(search) 메서드를 제공할 가능성이 높습니다.

매우 동등한 JS 변환

컴포넌트에서 다음과 같은 JS 변환을 상상해 보세요:

const AllUsers = [
  { id: 1, name: "Sebastien" },
  { id: 2, name: "John" },
];
const Parent = () => {
  const userIdsToHide = useUserIdsToHide();
  const users = AllUsers.filter((user) => !userIdsToHide.includes(user.id));
  return <UserList users={users} />;
};
const UserList = React.memo(({ users }) => (
  <ul>
    {users.map((user) => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
));

filter는 항상 새 배열 인스턴스를 반환하기 때문에 Parent 컴포넌트가 리렌더링될 때마다 UserList 컴포넌트도 리렌더링됩니다.

이는 userIdsToHide가 비어 있고 AllUsers 아이덴티티가 안정된 경우에도 마찬가지입니다! 이 경우 필터 연산은 실제로 아무것도 필터링하지 않고, 단지 쓸모없는 배열 인스턴스를 새로 생성하여 React.memo 최적화를 선택 해제합니다.

이러한 종류의 변환은 components, reducers, selectors, Redux 등 map 또는 filter와 같은 연산자를 사용하는 React 코드베이스에서 매우 흔합니다.

메모화를 통해 이 문제를 해결할 수 있지만, 레코드와 튜플을 사용하는 것이 더 관용적입니다:

const AllUsers = #[
  #{ id: 1, name: 'Sebastien' },
  #{ id: 2, name: 'John' },
];
const filteredUsers = AllUsers.filter(() => true);
AllUsers === filteredUsers;
// true

React 키로써의 레코드

렌더링할 항목 목록이 있다고 가정해 봅시다:

const list = [
  { country: "FR", localPhoneNumber: "111111" },
  { country: "FR", localPhoneNumber: "222222" },
  { country: "US", localPhoneNumber: "111111" },
];

어떤 키를 사용하시겠습니까?

countrylocalPhoneNumber가 모두 목록에서 독립적으로 고유하지 않다는 점을 고려하면 두 가지 선택지가 있습니다.

배열 인덱스 키:

<>
  {list.map((item, index) => (
    <Item key={`poormans_key_${index}`} item={item} />
  ))}
</>

이 방법은 항상 작동하지만, 특히 목록의 항목이 재정렬된 경우에는 이상적이지 않습니다.

복합 키:

<>
  {list.map((item) => (
    <Item key={`${item.country}_${item.localPhoneNumber}`} item={item} />
  ))}
</>

이 솔루션은 **목록 재순서를 더 잘 처리하지만 **커플/튜플이 고유하다는 것을 확실히 알고 있는 경우에만 가능합니다.

이런 경우에는 레코드를 키로 직접 사용하는 것이 **더 편리하지 않을까요?

const list = #[
  #{ country: 'FR', localPhoneNumber: '111111' },
  #{ country: 'FR', localPhoneNumber: '222222' },
  #{ country: 'US', localPhoneNumber: '111111' },
];
<>
  {list.map((item) => (
    <Item key={item} item={item} />
  ))}
</>

이것은 Morten Barklund제안했습니다.

명시적 API 서페이스

이 TypeScript 컴포넌트를 살펴봅시다:

const UsersPageContent = ({ usersFilters }: { usersFilters: UsersFilters }) => {
  const [users, setUsers] = useState([]);
  // poor-man's fetch
  useEffect(() => {
    fetchUsers(usersFilters).then(setUsers);
  }, [usersFilters]);
  return <Users users={users} />;
};

이 코드는 앞서 살펴본 것처럼 usersFilters 프로퍼티의 안정성에 따라 무한 루프를 생성할 수도 있고 생성하지 않을 수도 있습니다. 이렇게 하면 상위 컴포넌트의 구현자가 문서화하여 명확하게 이해해야 하는 암시적 API 계약이 생성되며, TypeScript를 사용하지만 유형 시스템에는 반영되지 않습니다.

다음은 무한 루프로 이어질 수 있지만 TypeScript에는 이를 방지할 방법이 없습니다:

<UsersPageContent usersFilters={{ nameFilter, ageFilter }} />

레코드 및 튜플을 사용하면 TypeScript가 레코드를 예상하도록 지시할 수 있습니다:

const UsersPageContent = ({
  usersFilters,
}: {
  usersFilters: #{nameFilter: string, ageFilter: string}
}) => {
  const [users, setUsers] = useState([]);
  // poor-man's fetch
  useEffect(() => {
    fetchUsers(usersFilters).then(setUsers);
  }, [usersFilters]);
  return <Users users={users} />;
};

참고: #{nameFilter: string, ageFilter: string}은 제가 직접 고안한 것입니다. 아직 TypeScript 구문이 어떻게 될지 알 수 없습니다.

다음 코드는 TypeScript 컴파일이 실패합니다:

<UsersPageContent usersFilters={{ nameFilter, ageFilter }} />

다음 코드는 컴파일을 문제없이 허용하지만요.

<UsersPageContent
  usersFilters={#{ nameFilter, ageFilter }}
/>

레코드와 튜플을 사용하면 컴파일 시간에 이러한 무한 루프를 방지할 수 있습니다.

컴파일러에 구현이 객체 식별에 민감(또는 값별 비교에 의존)하다는 것을 명시적으로 알릴 수 있는 방법이 있습니다.

참고: readonly은 이 문제를 해결하지 못합니다. 변이를 방지할 수는 있지만 안정적인 아이덴티티를 보장하지 못하기 때문입니다.

직렬화 보장

팀의 개발자가 직렬화할 수 없는 것을 글로벌 앱 상태에 넣지 않도록 하고 싶을 수 있습니다. 이는 상태를 백엔드로 보내거나 localStorage(또는 React-Native 사용자의 경우 AsyncStorage)에 로컬로 유지하려는 경우 중요합니다.

이를 보장하려면 루트 객체가 레코드인지 확인하기만 하면 됩니다. 이렇게 하면 중첩된 레코드와 튜플을 포함한 모든 중첩된 어트리뷰트도 프리미티브가 됩니다.

다음은 시간이 지나도 Redux 저장소를 계속 직렬화할 수 있도록 Redux와 통합한 예시입니다:

if (process.env.NODE_ENV === "development") {
  ReduxStore.subscribe(() => {
    if (typeof ReduxStore.getState() !== "record") {
      throw new Error(
        "Don't put non-serializable things in the Redux store! " +
          "The root Redux state must be a record!",
      );
    }
  });
}

참고: 레코드에 넣을 수 있으면서도 직렬화될 수는 없는 심볼이 있으므로 완벽하게 안정된 보장은 아닙니다.

CSS-in-JS 성능

인기 있는 라이브러리에서 css 프로퍼티를 사용한 CSS-in-JS를 살펴봅시다:

const Component = () => (
  <div
    css={{
      backgroundColor: "hotpink",
    }}
  >
    This has a hotpink background.
  </div>
);

CSS-in-JS 라이브러리는 리렌더링할 때마다 새 CSS 객체를 받습니다.

처음 렌더링할 때 이 객체를 고유한 클래스 이름으로 해시하고 CSS를 삽입합니다. 스타일 객체는 리렌더링할 때마다 다른 아이덴티티를 가지며, CSS-in-JS 라이브러리는 이를 해시하고 또 해시해야 합니다.

const insertedClassNames = new Set();
function handleStyleObject(styleObject) {
  // computeStyleHash re-executes every time
  const className = computeStyleHash(styleObject);
  // only insert the css for this className once
  if (!insertedClassNames.has(className)) {
    insertCSS(className, styleObject);
    insertedClassNames.add(className);
  }
  return className;
}

레코드 및 튜플을 사용하면 이러한 스타일 객체의 ID가 시간이 지나도 보존됩니다.

const Component = () => (
  <div
    css={#{
      backgroundColor: 'hotpink',
    }}
  >
    This has a hotpink background.
  </div>
);

레코드 및 튜플을 맵 키로 사용할 수 있습니다. 이렇게 하면 CSS-in-JS 라이브러리를 더 빠르게 구현할 수 있습니다:

const insertedStyleRecords = new Map();
function handleStyleRecord(styleRecord) {
  let className = insertedStyleRecords.get(styleRecord);
  if (!className) {
    // computeStyleHash is only executed once!
    className = computeStyleHash(styleRecord);
    insertCSS(className, styleRecord);
    insertedStyleRecords.add(styleRecord, className);
  }
  return className;
}

레코드 및 튜플의 성능에 대해서는 아직 알 수 없지만(브라우저 공급업체 구현에 따라 달라질 수 있음), 동등한 객체를 생성한 다음 클래스 이름으로 해싱하는 것보다 빠를 것이라고 말하는 것이 안전할 것 같습니다.

참고: 좋은 Babel 플러그인이 포함된 일부 CSS-in-JS 라이브러리는 컴파일 시 정적 스타일 객체를 상수로 변환할 수 있지만, 동적 스타일에서는 변환하는 데 어려움을 겪을 수 있습니다.

const staticStyleObject = { backgroundColor: "hotpink" };
const Component = () => (
  <div css={staticStyleObject}>This has a hotpink background.</div>
);

결론

많은 React 성능 및 동작 문제는 객체 ID와 관련이 있습니다.

레코드와 튜플은 일종의 "자동 메모화"를 제공함으로써 객체 아이덴티티가 기본적으로 "더 안정적"이 되도록 보장하고, 이러한 React 문제를 더 쉽게 해결할 수 있도록 도와줍니다.

TypeScript를 사용하면 API 표면이 객체 ID에 민감하다는 것을 더 잘 표현할 수 있습니다.

여러분도 저만큼이나 이 제안에 흥미를 느끼셨으면 좋겠습니다!

읽어주셔서 감사합니다!

이 멋진 제안서를 작성하고 제 글을 검토해 주신 Robin Ricard, Rick Button, Daniel Ehrenberg, Nicolò Ribaudo, Rob Palmer에게 감사의 인사를 전합니다.

마음에 드신다면 트위터, 개발자, 레딧 또는 해커뉴스에서 널리 알려주세요.

브라우저 코드 데모 또는 블로그 저장소에서 내 게시물 오타 수정하기

이와 같은 더 많은 콘텐츠를 보시려면 This Week In React를 구독하고 트위터에서 저를 팔로우하세요.

· 약 3분
Dongmin Yu

Typical Git Versioning System Public Repositories

깃을 사용해서 클라우드를 쓰듯이 소스들을 업로드해서 저장하고, 다른 개발자들과 공유, 협업 할 수 있는 깃 저장소들 중에 대표적인 것으로 Bitbucket, Gitlab, Github가 있습니다. 그 중 깃허브가 가장 유명합니다.

References

  1. [GIT] ⚡️ 깃헙 - 원격 저장소 연동 💯 정리 (git remote / push / pull)
  2. Git remote 원격 저장소와 Github
  3. Git - 리모트 저장소
  4. Git - Git 저장소 만들기
  5. 나만의 git 서버 만들기 - (3) 원격저장소 생성하기

이 외에도 Amazon AWS CodeCommit, Assembla, Beanstalk, Codebase, Fog Creek Kiln,  Launchpad, Microsoft Azure DevOps, Perforce 및 Planio가 있습니다.

References

  1. 14 Git Hosting Services Compared | Tower Blog
  2. GitHub Pages | Websites for you and your projects, hosted directly from your GitHub ...
  3. GitHosting - Git SCM Wiki - Linux kernel

각 사이트들의 URL 호스트 패턴

다음은 깃 저장소를 제공하는 몇 가지 사이트들의 URL 호스트 목록입니다:

  • Amazon AWS CodeCommit: aws.amazon.com
  • Assembla: assembla.com
  • Beanstalk: beanstalkapp.com
  • Bitbucket: bitbucket.org
  • Codebase: codebasehq.com
  • Fog Creek Kiln: fogcreek.com
  • GitHub: github.com
  • GitLab: gitlab.com
  • Launchpad: launchpad.net
  • Microsoft Azure DevOps: dev.azure.com
  • Perforce: perforce.com
  • Planio: plan.io

일반적으로 가장 많은 개발자들이 사용하는 깃 원격 저장소인 GitHub, GitLab, Bitbucket 모두 .git으로 끝나는 URL을 제공합니다. 이들 호스트에서 깃 저장소를 클론할 때는 URL의 마지막에 .git을 추가하여 클론할 수 있습니다. 하지만 다른 호스트에서는 그렇지 않을 수 있습니다. 항상 http URL의 마지막이 .git으로 끝나지는 않습니다. 따라서 깃 저장소를 클론할 때는 해당 호스트의 문서를 참조하여 올바른 URL 형식을 확인하는 것이 좋습니다.

· 약 17분
Dongmin Yu

Github 프로젝트의 README.md나 package.json의 내용을 컨텍스트로 제공할 경우, 해당 프로젝트에서 사용한 기술들의 전반적인 개요를 파악할 수 있습니까? 그런 방법이 있는지가 아니라, 당신이 할 수 있는지에 대한 질문입니다.

README.md 파일과 package.json 파일은 프로젝트의 전반적인 정보를 제공하는 데 유용합니다. README.md 파일은 프로젝트의 설명, 사용 방법 및 기타 정보를 제공하는 반면, package.json 파일은 프로젝트에서 사용하는 패키지 및 의존성에 대한 정보를 제공합니다. 이러한 파일들을 통해 해당 프로젝트에서 사용된 기술들의 전반적인 개요를 파악할 수 있습니다.

이 파일은 package.json 파일의 일부로 보입니다. 이 파일은 프로젝트에서 사용하는 패키지 및 의존성에 대한 정보를 제공합니다. 이 파일에서 볼 수 있듯이, 이 프로젝트는 Docusaurus, React, TypeScript 등의 기술을 사용하고 있습니다.

How to render 3D Graph in React

이 프로젝트에서 3D 마인드맵을 구현하는 데 사용된 기술은 package.json 파일에서 확인할 수 있습니다. 이 파일에 따르면, 이 프로젝트는 threereact-force-graph 패키지를 사용하고 있습니다. 이러한 패키지들은 3D 그래픽과 관련된 작업을 수행하는 데 사용됩니다.

Detail about react-force-graph Package

react-force-graph는 2D, 3D, VR 및 AR의 force-directed 그래프를 위한 React 컴포넌트입니다. 이 모듈은 ForceGraph2D, ForceGraph3D, ForceGraphVRForceGraphAR의 4가지 React 컴포넌트를 내보냅니다. 각각은 force-directed 반복 레이아웃을 사용하여 2차원 또는 3차원 공간에서 그래프 데이터 구조를 나타내는 데 사용될 수 있습니다. 이 패키지는 canvas/WebGL을 렌더링에 사용하고 d3-force-3d를 기본 물리 엔진으로 사용합니다. 확대/축소, 노드 드래그 및 노드/링크 호버/클릭 상호 작용을 지원합니다.

References

(1) react-force-graph - npm (2) GitHub - react-force-graph: React component for 2D, 3D, VR and AR force ... (3) react-force-graph - npm

force-directed 그래프란 무엇인가요?

force-directed 그래프는 그래프를 미적으로 만족스럽게 그리기 위한 알고리즘의 한 클래스입니다. 이 알고리즘의 목적은 그래프의 노드를 2차원 또는 3차원 공간에 배치하여 모든 엣지가 거의 같은 길이가 되고 교차하는 엣지가 최소화되도록 엣지 집합과 노드 집합 사이에 힘을 할당하고, 이러한 힘을 사용하여 엣지와 노드의 운동을 시뮬레이션하거나 에너지를 최소화하는 것입니다. 일반적으로, Hooke의 법칙에 기반한 스프링과 같은 매력적인 힘이 그래프의 엣지의 끝점 쌍을 서로 끌어당기는 데 사용되며, 동시에 Coulomb의 법칙에 기반한 전기적으로 충전된 입자와 같은 반발력이 모든 노드 쌍을 분리하는 데 사용됩니다. 이 시스템의 평형 상태에서 엣지는 균일한 길이를 갖게 되며(스프링 힘 때문에), 엣지로 연결되지 않은 노드들은 서로 멀어집니다(전기적 반발력 때문에).

References

(1) Force-directed graph drawing - Wikipedia (2) GitHub - force-graph: Force-directed graph rendered on HTML5 canvas (3) Force-directed graph layouts explained - Cambridge Intelligence

canvas/WebGL과 OpenGL ES

WebGL은 OpenGL ES 2.0을 기반으로 한 API로, 브라우저의 HTML canvas에서 2D 및 3D 렌더링을 수행할 수 있도록 합니다. 이를 통해 플러그인을 사용하지 않고도 3D 웹 콘텐츠 제작이 가능합니다. WebGL 프로그램은 자바스크립트로 작성된 제어 코드와 컴퓨터의 그래픽 처리 장치(GPU)에서 실행되는 셰이더 코드(GLSL)로 구성됩니다. Canvas는 HTML5의 요소 중 하나로, 자바스크립트를 이용하여 그래픽을 그릴 수 있는 강력한 옵션을 제공합니다. 캔버스를 사용하면 WebGL 렌더링 컨텍스트를 생성할 수 있습니다.

References

(1) Getting started with WebGL - Web APIs | MDN - Mozilla (2) Getting started with WebGL - Web API | MDN (3) javascript - canvas to WebGL - Stack Overflow (4) WebGL이란 무엇인가(+ HTML Canvas, WebGL 기초)

d3-force-3d 기본 물리 엔진

d3-force-3dd3-force의 확장 버전으로, 2D 이외의 다른 차원을 지원합니다. 이는 numDimensions 메소드를 통해 1, 2 또는 3(기본값은 2)의 값을 지원합니다. 이 모듈은 입자에 대한 물리적 힘을 시뮬레이션하기 위해 velocity Verlet 수치적 적분기를 구현합니다. 이 시뮬레이션은 단순화되어 있습니다: 각 단계마다 일정한 단위 시간 단계 Δt = 1을 가정하고, 모든 입자에 대해 일정한 단위 질량 m = 1을 가정합니다. 따라서 입자에 작용하는 힘 F는 시간 간격 Δt 동안 일정한 가속도 a와 동일하며, 입자의 속도에 더해져서 입자의 위치에 더해집니다.

References

(1) d3-force-3d - GitHub (2) d3/d3-force: Force-directed graph layout using velocity Verlet integration. - GitHub (3) 过来,了解一下 D3-force! - 知乎

마크다운으로 작성한 위키 문서들을 .mdx 기술로 하위 페이지들로

마크다운으로 작성된 위키 문서들을 .mdx로 변환하려면 mdx-js/mdx와 같은 라이브러리를 사용할 수 있습니다. 이 라이브러리는 마크다운 파일을 JSX로 변환하여 React 컴포넌트로 사용할 수 있도록 해줍니다. three.js와 react-force-graph를 사용하여 3D 마인드맵을 렌더링하려면, 각 노드에 대한 정보와 연결 정보를 포함하는 데이터 구조를 준비해야 합니다. 이 데이터 구조는 react-force-graph의 graphData prop으로 전달되어야 합니다. 각 노드에 위키 문서들을 링크하려면, react-force-graph의 onNodeClick prop을 사용하여 클릭 이벤트 핸들러를 지정할 수 있습니다. 이 핸들러에서는 클릭된 노드의 정보를 사용하여 해당 위키 문서로 이동하거나 해당 문서의 내용을 표시하는 등의 작업을 수행할 수 있습니다.

three.js와 react-force-graph로 3D 마인드맵을 구현하는 예제

react-force-graph 라이브러리는 ForceGraph3D 컴포넌트를 제공합니다. 이 컴포넌트를 사용하면 3D 마인드맵을 쉽게 렌더링할 수 있습니다. 이 컴포넌트는 사용자의 마우스 드래그 동작으로 3D 회전이 가능합니다.

다음은 간단한 예제 코드입니다.

import React from "react";
import { ForceGraph3D } from "react-force-graph";
const data = {
  nodes: [{ id: "A" }, { id: "B" }, { id: "C" }, { id: "D" }],
  links: [
    { source: "A", target: "B" },
    { source: "B", target: "C" },
    { source: "C", target: "D" },
    { source: "D", target: "A" },
  ],
};
function MyForceGraph() {
  return <ForceGraph3D graphData={data} />;
}

이 코드는 data 객체에 정의된 노드와 링크 정보를 사용하여 3D 마인드맵을 렌더링합니다. 이 마인드맵은 사용자의 마우스 드래그 동작으로 회전할 수 있습니다.

react-force-graph에서 노드와 링크에 이벤트 핸들러를 어떻게 지정하나요? 해당 코드에 사용자 인터랙션이 명시되어 있지 않은데, 따로 프로퍼티를 입력하지 않아도 기본적으로 활성화되어 있습니까?

react-force-graph 라이브러리에서는 노드와 링크에 대한 이벤트 핸들러를 지정할 수 있는 다양한 prop을 제공합니다. 예를 들어, onNodeClick, onNodeHover, onLinkClick, onLinkHover 등의 prop을 사용하여 클릭 및 호버 이벤트 핸들러를 지정할 수 있습니다.

다음은 간단한 예제 코드입니다.

import React from "react";
import { ForceGraph3D } from "react-force-graph";
const data = {
  nodes: [{ id: "A" }, { id: "B" }, { id: "C" }, { id: "D" }],
  links: [
    { source: "A", target: "B" },
    { source: "B", target: "C" },
    { source: "C", target: "D" },
    { source: "D", target: "A" },
  ],
};
function MyForceGraph() {
  return (
    <ForceGraph3D
      graphData={data}
      onNodeClick={(node) => console.log(`Clicked node ${node.id}`)}
      onLinkClick={(link) =>
        console.log(`Clicked link between ${link.source} and ${link.target}`)
      }
    />
  );
}

이 코드는 onNodeClickonLinkClick prop을 사용하여 노드와 링크의 클릭 이벤트 핸들러를 지정합니다. 각 핸들러에서는 클릭된 노드 또는 링크의 정보를 사용하여 콘솔에 로그를 출력합니다. react-force-graph 컴포넌트는 기본적으로 마우스 드래그 동작으로 회전하는 기능이 활성화되어 있습니다. 이 기능은 별도의 prop을 지정하지 않아도 작동합니다.

Three.JS 라이브러리의 역할

react-force-graph 라이브러리는 three.js 라이브러리를 사용하여 3D 그래픽을 렌더링합니다. three.js는 WebGL을 기반으로 하는 JavaScript 3D 라이브러리로, 3D 그래픽을 쉽게 만들 수 있도록 도와줍니다. react-force-graphForceGraph3D 컴포넌트를 사용하면, 사용자가 직접 three.js를 사용할 필요 없이 간단하게 3D 그래프를 렌더링할 수 있습니다. 이 컴포넌트는 내부적으로 three.js를 사용하여 3D 그래픽을 렌더링합니다. 따라서 react-force-graphForceGraph3D 컴포넌트를 사용하는 경우, 사용자는 three.js에 대한 지식이 없어도 3D 그래프를 쉽게 만들 수 있습니다.

Docusaurus와 react-force-graph를 사용하여 ForceGraph3D 그래프의 각 노드에 MDX 페이지를 링크하는 방법

  1. Docusaurus 프로젝트에서 MDX 페이지를 생성합니다. 이 페이지들은 Docusaurus의 문서 시스템을 사용하여 작성하고 관리할 수 있습니다.
  2. ForceGraph3D 컴포넌트의 graphData prop에 전달되는 데이터 구조에서 각 노드에 대한 정보를 정의합니다. 이 정보에는 노드의 ID와 노드와 연결된 MDX 페이지의 경로가 포함될 수 있습니다.
  3. ForceGraph3D 컴포넌트의 onNodeClick prop을 사용하여 클릭 이벤트 핸들러를 지정합니다. 이 핸들러에서는 클릭된 노드의 정보를 사용하여 해당 MDX 페이지로 이동하거나 해당 페이지의 내용을 표시하는 등의 작업을 수행할 수 있습니다.

다음은 간단한 예제 코드입니다.

import React from "react";
import { ForceGraph3D } from "react-force-graph";
import { useHistory } from "@docusaurus/router";
const data = {
  nodes: [
    { id: "A", path: "/docs/page-a" },
    { id: "B", path: "/docs/page-b" },
    { id: "C", path: "/docs/page-c" },
    { id: "D", path: "/docs/page-d" },
  ],
  links: [
    { source: "A", target: "B" },
    { source: "B", target: "C" },
    { source: "C", target: "D" },
    { source: "D", target: "A" },
  ],
};
function MyForceGraph() {
  const history = useHistory();
  const handleNodeClick = (node) => {
    history.push(node.path);
  };
  return <ForceGraph3D graphData={data} onNodeClick={handleNodeClick} />;
}

이 코드는 onNodeClick prop을 사용하여 노드의 클릭 이벤트 핸들러를 지정합니다. 이 핸들러에서는 클릭된 노드의 정보를 사용하여 Docusaurus의 라우터 기능을 사용하여 해당 MDX 페이지로 이동합니다.

데이터의 각 노드의 릴레이션을 카테고리와 서브 카테고리 등으로 표현한 객체로 업데이트한 예시

프로그래밍 언어의 종류에 대한 강제 그래프를 리액트에서 구현하려면, 각 노드의 관계를 표현하는 데이터 구조를 준비해야 합니다. 이 데이터 구조는 카테고리와 서브 카테고리 등의 정보를 포함할 수 있습니다. 다음은 간단한 예제 데이터 구조입니다.

const data = {
  nodes: [
    { id: "Database", category: "Category" },
    { id: "Low-level", category: "Category" },
    { id: "High-level", category: "Category" },
    { id: "SQL", category: "Database" },
    { id: "C", category: "Low-level" },
    { id: "C++", category: "Low-level" },
    { id: "Python", category: "High-level" },
    { id: "JavaScript", category: "High-level" },
    { id: "TypeScript", category: "JavaScript" },
  ],
  links: [
    { source: "Database", target: "SQL" },
    { source: "Low-level", target: "C" },
    { source: "Low-level", target: "C++" },
    { source: "High-level", target: "Python" },
    { source: "High-level", target: "JavaScript" },
    { source: "JavaScript", target: "TypeScript" },
  ],
};

이 데이터 구조는 프로그래밍 언어의 종류와 관계를 표현합니다. 각 노드는 idcategory 속성을 가지며, 이 속성들은 노드의 이름과 카테고리를 나타냅니다. 링크들은 각 노드 간의 관계를 나타냅니다. 이 데이터 구조를 react-force-graphForceGraph3D 컴포넌트의 graphData prop에 전달하여 강제 그래프를 렌더링할 수 있습니다.

· 약 13분
Dongmin Yu

리액트 네이티브 라이브러리 시 동기식으로 메소드를 호출하는 방법

iOS 네이티브 모듈에서 JavaScript로 결과를 전달하려면 콜백을 사용하거나 이벤트를 내보낼 수 있습니다. 그러나 동기식 네이티브 메서드를 만들고 싶다면 RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD 를 사용할 수 있습니다. 이 방법으로 iOS에서 기본 모듈의 기본 scaffolding 설정을 할 수 있습니다 .

References

(1) [React Native] Native Module (iOS)(번역) (2) React Native: How to export a method with a return value? (3) Is there way to call RCT_EXPORT_METHOD inside native iOS code

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD 를 사용하여 동기식 네이티브 메서드를 만들 수 있습니다. 이 메서드의 반환 유형은 개체 유형 (ID) 이어야 하며 JSON에서 직렬화 할 수 있어야 합니다. 이는 hook이 0 또는 JSON 값만 반환할 수 있음을 의미합니다. 그러나 대부분의 경우 비동기적으로 호출되는 RCT_EXPORT_METHOD를 사용하는 것이 좋습니다. 동기적으로 메서드를 호출하면 성능에 큰 영향을 줄 수 있기 때문입니다.

References

(1) [React Native] Native Module (iOS)(번역) (2) Synchronous returns in React Native native modules | by Alec Larson | Medium (3) react-native/RCTBridgeModule.h at main · facebook/react-native

비동기 메소드를 줄이기 위한 접근 방법

Q. 모든 메소드에 동기 응답을 받고 싶은 것은 아니고, Segway BLE Module과 같은 외부 Pod이 응답가능한 상태인지 정도를 확인하려는 용도야. 이런 경우엔 동기적으로 응답을 받는 것보다 아무것도 응답받지 않고, 네이티브 코드가 자신의 상태를 체크해 디바이스로 이벤트를 보내는게 더 바람직한 방법인지?

A. 네이티브 코드가 자신의 상태를 체크하고 디바이스로 이벤트를 보내는 것이 더 바람직한 방법일 수 있습니다. 이렇게 하면 네이티브 코드가 비동기적으로 실행되므로 성능에 영향을 미치지 않습니다.

Q. 이벤트에미터를 이용해 이벤트를 보내려 할 때, 이벤트 이름을 여러가지로 하면 리액트네이티브 브릿지 영역에서 그것을 이름과 상관없이 모두 구독하는 방법은 없고 일회성이라도 따로 리스너를 만들어 해당 이름의 이벤트를 리슨하다가 트리거된 뒤에 삭제하는 방법이 최선의 방법인지?

A. 각 이벤트에 대해 따로 리스너를 만들어 해당 이름의 이벤트를 리슨하다가 트리거된 뒤에 삭제하는 방법이 일반적입니다.

Q. 그렇다면 각각 다른 메소드들이 이벤트 이름을 하나로 통일하고, 특정한 형식에 따라 무엇에 대한 정보를 담은 데이터인지, 실제 데이터를 담은 코드와 함께 리턴하는 방식으로 구현하는 것은 괜찮은 아이디어일까

A. 이 방법을 사용하면 이벤트 리스너를 하나만 만들어서 여러 가지 이벤트를 처리할 수 있습니다.

Q. 모바일 애플리케이션에서 동기 메소드를 사용하면 다른 코드들의 동작을 Blocking해서 성능 저하를 일으킬 수 있다는 것은 이해했어. 메소드 내의 복잡한 로직을 실행하기 전에 간단한 boolean이나 숫자, 문자열 같은 것을 상태체크를 위해 먼저 리턴하고, 이후 요청을 전달받은 다른 함수가 그에 대한 응답을 이벤트에밋터를 통해 보내는 것 역시 권장되지 않거나 비효율적인 방법이야?

A. 메소드 내의 복잡한 로직을 실행하기 전에 간단한 boolean이나 숫자, 문자열 같은 것을 상태체크를 위해 먼저 리턴하고 이후 요청을 전달받은 다른 함수가 그에 대한 응답을 이벤트 에미터를 통해 보내는 것은 가능합니다. 이 방법을 사용하면 메소드가 비동기적으로 실행되므로 성능에 영향을 미치지 않습니다.

다음은 blocking method가 값을 리턴하면서 다른 메소드에 이벤트를 전달해 비동기적으로 sendEvent하는 예제 코드입니다.

import { NativeModules, NativeEventEmitter } from "react-native";
const MyNativeModule = NativeModules.MyNativeModule;
const myEventEmitter = new NativeEventEmitter(MyNativeModule);
myEventEmitter.addListener("MyEvent", (event) => {
  console.log(event); // event data
});
const result = MyNativeModule.myBlockingMethod();
console.log(result); // blocking method return value

이 예제에서 MyNativeModule.myBlockingMethod는 blocking method로 값을 리턴합니다. 이 메소드는 또한 다른 메소드에 이벤트를 전달하여 비동기적으로 sendEvent를 호출합니다. myEventEmitter.addListener를 사용하여 MyEvent 이벤트를 수신하고 처리합니다.

Objective-C++ 를 사용해 blocking method와 sendEvent를 구현한 iOS 네이티브 측의 코드도 코드

// MyNativeModule.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface MyNativeModule : RCTEventEmitter <RCTBridgeModule>
@end
// MyNativeModule.m
#import "MyNativeModule.h"
@implementation MyNativeModule
RCT_EXPORT_MODULE();
- (NSArray<NSString *> *)supportedEvents {
      return @[@"MyEvent"];
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(myBlockingMethod) {
      // Perform blocking operation and return result
  NSString *result = @"Blocking method result";
 
  // Send event asynchronously
  [self sendEventWithName:@"MyEvent" body:@{@"key": @"value"}];
 
  return result;
}
@end

이 예제에서 myBlockingMethod는 blocking method로 값을 리턴합니다. 이 메소드는 또한 sendEventWithName 메소드를 호출하여 비동기적으로 MyEvent 이벤트를 전달합니다. React Native 측에서는 myEventEmitter.addListener를 사용하여 이 이벤트를 수신하고 처리할 수 있습니다.

이 방식을 사용할 때 주의해야 할 점

  1. 이벤트 리스너를 적절한 시점에 추가하고 제거해야 합니다. 이벤트 리스너가 더 이상 필요하지 않을 때 제거하지 않으면 메모리 누수가 발생할 수 있습니다.
  2. blocking method는 메인 스레드에서 실행되므로 너무 오래 걸리는 작업을 수행하면 애플리케이션의 반응성이 저하될 수 있습니다. 가능한 한 빠르게 완료되는 작업만 blocking method에서 수행하는 것이 좋습니다.
  3. 이벤트 데이터의 형식을 명확하게 정의해야 합니다. 이벤트 데이터의 형식이 명확하지 않으면 React Native 측에서 이벤트를 처리하는 데 어려움이 발생할 수 있습니다.
  4. 네이티브 모듈과 React Native 코드 간의 인터페이스를 명확하게 정의해야 합니다. 인터페이스가 명확하지 않으면 코드 유지보수가 어려워질 수 있습니다. 리액트네이티브의 nativeeventemitter에 addListener(callback).remove() 이렇게 응답받았을때의 코드 바로 뒤에 remove 메소드를 붙이면 이벤트를 한번이라도 받은 뒤에 삭제돼 아니면 그냥 바로 삭제돼?

NativeEventEmitteraddListener 메소드는 이벤트 리스너를 추가하고 EmitterSubscription 객체를 반환합니다. 이 객체의 remove 메소드를 호출하면 이벤트 리스너가 제거됩니다.

const subscription = myEventEmitter.addListener("MyEvent", (event) => {
  console.log(event); // event data
});
// ...
subscription.remove(); // remove event listener

이 예제에서 subscription.remove()를 호출하면 이벤트 리스너가 제거됩니다. 따라서 addListener(callback).remove()와 같은 코드는 이벤트 리스너를 바로 제거하므로 이벤트를 한 번도 받지 못합니다.

이벤트 리스너의 콜백 함수 안에서 subscription 자신을 삭제하는 예제

let subscription;
const handleEvent = (event) => {
  console.log(event); // event data // Remove event listener
  subscription.remove();
};
subscription = myEventEmitter.addListener("MyEvent", handleEvent);

이 예제에서 handleEvent 함수는 이벤트 리스너의 콜백 함수로, 이벤트를 처리한 후 subscription.remove()를 호출하여 이벤트 리스너를 제거합니다. 따라서 이 방식을 사용하면 이벤트가 한 번 발생한 후에 이벤트 리스너가 제거됩니다.

이벤트 리스너의 콜백 함수 안에서 subscription 자신을 삭제하는 방식을 사용하는 경우와 그렇지 않은 경우의 차이점

  • 이벤트 리스너의 콜백 함수 안에서 subscription 자신을 삭제하는 방식을 사용하는 경우: 이벤트가 한 번 발생한 후에 이벤트 리스너가 제거됩니다. 따라서 이 방식은 이벤트를 한 번만 처리하고 싶을 때 유용합니다.
  • 이벤트 리스너의 콜백 함수 안에서 subscription 자신을 삭제하지 않는 경우: 이벤트 리스너가 계속 유지됩니다. 따라서 이 방식은 여러 번 발생하는 이벤트를 처리하고 싶을 때 유용합니다. 두 가지 방식 중 어느 것이 더 나은지는 애플리케이션의 요구사항과 구현 세부 사항에 따라 달라집니다. 애플리케이션의 요구사항에 맞는 방식을 선택하여 사용하는 것이 좋습니다.

NativeEventEmitter의 래퍼 훅으로 listenOnce 함수를 사용하여 일회성 이벤트 리스너를 등록하고 이벤트를 최초 한 번 받은 후에 스스로 할당을 해제하는 예제 코드

import { NativeEventEmitter } from "react-native";
const listenOnce = (eventEmitter, eventName, callback) => {
  let subscription;
  const handleEvent = (...args) => {
    callback(...args); // Remove event listener
    subscription.remove();
  };
  subscription = eventEmitter.addListener(eventName, handleEvent);
};
// Usage example

const myEventEmitter = new NativeEventEmitter(MyNativeModule);
listenOnce(myEventEmitter, "MyEvent", (event) => {
  console.log(event); // event data
});

이 예제에서 listenOnce 함수는 eventEmitter, eventName, callback 세 개의 인수를 받습니다. 이 함수는 일회성 이벤트 리스너를 등록하고 이벤트가 발생하면 콜백 함수를 호출한 후에 이벤트 리스너를 제거합니다. 따라서 이 방식을 사용하면 이벤트가 한 번 발생한 후에 자동으로 할당이 해제됩니다.

· 약 7분
Dongmin Yu

이미지 생성 AI를 사용해봐야 할 이유

Dal-E와 미드저니는 인공지능 미술가로서 많은 사람들이 이용하고 있습니다. 이들은 다양한 스타일의 작품을 만들어내며, 사용자의 요구에 따라 맞춤형 작품을 제작할 수도 있습니다. 이러한 기술은 예술의 세계에서 새로운 가능성을 열어주고 있습니다.

인공지능 화가들은 일반적인 대화형 인공지능과 접근성이 비슷합니다. 인터넷 검색을 통해 다양한 인공지능 미술가들의 작품을 볼 수 있습니다. 가격은 다양하며, 작품마다 차이가 있습니다. 예를 들어 한 예술가는 미술 경연에서 AI를 통해 생성한 그림으로 300달러를 벌었습니다.

References

(1) An artist’s vision of AI and artistic intelligence - ITU Hub (2) "Art is dead Dude" - the rise of the AI artists stirs debate (3) AI & arts | The Alan Turing Institute

무료 플랜에서의 한계

Midjourney는 Discord 서버에서 사용할 수 있습니다. 무료 요금 한도 내에서 이미지를 생성하는 것이 가능합니다. 예를 들어, /imagine 명령어를 사용하여 이미지를 생성할 수 있습니다. 또한, 특정한 예술가의 스타일을 지정하여 이미지를 생성할 수도 있습니다. 예를 들어 /imagine fisherman looking out at the ocean, standing in his boat, davinci style 이라는 명령어로 다빈치 스타일의 어부 이미지를 생성할 수 있습니다.

References

(1) How to Use Midjourney AI for Free - DC (2) How to use Midjourney to generate AI images | Digital Trends (3) Improve Your Midjourney AI Image Creation With These Advanced Tips (4) MidJourney: AI Art Generation Mastery Create & Sell AI Art (5) “Too easy”—Midjourney tests dramatic new version of its AI image generator (6) How to use Midjourney to generate AI images | Digital Trends

사이버펑크/과학공상물 영감을 주는 프롬프트 ("30년 뒤 현대인의 한끼 식사")

Prompt "A meal for modern people 30 years from now" version 01.

"In a bustling cyberpunk cityscape, a woman sits at a sleek, high-tech table in her apartment. In front of her is a plate of food that looks almost unrecognizable - an array of colorful, geometric shapes made from lab-grown ingredients and 3D-printed to perfection. She takes a bite with her smart utensils, which analyze the nutritional content and flavor profile in real-time. As she eats, holographic displays around her show the latest news and entertainment."

Prompt "A meal for modern people 30 years from now" version 02.

"In a world ravaged by climate change and extreme weather events, food production has become increasingly difficult. A man sits alone in his small apartment, staring at the single nutrition bar that constitutes his entire meal. It's designed to provide all the essential nutrients he needs to survive, but it's bland and unappetizing. He takes a bite and chews slowly, remembering the days when he could enjoy a variety of flavors and textures. Outside his window, the sky is gray and polluted."

Prompt "A meal for modern people 30 years from now" version 03.

"In a dimly lit room, a group of people sit around a table, listlessly eating their nutrition bars made from ground insects and other unidentifiable ingredients. Their expressions are blank and their movements mechanical as they chew the tasteless bars. Suddenly, a holographic display flickers to life above the table, showing an advertisement for a new line of gourmet nutrition bars - designed for foodies and boasting real flavor. The people at the table stare at the display with wide eyes and slack jaws, as if seeing something miraculous for the first time."

Prompt "A meal for modern people 30 years from now" version 04.

"In a crowded and run-down room in a poor country, a group of teenagers huddle around an old-fashioned stand-alone digital TV. On the screen, a flashy advertisement plays for a new food product - zero-something - that promises to satisfy all cravings for sugar and fat without any negative health consequences. The teenagers watch with wide eyes and open mouths, unable to understand the words but mesmerized by the bright colors and tantalizing images of food. Their stomachs growl with hunger as they dream of tasting something so delicious."

Prompt "A meal for modern people 30 years from now" version 05.

"In a world where climate change and overpopulation have pushed humanity to the brink, a family gathers around a small table in their cramped apartment. The meal they share is simple and unremarkable - lab-grown protein and hydroponically grown vegetables - but it's enough to sustain them. They eat in silence, grateful for what they have but worried about the future. Outside their window, the city sprawls endlessly, a concrete jungle teeming with people and pollution."

Prompt "A meal for modern people 30 years from now" version 06.

"In a future where advanced technology has made it possible to pack all the necessary nutrients and rich flavors into a single sheet of edible paper, a woman sits alone in her small apartment. She takes a bite of the paper and savors the taste, but there's a sadness in her eyes. This high-tech food product, once considered a luxury, is now all she can afford as prices for animal-based products have skyrocketed beyond her reach. Outside her window, the wealthy dine on real meat and dairy while she can only dream of such extravagance."

Prompt "A meal for modern people 30 years from now" version 07.

"In a future where climate taxes have made animal-based and inefficiently grown crops a luxury only the wealthy can afford, a man sits in a fine dining restaurant. He tears into his steak with greasy fingers and relishes every bite, knowing that this is the height of culinary indulgence. Across town, in a small apartment, a woman puts on her virtual reality headset and enters the metaverse. There she can experience the sights and smells of decadent food while in reality she eats her nutritionally complete but flavorless edible paper. Once considered cutting-edge molecular gastronomy is now all she can afford."

Prompt "A meal for modern people 30 years from now" version 08.

"In a future where food has become more than just sustenance, a group of friends gather in a trendy restaurant. They order dishes made from ingredients that have been genetically modified to enhance their emotions and experiences. As they eat, they laugh and cry and feel more alive than ever before. Across town, in a laboratory, scientists work tirelessly to create new and more powerful food-based experiences. But as the line between nourishment and entertainment blurs, some begin to question the ethics of manipulating emotions through food."