본문 바로가기

WEB개발/REACT

[환경구성] React개발을 위한 유용한 노드모듈 (node_modules)

 

[Dev] @vitejs/plugin-react

더보기

@vitejs/plugin-react는 Vite에서 React를 원활하게 사용할 수 있도록 도와주는 공식 플러그인입니다.

📌 역할:

  • Vite에서 React Fast Refresh 지원
  • JSX/TSX 변환 최적화
  • Babel을 활용한 코드 트랜스파일링

 

react-infinite-scroller

더보기
import InfiniteScroll from "react-infinite-scroller";
import {useInfiniteQuery} from "@tanstack/react-query";
import {People} from "./People";

interface PageData {
  next: string | null;
  results: People[];
}

const initialUrl = "https://swapi.py4e.com/api/people/";
const fetchUrl = async (url: string) => {
  const response = await fetch(url);
  return response.json();
};

export function InfinitePeople() {
  const {data, fetchNextPage, hasNextPage, isFetching, isError, error} = useInfiniteQuery({
    queryKey: ["sw-people"],
    queryFn: ({pageParam = initialUrl}) => fetchUrl(pageParam),
    getNextPageParam: (lastPage: PageData) => lastPage.next || undefined,
    initialPageParam: initialUrl
  });

  if (isError) return <div>Error: {error.message}</div>;

  return (
    <div className="content__wrap">
      <h2 className="title">People - Infinite Scroll</h2>
      <div className="infinite__wrap" style={{overflow: "hidden", padding: "0"}}>
        <ul className="infinite__wrap-item" style={{height: "450px", overflow: "auto"}}>
          {data && (
            <InfiniteScroll
              initialLoad={false}
              useWindow={false}
              loadMore={() => {
                if (!isFetching) {
                  fetchNextPage();
                }
              }}
              hasMore={hasNextPage}
            >
              {data?.pages.map(pageData => {
                return pageData.results.map(people => {
                  return (
                    <People
                      key={people.name}
                      name={people.name}
                      hair_color={people.hair_color}
                      eye_color={people.eye_color}
                    />
                  );
                });
              })}
            </InfiniteScroll>
          )}
        </ul>
      </div>
      {isFetching && (
        <div className="loading-bar">
          <div className="loading"></div>
        </div>
      )}
    </div>
  );
}

 

[Dev] globals

더보기

globals는 Node.js 및 브라우저 환경에서 사용되는 전역 변수 목록을 제공하는 npm 패키지입니다.
즉, 어떤 전역 변수들이 사용할 수 있는지 쉽게 확인할 수 있도록 도와주는 모듈입니다.

 

browser

import globals from 'globals';

console.log(globals.browser);
// { window: true, document: true, fetch: true, console: true, ... }

// 브라우저 전역 객체 사용
window.alert("Hello, world!");
document.body.style.backgroundColor = "lightblue";

 

Node.js

import globals from 'globals';

console.log(globals.node);
// { process: true, __dirname: true, Buffer: true, global: true, ... }

// Node.js 전역 객체 사용
console.log(__dirname); // 현재 실행 중인 파일의 디렉토리 경로
console.log(process.env.NODE_ENV); // 환경 변수 출력

 

ES6

import globals from 'globals';

console.log(globals.es6);
// { Promise: true, Map: true, Set: true, Symbol: true, ... }

// ES6 전역 객체 사용
const map = new Map();
map.set("key", "value");
console.log(map.get("key")); // value

 

Web Worker

import globals from 'globals';

console.log(globals.worker);
// { self: true, postMessage: true, importScripts: true, ... }

// Web Worker 내부 코드
self.onmessage = (event) => {
    console.log("Received:", event.data);
    self.postMessage("Hello from Worker!");
};

 

 CommonJs

import globals from 'globals';

console.log(globals.commonjs);
// { require: true, module: true, exports: true, ... }

// CommonJS 모듈 사용
const fs = require("fs");

module.exports = {
    readFile: (path) => fs.readFileSync(path, "utf-8")
};

 

 

react-toastify

더보기
toast.success(<h4>🔔 새로운 알림</h4>, {
        toastId: `copy-account-number`,
        autoClose: 1000,
        position: "bottom-center",
        pauseOnHover: false,
        closeButton: false,
        closeOnClick: true,
        hideProgressBar: true
 });

 

isbot

더보기

User-Agent 헤더를 읽어 봇을 감지할 수 있습니다.

 

import express from "express";
import isbot from "isbot";

const app = express();

app.get("/", (req, res) => {
  if (isbot(req.get("User-Agent"))) {
    return res.status(403).send("Bot access denied");
  }
  res.send("Hello, Human! 🤖❌");
});

app.listen(3000, () => console.log("Server is running on port 3000"));

 

 

sass

더보기

1. styles.scss 파일 작성

// styles.scss
$primary-color: #007bff;

.button {
  background-color: $primary-color;
  color: white;
  padding: 10px 20px;
  border-radius: 5px;
  cursor: pointer;

  &:hover {
    background-color: darken($primary-color, 10%);
  }
}

 

2. React 컴포넌트에서 SCSS 파일 불러오기

import React from "react";
import "./styles.scss"; // SCSS 파일을 직접 import

const App = () => {
  return <button className="button">클릭하세요</button>;
};

export default App;

 

zustand

더보기

persist는 상태를 로컬 스토리지에 저장하고, 페이지 새로고침 후에도 상태를 유지하게 해줍니다.

import create from 'zustand';
import { persist } from 'zustand/middleware';
 
interface CounterState {
  count: number;
  increase: () => void;
  reset: () => void;
}
 
const useCounterStore = create(
  persist<CounterState>(
    (set) => ({
      count: 0,
      increase: () => set((state) => ({ count: state.count + 1 })),
      reset: () => set({ count: 0 }),
    }),
    {
      name: 'counter-storage', // 로컬 스토리지 키 이름
    }
  )
);
 
export default useCounterStore;

 

devtools를 사용하면 Redux DevTools에 상태 변경 기록을 표시할 수 있습니다.

import create from 'zustand';
import { devtools } from 'zustand/middleware';

interface CounterState {
  count: number;
  increase: () => void;
  reset: () => void;
}

const useCounterStore = create<CounterState>()(
  devtools((set) => ({
    count: 0,
    increase: () => set((state) => ({ count: state.count + 1 })),
    reset: () => set({ count: 0 }),
  }))
);

export default useCounterStore;

 

 

subscribeWithSelector를 사용하면 특정 상태만 감지할 수 있습니다.

import create from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';

interface CounterState {
  count: number;
  increase: () => void;
}

const useCounterStore = create(
  subscribeWithSelector<CounterState>((set) => ({
    count: 0,
    increase: () => set((state) => ({ count: state.count + 1 })),
  }))
);

// 상태 변경 감지
const unsubscribe = useCounterStore.subscribe(
  (state) => state.count, // count 상태만 감지
  (count) => {
    console.log(`Count changed to: ${count}`);
  }
);

// unsubscribe()를 호출해 감지 중단 가능

 

 

 

@uidotdev/usehooks

더보기

@uidotdev/usehooks는 React에서 자주 사용하는 유용한 훅들을 모아 놓은 라이브러리입니다. 이 라이브러리는 커스텀 훅을 제공하여 개발자가 쉽게 재사용할 수 있도록 돕습니다. 다양한 UI 및 상태 관리 작업을 간소화할 수 있는 훅들이 포함되어 있어 개발 시간을 줄이는 데 유용합니다.


useDebounce:값이 변경될 때마다 딜레이를 두고 업데이트하도록 하는 훅입니다. 일반적으로 입력 필드에서 사용되어, 입력이 끝난 후에만 값을 업데이트합니다.

 

import React, { useState } from 'react';
import { useDebounce } from '@uidotdev/usehooks';

const DebounceInput = () => {
    const [inputValue, setInputValue] = useState('');
    const debouncedValue = useDebounce(inputValue, 500); // 500ms 후에 업데이트

    return (
        <div>
            <input
                type="text"
                value={inputValue}
                onChange={(e) => setInputValue(e.target.value)}
                placeholder="Type something..."
            />
            <p>Debounced Value: {debouncedValue}</p>
        </div>
    );
};

export default DebounceInput;


useThrottle:일정 시간 간격으로 값의 변화를 제한하는 훅입니다. 연속적인 이벤트에서 과도한 호출을 방지하는 데 유용합니다.

const ThrottleButton = () => {
    const [count, setCount] = useState(0);
    const throttledCount = useThrottle(count, 1000); // 1초 간격으로 업데이트

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

    return (
        <div>
            <button onClick={handleClick}>Click me!</button>
            <p>Throttled Count: {throttledCount}</p>
        </div>
    );
};

 

그 외.... 버튼 throttle (중복클릭방지)

const ThrottleButton = () => {
    const [count, setCount] = useState(0);
    const isThrottled = useRef(false); // 클릭 제한 상태
    const handleClick = useCallback(() => {
        if (isThrottled.current) return; // 클릭이 제한된 경우 함수 종료

        isThrottled.current = true; // 클릭 제한 시작
        setCount((prev) => prev + 1);
        setTimeout(() => {
            isThrottled.current = false; // 1초 후 클릭 제한 해제
        }, 1000); // 1초 후
    }, []);
    return (
        <div>
            <button onClick={handleClick}>Click me!</button>
            <p>Throttled Count: {count}</p>
        </div>
    );
};

 

 

 

useFetch:API 요청을 간편하게 처리하는 훅입니다. URL을 입력하면 데이터를 가져오고, 로딩 상태와 오류 처리를 자동으로 관리합니다.

import React from 'react';
import { useFetch } from '@uidotdev/usehooks';

const DataFetchingComponent = () => {
    const { data, error, loading } = useFetch('https://jsonplaceholder.typicode.com/posts');

    if (loading) return <div>Loading...</div>;
    if (error) return <div>Error: {error.message}</div>;

    return (
        <div>
            <h1>Fetched Posts:</h1>
            <ul>
                {data.map((post) => (
                    <li key={post.id}>{post.title}</li>
                ))}
            </ul>
        </div>
    );
};

export default DataFetchingComponent;

 

useLocalStorage:로컬 스토리지에 값을 저장하고, 해당 값을 쉽게 읽고 쓸 수 있도록 돕는 훅입니다. 페이지를 새로 고침해도 데이터가 유지됩니다.

import React from 'react';
import { useLocalStorage } from '@uidotdev/usehooks';

const LocalStorageComponent = () => {
    const [name, setName] = useLocalStorage('name', ''); // 초기값은 ''

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

    return (
        <div>
            <input
                type="text"
                value={name}
                onChange={handleChange}
                placeholder="Enter your name"
            />
            <p>Your name in local storage: {name}</p>
        </div>
    );
};

export default LocalStorageComponent;

 

 

useWindowSize:브라우저 창의 크기를 추적하는 훅으로, 반응형 디자인에 유용합니다. 창 크기가 변경될 때마다 자동으로 업데이트됩니다.

import React from 'react';
import { useWindowSize } from '@uidotdev/usehooks';

const WindowSizeComponent = () => {
    const { width, height } = useWindowSize();

    return (
        <div>
            <h1>Window Size</h1>
            <p>Width: {width}px</p>
            <p>Height: {height}px</p>
        </div>
    );
};

export default WindowSizeComponent;

 

 

@emotion/styled, @emotion/react

더보기

Emotion 은 JS 로 css 스타일을 작성하도록 설계된 라이브러리다.

/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";

export default function TestMain() {
    return (
        <>
            <div>
                <p>Test Main...</p>
                <div
                    css={css`
                        padding: 32px;
                        background-color: hotpink;
                        font-size: 24px;
                        border-radius: 4px;
                        &:hover {
                            color: red;
                        }
                    `}
                >
                    Hover to change color.
                </div>
            </div>
        </>
    );
}

 

@headlessui/react

더보기

https://headlessui.com/react/listbox#installation

 

Headless UI

Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.

headlessui.com

 

React 애플리케이션을 위한 UI 컴포넌트를 제공하는 라이브러리입니다. 이 라이브러리는 접근성이 뛰어난 기본 UI 요소를 만드는 데 도움을 주며, 스타일링을 개발자가 직접 할 수 있도록 "헤드리스" 방식으로 설계되었습니다.

 

@lottiefiles/react-lottie-player

더보기

Lottie 애니메이션 파일을 React에서 쉽게 재생하고 제어할 수 있는 라이브러리입니다.

Lottie는 Adobe After Effects로 만든 애니메이션을 JSON 형식으로 변환하여 웹과 모바일에서 사용할 수 있게 해주는 라이브러리입니다.

 

import animationData from '@/assets/lottie.json';
import React, { useEffect, useState } from 'react';

const LottieAnimation: React.FC = () => {
    const [isClient, setIsClient] = useState(false);
    const [LottiePlayer, setLottiePlayer] = useState<any>(null);

    useEffect(() => {
        setIsClient(true);
        import('@lottiefiles/react-lottie-player').then((module) => {
            setLottiePlayer(() => module.Player);
        });
    }, []);

    if (!isClient || !LottiePlayer) {
        return null; // 클라이언트가 아니거나 LottiePlayer가 로드될 때까지 아무것도 렌더링하지 않음
    }

    return (
        <LottiePlayer
            autoplay
            loop
            src={animationData}
            style={{ height: '300px', width: '300px' }}
        />
    );
};

export default LottieAnimation;

 

https://lottiefiles.com/kr/?finish_register=true&cb_payload=eyJsb2dpbk1ldGhvZCI6InNvY2lhbCIsInByb3ZpZGVyIjoiZ29vZ2xlIiwibWV0YWRhdGEiOiJ7XCJyZWRpcmVjdFVybFwiOlwiaHR0cHM6Ly9hcHAubG90dGllZmlsZXMuY29tLz91dG1fbWVkaXVtPXdlYiZ1dG1fc291cmNlPXJlZ2lzdGVyLW1haW4tbmF2XCIsXCJleHRlcm5hbFJlZGlyZWN0XCI6dHJ1ZX0ifQ%3D%3D

 

무료 로티 애니메이션, 모션 그래픽을 위한 모든 플러그인과 도구를 한 곳에 - 로티파일즈/LottieFi

로티파일즈/LottieFiles는 오픈소스 애니메이션 포맷 로티를 제공하는 세상에서 가장 큰 무료 플랫폼입니다. 웹, iOS, 안드로이드, 윈도우를 위한 모션 그래픽 작업을 지원하는 플러그인과 툴을 통

lottiefiles.com

 

 

motion 

더보기

framer-motion은 강력한 애니메이션 라이브러리로, 다양한 애니메이션을 쉽게 구현할 수 있도록 도와줍니다

 

https://motion.dev/docs/react-use-velocity

 

useVelocity | Motion for React (prev Framer Motion)

Create a motion value that tracks the velocity of another motion value.

motion.dev

 

react-spring

 

react-use

더보기

`react-use` 는 다양한 React 훅을 제공하는 유틸리티 라이브러리입니다.
브라우저 API, 상태 관리, 센서 감지, 애니메이션, 타이머 등을 쉽게 다룰 수 있는 훅들이 포함되어 있어 생산성을 높여줍니다.

 

 

Vite의 vite.config.ts 파일에서 ssr.noExternal 옵션을 설정하여 react-use를 ESM 방식으로 번들링하도록 강제할 수 있습니다

vite.config.ts

import { defineConfig } from 'vite';

export default defineConfig({
  ssr: {
    noExternal: ['react-use']
  }
});

 

useLocalStroage - 로컬스토리지 활용

import { useLocalStorage } from 'react-use';

const Example = () => {
  const [value, setValue, remove] = useLocalStorage('my-key', '초기값');

  return (
    <div>
      <p>저장된 값: {value}</p>
      <button onClick={() => setValue('새로운 값')}>값 변경</button>
      <button onClick={remove}>삭제</button>
    </div>
  );
};

 

usePrevious - 이전 상태값

import { useState } from 'react';
import { usePrevious } from 'react-use';

const Example = () => {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);

  return (
    <div>
      <p>현재 카운트: {count}</p>
      <p>이전 카운트: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </div>
  );
};

 

useMeasure - dom요소 크기 측정

import { useMeasure } from 'react-use';

const Example = () => {
  const [ref, { width, height }] = useMeasure<HTMLDivElement>();

  return (
    <div ref={ref} style={{ resize: 'both', overflow: 'auto', padding: '20px', border: '1px solid black' }}>
      <p>너비: {width}px</p>
      <p>높이: {height}px</p>
    </div>
  );
};

 

 useMouse - 마우스 위치 감지

import { useMouse } from "react-use";
import { useRef } from "react";

const Example = () => {
  const ref = useRef<HTMLDivElement | null>(null);
  const { elX, elY } = useMouse(ref as React.RefObject<HTMLDivElement>); // 타입 단언 사용

  return (
    <div
      ref={ref}
      style={{
        width: "300px",
        height: "200px",
        border: "1px solid black",
        position: "relative",
      }}
    >
      <p>요소 기준 X: {elX}px</p>
      <p>요소 기준 Y: {elY}px</p>
    </div>
  );
};

 

 

useInterval - 간격 반복 실행

마운트 시 자동으로 정리(clean-up)됨.

import { useState } from 'react';
import { useInterval } from 'react-use';

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

  useInterval(() => {
    setCount(count + 1);
  }, 1000); // 1초마다 실행

  return <p>카운트: {count}</p>;
};

 

useClickAway - 외부 클릭 감지

import React, { useRef } from 'react';
import { useClickAway } from 'react-use';

const Example = () => {
  const ref = useRef(null);
  const [open, setOpen] = React.useState(false);

  useClickAway(ref, () => setOpen(false));

  return (
    <div>
      <button onClick={() => setOpen(true)}>메뉴 열기</button>
      {open && (
        <div ref={ref} style={{ border: '1px solid black', padding: '10px' }}>
          외부 클릭하면 닫힘
        </div>
      )}
    </div>
  );
};

 

useToggle - 값 토글 (true ↔ false)

import { useToggle } from 'react-use';

const Example = () => {
  const [isOn, toggle] = useToggle(false);

  return (
    <div>
      <p>상태: {isOn ? 'ON' : 'OFF'}</p>
      <button onClick={toggle}>토글</button>
    </div>
  );
};

 

useWindowSize - 브라우저 창 크기 가져오기

import { useWindowSize } from 'react-use';

const Example = () => {
  const { width, height } = useWindowSize();

  return (
    <p>브라우저 크기: {width}px × {height}px</p>
  );
};

 

useNetworkState - 네트워크 상태 감지 (online/offline)

import { useNetworkState } from 'react-use';

const Example = () => {
  const { online } = useNetworkState();

  return (
    <p>네트워크 상태: {online ? '🟢 온라인' : '🔴 오프라인'}</p>
  );
};

 

useBattery - 배터리 상태 감지

import { useBattery } from 'react-use';

const Example = () => {
  const battery = useBattery();

  if (!battery.isSupported) return <p>배터리 상태를 확인할 수 없는 기기입니다.</p>;
  if (!battery.fetched) return <p>배터리 정보를 가져오는 중...</p>;

  return (
    <div>
      <p>배터리 레벨: {Math.round(battery.level * 100)}%</p>
      <p>{battery.charging ? '🔌 충전 중' : '🔋 방전 중'}</p>
    </div>
  );
};

 

useLockBodyScroll - 스크롤 잠금 (modal 등에서 유용)

import { useLockBodyScroll } from "react-use";
import { useEffect, useState } from "react";

const Example = () => {
    const [open, setOpen] = useState(false);
    useLockBodyScroll(open); // 모달이 열리면 스크롤 잠금
    return (
        <div>
            <button onClick={() => setOpen(true)}>모달 열기</button>
            {open && (
                <div
                    style={{
                        position: "fixed",
                        inset: 0,
                        background: "rgba(0,0,0,0.5)",
                        color: "white",
                        padding: "20px",
                    }}
                >
                    <p>모달 내용</p>
                    <button onClick={() => setOpen(false)}>닫기</button>
                </div>
            )}
        </div>
    );
};

 

useFullscreen - 풀스크린 모드 전환

import { useRef } from "react";
import { useFullscreen, useToggle } from "react-use";

const Example = () => {
    const ref = useRef<HTMLDivElement>(null);
    const [show, toggle] = useToggle(false);
    const isFullscreen = useFullscreen(ref as (React.RefObject<HTMLDivElement>), show, {
        onClose: () => toggle(false),
    });

    return (
        <div ref={ref} style={{ backgroundColor: "white" }}>
            <div>{isFullscreen ? "Fullscreen" : "Not fullscreen"}</div>
            <button onClick={() => toggle()}>Toggle</button>
            <video
                src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"
                autoPlay
            />
        </div>
    );
};

 

@lukemorales/query-key-factory

더보기

React Query의 키(key)를 생성하고 관리하는 데 도움을 주는 유틸리티 라이브러리입니다.

import { createQueryKey } from '@lukemorales/query-key-factory';
// 기본 쿼리 키 생성
const userQueryKey = createQueryKey('user', { id: 1 });
// 쿼리 키를 사용하는 React Query
const { data } = useQuery(userQueryKey, fetchUserById);

 

compare-versions

더보기

`compare-versions`는 두 버전 문자열을 비교할 수 있도록 도와주는 JavaScript 라이브러리입니다

import {compareVersions} from "compare-versions";

export default function TestMain() {
    // 두 버전 비교
    const version1 = "1.0.0";
    const version2 = "1.1.0";

    // 버전 비교
    const result = compareVersions(version1, version2);

    if (result < 0) {
        console.log(`${version1} is less than ${version2}`);
    } else if (result > 0) {
        console.log(`${version1} is greater than ${version2}`);
    } else {
        console.log(`${version1} is equal to ${version2}`);
    }
    return <></>;
}
const versions = ['1.0.0', '1.1.0', '0.9.0', '1.0.1'];
const sortedVersions = versions.sort(compareVersions);
console.log(sortedVersions); // ['0.9.0', '1.0.0', '1.0.1', '1.1.0']

 

 

react-barcode:

더보기

React에서 바코드를 생성하기 위한 라이브러리입니다.

import Barcode from 'react-barcode';

const BarcodeExample = () => {
    return (
        <div>
            <h1>바코드 생성 예시</h1>
            <Barcode 
                value="123456789012" // 바코드로 변환할 문자열
                width={2} // 바코드 너비
                height={100} // 바코드 높이
                displayValue={true} // 바코드 아래에 값을 표시할지 여부
                fontSize={20} // 표시할 글꼴 크기
            />
        </div>
    );
};

 

react-qr-code

더보기

React에서 QR 코드를 생성하기 위한 라이브러리입니다.

import QRCode from 'react-qr-code';

const Example = () => {
  return (
    <div>
      <h1>QR Code Example</h1>
      <QRCode value="https://www.naver.com" size={256} />
    </div>
  );
};

 

react-tooltip

더보기

React에서 툴팁을 쉽게 구현할 수 있도록 지원하는 라이브러리입니다.

import { Tooltip } from 'react-tooltip';
const Example = () => {

    return (
        <div style={{ padding: "50px" }}>
            <h1>React Tooltip Example</h1>
            <button data-tooltip-id="tooltip" data-tooltip-content="This is a tooltip!">
                Hover over me!
            </button>
            <Tooltip id="tooltip" place="top" delayShow={500}/>
        </div>
    );
};

 

react-cookie

: 서버 사이드 렌더링(SSR)에서 쿠키를 사용하려면 universal-cookie도 함께 설치하는 것이 좋습니다.

더보기

React에서 쿠키를 관리하고 사용할 수 있도록 도와주는 라이브러리입니다.

import { useCookies } from 'react-cookie';

const CookieExample = () => {
  const [cookies, setCookie, removeCookie] = useCookies(['user']);

  const handleSetCookie = () => {
    setCookie('user', 'JohnDoe', { path: '/', maxAge: 3600 }); // 1시간 동안 유지
  };

  const handleRemoveCookie = () => {
    removeCookie('user', { path: '/' });
  };

  return (
    <div>
      <h1>React Cookie Example</h1>
      <p>쿠키 값: {cookies.user || '쿠키 없음'}</p>
      <button onClick={handleSetCookie}>쿠키 설정</button>
      <button onClick={handleRemoveCookie}>쿠키 삭제</button>
    </div>
  );
};

export default CookieExample;

 

slick-carousel

: slick-carousel을 React에서 사용하려면 react-slick 라이브러리를 함께 설치해야 합니다.

더보기

슬라이드/캐러셀 컴포넌트를 구현하기 위한 라이브러리입니다

import Slider from 'react-slick';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';

const images = [
  'https://via.placeholder.com/600x300?text=Slide+1',
  'https://via.placeholder.com/600x300?text=Slide+2',
  'https://via.placeholder.com/600x300?text=Slide+3',
];

const SlickCarouselExample = () => {
  const settings = {
    dots: true,
    infinite: true,
    speed: 500,
    slidesToShow: 1,
    slidesToScroll: 1,
    autoplay: true,
    autoplaySpeed: 2000,
  };

  return (
    <div style={{ width: '600px', margin: 'auto' }}>
      <Slider {...settings}>
        {images.map((src, index) => (
          <div key={index}>
            <img src={src} alt={`Slide ${index + 1}`} style={{ width: '100%', borderRadius: '10px' }} />
          </div>
        ))}
      </Slider>
    </div>
  );
};

export default SlickCarouselExample;

 

.styled-components

더보기

컴포넌트 기반으로 스타일링할 수 있는 CSS-in-JS 라이브러리입니다.

아래 두개의 소스는 같은 소스

styled.button<StyledButtonProps>`
  background-color: ${props => (props.primary ? 'blue' : 'gray')};
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
`;


const StyledButton = styled.button((props: StyledButtonProps) => {
  return `
    background-color: ${props.primary ? 'blue' : 'gray'};
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
  `;
});

 

react-errorboundry

더보기

`react-error-boundary` 라이브러리는 React 컴포넌트에서 발생하는 오류를 잡아서 대체 UI(fallback)를 표시할 수 있도록 해줍니다.
이때, fallback UI를 제공하는 3가지 방법이 있습니다

 

1. fallback

import { ErrorBoundary } from "react-error-boundary";

function App() {
  return (
    <ErrorBoundary fallback={<p>Something went wrong!</p>}>
      <ProblematicComponent />
    </ErrorBoundary>
  );
}

 

2. fallbackRender

import { ErrorBoundary } from "react-error-boundary";

function ErrorFallback({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }) {
  return (
    <div>
      <p>Oops! An error occurred: {error.message}</p>
      <button onClick={resetErrorBoundary}>Try Again</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary
      fallbackRender={({ error, resetErrorBoundary }) => (
        <ErrorFallback error={error} resetErrorBoundary={resetErrorBoundary} />
      )}
    >
      <ProblematicComponent />
    </ErrorBoundary>
  );
}

 

3. fallbackComponent

import { ErrorBoundary, FallbackProps } from "react-error-boundary";

function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
  return (
    <div>
      <p>Oops! An error occurred: {error.message}</p>
      <button onClick={resetErrorBoundary}>Try Again</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <ProblematicComponent />
    </ErrorBoundary>
  );
}

 

react-slick, slick-carousel

더보기

`react-slick`은 React에서 슬라이더(캐러셀, Carousel) 를 쉽게 만들 수 있도록 해주는 라이브러리입니다.
이 라이브러리는 내부적으로 Slick.js(jQuery 기반)를 감싸서 사용합니다.
따라서, CSS 스타일과 기능을 정상적으로 사용하려면 `slick-carousel`도 함께 설치해야 합니다.

import Slider from "react-slick";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import type { JSX } from "react/jsx-runtime";

const images = [
    "https://via.placeholder.com/600x300?text=Slide+1",
    "https://via.placeholder.com/600x300?text=Slide+2",
    "https://via.placeholder.com/600x300?text=Slide+3",
];

export default function ReactSlickDemo() {
    const settings = {
        dots: true,
        infinite: true,
        speed: 500,
        slidesToShow: 1, //한 번에 화면에 보이는 슬라이드 개수를 1로 설정합니다.
        slidesToScroll: 1,//슬라이드를 넘길 때 한 번에 이동하는 슬라이드 개수를 1로 설정합니다.
        autoplay: true,
        autoplaySpeed: 200,
    };

    function retrun(arg0: JSX.Element) {
        throw new Error("Function not implemented.");
    }

    return (
        <>
            <Slider {...settings}>
                {images.map((src, index) => {
                    return (
                        <div key={index}>
                            <img
                                src={src}
                                alt={`Slide ${index + 1}`}
                                style={{ width: "100%", borderRadius: "10px" }}
                            />
                        </div>
                    );
                })}
            </Slider>
        </>
    );
}

 

overlay-kit

더보기
import { overlay, OverlayProvider } from "overlay-kit";
import { useState } from "react";
import { AnimatePresence, motion, type Variants } from "framer-motion";
import { useRef, type PropsWithChildren } from "react";

export function OverlayKitDemo() {
    return (
        <OverlayProvider>
        <div>
            <DemoWithState />
            <DemoWithEsOverlay />
        </div>
        </OverlayProvider>
    );
}

function DemoWithState() {
    const [isOpen, setIsOpen] = useState(false);

    return (
        <div>
            <p>Demo with useState</p>
            <button onClick={() => setIsOpen(true)}>open modal</button>
            <Modal isOpen={isOpen}>
                <div
                    style={{
                        display: "flex",
                        flexDirection: "column",
                        alignItems: "center",
                        justifyContent: "center",
                    }}
                >
                    <p>MODAL CONTENT</p>
                    <button onClick={() => setIsOpen(false)}>
                        close modal
                    </button>
                </div>
            </Modal>
        </div>
    );
}

function DemoWithEsOverlay() {
    return (
        <div>
            <p>Demo with overlay-kit</p>
            <button
                onClick={() => {
                    overlay.open(({ isOpen, close, unmount }) => {
                        return (
                            <Modal isOpen={isOpen} onExit={unmount}>
                                <div
                                    style={{
                                        display: "flex",
                                        flexDirection: "column",
                                        alignItems: "center",
                                        justifyContent: "center",
                                    }}
                                >
                                    <p>MODAL CONTENT</p>
                                    <button onClick={close}>close modal</button>
                                </div>
                            </Modal>
                        );
                    });
                }}
            >
                open modal
            </button>
        </div>
    );
}



type ModalProps = {
    isOpen?: boolean;
    onExit?: () => void;
};

export function Modal({
    children,
    isOpen = false,
    onExit,
}: PropsWithChildren<ModalProps>) {
    const prevIsOpenRef = useRef(isOpen);

    if (isOpen !== prevIsOpenRef.current) {
        prevIsOpenRef.current = isOpen;

        if (prevIsOpenRef.current === false) {
            setTimeout(() => onExit?.(), 300);
        }
    }

    return (
        <AnimatePresence>
            {isOpen === true && (
                <ModalContent isOpen={isOpen}>{children}</ModalContent>
            )}
        </AnimatePresence>
    );
}

const MODAL_CONTENT_VARIANTS: Variants = {
    hidden: { opacity: 0, scale: 0.75 },
    show: { opacity: 1, scale: 1 },
};

function ModalContent({ children, isOpen }: PropsWithChildren<ModalProps>) {
    return (
        <div
            style={{
                zIndex: 100,
                position: "fixed",
                top: 0,
                left: 0,
                bottom: 0,
                width: "100%",
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
            }}
        >
            <motion.section
                variants={MODAL_CONTENT_VARIANTS}
                initial="hidden"
                exit="hidden"
                animate={isOpen ? "show" : "hidden"}
                style={{
                    padding: 120,
                    borderWidth: 1,
                    borderStyle: "solid",
                    borderColor: "gray",
                    borderRadius: 12,
                }}
            >
                {children}
            </motion.section>
        </div>
    );
}

 

react-infinite-scroller

더보기

React에서 무한 스크롤(Infinite Scroll) 을 구현할 때 주로 사용하는 기능입니다.

  • InfiniteScroll: 사용자가 페이지를 스크롤할 때 데이터를 추가로 불러오는 UI 컴포넌트
  • useInfiniteQuery: React Query 라이브러리에서 제공하는 무한 스크롤을 위한 데이터 패칭 함수
import { useInfiniteQuery } from "@tanstack/react-query";
import InfiniteScroll from "react-infinite-scroller";

export interface People {
    name: string;
    hair_color: string;
    eye_color: string;
}

type PeopleProps = People;

export function People({ name, hair_color, eye_color }: PeopleProps) {
    return (
        <li>
            {name}
            <ul>
                <li>hair: {hair_color}</li>
                <li>eyes: {eye_color}</li>
            </ul>
        </li>
    );
}

interface PageData {
    next: string | null;
    results: People[];
}

interface PageData {
    next: string | null;
    results: People[];
}

const initialUrl = "https://swapi.py4e.com/api/people/";
const fetchUrl = async (url: string) => {
    const response = await fetch(url);
    return response.json();
};

export function InfinitePeople() {
    const { data, fetchNextPage, hasNextPage, isFetching, isError, error } =
        useInfiniteQuery({
            queryKey: ["sw-people"],
            queryFn: ({ pageParam = initialUrl }) => fetchUrl(pageParam),
            getNextPageParam: (lastPage: PageData) =>
                lastPage.next || undefined,
            initialPageParam: initialUrl,
        });

    if (isError) return <div>Error: {error.message}</div>;

    return (
        <div className="content__wrap">
            <h2 className="title">People - Infinite Scroll</h2>
            <div
                className="infinite__wrap"
                style={{ overflow: "hidden", padding: "0" }}
            >
                <ul
                    className="infinite__wrap-item"
                    style={{ height: "450px", overflow: "auto" }}
                >
                    {data && (
                        <InfiniteScroll
                            initialLoad={false}
                            useWindow={false}
                            loadMore={() => {
                                if (!isFetching) {
                                    fetchNextPage();
                                }
                            }}
                            hasMore={hasNextPage}
                        >
                            {data?.pages.map((pageData) => {
                                return pageData.results.map((people) => {
                                    return (
                                        <People
                                            key={people.name}
                                            name={people.name}
                                            hair_color={people.hair_color}
                                            eye_color={people.eye_color}
                                        />
                                    );
                                });
                            })}
                        </InfiniteScroll>
                    )}
                </ul>
            </div>
            {isFetching && (
                <div className="loading-bar">
                    <div className="loading"></div>
                </div>
            )}
        </div>
    );
}

 

예시응답

{
    "count": 87,
    "next": "https://swapi.py4e.com/api/people/?page=2",
    "previous": null,
    "results": [
        {
            "name": "Obi-Wan Kenobi",
            "height": "182",
            "mass": "77",
            "hair_color": "auburn, white",
            "skin_color": "fair",
            "eye_color": "blue-gray",
            "birth_year": "57BBY",
            "gender": "male",
            "homeworld": "https://swapi.py4e.com/api/planets/20/",
            "films": [
                "https://swapi.py4e.com/api/films/1/",
                "https://swapi.py4e.com/api/films/2/",
                "https://swapi.py4e.com/api/films/3/",
                "https://swapi.py4e.com/api/films/4/",
                "https://swapi.py4e.com/api/films/5/",
                "https://swapi.py4e.com/api/films/6/"
            ],
            "species": [
                "https://swapi.py4e.com/api/species/1/"
            ],
            "vehicles": [
                "https://swapi.py4e.com/api/vehicles/38/"
            ],
            "starships": [
                "https://swapi.py4e.com/api/starships/48/",
                "https://swapi.py4e.com/api/starships/59/",
                "https://swapi.py4e.com/api/starships/64/",
                "https://swapi.py4e.com/api/starships/65/",
                "https://swapi.py4e.com/api/starships/74/"
            ],
            "created": "2014-12-10T16:16:29.192000Z",
            "edited": "2014-12-20T21:17:50.325000Z",
            "url": "https://swapi.py4e.com/api/people/10/"
        }
    ]
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'WEB개발 > REACT' 카테고리의 다른 글

React 참고, 오류  (0) 2025.02.20
[환경구성] tsconfig.js, tsc  (0) 2025.02.13
[React] Next, Typescript... 오류모음  (0) 2024.01.26
[React] web성능 메트릭  (0) 2023.12.18
[React] Typescript  (1) 2023.12.18