(ES6) Canvas + Javascript로 웹 게임 만들기 - 이벤트 만들기

|

비게임 업종 서버개발자가 HTML5의 <canvas>와 Javascript(ES6)를 이용하여 취미로 개발해 본 웹게임에 대한 글로 개발 코드 자체에 대한 설명보다는 어떤 원리와 방식으로 개발하였는지에 대한 내용을 기술하고 있습니다.

순서 제목 링크
1 캐릭터 그리기 https://jistol.github.io/frontend/2019/09/25/create-webgame-1/
2 캐릭터 움직이기 https://jistol.github.io/frontend/2019/09/28/create-webgame-2/
3 이벤트 만들기 https://jistol.github.io/frontend/2019/09/29/create-webgame-3/

게임명은 “도마뱀플라이트”로 앱게임 “드래곤플라이트”를 모방하여 일부 기능을 따라 구현하였으며 실제 게임은 아래 링크에서 실행 해 볼 수 있습니다.

GAME : https://jistol.github.io/lizard/

SOURCE : https://github.com/jistol/lizard-flight

game capture

키 이벤트 만들기

캐릭터를 그렸으니 조정하는 방법을 추가 하도록 합니다. 전 포스팅에서도 언급했듯이 Worker내에서는 직접 DOM을 접근 할 수가 없습니다. 따라서 메인 페이지에서 키 이벤트를 받아 Worker에게 전달해야하는데, 이 때도 역시 postMessage를 사용하게 됩니다.

// main page
const worker = new Worker('worker.js');
const onKeyEvent = (e) => {
    worker.postMessage({ key : event.key });
};
document.addEventListener('keydown', onKeyEvent, false);

// worker.js
self.onmessage = function(e) {
  console.log(e.data.key);
}

이전 코드와 달라진 부분은 keydown 이벤트시 움직이는 방향으로 원을 이동시키고 keyup 이벤트시 움직임을 멈추도록 direct라는 변수를 추가하였으며 화면밖으로 원이 나가지 않도록 x의 크기를 제어하는 부분입니다.

터치 이벤트 만들기

요즘 대부분의 접속 환경이 모바일인 만큼 터치 이벤트를 이용한 움직임도 처리해보도록 하겠습니다. 키의 경우 명확하게 어떤 키를 누르고 땠는지 확인이 가능하나 터치는 같은 이벤트로 다른 처리를 해야하기에 키 이벤트와는 다르게 코딩 되어야 합니다.
터치 이벤트는 대표적으로 아래와 같이 존재합니다.

이벤트명 설명
touchstart 디바이스 화면에 손가락이 닿는 순간 발생하는 이벤트입니다.
touchmove 디바이스 화면에 손가락이 닿은 상태에서 손가락을 움직이면 발생하는 이벤트 입니다
touchend 디바이스 화면에 손가락이 닿은 상태에서 손가락을 떼어내면 발생하는 이벤트입니다.
touchcancel 터치 이벤트가 시스템으로 인해 취소 될 때 발생하는 이벤트입니다.

좀 더 자세한 설명은 Javascript Mobile Events의 이해를 참고하시기 바랍니다.
다시 구현으로 돌아가서, 캐릭터를 이동 시키기 위해서는 두가지 선택지가 있습니다. 첫번째로는 터치된 포인트 지점으로 캐릭터를 바로 이동시키는 방법과 두번째는 터치 후 움직이는 방향으로 이동시키는 방법입니다. 본 코드에서는 두번째 방법을 이용해 구현했습니다.

(ES6) Canvas + Javascript로 웹 게임 만들기 - 캐릭터 움직이기

|

비게임 업종 서버개발자가 HTML5의 <canvas>와 Javascript(ES6)를 이용하여 취미로 개발해 본 웹게임에 대한 글로 개발 코드 자체에 대한 설명보다는 어떤 원리와 방식으로 개발하였는지에 대한 내용을 기술하고 있습니다.

순서 제목 링크
1 캐릭터 그리기 https://jistol.github.io/frontend/2019/09/25/create-webgame-1/
2 캐릭터 움직이기 https://jistol.github.io/frontend/2019/09/28/create-webgame-2/
3 이벤트 만들기 https://jistol.github.io/frontend/2019/09/29/create-webgame-3/

게임명은 “도마뱀플라이트”로 앱게임 “드래곤플라이트”를 모방하여 일부 기능을 따라 구현하였으며 실제 게임은 아래 링크에서 실행 해 볼 수 있습니다.

GAME : https://jistol.github.io/lizard/

SOURCE : https://github.com/jistol/lizard-flight

game capture

setTimeout을 이용하여 캐릭터 움직이기

캐릭터를 움직이는 원리는 애니메이션의 원리와 같습니다. canvas에 캐릭터를 빠르게 다시 그려서 마치 위치가 바뀐것처럼 보이게 하는겁니다.
본 코드에서는 전체화면을 지우고 다시 그리는데 화면이 끊기거나 속도가 느려지지 않을까 하는 우려와는 달리 빠르게 처리되고 크게 이질감도 없었습니다.

화면을 다시 그리도록 반복하는 방법은 두가지가 있는데 그 중 가장 쉬운 방법은 setTimeout(또는 setInterval)을 이용하는 방법입니다.

위와 같이 코딩시에는 단점이 있습니다. setTimeout의 경우 해당 시간에 특정 함수를 동작시킬뿐 화면 프레임을 전혀 고려하지 않습니다.
브라우저가 화면을 그리기까지 몇 가지 단계가 있는데 그 단계를 모두 기다리지 못하거나 더 많은 텀을 가질 확률이 큽니다.
더 자세한 설명은 requestAnimationFrame() 개념 정리하기를 참고하세요.

requestAnimationFrame을 이용하여 캐릭터 움직이기

requestAnimationFrame 함수는 브라우저가 다음 리페인트를 수행하기 전에 해당 함수를 실행시켜 변경된 데이터로 그리도록 실행해주는 함수입니다.
자세한 설명은 MDN - window.requestAnimationFrame() 참고하시길 바라며 위 setTimeout을 이용한 코드에서 반복 부분만 requestAnimationFrame로 바꾼 코드입니다.

Web Worker를 이용하여 별도 스레드에서 그려보자

JavaScript는 기본적으로 단일 스레드 기반으로 동작합니다. 동시에 작업이 이뤄지는듯 보이지만 빠르게 하나씩 처리하거나 시분할하여 자원을 나눠쓰게 됩니다.
화면을 계속 갱신해야하는데 중간에 메인 스레드에 오래걸리는 작업을 실행하게 된다면 화면이 끊기거나 딜레이 될 수 있는데, 이 때 해결 할 수 있는 방법이 Web Worker를 이용하는 것입니다.

Web Worker는 메인 스레드가 아닌 별도 스레드를 이용하여 동작하기 때문에 메인 스레드의 부하에 영향없이 동작 할 수 있는 장점이 있으나, DOM 개체에 직접 접근 제어 할 수 없으며 메인 스레드와 message 이벤트를 통해서만 통신이 가능한 단점이 존재합니다. 추가로 new Worker()사용시 기본적으로 Dedicated Worker를 사용하게 되는데 이 때 모듈방식을 사용 할 수 없으며 import, export 방식 대신 importScripts() 함수를 이용하여 추가 가능합니다.
자세한 설명은 MDN - 웹 워커 사용하기을 참고하세요.

Worker는 직접 DOM 제어를 할 수 없기 때문에 canvas에 렌더링 하기 위해서는 메인 스레드로 부터 canvas를 전달 받아야 합니다. postMessage로 전달 가능한데 이 때 전달 가능한 객체는 Transferable인터페이스를 구현한 객체만 가능하며 나머지 객체는 오류를 발생 시킵니다. 관련한 사항은 MDN - Worker.postMessage() MDN - Transferable를 참고하세요.
다행히도 canvas는 위 인터페이스를 구현한 객체를 제공하는데 OffscreenCanvas객체로 HTMLCanvasElement.transferControlToOffscreen()함수를 통해 얻을 수 있습니다.
아직 모든 브라우저에서 제공하고 있진 않습니다.

(ES6) Canvas + Javascript로 웹 게임 만들기 - 캐릭터 그리기

|

비게임 업종 서버개발자가 HTML5의 <canvas>와 Javascript(ES6)를 이용하여 취미로 개발해 본 웹게임에 대한 글로 개발 코드 자체에 대한 설명보다는 어떤 원리와 방식으로 개발하였는지에 대한 내용을 기술하고 있습니다.

순서 제목 링크
1 캐릭터 그리기 https://jistol.github.io/frontend/2019/09/25/create-webgame-1/
2 캐릭터 움직이기 https://jistol.github.io/frontend/2019/09/28/create-webgame-2/
3 이벤트 만들기 https://jistol.github.io/frontend/2019/09/29/create-webgame-3/

게임명은 “도마뱀플라이트”로 앱게임 “드래곤플라이트”를 모방하여 일부 기능을 따라 구현하였으며 실제 게임은 아래 링크에서 실행 해 볼 수 있습니다.

GAME : https://jistol.github.io/lizard/

SOURCE : https://github.com/jistol/lizard-flight

game capture

canvas에 캐릭터 그리기

<canvas>에 그림을 그리기 위해서는 기본적으로 렌더링 컨텍스트를 노출하여 작업하게 됩니다. 본 게임은 2d만 사용하였으나 webgl을 이용한 3d도 그릴 수 있습니다.

let canvas = document.getElementById('canvas');
let context = canvas.getContext('2d');

위와 같이 컨텍스트를 노출하여 그리게 되는데 조종 할 게임 캐릭터를 그려보도록 하겠습니다.
(디자이너도 없고 하니 간단하게 원으로 생긴 캐릭터입니다.)

조종할 캐릭터를 그렸습니다. beginPath는 선을 그릴때 시작하는, closePath는 그리는 선을 닫아 시작점과 이어주는 역활을 합니다.
fill함수 사용시 열린 도형이 자동으로 닫히게 되어 closePath를 명시 할 필요가 없으나 코딩상 명확하게 열고 닫는것이 실수의 여지를 줄여줍니다.
arc함수를 사용하여 몸통,양쪽눈을 그렸습니다. arc함수에 대한 자세한 사용법은 MDN - CanvasRenderingContext2D.arc()를 참고하세요.

canvas 스케일 적용

위 예제에서 간단히 캐릭터를 그려보았습니다. 하지만 요즘 웹은 모바일 환경에서 많이 노출되며 각 폰마다 크기가 다르기 때문에 우리가 그린 캐릭터는 폰마다 다른 크기로 나올 수 있습니다. 이를 방지하기 위해 크기와 비율을 고정해 보도록 하겠습니다.
우선 모바일 디바이스 크기에 스케일을 맞추기 위해 HTML head에 아래와 같이 viewport를 추가합니다.

<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">
  </head>
  <body>
    <canvas id="mainCanvas"></canvas> 
    <script type="text/javascript">
      ...
    </script>
  </body>
</html>

viweport에 대한 자세한 설명은 모바일 화면을 위해 Viewport 사용하기 글을 참고하세요.
첫번째 예제에서는 canvas에 대한 width/height 값을 직접 입력했지만 화면 크기에 맞게 비율을 확대하려고 합니다.

// 전체 화면을 사용하기 위해 body의 속성을 정의해줍니다.
let body = document.body;
body.style.width = '100%';
body.style.height = '100%';
body.style.margin = '0';
body.style.padding = '0';

// canvas의 크기는 width=100%, height는 width의 1.5 비율로 사용할 예정입니다.
let canvas = document.getElementById('mainCanvas');
canvas.width = body.clientWidth;
canvas.height = Math.min(body.clientWidth * 1.5, body.clientHeight);
canvas.style.backgroundColor = '#000000';

// 실제 화면을 그릴 비율입니다.
// context를 이용하여 그림을 그릴 때 화면 넓이가 400, 높이는 넓이*1.5배라는 계산하에 작업할 예정입니다.
const rWidth = 400;
const rHeight = 400 * 1.5; // 600

// 실제 canvas 넓이와 그림 비율이 맞지 않기 때문에 scale을 변경해줍니다.
let context = canvas.getContext('2d');
let ratioX = canvas.width / rWidth;
let ratioY = canvas.height / rHeight;
context.scale(ratioX, ratioY);

위와 같이 화면 스케일을 자동으로 조절하게 만들어 두면 게임 화면을 그리기가 훨씬 수월해집니다.
디바이스 크기에 상관없이 화면 넓이가 400이란 전제하에 계산하여 캐릭터 크기를 조절 할 수 있게 됩니다.

docker-compose를 이용한 ElasticSearch Cluster구성

|

개발을 하다보면 공용장비가 아닌 로컬장비에서 DB나 캐시, 검색엔진등을 실행해야하는 경우가 있는데 이 때 Docker를 사용하면 필요할 때만 올려 사용할 수 있어 자원 관리가 편하고 docker-compose를 이용하면 여러 프로그램을 동시에 실행하고 종료 할 수 있어 편하게 사용할 수 있습니다.
본 글은 docker-compose를 이용하여 ElasticSearch 6.5.3 버전 기반으로 Cluster 환경을 구성하며 Kibana까지 같이 올리는 방법에 관한 글로 이미 elstic reference에 docker를 이용하여 설치하는 방법이 친절하게 설명되어 있으나 실제 설치하면서 추가로 필요했던 부분에 대해 보충하였습니다.

구성

구성은 master-node 1대, data-node 1대, kibana 1대 입니다.
ElasticSearch(이하 ES)와 함께 Celebro를 모니터링 툴로 쓰는 경우가 있는데 Kibana 최신 버전은 xpack을 통해 모니터링하는 기능이 있어 구지 필요가 없어 제외했습니다.

kibana-Monitoring

docker-compose.yml

version: '2.2'
services:
# master-node의 Docker 서비스입니다.
# Kibana에서 기본적으로 호출하는 ES host주소가 'http://elsaticsearch:9200'이기 때문에 서비스명은 elasticsearch로 쓰시는게 편합니다. 
# 다른 서비스명을 사용시 Kibana ES host 설정도 같이 추가해주어야 정상 동작합니다.
  elasticsearch:
    container_name: elasticsearch
    image: elasticsearch:6.5.3
    environment:
# ES Cluster명입니다. ES 서비스마다 동일한 명칭을 사용해야합니다.    
      - cluster.name=docker-cluster
# ES Node명을 설정합니다.
      - node.name=master-node1
# ES운영중 메모리 스왑을 막기 위한 설정을 추가합니다.
# 자세한 설명은 페이지 하단의 [Disable swapping]을 참고하세요.
      - bootstrap.memory_lock=true
# JVM Heap메모리 설정입니다. Xms/Xmx 옵션은 항상 같게 설정합니다.  
# 자세한 설명은 페이지 하단의 [Setting the heap size]을 참고하세요.
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
# 리눅스 시스템 자원제한 관련 옵션입니다.
# ES는 많은 파일디스크립터와 핸들러를 사용하기 때문에 제한 해제가 필요합니다.
# 자세한 설명은 페이지 하단의 [File Descriptors]을 참고하세요.
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - es1:/usr/share/elasticsearch/data
# Kibana에서 본 노드를 호출하기 때문에 외부 9200포트는 master-node에 연결해줍니다.
    ports:
      - 9200:9200
      - 9300:9300
    networks:
      - esnet
# 컨테이너에 bash로 붙고 싶을경우 아래 두 옵션을 추가해주면 됩니다.
    stdin_open: true
    tty: true
# data-node의 Docker 서비스입니다.
# 대부분의 내용이 master-node와 동일하나 몇가지 차이점이 있습니다.
  elasticsearch2:
    container_name: elasticsearch2
    image: elasticsearch:6.5.3
    environment:
      - cluster.name=docker-cluster
      - node.name=data-node1
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
# 다른 Cluster내 노드를 발견하기 위한 설정입니다.
      - "discovery.zen.ping.unicast.hosts=elasticsearch"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - es2:/usr/share/elasticsearch/data
# 외부 연결포트가 master-node와 겹치기 때문에 다르게 설정했습니다.
    ports:
      - 9301:9300
    networks:
      - esnet
    stdin_open: true
    tty: true
# 각 서비스를 순차적으로 실행하기 위해 설정해주었습니다. (필수아님) 
    depends_on:
      - elasticsearch
# Kibana 설정입니다.
  kibana:
    container_name: kibana
    image: kibana:6.5.3
    ports:
      - 5601:5601
    networks:
      - esnet
    depends_on:
      - elasticsearch
      - elasticsearch2
volumes:
  es1:
    driver: local
  es2:
    driver: local
networks:
  esnet:

실행 / 종료

위와 같이 설정파일을 작성 후 docker-compose를 실행하면 아래와 같이 ES가 기동하는것을 볼 수 있습니다.

$ docker-compose up -d
Creating network "elasticsearch_esnet" with the default driver
Creating volume "elasticsearch_es1" with local driver
Creating volume "elasticsearch_es2" with local driver
Pulling elasticsearch (elasticsearch:6.5.3)...
6.5.3: Pulling from library/elasticsearch
Pulling elasticsearch2 (elasticsearch:6.5.3)...
6.5.3: Pulling from library/elasticsearch
Pulling kibana (kibana:6.5.3)...
6.5.3: Pulling from library/kibana
Creating elasticsearch ... done
Creating elasticsearch2 ... done
Creating kibana         ... done

종료방법은 아래와 같습니다.

$ docker-compose down
Stopping kibana         ... done
Stopping elasticsearch2 ... done
Stopping elasticsearch  ... done
Removing kibana         ... done
Removing elasticsearch2 ... done
Removing elasticsearch  ... done
Removing network elasticsearch_esnet

Reference

Install Elasticsearch with Docker:https://www.elastic.co/guide/en/elasticsearch/reference/6.5/docker.html
Disable swapping:https://www.elastic.co/guide/en/elasticsearch/reference/6.4/setup-configuration-memory.html
Setting the heap size:https://www.elastic.co/guide/en/elasticsearch/reference/6.5/heap-size.html
File Descriptors:https://www.elastic.co/guide/en/elasticsearch/reference/6.5/file-descriptors.html

(React) Component Life Cycle Methods

|

React Component의 생명주기에 대해 정리하고 테스트 예제를 포스팅합니다.
참고로 version 17부터 deprecated 되는 항목(componentWillMount, componentWillUpdate, componentWillReceiveProps)은 제외했습니다.
위 3개의 lifecycle은 오용되는 케이스가 많아 삭제 되었으며 ‘UNSAFE_‘라는 prefix를 붙여 메소드가 남아있는 상태로 자세한 내용은 Update on Async Rendering문서를 참고하세요.

React 버전에 따라 생명주기가 살짝 다른데 아래 그림을 참고하세요.

React v16.3 : https://code.likeagirl.io/understanding-react-component-life-cycle-49bf4b8674de
React Component LifeCycle v16.3

React v16.4 : https://medium.com/@nancydo7/understanding-react-16-4-component-lifecycle-methods-e376710e5157
React Component LifeCycle v16.4

위 그림과 같이 React Component의 생명주기는 실행 이벤트 관점에서 “mount/update/unmount”로 구분 할 수 있으며 실행 단계 관점에서는 “랜더링전/DOM 반영전/DOM 반영이후”로 구분 할 수 있습니다.
아래 메서드들은 실행 순서 보다는 “일반적으로 사용되는”, “드믈게 사용되는” 생명주기로 구분합니다. 버전별 일반 생명주기 메서드 표는 아래 링크를 참고하세요.

react-lifecycle-methods-diagram : http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

1. Commonly Used Lifecycle Methods

render()

React.Component에서 유일하게 필수 구현되어야 하는 함수입니다. 미구현시 아래와 같은 오류를 만나게 됩니다.

Warning: TodoApp(...): No `render` method found on the returned component instance: you may have forgotten to define `render`.

Uncaught TypeError: instance.render is not a function

이 메서드 안에서는 this.props와 this.state를 사용할 수 있으나 state의 값을 변경하면 안됩니다. 변경시 아래와 같은 오류를 만나게 됩니다.

Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

반환 값으로 일반적으로 DOM노드를 반환하나 아래와 같이 여러종류의 데이터를 넘길 수 있습니다.

1. React Element

  • JSX타입의 요소를 반환할 수 있습니다.
  • 반환시 최상위 DOM은 단일노드여야 합니다.
render() {
  return <div> ... </div>;
}

render() {
  return <MyComponent/>;
}

// error case
render() {
  return <div>1</div><div>2</div>;
}
/**
* Uncaught SyntaxError: Inline Babel script: Adjacent JSX elements must be wrapped in an enclosing tag
*/

2. Fragment

  • 특정 태그로 묶고 싶지 않다면 태그로 묶어 반환할 수 있습니다.
  • 해당 태그로 묶을 경우 렌더링시 Fragment태그는 제거됩니다.
render() {
  return (
    <div>
      <div>1</div>
      <div>2</div>
    </div>
  );
}
/**
* Result
* <div id="app">
*   <div>
*     <div>1</div>
*     <div>2</div>
*   </div>
* </div>
*/

render() {
  return (
    <React.Fragment>
      <div>1</div>
      <div>2</div>
    </React.Fragment>
  );
}
/**
* Result
* <div id="app">
*   <div>1</div>
*   <div>2</div>
* </div>
*/

3. Portal

  • ReactDOM.createPortal 메서드를 이용하여 다른 DOM의 서브트리로 만들수 있습니다.
  • 반드시 리턴 할 경우에만 적용됩니다.
  • 일반 렌더링값과는 달리 해당 DOM의 하위 노드를 제거후 렌더링하는것이 아니라 서브 노드로 추가가 됩니다.
// 본 컴포넌트가 속한 부모 노드 하위가 아닌 #portal 노드 하위에 그려집니다. 
render() {
    let domNode = document.querySelector("#portal");
    return ReactDOM.createPortal(
        <div>Portal</div>,
        domNode
    );
}
// 아래와 같은 경우 아무것도 그려지지 않습니다.    
render() {
    let domNode = document.querySelector("#portal");
    ReactDOM.createPortal(
        <div>Portal</div>,
        domNode
    );
    return true;
}

Portal의 경우 컴포넌트가 속하지 않은 노드에 렌더링을 할 수 있게 하는데 다음과 같은 경우에 사용하기 유용합니다.
ex) dialog, hovercard, tooltips …
자세한 가이드는 React Portals 문서를 참고하세요.

4. String, Number

  • 문자열이나 숫자를 반환 할 수 있으며 TextNode로 렌더링 됩니다.
render() {
  return 'Text';
}
/**
* Result
* <div id="app">Text</div>
*/

5. boolean, null

  • boolean(true/false)값이나 null값 역시 반환 가능합니다.
  • 반환시 화면에 아무것도 안그리는 것으로 보일수 있으나 사실상 공백을 그려줍니다. 렌더링되는 요소 하위에 다른 요소가 존재한다면 삭제 됩니다.
  • 심지어 undefined도 가능합니다. 테스트 해보면 에러는 안나지만 공식문서에 undefined는 명시되 있지 않습니다.

6. Array

  • 배열을 반환할 수 있습니다.
  • 배열 요소들은 렌더링 가능한 모든 타입이 가능합니다. (function은 불가능합니다.)
  • 컴포넌트 배열을 렌더링 할 경우 어떤 원소에 변동이 있는지 알아내기 위해 각 원소에 고유 key가 포함되어야 합니다.
render() {
  return [
    "start",
    1234,
    false,
    null,
    <React.Fragment key="frag"><div key="1">1</div><div key="2">2</div></React.Fragment>,
    <b key="3">{this.state.status}</b>,
    ReactDOM.createPortal(
      <div>Portal</div>,
      this.props.portal
    )
  ];
}

constructor(props)

constructor(props) {
  super(props);
  // Don't call this.setState() here!
  this.state = { counter: 0 };
  this.handleClick = this.handleClick.bind(this);
}

컴포넌트 생성자로 생성시 맨 처음에 실행하게 되는데 props를 인자로 받는데 React.Component를 상속했을 경우 반드시 super(props);를 호출해야합니다.
그리고 constructor는 유일하게 this.state 를 직접 할당하는 메서드입니다. 이 메서드 안에서는 setState()를 호출하지 말아야 합니다. 호출시 아래와 같은 오류가 발생합니다.
그 외 이벤트를 해당 인스턴스로 바인드 하는 등의 작업을 할 수 있습니다.

Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to `this.state` directly or define a `state = {};` class property with the desired state in the YourApp component.

componentDidMount()

Component가 DOM트리에 마운트 될 때 실행되며 일반적으로 DOM노드가 필요한 초기화 작업은 본 메서드에서 실행해야 합니다. 또한 여기서 실행한 트리거나 이벤트, 비동기 작업등은 componentWillUnmount메서드에서 만드시 해제 해주는 것이 좋습니다.
componentDidMount() 메서드에도 안티패턴이 존재하는데 아래와 같은 케이스입니다.

componentDidMount() {
  ...
  this.setState({ ... });
}

위와 같이 호출 가능하나 화면을 업데이트 하기 전에 추가 렌더링이 발생하므로 뷰에는 중간 상태를 나타내지 않으며 성능상 이슈를 발생하기도 합니다.
하지만 DOM에 렌더링 되기 전에 크기 측정이 필요 할 경우 위와 같이 사용 할 수도 있습니다. (ex. 툴팁이나 모달창등을 만들때.. )

componentDidUpdate(prevProps, prevState, snapshot)

리렌더링 발생 이후 실행되며 초기 렌더링 시에는 실행되지 않습니다. DOM업데이트가 완료된 이후에 실행되므로 DOM관련 처리를 해도 됩니다.
그리고 이전 상태의 props/state값과 getSnapshotBeforeUpdate()메서드를 통해 전달 받은 데이터가 넘어오기 때문에 이전 상태와 현 상태의 변화에 따른 네트워크 작업을 하기에 좋습니다.
단, 주의해야할 점은 본 메서드에서 setState()를 실행할 수 있으나 제약조건없이 실행 할 경우 무한루프에 빠질 수 있습니다.

componentWillUnmount()

Component가 DOM에서 제거되고 파기 되기 직전에 호출 됩니다. 본 Component에서 구독한 이벤트나 트리거등을 제거할때 사용되며 Component가 리렌더링 되지 않기 때문에 setState()를 호출 할 수 업습니다.

2. Rarely Used Lifecycle Methods

shouldComponentUpdate(nextProps, nextState)

본 메서드에서 리렌더링 여부를 결정하는 메서드로 새로운 props/state 데이터를 받을 경우 동작하며, 초기 렌더링시나 forceUpdate()를 통한 리렌더링시에는 동작하지 않습니다.
재정의 하지 않을 경우 기본적으로는 모든 변경시마다 리렌더링을 실행하며 false를 반환 할 경우 render() 메서드를 실행하지 않습니다.
shouldComponentUpdate메서드는 보통 성능 최적화를 위해 특정 값의 변경에 따라서만 리렌더링을 조절가능하나 버그를 양산하기 쉽기 때문에 대신 React.PureComponent를 사용할 것을 권장하고 있습니다.
PureComponent는 shouldComponentUpdate()메서드가 이미 구현되어 있는 클래스로 React.Component 대신 상속 받아 사용할 경우 props/state의 변화시 얕은 비교를 통해 변경된 것이 있을 경우에만 리렌더링을 해줍니다. PureComponent및 성능 최적화에 관련된 내용은 아래 링크를 참고하세요.

리액트 성능 향상 시키기 - React.PureComponent : https://wonism.github.io/react-pure-component/
Optimizing Performance : https://reactjs.org/docs/optimizing-performance.html

getDerivedStateFromProps(nextProps, prevState)

React v16.3이후 새로 생긴 메서드로 모든 render()메서드 실행 전에 시작됩니다.
업데이트가 필요한 state값을 반환하거나 null을 반환해야 하고 본 메서드는 static 메서드로 Component에 직접 접근 할 수 없습니다.
레퍼런스 문서에는 getDerivedStateFromProps 메서드의 용도를 오직 props의 변화에 따른 state 상태 변화를 위한 용도로만 정의하고 있습니다.
이를 위해 일부 개발자가 양산하는 아래와 같은 버그를 막는 용도로 설명하고 있으며 더 자세한 내용은 You Probably Don’t Need Derived State에서 확인 할 수 있습니다.

constructor(props) {
 super(props);
 // Don't do this!
 this.state = { color: props.color };
}

위 예제는 props값을 직접 참조함으로써 props값이 변할 때 마다 state에 영향을 미치길 바라지만 실제로는 변경되지 않습니다.
하지만 아래와 같이 getDerivedStateFromProps 메서드를 이용하면 props값이 변경시마다 getDerivedStateFromProps 메서드를 호출하기 때문에 state값을 변경 가능합니다.

static getDerivedStateFromProps(nextProps, prevState) {
  if (nextProps.status != prevState.status) {
    return { status : nextProps.status };
  }
  
  return null;
}

getSnapshotBeforeUpdate(prevProps, prevState)

React v16.3이후 새로 생긴 메서드로 render()메서드 호출 이후 DOM트리에 반영전에 호출 됩니다.
특정 동작에 따른 Component의 변화가 적용 되기전에 이전 scroll position와 같은 이전 상태의 snapshot을 남겨 넘길수 있는데 이 때 리턴된 값은 componentDidUpdate() 메서드에 전달 됩니다. 재정의 하지 않을 경우 기본값은 null이 리턴됩니다.

잘못 사용하는 케이스로 본 메서드에서 현재 렌더링된 DOM의 데이터를 읽어들이는 케이스가 있습니다.

getSnapshotBeforeUpdate(prevProps, prevState) {
  if (prevProps.list.length < this.props.list.length) {
    const list = this.listRef.current;
    return list.scrollHeight - list.scrollTop;
  }
  return null;
}

위와 같이 사용할 경우 render과정과 commit과정 사이의 딜레이가 존재하기 때문에 원하는 결과를 얻을수 없습니다. 본 메서드에서는 과거 상태만을 반환하고 componentDidUpdate()메서드에서 처리하는 것이 좋습니다.

3. Example

위 생명주기를 테스트 해 볼수 있는 샘플 코드입니다.

4. 그 외…

Error Handling : https://reactjs.org/docs/error-boundaries.html