728x90

https://react.dev/reference/react/useRef

 

useRef – React

The library for web and native user interfaces

react.dev

 

ref는 우리가 익히 알듯, 랜더링과 관계없는 값을 참조할 때 사용한다.

 

주로 js에서의 getElementById와같이 dom 요소에 직접 접근하고 싶을때 대신 사용하는것이 useRef다.

useRef를 통해 값을 변경하는 경우에는 화면이 재랜더링 되지 않으므로

 

보통 input의 focus 상태를 제어하거나, dom의 css를 직접 조작하고 싶을 때, 또는 className을 변경하고 싶을 때  사용한다.

 

예시 

function ToggleComponet() {
  const myRef = useRef(null);

  const toggleClassName = () => {
    if (myRef.current.classList.contains("second")) {
      myRef.current.classList.remove("second");
    } else {
      myRef.current.classList.add("second");
    }
  };

  return (
    <div>
      <div ref={myRef} className="first">
        test ref 
      </div>
      <button onClick={toggleClassName}>Toggle</button>
    </div>
  );
}

버튼을 클릭하는경우 "first" -> "first second" -> "first"로 변경된다.

 

 

다만 js의 돔 조작 방식처럼 내부에 새로운 요소(div등)을 추가하는 등 랜더링을 필요로 하는 작업들은 useState를 통해 처리하는것이 적절하다. 

 

createRef 는 useRef를 사용할 수 없는 클래스형 컴포넌트에서 사용된다 useRef와는 다르게 랜더링될때마다 다시 생성되어 이전의 값을 기억하지 않는다.

 

 

https://react.dev/reference/react/forwardRef

 

forwardRef – React

The library for web and native user interfaces

react.dev

forwardRef 는 기억해두는것이 좋다 ~.~ 

 

부모컴포넌트가 자식컴포넌트의 ref를 제어해야할 때 사용한다. 

보통 리액트의 데이터흐름은 자식 -> 부모 순이지만 가~끔 이렇게 부모요소가 자식요소를 제어해야하는 경우가 발생한다. 

그럴때 사용하는것이 forwardRef이다.

(위 페이지의 예제를 가져옴)

 

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const { label, ...otherProps } = props;
  return (
    <label>
      {label}
      <input {...otherProps} ref={ref} />
    </label>
  );
});

 

자식요소인 MyInput을 forwardRef 컴포넌트로 선언하면 

 

import { useRef } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
  }

  return (
    <form>
      <MyInput label="Enter your name:" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}

부모요소인 Form에서 선언한 ref는 부모가 아닌 자식컴포넌트인 MyInput을 가리키게된다. 

따라서 핸들클릭을 통해 이벤트가 발생하면 자식요소인 input에 focus가 발생한다. 

728x90

https://ko.vite.dev/guide/

 

Vite

Vite, 프런트엔드 개발의 새로운 기준

ko.vite.dev

 

Vite는 

성능에 집중한 번들러로

 

개발 환경에서 코드를 변경할 때 parcel, webapack에 비해 빠른 속도를 자랑한다. 

 

이유는 vite의 코드 갱신 방식이 다른 번들러들과 다르기 때문인데, 다른 번들러들은 코드의 업데이트가 있을 때 전체 코드를 다시 번들링 하지만, vite는 브라우저의 요청에 따라 변경된 모듈만 전달하는 방식으로 화면을 갱신하기 때문에,

프로젝트의 코드 크기가 커질수록 갱신 속도가 느려지는 다른 번들러들에 비해, vite는 빠른 속도를 유지할 수 있다는 것이다. ~.~ 

 

또한 배포시에도 ESbuild(트랜스파일러), Rollup(트리셰이킹, 불필요한 코드를 쳐내고 번들로 합침)을 기반으로 동작하기때문에, 웹팩과 같이 증분 빌드를 지원하지는 않지만 빌드 속도 또한 매우 빠르다.

 

 

 

 

리액트 typescript 프로젝트를 vite 기반으로 생성하는 npm 명령어 

npm create vite@latest my-project-name --template react-ts

 

일할때는 webpack만을 사용해봤는데 개인적으로 공부할때 vite를 써보니 확실히 기본 설정도 간단하고 로드 속도도 빨라서 편하다고 느껴졌다. 

728x90

커스텀 훅은 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

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

 

 

 

 

 

 

 

 

728x90

타입스크립트를 사용하는 프론트앤드 개발자라면 반드시 사용하게되는 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;
}
}

 

 

 

728x90

 
 
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 사용 관련 설명 또한 추후에 첨부하겠음! 
 

728x90

코드 스플리팅이란? 
초기 로드 시점에 모든 코드를 다운로드 하지않고 필요한 컴포넌트만 로드하여 초기로딩 시간을 줄이고 성능을 개선하는 방법 (비동기 컴포넌트 랜더링!)
 
리액트에서는 
React.lazy와 Suspense를 사용해 구현이 가능하다. 
둘 다 16.6버전에서 처음 도입되었고
18버전부터는 Suspense의 기능이 더욱 강회되어서 비동기 데이터 처리에도 사용이 가능해졌다! 
 
 
Suspense와 Lazy 사용법

import React, { Suspense, lazy } from 'react';

.... {

const LazyComponent = lazy(() => import('./로드가 오래 걸리는 컴포넌트명'));
.
.
.
return <Suspense fallback={<div>Loading component...</div>}> <LazyComponent/></Suspense>

}

 
이렇게 구현하면 초기 번들에 모든 코드가 포함되는것이 아니라 
해당 컴포넌트가 필요한 시점에 로드된다! 
 
SEO(검색 엔진 최적화)에도 도움이 되고 
초기 로딩속도가 줄어드는 장점이 있음 ~,~ 
 
 

728x90

 

https://emotion.sh/docs/typescript

 

Emotion – TypeScript

Emotion includes TypeScript definitions for @emotion/react and @emotion/styled. These definitions infer types for css properties with the object syntax, HTML/SVG tag names, and prop types. @emotion/react The easiest way to use the css prop with TypeScript

emotion.sh

이모션 공식 문서대로 

 

tsconfig.json 파일 안에 

 

{
.
.
.
.

"jsx": "react-jsx",
"jsxImportSource": "@emotion/react"
}

 

이 부분을 추가하자!

 

 

 

 

css-in-js 스타일 라이브러리중에 emotion이 코드도 깔끔하고 수정이 쉬워서 자주 사용중인데

프로젝트에 typescript를 도입하니 여기저기 에러가 튀어나온다 ...

 

번들러랑 바벨 다루는게 더 익숙해져야겠지 ~.~ 

728x90

 

npm run start  또는 

yarn start 등을 사용해 리액트 프로젝트를 실행했을 때,

react-scripts 관련 오류가 발생할 때가 있다. 

 

보통은 npm install --save react-scripts 또는

yarn add react-scripts를 입력해 설치하면 문제가 해결되지만 

 

typescript를 사용하는 경우엔 위의 방법으로 해결이 안되는 경우가 있다. 

 

그럴땐 우선 package.json 파일 안의 dependencies를 살펴보자 

 

  "dependencies": {
        .
        .
        .
        .
        .
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-router-dom": "^6.24.0",
    "react-scripts": "5.0.0",
    "typescript": "^5.5.3",
    }

나처럼 react-scripts가 5버전 이상인 경우에는

typesciprt가 4버전까지 지원이 된다. 

 

(2024.7 기준. 이후 버전에는 달라질 수도 있음)

나의 경우엔 "typescript" :  5.5.3 버전이 설치되어 있었는데 

저 부분을 ^4.9.5로 수정한 후 다시 실행했더니

정상적으로 로컬호스트가 실행되었다! 

 

 

 

 

 

728x90

+ Recent posts