Queue
FIFO구조. 자바스트립트의 이벤트 루프에서 비동기 작업을 처리하는데 사용됨.

자바스크립트에서는 Microtask Queue, Macrotask Queue, Animation frame Queue가 있으며 이들 각각의 역할이 존재한다.
(추후 추가예정)


Heap
자바스크립트에서 객체를 선언할때 힙에 할당. 참조로 관리한다

Stack
LIFO구조. 함수호출 or 코드를 순차적으로 처리할때 사용된다.


다음 글에서는 자바스크립트의 이벤트 루프에 대해 장리해야겠다~.~

 

 

정적 타입 검사란 ? 타입스크립트가 기본적으로 제공하는 기능으로,

컴파일 단계에서 변수나 리턴값, 파라미터 등의 타입에러를 찾아주는 것을 말한다. 

 

동적 타입 언어인 javascript는 암묵적 타입 변환 등을 허용하기 때문에 서로 다른 타입간의 계산을 허용하는 경우도 있고,

그로인해 예기치못한 런타임 에러가 발생할 수도 있다. 

 

그러나 타입스크립트는 컴파일시에 모든 변수, 매개변수 등의 정적 타입 검사를 진행하기 때문에, 

런타임시 발생할 에러를 사전에 방지할 수 있다. 

 

 

예를 들어, 

 

자바스크립트의 경우에는 

let num = 30;
let strNum = "2";

let plus = num+strNum;
console.log(plus); // "302" 출력 ;

let minus = num - strNum;
console.log(minus); //28 출력

암묵적 타입 변환을 허용해서 덧셈 연산인 경우, 숫자를 문자열로 변환한 계산을 시도하고, 뺄셈 연산의 경우엔 문자열을 숫자로 바꾸는 것을 시도한다. 

 

그러나 타입스크립트는 위와 같은 암묵적 타입 변환을 허용하지 않고, 코드에서부터 타입 에러를 표시해준다. 

 

또한, 

tsconfig.json 파일을 수정해서 더욱 엄격한 모드로 변경하는 것도 가능하다. (any를 허용하지 않음)

타입 추론이란, 

타입을 코드상에 명시적으로 지정하지 않아도 컴파일러가 변수 or 상수의 값을 자동으로 추론해주는 것을 말한다. 

 

예를 들어서, 

const x = 22;

const y = 10;

const z = x+ y ;

일때 컴파일러는 z의 타입이 number임을 자동으로 추론한다.

 

재할당이 가능한 let의 경우에는,

 

let MyName = "Ann";

MyName = 223; //MyName의 타입을 string으로 추론중이므로 에러 발생!

 

이것은 함수의 리턴값이나 객체의 타입을 추론하는 경우에도 동일하다. 

 

그러나 const와 let의 타입 추론 방식은 조금 다르다. 

 

상수인 const 는 리터럴 타입, 변수인 let은 원시 타입 추론이 이루어진다. 

 

여기서 리터럴 타입이란, string, number 와 같이 넓은 의미의 타입이 아니라, 

const x = 30의 경우 x는 30 이라는 구체적인 리터럴 타입으로 추론되는것을 말한다.  

 

원시타입은 우리가 일반적으로 아는 string, number, boolean, undefined 등 포괄적으로 사용되는 타입을 말한다. 

 

 

재할당이 가능한 let은 넓은 범위의 원시 타입으로 추론되지만. 재할당이 불가능한 상수 const는 리터럴 타입으로 추론되는 것이다. 

나는 주로 업무를 할 때 styled-component와 emotion을 사용해 컴포넌트들을 관리해왔다. 

props를 넘겨줄 수 있어서 동적인 스타일을 적용하기 편리하고, 러닝커브가 낮아서 쉽게 접할 수 있었기 때문이다. 

 

그러나 최근들어 tailwind css를 쓰는 기업들이 많아지면서, 과연 어떤 부분이 css in js 보다 더 좋은지, 혹은 단점은 없는지 알고싶어졌다. ~,~ 

 

 

tailwind 사용방법은 공식 문서를 참고하자 

 

https://tailwindcss.com/docs/installation

 

Installation - Tailwind CSS

The simplest and fastest way to get up and running with Tailwind CSS from scratch is with the Tailwind CLI tool.

tailwindcss.com

 

 

아래에 내가 알게된 둘의 장단점과 차이에 대해 정리해보겠다. 

 

  • CSS in JS 
    • javascript 파일 내에서 스타일을 정의해서 사용한다. 대표적으로 Styled-component, Emotion이 있다. 
    • styled의 경우 const MyComponent = styled.div`...`; 태그를 변수 형태로 정의해서 사용한다.
    • emotion의 경우 const MyStyle = css``;  => <div css={MyStyle}/> css를 변수로 정의해서 css라는 속성으로 넣어준다.
    • props,state에 따라 간단하게 동적인 스타일을 적용할 수 있다. 
    • 러닝커브가 낮아서 쉽게 사용할 수 있다. 
    • 랜더링 시 javascript 파일 안에 style 태그로 들어간다. 
    • js 코드가 업데이트될때마다 함깨 렌더링 되기 때문에 랜더링 속도가 더 오래걸릴 수 있다. (일부 스타일은 브라우저에서 캐싱되기도 함)

 

 

  • Tailwind CSS
    • 랜더링 시 단일 css파일로 동작해 js 코드의 리랜더링 등에 영항을 받지 않는다. html에서 직접 css를 참조. 
    • 정적 css파일로 동작하기때문에 동적인 스타일을 적용하기위해서는 여러 클래스를 조건에따라 관리해야하는 불편함이 있다. (불가능한것은 아님! classnames 로 조건에 따른 복수의 클래스네임을 관리할 수 있다.)
    • 클래스기반의 캐싱을 사용 class name 안에 <a href="#" class="text-blue-500">+ 198 others</a> 기본적으로 제공하는 스타일을 넣거나,
    • 이렇게 커스텀 스타일을 정의해서 사용하는 것도 가능하다.  
module.exports = {
  theme: {
    colors: {
      'blue': '#1fb6ff',
      'pink': '#ff49db',
      'orange': '#ff7849',
    },
  }
}
------------------------------------------------
<div class="bg-blue text-orange p-4">
  custom text
</div>

   

 

우선 기본적으로 tailwind에서 제공하는 유틸리티 클래스 구조와 rem 단위에 익숙해질 필요가 있겠군... 

 

e.g ) className-"p-4" 는 위아래좌우에 1rem(보통 16px)의 패딩을 넣는다는 뜻,

        className="py-2.5 px-3"은 위아래 10px, 좌우 12px의 패딩을 넣는다는 뜻. 

 

       (1: 0.25rem  ==  4px)

커스텀 훅은 React 에서 기본적으로 제공하는 기본적인 훅(useState, useEffect, useCallback, useMemo, useContext 등)과 비슷한 방식으로 작동하는 사용자 정의 hook 이다.

 

useState나 useEffect를 내장하여 기능을 확장하거나, 프로젝트 전체에서 반복적으로 사용되는 로직 등을 모듈화하여 여러 컴포넌트에서 재사용할 수 있도록 하는 것이 커스텀 훅의 목적이다. 

 

 

[커스텀 훅을 쓸 때의 장점 ]

 

-  반복적으로 사용되는 로직의 코드 반복을 줄이고, 재사용성을 높일 수 있다.

:예를 들어 동일한 데이터를 여러 컴포넌트에서 사용해야 하는 경우, 데이터를 불러오는 로직을 커스텀훅으로 분리하면 된다.

 

-  UI를 구성하는 부분과, 비즈니스 로직을 구성하는 부분이 분리되어 유지보수가 용이해지고 공동 작업에 유리해진다. 

: 데이터를 요청하거나 수정작업하는 부분을 별도의 custom hook 파일로 분리하면 코드 분량 자체가 간결해져서 유지보수가 편하고 여러 사람이 동일한 코드를 반복해서 작성할 필요가 없어진다. 

 

 

 

그리고 커스텀 훅을 작성하기 위해서는 몇가지 규칙이 있다. 

 

1. 훅의 이름은 반드시 use로 시작해야한다. 

const useMyHook = ()=>{

//커스텀 훅 안에서 useState,useEffect등을 사용하는 것도 가능하다! 

return ..

}

 

 

2. 반복문이나, 조건문 안에서 사용해서는 안된다. 

.
.
if(...){
useMyHook(); //조건에 위배됨
}

for(... of ..){
useMyHook() // 이것도 안 됨! error 발생  
}

올바른 방식으로 작성된 커스텀 훅은 리액트에서 useState 와 같이 리액트 훅으로 인식되기때문에 

반복문과 조건문 안에서 사용할 경우 에러가 발생한다. 

 

 

3. 커스텀 훅을 사용하는 각 컴포넌트는 독립적인 상태를 가진다. 커스텀 훅을 호출하는 컴포넌트마다 별도의 state가 생성되고, 그 state들은 서로 값을 공유하지 않는다. 

 

그 예시로 간단한 카운터 커스텀 훅을 작성해봤다. 

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

  const increment = () => {
    setCount(count + 1);
  };

  const decrement = () => {
    setCount(count - 1);
  };

  
  return { count, increment, decrement };
};
/////////////////////////////////////////////////
const AComponent = () => {
  const { count, increment, decrement } = useMyCounter();

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

const BComponent = () => {
  const { count } = useMyCounter();

  return (
    <div>
      <span>값: {count}</span>
    </div>
  );
};

 

이 카운터 훅을 A컴포넌트와 B컴포넌트에서 각각 사용하는 경우, 

A 컴포넌트에서 버튼을 통해 값을 증가하거나 감소시켜도 B컴포넌트의 counter state에는 영향을 미치지 않는다. 

 

 

 

 

커스텀 훅을 사용하면 좋은 예시의경우는 우선 아래 문서를 참고하면 좋다

(추후 카운터보다 더 좋은 예시를 첨부하겠음.. )

 

https://ko.react.dev/learn/reusing-logic-with-custom-hooks#extracting-your-own-custom-hook-from-a-component

 

커스텀 Hook으로 로직 재사용하기 – React

The library for web and native user interfaces

ko.react.dev

 

 

 

또는 React-query 등 라이브러리를 통해 서버 상태를 커스텀 훅으로 관리하는 방법도 있다.

 

리액트 쿼리는 자체 커스텀 훅을 제공하는 상태관리 라이브러리로 주로 대규모 프로젝트에서의 api통신과 비동기 데이터 처리를 편리하게 해주는 도구이다. 

 

useQueryClient 라는 훅을 통해 쿼리 객체를 선언하고, 

useQuery로 데이터 fetch 작업을, useMutation로 데이터의 수정과 삭제, 생성 작업을 수행한다. 

 

 

https://house-of-ham.tistory.com/entry/React-Query%EC%99%80-Axios%EB%A1%9C-%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EB%A1%9C%EC%A7%81%EC%9D%84-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-feat-%EC%84%B1%EB%8A%A5%EC%B5%9C%EC%A0%81%ED%99%94-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC

 

React Query와 Axios로 비즈니스 로직을 최적화 하는 방법 (feat. 성능최적화, 데이터 처리)

백오피스 시스템을 개발하면서 복잡한 기능 구현과 데이터 처리가 큰 도전과제였다. 특히 대용량 데이터를 효율적으로 관리하고, 비즈니스 로직을 유연하게 확장하며, 일관된 방식으로 유지해

house-of-ham.tistory.com

자세한 사용법은 이 글을 참고하길! 

 

 

 

 

 

 

 

 

타입스크립트를 사용하는 프론트앤드 개발자라면 반드시 사용하게되는 type과 interface ! 

둘의 차이를 알고 적재적소에 사용한다면  안정적이고, 유지보수가 용이한 높은 품질의 코드를 작성할 수 있게 될 것이다

 

 

interface 

'객체' 의 구조를 정의하는데 사용된다. 클래스기반의 코드를 작성하는데 주로 사용되며 병합이 가능해 확장 가능성이 있는 타입을 정의할 때 용이하다. 객체의 타입만 정의 가능. 

 

interface의 자동 병합 예시로는 

 

interface Family {
mother: string;
father: string;
}

interface Family {
daughter: string;
son :string;
}

 

이렇게 Family 인터페이스를 두 번 정의할 경우 

첫번째 내용에 두번째 내용이 덧씌워지거나 오류가 발생하지 않고,

 

interface Family{
mother:string;
father:string;
daughter:string;
son:string
}

이렇게 하나로 병합된다는 특징을 가지고 있다. 

 

 

 

type

객체 뿐만 아니라 모든 요소의 타입 정의가 가능하다. 유니온 타입이나, 컴포넌트의 props, state, 이벤트 등의 타입도 정의가 가능해서 React 환경 개발에서는 type을 주로 사용한다. 

 

**유니온 타입 : 여러 타입 중 하나를 선택하는 것

type Info = { phoneNum : string | number; }  //유니온 타입 예시

 

 

interface와 type은 확장 방식도 다르다. 

 

//interface의 경우

interface Member {
name:string;
age: number
}

interface Student extends Member {
stuId:number;
major:string;
}


//type 의 경우

type Member = {
name:string;
age: number
}

Type Teacher = Member & {
tcId:number;
major:string;
}
}

 

 

 

 
 
Next.js는 서버사이드랜더링과 정적 페이지 생성을 제공하는 React프레임워크이다 
리엑트를 기반으로 동작하기 때문에 리엑트에서 사용되는 기본적인 상태관리나 스타일 관련 라이브러리, 코드 번들러, babel 등을 동일하게 사용할 수 있어 리액트 개발자들이 쉽게 사용할 수 있다. 
 
Next에 대해 알기 전에는(어깨너머로 알 때는)
서버사이드랜더링을 써야하는 이유에 대해 이해하지 못했다.
어차피 유저 인터렉션 많은 페이지에서는 큰 의미가 없는게 아닌가 라고 생각했기 때문이다.

그러나 실제로 사용해보면서,  단순히 서버에서 페이지를 렌더링하는 것 이상의 다양한 성능 최적화와 기본 설정이 제공된다는 사실을 알게 되었다.

서버-클라이언트 구조가 사용자 경험에 긍정적인 영향을  미치는지를 경험하면서, 왜 많은 회사에서 Next를 사용하게 되었는지 이해했다.


내가 알게된 내용에 대해 요약하고, 코드에 적용한 것을 아래에 정리해봤다.



 
1.서버사이드 랜더링과 정적 페이지 생성
데이터가 실시간으로 업데이트 되지 않는 경우, 매번 계속 페이지를 생성하는 것은 비효율적이다. 이럴때
 

  • ISR 을 사용하면 데이터가 갱신될 때만 페이지가 새로 생성하거나, 
  • SSG를 통해 한 번 생성된 페이지를 정적으로 제공해서 서비스의 성능을 높일 수 있다. 
  • 또한 SSR이 기본적으로 제공되기 때문에 서버에서 미리 html을 랜더링해와 초기 로딩속도를 줄일 수 있다. -> SEO최적화에 도움됨. 


** ISR과 SSG의 차이**

- ISR (incremental static regeneration):특정 주기(revalidate)로 페이지를 갱신함. 특정 주기마다 데이터가 업데이트 되는 경우 적합하다. e.g) 뉴스 등 

- SSG (static site generation) : 빌드 타임에 모든 페이지를 정적으로 미리 생성한다. 페이지가 빌드될 때 한 번 생성하고, 그 이후에는 동일한 정적 파일을 계속 제공한다. 데이터를 업데이트하기위해서는 사이트 전체를 다시 빌드해야한다.
자주 변하지 않는 서비스에 적합.  e.g)블로그나 기술 문서 등 
 
 
2. 파일 기반 라우팅
Next.js의 파일 기반 라우팅(App Routing)은 파일 경로만 설정하면 자동으로 URL이 생성되기 때문에 기존 리액트에 비해 프로젝트의 구조가 훨씬 간결해진다. 폴더명이 url이 되는 형식.
 
e.g) App/News/Page.js 의 구조일 때 myappUrl/News 로 접속하면 news 폴더 안의 pages.js 내용이 랜더링 된다. 
 
 
3. 성능 최적화
Next.js는 기본적으로 코드 스플리팅이미지 최적화를 제공하기 때문에 로딩 속도 최적화가 가능하다. 단순히 기능을 제공할 뿐만 아니라, 유저 사용성을 높이기 위해서는 next를 쓰는 것이 좋다. 물론 기본 리엑트에서도 스플리팅과 이미지 최적화 모두 가능하지만 설정이 필요하다. 
 
 
 


스팀에서 제공하는 api를 사용해서 간단한 서버사이드 랜더링 페이지를 구현 해보자 !
 
 
파일 구조(root)
 

App 폴더

ㄴ page.tsx

Component 폴더

ㄴ gamelist.tsx

ㄴ newslist.tsx

API 폴더

ㄴ api.tsx

 
 
 
page.tsx  : 서버 사이드 렌더링을 통해 Steam API 데이터를 가져오는 메인 페이지  
현재는 내 게임정보와 스팀 뉴스를 가져오고 있다.

  • 서버 컴포넌트에서 가져온 데이터를 클라이언트 컴포넌트로 넘겨줌 으로서 빠른 초기 로딩, SEO 최적화라는 장점이 있다. 
import { fetchMyGames, fetchSteamNews } from "@/api/getSteamApi";
import GameList from "@/components/GameList";
import SteamNewsList from "@/components/NewsItem";

export default async function HomePage() {
  const news = await fetchSteamNews();
  if (!news || news.length === 0) {
    return <p>No news available</p>;
  }

  const myGame = await fetchMyGames();

  return (
    <div>
      <h3>Steam News</h3>
      <SteamNewsList news={news} /> 
      <h3>You Played</h3>
      <GameList games={myGame} />
    </div>
  );
}

 
 
 
api.tsx : 스팀에서 내 개임 정보를 가져오는 api .
api와 id는 .env.local에서 환경변수 정의 후 가져오면 된다. 

export async function fetchMyGames() {
  const apiKey = process.env.API_KEY;
  const steamId = process.env.MY_STEAM_ID;
  const url = `url 생략...`;
  try {
    const res = await fetch(url);

    if (!res.ok) {
      throw new Error(
        `Mygame API url을 가져올 수 없습니다: code ${res.status}`
      );
    }

    const data = await res.json();

    const games: Game[] = data.response.games;

    if (games && games.length > 0) {
      console.log(`total ${games.length} games:`);
    } else {
      console.log("total 0 games");
    }

    return games; // 게임 목록 반환
  } catch (error) {
    console.error("[error] Fetch game info failed:", error);
    return [];
  }
}

 
gamelist.tsx : 스팀 아이디를 통해 플레이한 게임의 정보를 가져오는 코드 
 

  • 서버 컴포넌트에서 데이터를 가져오고, 클라이언트 컴포넌트에서 UI를 렌더링하는 방식으로 서버와 클라이언트가 분리됨. 서버 부하를 줄이고 성능을 최적화 할 수 있다. 
import React from "react";
import { Game } from "./type/type";
function GameIcon({
  appid,
  img_icon_url,
}: {
  appid: number;
  img_icon_url: string;
}) {
  const imageUrl = `https://media.steampowered.com/steamcommunity/public/images/apps/${appid}/${img_icon_url}.jpg`;

  return (
    <img
      src={imageUrl}
      alt="Game Icon"
      width="50"
      height="50"
      style={{ borderRadius: 50 }}
    />
  );
}

export default function GameList({ games }: { games: Game[] }) {
  return (
    <ul>
      {games.map((game) => (
        <li
          key={game.appid}
          style={{ listStyle: "none", marginBottom: "10px" }}
        >
          <h4>{game.name}</h4>
          <GameIcon appid={game.appid} img_icon_url={game.img_icon_url} />
          <p>Playtime: {Math.floor(game.playtime_forever / 60)} hours</p>
        </li>
      ))}
    </ul>
  );
}

 
 
 
 
 
지금까지 서버 -클라이언트 컴포넌트를 분리하여 최적화된 페이지를 만드는 법을 정리해보았다.
 
현재 코드는 페이지가 하나로 파일 기반 라우팅이 적용되지 않았다. 
코드가 업데이트되는대로 해당 부분을 업로드하겠다.
 
더불어 뉴스 컴포넌트나 스팀 api 사용 관련 설명 또한 추후에 첨부하겠음! 
 

백오피스 시스템을 개발하면서 복잡한 기능 구현과 데이터 처리가 큰 도전과제였다.
특히 대용량 데이터를 효율적으로 관리하고, 비즈니스 로직을 유연하게 확장하며, 일관된 방식으로 유지해야 했다. 
 
이 과정에서 데이터 요청이 많고, 중복 요청을 유연하게 처리하기 위하여 React Query(Tanstack Query)를 사용하게 되었다. 
리액트 쿼리를 통해 데이터를 빠르게 가져오고, 캐시를 통해 중복 요청을 줄이면서 서비스를 최적화할 수 있었다. 
 
이번 글에서는 백오피스 시스템에서 복잡한 데이터 처리 기능을 효율적으로 관리하고, 성능을 최적화 했는지에 대해 공유하겠다! 
 
 

 


 
 
 
 
코드의 일관성과 유지보수성을 높이기 위해, 쿼리 키와 API 호출 로직을 별도로 분리했다. 이를 통해 팀원들과 코드 스타일을 맞추고, 유지보수가 쉬운 코드를 작성할 수 있었다.
또한 Axios를 적용하여 HTTP 요청을 간결하게 처리하고, 응답 데이터를 자동으로 JSON으로 파싱하여 쉽게 다루도록 했다. (Axios를 사용하면 JSON으로 응답이 돌아와서 추가 처리가 필요 없다!)
 
 
 
 
** 예시를 들기 위해 작성한 코드 입니다   ~.~  실제 업무용 코드 x 
 
쿼리 키 작성 

const employeeKey = {
  employee: ['employee'] as const,
  employeeWithID: (id: string) => ['employee', { id }] as const,
};

 
쿼리 키를 따로 파일로 분리하면 팀원들이 중복된 키를 작성하여 잘못된 데이터를 가져오거나 전송하는 문제를 방지할 수 있다. 유지보수 시에도 편리하므로 쿼리 키는 별도로 관리하는 것이 좋다.
 
 
 
커스텀 훅 작성 
 

import { useQuery } from 'react-query';
import axios from 'axios';
import { employeeKey } from "./employeeQueryKey";

// API 호출 함수 (전체 또는 개별 사원 정보 패칭)
const fetchEmployees = (id?: string) => 
  axios.get(`/api/employees${id ? `/${id}` : ''}`).then(res => res.data); //api 주소를 여기에 넣으면 됨

// useEmployeeInfo 커스텀 훅
const useEmployeeInfo = (employeeId?: string) => {
  const { data, isError, isLoading, error } = useQuery({
    queryKey: employeeId ? employeeKey.employeeWithID(employeeId) : employeeKey.employee,
    queryFn: () => fetchEmployees(employeeId), // employeeId가 없으면 undefined 전달
    staleTime: 1000 * 60 * 10, //캐시 지속 시간 
  });

  return {
    fetchData: data, // 패칭된 데이터
    isFetchDataLoading: isLoading, // 로딩 상태
    isFetchDataError: isError, // 에러 상태
    fetchError: error, // 에러 정보
  };
};

 
 
 
 
이제 컴포넌트에서 위에서 구현한 커스텀 훅을 통해 데이터를 가져오자 

const EmployeeInfo = ({ employeeId }: { employeeId: string }) => {
  // 사원 ID를 넘겨서 특정 사원의 정보를 패칭
  const { fetchData, isFetchDataLoading, isFetchDataError, fetchError } = useEmployeeInfo(employeeId);

  if (isFetchDataLoading) return <div>Loading employee data...</div>;
  if (isFetchDataError) return <div>Error loading data: {fetchError?.message}</div>;

  return (
    <div>
      <h1>Employee Name: {fetchData.name}</h1>
      <p>Position: {fetchData.position}</p>
      <p>Department: {fetchData.department}</p>
    </div>
  );
};

 
 
 
여기서 useQuery안에 들어가는 옵션 등을 props로 받아온다면 
더 높은 수준의 쿼리문을 작성할 수 있을 것이다 ~,~ 그 부분에 대해서는 추후 서술하겠음! 
 
 

+ Recent posts