redux toolkit 없이 전역 상태 관리 하는 방법을 알아봅니다. 현재는 toolkit 사용을 권장하고 있어, 오늘날에 사용되는 코드는 아닙니다.

redux 기본 라이브러리로 전역상태 관리하기 (without toolkit, 레거시)

 

 목차

 

1. redux란?

2. redux의 구성

3. 장점

4. 단점

5. 구현

6. 구현된 코드로 보기

 

1. redux란?

  • react에서 사용되는 전역 상태 관리 라이브러리
  • 기존의 하향식 전달에서 벗어나 어디서든 상태를 참조, 변경을 가능하게끔 함

2. redux의 구성

  • state
    • redux에서 관리하는 상태 정보

 

  • action
    • 상태를 변경하는 요청
    • 어떠한 상태 변경을 하겠다를 타입에 명시
    • ex) 이름을 변경시키겠다, 나이를 변경시키겠다

 

  • reducer
    • 이전 state와 action을 받아 새로운 상태를 반환하는 함수
    • 액션에 담긴 타입에 맞춰 로직 실행
    • 상태를 변경시키는 로직이 담겨 있음

 

  • store
    • state, action, reducer 모두를 관리하는 중앙 저장소
    • action을 reducer로 전달하여 상태 변경을 적용

 

  • dispatch
    • action을 store로 전달하는 메서드
redux 작동 과정

3. 장점

  • 쉬운 디버깅
    • redux devtools를 사용하면 디버깅이 쉬움

 

  • 높은 점유율
    • redux는 다른 상태 관리 라이브러리와 비교해 점유율이 높음
    • 따라서 관련 자료들을 쉽게 찾을 수 있음

 

  • middleware 지원
    • middleware는 reducer가 action을 처리하기 전에 추가적인 로직을 가능하게 해줌
    • 비동기 작업, 인증 등 추가적인 작업을 처리가능

 

4. 단점

  • boilerplate (작성해야 하는 코드가 많음)
    • redux를 사용하기 위해 기본적으로 작성해야 하는 코드가 많은 편
    • redux-toolkit 등의 라이브러리를 통해 코드를 줄일 수 있지만, 그럼에도 다른 상태 관리 라이브러리로 보다는 코드가 긴편

 

  • 비동기 작업 복잡
    • 비동기 작업을 처리하기 위해 Redux Thunk, Redux Saga 등 middleware를 사용할 수 있지만, 이 역시 다른 상태 관리 라이브러리 보다 사용법이 다소 복잡한 편

5. 구현

  • reducer 설치

 

npm i redux react-redux redux-devtools-extension

 

  • reducer를 작성
    • 초기 상태 값을 작성
    • reducer 내부에 각 액션에 따라 어떠한 로직을 실행할 지 작성
    • action을 작성, payload는 action의 parameter를 의미
    • 생성한 reducer와 action을 export

 

// store/reducers/infoReducer.js

// 기본 상태 값을 설정
const initialState = {
  name: "홍길동",
  age: 20,
};

// reducer를 작성
// 각 액션에 따라 어떠한 로직을 실행할 지 작성
const infoReducer = (state = initialState, action) => {
  switch (action.type) {
    case "CHANGENAME":
      return { ...state, name: action.payload };
    case "CHANGEAGE":
      return { ...state, age: action.payload };
    default:
      return state;
  }
};

// 액션 작성

// changeName 액션 작성
// payload는 action에 담긴 parameter를 의미
const changeName = (value) => ({
  type: "CHANGENAME",
  payload: value,
});

// changeAge 액션 작성
const changeAge = (value) => ({
  type: "CHANGEAGE",
  payload: value,
});

// 생성한 reducer와 action export
export { infoReducer, changeName, changeAge };

 

 

  • rootReducer 작성
    •  combineReducers를 통해 생성한 reducer를 모두 묶어줌
    • comibineReducers에서 reducer에 지정한 키를 통해 상태를 불러 올 수 있음 (useSelector 부분 참고)

 

// store/reducers/rootReducer.js

import { combineReducers } from "redux";
import { infoReducer } from "./infoReducer";

// 생성한 리듀서를 하나로 묶어 줌
const rootReducer = combineReducers({

// state에서 해당 상태를 불러올 때 state.info를 통해 접근할 수 있음
  info: infoReducer,
});

export default rootReducer;

 

 

  • store 작성
    • createStore은 현재 사용을 권장하지 않음. 현재 코드에서는 redux toolkit을 사용하지 않기에 사용
    • store에 rootReducer 추가
    • composeWithDevTools()를 적용하여 redux 개발자 도구 사용 가능

 

import { createStore } from "redux";
import rootReducer from "./reducers/rootReducer";
import { composeWithDevTools } from "redux-devtools-extension";

// store를 생성
// createStore은 현재 권장되지 않는 방법, 앞으로는 toolkit의 configureStore를 사용합시다.
// store에는 rootReducer를 넣어줌
// composeWithDevTools를 넣어 redux 스토어의 변화를 확인할 수 있음
const store = createStore(rootReducer, composeWithDevTools());

export default store;

 

  • redux를 사용하고자 하는 컴포넌트 최상단에 store 적용

 

// main.jsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store/store";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
  
    {/* redux를 이용하고자 하는 컴포넌트 최상단에 store를 적용 */}
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

 

  • redux 사용하기
    • useSelector로 store 상태 불러오기
    • useDispatch로 dispatch 사용하기
    • dispatch를 통해 action을 store로 전달하기

 

import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { changeAge, changeName } from "./store/reducers/infoReducer";

function App() {

  // useSelector를 통해 store의 상태를 불러올 수 있음
  // rootReducer의 할당한 키를 통해 불러올 수 있음
  const info = useSelector((state) => state.info);

  // useDispatch에 action을 이용하면 상태 변화 로직을 실행할 수 있음
  const dispatch = useDispatch();

  const [name, setName] = useState("홍길동");
  const [age, setAge] = useState(20);

  const handleInputName = (e) => {
    setName(e.target.value);
  };

  const handleInputAge = (e) => {
    setAge(e.target.value);
  };

  const submitName = () => {
  
    // action을 dispatch를 통해 store로 전달
    // changeName에 해당하는 상태 변화 로직 실행
    dispatch(changeName(name));
  };

  const submitAge = () => {
  
    // action을 dispatch를 통해 store로 전달
    // changeAge에 해당하는 상태 변화 로직 실행
    dispatch(changeAge(age));
  };

  return (
    <div className="App" style={{ display: "flex", flexDirection: "column" }}>
      <div>{`현재 이름은 ${info.name} 입니다.`}</div>
      <div>{`현재 나이는 ${info.age}세 입니다.`}</div>
      <div>
        <label>
          이름
          <input onChange={handleInputName} />
        </label>
        <button onClick={submitName}>이름 변경</button>
      </div>
      <div>
        <label>
          나이
          <input type="number" onChange={handleInputAge} />
        </label>
        <button onClick={submitAge}>나이 변경</button>
      </div>
    </div>
  );
}

export default App;

 

  • devtools 사용
    • chrome extension 에서 redux devtools 설치 
    • 현재 state와 실행 되었던 action 확인
     

redux devtools 설치

 

redux devtools 실행

 

좌측 action, 우측 state 확인

6. 구현된 코드로 보기

'react > 상태관리' 카테고리의 다른 글

redux toolkit으로 전역 상태 관리하기  (1) 2023.10.09
mobx 사용하기  (0) 2023.10.06

 캔버스는 웹 페이지에 그래픽을 그리는 데에 사용되며, 이미지, 그래프, 애니메이션 등 시각적 요소를 생성하고 조작할 수 있습니다. 이 포스팅에서는 캔버스를 이용해 2d 기본 도형을 그리는 방법을 알아보겠습니다.

캔버스로 기본 도형(2d) 그리기

 

 목차

 

1. 캔버스란?

2. 직선 그리기

3. 사각형 그리기

4. 삼각형 그리기

5. 곡선 그리기

6. 원 그리기

7. 구현된 코드로 보기

 

1. 캔버스란?

  • 웹 페이지에 그래픽을 그리는 데 사용
  • 이미지, 그래프, 애니메이션, 게임에 활용 가능
  • 빠른 그래픽 렌더링으로 고성능
  • 복잡한 시각적 효과, 애니메이션에 적용 가능

 

  • 캔버스 생성하기
    • canvas 생성 후, id를 이용해 canvas 변수에 할당하기
    • canvas에서 ctx 가져오기
   <canvas id="my-canvas1" width="400" height="400"></canvas>
   
   <script>
   	
    // canvas 변수에 할당하기
 	const canvas1 = document.getElementById("my-canvas1");
    
    // "context"의 약자로, 캔버스의 요소를 조작할 때 사용
	const ctx1 = canvas1.getContext("2d");
   </script>

2. 직선 그리기

  • 시작점, 끝점 좌표 정하기
  • 선 설정하기
  • 좌표 이으면서 선 그리기
const canvas1 = document.getElementById("my-canvas1");
const ctx1 = canvas1.getContext("2d");

// 시작점, 끝점 좌표 정하기
const startX = 50;
const startY = 50;
const endX = 100;
const endY = 100;

// 선 설정 정하기
ctx1.strokeStyle = "black";
ctx1.lineWidth = 2;

// 선 그리기 시작
ctx1.beginPath();

// 시작점 설정
ctx1.moveTo(startX, startY);

// 끝점 설정
ctx1.lineTo(endX, endY);

// 선 그리기
ctx1.stroke();

3. 사각형 그리기

  • 사각형 스타일 설정하기
  • 사각형 위치, 크기 설정하기
    • ctx.fillRect(x 좌표,  y 좌표, 가로 길이, 세로 길이)
    • fillRect는 색이 채워진 사각형, strokeRect는 테두리만 그려진 사각형이 그려짐
const canvas2 = document.getElementById("my-canvas2");
const ctx2 = canvas2.getContext("2d");

// 사각형 스타일 설정

// 채우기 색상
ctx2.fillStyle = "black"; 

// 테두리 색상
ctx2.strokeStyle = "black"; 

// 테두리 두께
ctx2.lineWidth = 2; 

// 사각형 그리기
// x 좌표, y 좌표, 가로 크기, 세로 크기
// 색깔 채워진 사각형
ctx2.fillRect(50, 50, 100, 100); 

// 테두리만 있는 사각형
ctx2.strokeRect(50, 150, 100, 100);

4. 삼각형 그리기

  • 삼각형의 세 꼭지점 좌표 정한 후 이어주기
const canvas3 = document.getElementById("my-canvas3");
const ctx3 = canvas3.getContext("2d");

ctx3.beginPath();

// 시작점
ctx3.moveTo(100, 50);

// 두번째 점
ctx3.lineTo(150, 150);

// 세번째 점
ctx3.lineTo(50, 150); 

// 경로 닫기
ctx3.closePath(); 

// 테두리 색상
ctx3.strokeStyle = "black"; 

// 테두리 두께
ctx3.lineWidth = 2; 

// 삼각형 채울 색상
ctx3.fillStyle = "blue"; 

// 삼각형 내부 색상 채우기
ctx3.fill(); 

// 삼각형 테두리 그리기
ctx3.stroke();

5. 곡선 그리기

  • quadraticCurveTo() 혹은 bezierCurveTo() 함수 사용
    • quadraticCurveTo(곡선 제어점 x 좌표, 곡선 제어점 y 좌표, 최종 x 좌표, 최종 y좌표)
    • bezierCurveTo(1차 곡선 제어점 x 좌표, 1차 곡선 제어점 y 좌표, 2차 제어점 x, 2차 제어점 y, 최종  x, 최종 y)
const canvas4 = document.getElementById("my-canvas4");
const ctx4 = canvas4.getContext("2d");

ctx4.beginPath();

// 시작점 정하기
ctx4.moveTo(50, 50);

// 두 개의 제어점을 사용한 곡선 그리기
// 첫번째로 꺾을 지점 x 좌표, y좌표, 2번째로 꺾을 지점 x 좌표, y 좌표, 최종 x 좌표, y 좌표
ctx4.bezierCurveTo(50, 100, 150, 20, 250, 50);

ctx4.strokeStyle = "black";
ctx4.lineWidth = 2;

// 곡선을 테두리로 그리기
ctx4.stroke();

6. 원 그리기

  • arc 메서드 사용
    • arc(중심 x 좌표, 중심 y좌표, 반지름, 원의 시작 각도, 원의 끝 각도)
    • 시작 각도, 끝 각도를 통해 원의 일부만 그릴 수도 있음
const canvas5 = document.getElementById("my-canvas5");
const ctx5 = canvas5.getContext("2d");

ctx5.beginPath();

// 중심 x 좌표,  중심 y, 좌표, 반지름, 원의 시작 각도, 원의 끝 각도
// 시작각도, 끝각도를 통해 원의 일부만 그릴 수도 있음
ctx5.arc(100, 100, 50, 0, 2 * Math.PI);

ctx5.fillStyle = "white";
ctx5.fill();
ctx5.strokeStyle = "black";
ctx5.lineWidth = 2;
ctx5.stroke();

7. 구현된 코드로 보기

'Set'은 중복된 값을 허용하지 않는 자료 구조 입니다. Set 자료구조를 사용하면 배열 내에서 중복된 값을 빠르게 제거할 수 있습니다.

Set 이용하여 배열에서 중복되는 값 제외하기

 

 목차

 

1. Set이란?

2. Set 사용법

3. Set으로 배열의 중복된 값 제거하기

 

1. Set이란?

  • Javascript에서 제공하는 내장 객체로, 중복된 값을 허용하지 않는 값들의 집합
  • 중복된 값을 허용하지 않음
  • 순서를 보장하지 않음(인덱스 없음)
  • 고유 메서드 존재 (add, delete, has, clear 등의 메서드 사용)

2. Set 사용법

  • add : 값을 추가
    - 이미 해당 값이 있다면 무시
const mySet = new Set();
mySet.add(1);
mySet.add(2);
mySet.add(2); // 이미 중복된 값이 있으므로 무시됨
console.log(mySet); // Set { 1, 2 }
  • delete: 값을 삭제
    - 해당 요소가 없다면 무시
const mySet = new Set([1, 2, 3]);
mySet.delete(2); // 2를 제거
mySet.delete(4); // Set에 없으므로 아무런 작업도 하지 않음
console.log(mySet); // Set { 1, 3 }
  • has: 해당 값이 있는지 확인
    - 있으면 true, 없으면 false 반환
const mySet = new Set([1, 2, 3]);
console.log(mySet.has(2)); // true
console.log(mySet.has(4)); // false
  • clear: 모든 요소 제거
const mySet = new Set([1, 2, 3]);
mySet.clear(); // Set을 비움
console.log(mySet); // Set {}

3. Set으로 배열의 중복된 값 제거하기

  • 해당 배열 Set으로 변환
  • 생성한 Set 다시 배열로 변경
const arr = [1, 2, 1, 4, 2];
const mySet = new Set(arr);
const result = [...mySet];
console.log(result); // [1, 2, 4]

debounce와 throttle은 연속된 사용자의 입력을 처리하는 기술 입니다. debounce는 특정 기간 내의 입력 중 가장 마지막 입력을 처리하고, throttle은 특정 기간 내에 입력이 한번 되면, 이후 받는 다른 입력들은 모두 무시합니다. 이를 통해 성능 최적화 혹은 연속 입력으로 인한 에러를 방지할 수 있습니다.

debounce, throttle로 연속 입력 관리하기 (with react)

 

목차

 

1. debounce란?

2. throttle이란?

3. debounce, throttle 구현하기

4. 구현된 코드로 보기

 

1. debounce란?

  • 연속 이벤트 발생시, 일정한 시간 동안 이벤트가 연속으로 발생 할 시, 마지막 이벤트 발생에만 핸들러를 호출
  • 주로 텍스트 입력, 스크롤 이벤트(새로운 데이터 호출)에 적용
  • 불필요한 처리 혹은 네트워크 요청 방지
  • ex) 사용자가 검색어를 입력할 때 검색 결과 업데이트

2. throttle이란?

  • 일정한 주기로 이벤트 핸들러를 호출
  • 일정 시간 동안 이벤트가 연속 발생하더라도, 맨 처음 한번만 핸들러를 호출하고, 다음 주기에 다시 실행 가능해짐
  • 스크롤 위치에 따라 이벤트를 발생할 경우 사용

3. debounce, throttle 구현하기

  • debounce 함수 작성
    - setTimeout 조작을 위한 변수생성
    - delay 시간 안에 새로운 입력이 들어오면 기존에 있던 setTimeout 실행 취소
    - 새로운 setTimeout  등록

 

/utils/debounce.js

const debounce = (func, delay) => {
  // setTimeout 조작을 위한 변수 생성
  let timerId;

  return (...args) => {
    // delay 시간 안에 새로운 입력이 들어오면 기존에 있던 setTimeout 실행 취소
    clearTimeout(timerId);

    // 새로운 setTimeout 등록하기
    timerId = setTimeout(() => {
      func(...args);
    }, delay);
  };
};

export default debounce;

 

  • throttle 함수 작성
    - setTimeout 조작을 위한 변수 추가
    - delay 시간 안에 새로운 입력이 들어오지 않았을 경우에만 실행, delay 기간 내일 경우 입력 무시
    - delay 시간 이후에 setTimeout 변수 초기화 시켜서 재실행 가능하게끔 처리

 

// utils/throttle.js

const throttle = (func, delay) => {
  // setTimeout 조작을 위한 변수 추가
  let timerId;

  return (...args) => {
    // delay 시간 안에 새로운 입력이 들어오지 않았을 경우에만 실행, delay 기간 내일 경우 입력 무시
    if (!timerId) {
      timerId = setTimeout(() => {
        func(...args);

        // delay 시간 이후에 setTimeout 변수 초기화 시켜서 재실행 가능하게끔 처리
        timerId = undefined;
      }, delay);
    }
  };
};

export default throttle;

 

  • debounce, throttle 적용하기
    - 이벤트 핸들러 내부에서 debounce, throttle 사용 함수 정의하지 않도록 주의
    -  핸들러 내부에서 해당 함수를 정의할 경우, setTimeout 자체가 여러개 생겨 동작 오류가 남

 

import { useState } from "react";
import "./App.css";
import debounce from "./assets/utils/debounce";
import throttle from "./assets/utils/throttle";

function App() {
  const [search1, setSearch1] = useState("");
  const [search2, setSearch2] = useState("");

  // debounce 적용
  const debounceSearch = debounce((value) => setSearch1(value), 1000);

  // throttle 적용
  const throttleSearch = throttle((value) => setSearch2(value), 1000);

  // parameter 넣어주기
  const handleDebounce = (e) => {
    debounceSearch(e.target.value);
  };

  // parameter 넣어주기
  const handleThrottle = (e) => {
    throttleSearch(e.target.value);
  };

  return (
    <div className="App">
      <div className="title">{`< debounce 적용시켰을 때 (1초) >`}</div>

      {/* input에 이벤트 적용 */}
      <input onChange={handleDebounce}></input>
      <div>{`현재 입력된 값은 ${search1} 입니다.`}</div>
      <hr />
      <div className="title">{`< throttle 적용시켰을 때 (1초) >`}</div>

      {/* input에 이벤트 적용 */}
      <input onChange={handleThrottle}></input>
      <div>{`현재 입력된 값은 ${search2} 입니다.`}</div>
      <div className="throttle"></div>
    </div>
  );
}

export default App;

4. 구현된 코드로 보기

 카드를 뒤집는 애니메이션을 구현합니다. 앞, 뒤로 이루어진 카드를 만드는 방법과, 뒤집는 애니메이션에 대해 알아보겠습니다.

카드 뒤집는 애니메이션 구현하기(with react)

 

목차

 

1. 기능 설명

2. 적용법

3. 적용된 코드로 보기

 

1. 기능 설명

  • 앞, 뒤 내용이 다른 카드를 뒤집어 각기 다른 내용을 보여주는 애니메이션을 구현
  • 앞, 뒤로 구성된 카드를 구현하는 방법을 알아봄
  • 카드를 뒤집는 방법을 알아봄

2. 적용법

  • Card 컴포넌트를 작성
    • 카드를 뒤집는 여부를 관리하는 상태를 작성
    • 마우스가 카드 공간에 진입했을 때, 발생하는 이벤트 함수를 작성
    • 카드를 뒤집는 여부 값에 따라 특정 class를 부여할 지 말 지 정함
    • 카드의 앞면과 뒷면은 front, back 클래스로 구분
 import React, { useState } from "react";

const Card = () => {
  const [filpped, setFlipped] = useState(false);

  // 마우스를 카드에 올렸을 때 실행되는 이벤트
  const handleMouseEnter = () => {
    setFlipped(true);
  };

  // 마우스가 카드에서 벗어났을 때 실행되는 이벤트
  const handleMouseLeave = () => {
    setFlipped(false);
  };

  return (
    <div

      // filpped 값에 따라 flipped 클래스 적용할 지 말지 선택, true일 시, flipped 클래스 적용
      className={`card ${filpped ? "flipped" : ""}`}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >

      {/* 앞면과 뒷면은 똑같은 곳에 작성하고 css를 통해 앞, 뒷면 레이아웃을 적용시킴 */}
      <div className="card-inner card-front">앞</div>
      <div className="card-inner card-back">뒤</div>
    </div>
  );
};

export default Card;
  • card
    • card의 가장 상위 태그에 position: absolute; 적용, 하위 태그 card-inner의 기준점으로 삼기 위해서임
    • 자식 요소에 3d 효과 적용
    • transition으로 뒤집는 효과 설정 추가
.card {
  margin: 8px;
  width: 50px;
  height: 80px;

  /*하위 태그 card-inner가 positon: aboluste;를 사용하므로 기준점으로 삼기 위해 relative 적용 */
  position: relative;

  /*자식 요소에 3D 공간 효과 적용, 기본 값은 flat으로 3D 효과를 적용하지 않음 */
  transform-style: preserve-3d;

  /*카드 뒤집는 효과 동작 시간 설정 */
  transition: transform 0.5s;
}
  • card-inner
    • absolute 사용해서 하위 태그 card-front, card-back이 겹쳐서 그려지도록 함
    • 3d 효과가 적용되었을 때 현재 보이는 화면의 뒷면이 보이지 않게끔 설정
    .card-inner {
    width: 100%;
    height: 100%;
    
    /*card-front와 card-back이 겹쳐야 하므로 사용, absolute 사용을 안할 시 card-back이 card-front 아래에 렌더링 됨 */
    position: absolute;
    
    /*뒷면을 보이게 할 것인지에 대한 설정, 해당 값을 없앨 시 앞면이 뒷면에 덮혀서 보여지지 않음 */
    backface-visibility: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 24px;
    font-weight: bold;
    }
  • card-back
    • 카드 반대편에 그려지게 끔 설정
 .card-back {
  background-color: #e74c3c;
  color: white;

  /*카드 반대편에 그려지게 됨, 만약 해당 속성이 없을 시 앞면과 똑같은 공간에 그려져서 카드 뒷면이 텅 비게 됨 */
  transform: rotateY(180deg);
}
  • flipped
    • 마우스를 올렸을 시 반대편으로 뒤집는 효과 적용
.flipped {
  /*마우스를 올렸을 시 반대편으로 뒤집는 효과*/
  transform: rotateY(180deg);
}
  • 전체 css 코드
.App {
  display: flex;
}

.card {
  margin: 8px;
  width: 50px;
  height: 80px;

  /*하위 태그 card-inner가 positon: aboluste;를 사용하므로 기준점으로 삼기 위해 relative 적용 */
  position: relative;

  /*자식 요소에 3D 공간 효과 적용, 기본 값은 flat으로 3D 효과를 적용하지 않음 */
  transform-style: preserve-3d;

  /*카드 뒤집는 효과 동작 시간 설정 */
  transition: transform 0.5s;
}

.card-inner {
  width: 100%;
  height: 100%;

  /*card-front와 card-back이 겹쳐야 하므로 사용, absolute 사용을 안할 시 card-back이 card-front 아래에 렌더링 됨 */
  position: absolute;

  /*뒷면을 보이게 할 것인지에 대한 설정, 해당 값을 없앨 시 앞면이 뒷면에 덮혀서 보여지지 않음 */
  backface-visibility: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 24px;
  font-weight: bold;
}

.card-front {
  background-color: #3498db;
  color: white;
}

.card-back {
  background-color: #e74c3c;
  color: white;

  /*카드 반대편에 그려지게 됨, 만약 해당 속성이 없을 시 앞면과 똑같은 공간에 그려져서 카드 뒷면이 텅 비게 됨 */
  transform: rotateY(180deg);
}

.flipped {
  /*마우스를 올렸을 시 반대편으로 뒤집는 효과*/
  transform: rotateY(180deg);
}

3. 적용된 코드로 보기

'css > 애니메이션' 카테고리의 다른 글

진동 애니메이션 구현하기  (0) 2023.10.07

 custom hook은 특정 상태 관리를 재사용하기 위해 사용자 임의로 만드는 hook입니다. 동일한 상태 관리 로직을 여러 컴포넌트에서 사용해야 할 때 유용합니다.

custom hook 패턴 적용하기

 

목차

 

1. custom hook이란?

2. 특징

3. 적용법

4. 적용된 코드로 보기

 

1. custom hook이란?

  • 상태 관리, 변경 로직을 재사용하기 위한 사용자 정의 hook
  • 여러 컴포넌트에서 동일한 상태 관리 로직을 사용할 때 유용

2. 특징

  • "use"접두사 사용
    • 작성한 custom hook은 "use" 접두사를 붙여 custom hook이라는 것을 명시

 

  • 기존 react hook 결합 가능
    • useState, useEffect 등 기존 hook과 결합하여 상태 관리 가능

 

  • 재사용 용이
    • 여러 컴포넌트에서 동일한 상태 관리 로직이 필요할 때 재사용 용이
    • 전역 상태 관리와는 달리 여러 컴포넌트에서 동일한 상태를 유지할 필요가 없을 때 사용
    • 전역 상태와 동일한 상태 관리 로직을 구분하여 생각할 것

 

  • 유지 보수성 향상
    • 하나의 상태에 대한 코드들을 모두 custom hook에서 관리하므로 문제 발생시 살펴봐야할 부분을 한정시킬 수 있음

3. 적용법

  • "use" 접두사를 붙여 custom hook 파일 생성
    • useState로 관리하고자 하는 상태 생성
    • 상태 관리 함수 생성
    • 외부에서 사용하고 하는 변수 반환

 

//useCounter.jsx

import React, { useState } from "react";

const useCounter = () => {

// 관리하고자 하는 상태 생성
  const [count, setCount] = useState(0);

// 상태 관리 함수 생성
  const handlePlus = () => {
    setCount((prev) => prev + 1);
  };

  const handleMinus = () => {
    setCount((prev) => prev - 1);
  };

// 외부에서 사용하고자 하는 변수 반환
  return { count, handlePlus, handleMinus };
};

export default useCounter;

 

  • 해당 상태를 사용하고자 하는 컴포넌트에서 반환 값 할당 받아서 사용

 

import "./App.css";
import useCounter from "./customHooks/useCounter";

function App() {

// 반환 값 할당받기
  const { count, handlePlus, handleMinus } = useCounter();

  return (
    <div className="App" style={{ display: "flex" }}>
      <button onClick={handleMinus}>-</button>
      {count}
      <button onClick={handlePlus}>+</button>
    </div>
  );
}

export default App;

4. 적용된 코드로 보기

'react > 디자인 패턴' 카테고리의 다른 글

container presenter 패턴 알아보기  (0) 2023.10.07

 container presenter는 react에서 쓰이는 디자인 패턴입니다. container는 상태, 데이터 관련 로직을, presenter는 UI 구성을 담당합니다.

container presenter 패턴 알아보기

 

목차

 

1. container presenter 패턴이란?

2. 장점

3. 단점

4. 적용법

5. 구현된 코드로 보기

 

1. container presenter 패턴이란?

  • 하나의 컴포넌트를 container, presenter로 나눠서 작성
  • container는 상태 혹은 데이터 로직을 관리하는 역할
  • presenter는 화면의 레이아웃, 스타일을 담당. container로부터 넘겨 받은 데이터를 렌더링
  • 따라서 container가 presenter를 감싸는 구조

2. 장점

  • 유지보수성
    • UI 담당하는 코드와 데이터 로직 코드가 분리되어, 하나의 파일에 담겨있는 코드가 줄어들어 코드를 이해하기 쉬워짐

 

  • 테스트 용이
    • 데이터 로직 테스트와 UI 테스트를 분리해서 진행 가능

3. 단점

  • 작은 규모의 프로젝트일 경우 오히려 복잡해 질 수 있음

 

  • hooks의 등장으로 상태, 생명 주기 관리가 간편해져 도입 의도 퇴색
    • 과거 클래스형 컴포넌트에서는 상태, 생명 주기 관리를 위한 코드가 너무 길었음
    • 그러나 hook, 함수형 컴포넌트의 등장으로 코드가 많이 간결해짐
    • hook을 사용하면 container와 presenter로 나누는 것보다 간결하게 표현할 수도 있으므로 무조건적인 사용은 지양해야함
    • 즉, 상황에 맞춰 사용할 것

4. 적용법

  • 만들고자 하는 컴포넌트 폴더를 생성
  • container 컴포넌트 생성
    • 상태, 상태관리 함수 생성 후 presenter 컴포넌트로 내려 줌
/components/counter/CounterContainer.jsx

import React, { useState } from "react";
import CounterPresenter from "./CounterPresenter";

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

  const handlePlus = () => {
    setCount((prev) => prev + 1);
  };

  const handleMinus = () => {
    setCount((prev) => prev - 1);
  };

  return (
    <CounterPresenter
      count={count}
      handlePlus={handlePlus}
      handleMinus={handleMinus}
    ></CounterPresenter>
  );
};

export default CounterContainer;
  • presenter 컴포넌트 생성
    • container에서 받은 상태, 상태관리 함수 적용
    • UI 작성
import React from "react";

const CounterPresenter = ({ count, handlePlus, handleMinus }) => {
  return (
    <div style={{ display: "flex" }}>
      <button onClick={handleMinus}>-</button>
      {count}
      <button onClick={handlePlus}>+</button>
    </div>
  );
};

export default CounterPresenter;
  • 사용하고자 하는 곳에서 container 불러오기
import "./App.css";
import CounterContainer from "./components/counter/CounterContainer";

function App() {
  return (
    <div className="App">
      <CounterContainer></CounterContainer>
    </div>
  );
}

export default App;

5. 구현된 코드로 보기

'react > 디자인 패턴' 카테고리의 다른 글

custom hook 패턴 적용하기  (0) 2023.10.07

 진동하는 애니메이션을 만듭니다. 에러가 났을 경우 해당 애니메이션을 사용하여 사용자에게 에러를 쉽게 인지할 수 있도록 합니다.

진동 애니메이션 구현하기

 

목차

 

1. 기능 소개

2. 구현 방법

3. 구현된 코드로 보기

 

1. 기능 소개

  • 의도치 않은 동작 시 사용자에게 이를 알리기 위해 시각적으로 눈에 띄는 애니메이션을 적용
  • 입력한 정보가 적절하지 않거나, 오류 메시지 등을 나타낼 때 사용 가능

2. 구현 방법

  • 회전, 역회전 애니메이션을 구현 후, 이를 반복
index.css

.modal {
  background: whitesmoke;
  position: absolute;
  width: 300px;
  height: 100px;
  border-radius: 8px;

  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 8px;
  animation: vibration 0.1s 3;  
  // 적용 애니메이션, 적용 시간, 반복 횟수
}


// 2도 회전, 2도 역회전 애니메이션 반복하기
@keyframes vibration {
  from {
    transform: rotate(2deg);
  }

  to {
    transform: rotate(-2deg);
  }
}

3. 구현된 코드로 보기

'css > 애니메이션' 카테고리의 다른 글

카드 뒤집는 애니메이션 구현하기(with react)  (0) 2023.10.07

 Docker와 github actions를 사용해 repository의 main branch가 업데이트 될 때마다 자동으로 이를 반영하여 배포를 진행하는 과정을 진행해보았습니다.

Docker 사용해서 자동 배포 하기(with springboot, aws ec2)

 

목차

 

1. Docker Hub 설치

2. Docker 이미지 생성 설정하기

3. Docker Hub에 이미지 만들기

4. Github Acitons로 push 할 때마다 이미지 자동 업데이트 하기

5. Docker 컨테이너가 이미지 동기화 하도록 만들기

6. 자동 배포 과정 간략 정리

 

1. Docker Hub 설치

2. Docker 이미지 생성 설정하기

  • 현재 사용중인 JDK 버전 맞춰 주기
  • Maven을 사용하냐, Gradle을 사용하냐에 따라 최종 build 파일의 위치가 다르므로 고려하여 설정할 것
  • root 위치에 생성할 것

 

// Dockerfile

// 자바 17버전 사용 중
FROM eclipse-temurin:17-jdk-alpine
ARG JAR_FILE

// Maven을 사용 중이므로 target 폴더 내부에 최종 build 파일 위치
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

3. Docker Hub에 이미지 만들기

  • Docker에 로그인
docker login
  • Docker 이미지 생성
    • '/'의 앞 부분은 본인의 Docker Hub 아이디를 넣어야 함
    • Docker를 로컬에서만 쓴다면 아이디 넣을 필요 없음
    • Dockerfile이 있는 경로에서 명령어 입력할 것, 그렇지 않을 시 경로를 명시해줘야 함

 

// Dokcerfile이 있는 곳에서 명령어를 실행했기 때문에 '.'으로 경로 지정 가능

docker build -t kahyungkim/noky:latest .

4. Github Acitons로 push 할 때마다 이미지 자동 업데이트 하기

  • 프로젝트 내용이 변경될 때마다, github 내에서 자동으로 Docker 이미지를 업데이트 하도록 함
  • Github의 Actions 탭으로 이동
  • New workflow 생성
  • 레파지토리에 새 내용이 push된 이후 실행하고 싶은 코드 작성
  • 현재 자신이 사용하고 있는 자바 버전 명시할 것
  • 주의) build를 actions에서 처리하지 않을 시 Docker 이미지에서 변경점을 못찾아 업데이트가 안 될 가능성이 있음
  • 따라서 build된 파일을 push 하는 것이 아닌, push된 파일들을 다시 build 하도록 과정을 만들 것

 

/docker-image.yml

name: Docker Image CI

// 해당 작업을 언제 실행 시키고 싶은지 설정
// main 브랜치에 'push'하거나 'pull request' 했을 때 동작
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

      // 자신이 사용하는 자바 버전 기입
    - name: Set up JDK 17
      uses: actions/setup-java@v2
      with:
        java-version: '17'
        distribution: 'temurin'

      // 경로가 root 폴더에서 시작되므로 front 폴더로 이동해 vue 프로젝트를 build
    - name: Build Vue project
      run: cd front && npm i && npm run build

      // 이전 과정에서 front 폴더에서 코드를 실행했더라도, 다음 과정에서는 root 폴더로 경로가 초기화 되므로 바로 backend 경로로 이동
    - name: Build Spring Boot JAR && Build the Docker image
      run: cd backend && mvn clean package -DskipTests && docker build -t kahyungkim/noky:latest .

      // Docker 로그인
    - name: Login to Docker Hub
      run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin

      // 새로 만들어진 Docker 이미지, Docker Hub에 업데이트
    - name: Push Docker image to Docker Hub
      run: docker push kahyungkim/noky:latest

5. Docker 컨테이너가 이미지 동기화 하도록 만들기

  • Docker Compose, Watchtower를 이용해 일정 주기로 이미지가 달라졌는지 확인하여, 달라졌을 시 업데이트 반영 후 컨테이너 재시작
    • EC2 인스턴스 접속
    • Docker와 Docker-compose 설치
sudo apt update
sudo apt install docker
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
  • docker-compose.yml 파일 작성
vi docker-compose.yml
  • 아래 내용 입력
  • 이미지 만들 때의 설정 외에, watchtower 설정 추가
/docker-compose.yml

version: "3"

services:
  app:
    image: kahyungkim/noky
    ports:
      - "8080:8080"
    working_dir: /app

  watchtower:
    container_name: watchtower
    restart: always
    image: containrrr/watchtower
    volumes:
      - '/var/run/docker.sock:/var/run/docker.sock'
    environment:
      TZ: Asia/Seoul
        // 이미지 변경점 확인 주기
        // "분 시 일 월 요일" 기준
        // 현재 값은 매 시간 정각 변경점 확인
      WATCHTOWER_SCHEDULE: "0 * * * *"

        // 이전 파일 삭제 여부
      WATCHTOWER_CLEANUP: "true"
      WATCHTOWER_DEBUG: "true"
  • docker-compose 명령어 사용해서 이미지를 컨테이너에 담아 실행
  • docker-compose.yml 파일이 있는 경로에서 실행할 것
docker-compose up

6. 자동 배포 과정 간략 정리

  • 깃허브 레파지토리의 main 브랜치에 push 혹은 pull request시 github actions 작동
  • actions 내에서 프론트엔드 build, backend build 후 이미지 생성하여 Docker Hub에 업데이트
  • 매 시간 정작, watchtower에서 이미지 변경점 감지
  • 이미지 업데이트 후 컨테이너 재시작
  • 변경점 반영된 프로젝트 배포

AWS의 EC2 인스턴스를 생성하는 법을 알아봅니다.

AWS EC2 인스턴스 만들기

 

목차

 

1. EC2 인스턴스 생성하기

2. 보안 그룹 생성

3. EC2 인스턴스 보안 그룹 변경

4. EC2 인스턴스 접속하기

 

1. EC2 인스턴스 생성하기

  • 운영체제 선택하기
  • 프리티어(무료) 지원하는 것 선택

 

운영체제 선택

 

 

  • 키페어 생성
  • RSA 유형, .pem 형식 선택
  • 생성한 인스턴스에 접속하려면 키 페어가 필요하므로 저장 위치 기억해 놓을 것

 

키 페어 생성

 

 

키페어 형식 설정

 

  • 용량 설정하기
  • 프리티어는 30기가 바이트를 제공하므로 해당 용량 이하로 설정할 것

 

최대 30GB로 설정

 

 

  • 인스턴스 생성

 

버튼을 눌러 인스턴스 생성

 

2. 보안 그룹 생성

 

  • 왼쪽 탭에서 보안 그룹 선택

 

보안그룹 탭 선택
  • 보안 그룹 생성 페이지 이동

 

보안 그룹 생성

 

  • 인스턴스 접근 허용 방법 설정하기
  • SSH, Anywhere-Ipv4 추가
  • HTTP, Anywhere-Ipv4 추가
  • HTTPS, Anywhere-Ipv4 추가

 

허용 방법 설정하기

 

3. EC2 인스턴스 보안 그룹 변경

  • 보안 그룹 변경 페이지 이동

 

보안 그룹 변경 페이지 이동

 

  • 생성한 보안 그룹으로 변경

 

보안 그룹 변경

 

4. EC2 인스턴스 접속하기

  • 연결 페이지 이동

 

연결 페이지로 이동

 

  • 생성한 키가 있는 경로로 이동 (.pem 확장자)
  • SSH 클라이언트 탭에 있는 명령어 입력
chmod 400 nokyssh.pem
ssh -i "nokyssh.pem" ubuntu@ec2-13-211-168-207.ap-southeast-2.compute.amazonaws.com

 

+ Recent posts