반응형

변환하면서 자주 만나는 에러 종류 정리

React에서 typescript로 작성하면서 자주 만나는 에러들을 정리해보려고 한다.

No overload matches this call.

export default function S_useMemo(){
    const [number, setNumber] = useState("");
    const [list, setList] = useState([]);

    const onInsert = (e: React.MouseEvent<HTMLButtonElement>) => {
        setList((prev) => prev.concat(parseInt(number))); //error No overload matches this call.
        setNumber("");
    };

    return (...)
}

기존 state의 값인 list에 concat으로 붙여서 기존 배열을 늘려나가는 방식으로 동작하는 insert함수이다.
해당부분에서 state에 담긴 문자열 number를 정수형으로 변환하여 붙여넣는 방식인데, No overload matcheds this call 에러가 발생하고 있다.
기본적으로 typescript이더라도 useState초기값에 따라 값을 추론하여 타입을 지정해주는데, const [list, setList] = useState([]);
이부분에서 list배열은 어떤 타입의 배열을 받는건지 정해진 값이 없어서 발생한 오류이다 제네릭으로 정수형 배열을 받는다고 처리해주면 된다.

해결

export default function S_useMemo(){
    const [number, setNumber] = useState("");
    const [list, setList] = useState<number[]>([]);

    const onInsert = (e: React.MouseEvent<HTMLButtonElement>) => {
        setList((prev) => prev.concat(parseInt(number))); //error No overload matches this call.
        setNumber("");
    };

    return (...)
}
반응형
반응형

지금까지 작성한 결과물을 동작을 확인해보고 회고록을 작성한다.
 

👀 결과물(캡쳐)

로그인 화면

로그인 페이지 동작

초기 로그인된 적이 없기 때문에 로그인 페이지가 렌더링 된다.

(recoil의 상태값 loginInfo로 체크)

이후 db에 저장된 정보로 로그인을 처리하고 서버쪽으로 응답을 받는다.

여기서 일치한다면 서버쪽에선 세션에 정보를 저장한다.

이후 정상적으로 응답을 받았다는 메시지를 통해 recoil쪽에 로그인된 유저라는 흔적용으로 로그인ID를 등록한다.

loginInfo에 값이 생겼기 때문에 MyInfo페이지로 렌더링된다.
 

마이페이지

변경된 MyPage 컴포넌트에선 useEffect에 의해 rest api로 현재 세션에 담긴 유저의 정보를 달라고 요청한다.

세션이 아직 살아있는 상태라면 세션에 등록된 id정보를 통해 유저 테이블에서 조회 후 적절한 데이터를 json으로 제공한다.

다만, React에서 recoil의 상태값은 새로고침하는 순간 날라간다.

이점을 임시적으로 처리하기 위해 로그인 페이지 useEffect에서 세션에 로그인된 정보가 있다면 recoil에 강제로 값을 처리하고 MyPage로 넘어가도록 처리해주었다.

강제로 새로고침을 많이 하게되면 서버와의 통신도 많아지고 효율적이지 못한 동작이 될 수 있다.
 

로그아웃

로그아웃을 요청하는 rest api가 처리되면 서버측에서 세션에 담긴 로그인 정보를 destory처리한다.

이후 응답에 따라 recoil상태값을 비워서 다시 로그인페이지로 렌더링 될 수 있게 하였다.
 

🤔 작업 후기

가볍게 노드서버도 익혀보고 리액트도 붙여서 동작을 해보려고 만만하게 보고 시작했는데, github에 소스도 올리고 블로그에 지금까지 사용된 라이브러리나 소스코드를 정리하다 보니 시간이 많이 걸렸다.

의외로 서버쪽 처리하는 부분을 설정하는게 시간을 많이 잡아먹었는데, 노드에 대해서 하나도 모르고 접근하다보니 시간 낭비하는 작업이 좀 많았던 것 같다.

일단 로그인 세션처리야 서버측에서 알아서 해주니 어느정도 작업을 마치고부턴 큰 문제가 없었는데, recoil로 유저의 정보를 담다보니 효율적이 못하다는 생각이 들었다. redux나 recoil에 localstorage를 붙여보는 방법 등등을 연구해보고 추가 글을 작성해보아야 할 듯 하다.
 

아래는 작업 해 본 내용을 정리한 글이다.

https://myhappyman.tistory.com/301

https://myhappyman.tistory.com/302

https://myhappyman.tistory.com/303
 

아래는 github에 지금까지 작성한 소스 부분을 올려놨다.

중간중간 가볍게 유효성검사나 로직등을 수정할 수도 있다.

https://github.com/myhappyman/NodeJsAndReactLoginModules

반응형
반응형

이전글 정보 ↓

https://myhappyman.tistory.com/302
 

서버 설정이 끝났으니 뷰를 작성하고 rest api를 통해 요청을 진행한다.
* 전부 클라이언트(react) 부분에 대한 설정이므로 모든 경로는 'client/~' 으로 시작한다.
 

🗂️ 작업 디렉토리 구조

 

⚙️ 추가 설치

추가할 라이브러리부터 설치한다.

$ cd client
$ npm install recoil
  • recoil: 전역 state 관리
     

🚀 React 설정 및 개발

전역 state 처리용

 

📄/src/atom.ts

import { atom } from "recoil";

export const loginInfo = atom({
    key: "loginInfo",
    default: "",
});

 

📄/src/index.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { RecoilRoot } from "recoil";

const root = ReactDOM.createRoot(
    document.getElementById("root") as HTMLElement
);
root.render(
    <React.StrictMode>
        <RecoilRoot>
            <App />
        </RecoilRoot>
    </React.StrictMode>
);

recoil 적용
 

📄/src/App.tsx

import React from "react";
import { useRecoilValue } from "recoil";
import { loginInfo } from "./atom";
import Login from "./components/login/login";
import MyPage from "./components/login/mypage";

function App() {
    const loginUser = useRecoilValue(loginInfo);
    return (
        <div className="App">{loginUser !== "" ? <MyPage /> : <Login />}</div>
    );
}

export default App;

loginInfo정보에 따라 로그인 페이지, 마이페이지 컴포넌트 설정
아직 MyPage에 대한 작성이 없으므로 작성을 시작한다.
 

🪄 마이페이지 작성

📄/src/components/login/mypage.tsx

import { useRecoilState } from "recoil";
import { loginInfo } from "../../atom";
import { useEffect, useState } from "react";

export default function MyPage() {
    const [loginUser, setLoginUser] = useRecoilState(loginInfo);
    const [userEmail, setUserEmail] = useState("");

    const logout = () => {
        fetch("/login/logout")
            .then((res) => res.json())
            .then((res) => {
                console.log(res);
                setLoginUser("");
            });
    };

    useEffect(() => {
        fetch("/login/getUserInfo")
            .then((res) => res.json())
            .then((res) => {
                const { user_email } = res.data;
                setUserEmail(user_email);
            });
    }, []);
    return (
        <div>
            <h2>My Page!</h2>
            <div>
                <label htmlFor="">접속한 아이디</label>
                <div>{loginUser}</div>
            </div>
            <div>
                <label htmlFor="">이메일</label>
                <div>{userEmail}</div>
            </div>
            <div>
                <button onClick={logout}>로그아웃</button>
            </div>
        </div>
    );
}

 

🔑 로그인 페이지 구현

📄/src/components/login/login.tsx

import { useState, useEffect } from "react";
import { useSetRecoilState } from "recoil";
import { loginInfo } from "../../atom";

export default function Login() {
    const [userId, setUserId] = useState("");
    const [userPw, setUserPw] = useState("");
    const setLoginInfo = useSetRecoilState(loginInfo);

    const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
        if (e.target.name === "userId") {
            setUserId(e.target.value);
        } else if (e.target.name === "userPw") {
            setUserPw(e.target.value);
        }
    };

    const goLogin = () => {
        const data = {
            userId: userId,
            userPw: userPw,
        };
        fetch("/login/login", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                Accept: "application / json",
            },
            body: JSON.stringify(data),
        })
            .then((res) => res.json())
            .then((data) => {
                const { msg } = data;
                if (msg === "success") {
                    setLoginInfo(userId);
                }
            })
            .catch((err) => console.log(err));
    };

    useEffect(() => {
        fetch("/login/getUserInfo")
            .then((res) => res.json())
            .then((res) => {
                const { msg, data } = res;
                if (msg === "success" && data?.user_id) {
                    setLoginInfo(data?.user_id);
                }
            });
    }, [setLoginInfo]);

    return (
        <div>
            <h2>Login Test</h2>
            <div>
                <label htmlFor="">아이디</label>
                <input
                    type="text"
                    name="userId"
                    value={userId}
                    onChange={handleInput}
                />
            </div>
            <div>
                <label htmlFor="">비밀번호</label>
                <input
                    type="password"
                    name="userPw"
                    value={userPw}
                    onChange={handleInput}
                />
            </div>
            <div>
                <button type="button" onClick={goLogin}>로그인</button>
            </div>
        </div>
    );
}
반응형
반응형

이전글 정보 ↓

https://myhappyman.tistory.com/301


이번 글에선 NodeJS가 로그인, 로그아웃에 대한 세션 처리를 동작 할 수 있도록 서버에 관한 설정을 한다.

2가지 파일 작성(/api/login.js, db/mysql_pool.js)

 

🗂️ Mysql Pool moduel 작성(DB 연결)

📄/db/mysql_pool.js

const mysql = require("mysql2/promise");

// require("dotenv").config(); // -> 환경변수 사용시 추가

const config = {
    host: process.env.host,
    user: process.env.user,
    password: process.env.password,
    database: process.env.database,
};

//mysql connection pool 생성
let pool = mysql.createPool(config);

/**
 * 1. 생성한 커넥션 풀을 통해 비동기로 쿼리를 던짐
 * 2. 응답이 오면 리소스 제거
 * 3. 조회된 결과값 리턴
 * @param {*} query
 * @returns array || boolean
 */
async function getConnection(query) {
    try {
        const connect = await pool.getConnection(async (conn) => conn);
        try {
            const [rows] = await connect.query(query);
            connect.release();
            return rows;
        } catch (err) {
            console.log("Error", err);
            connect.release();
            return false;
        }
    } catch (err) {
        console.log("DB Error");
        return false;
    }
}

module.exports = getConnection;

config부분에 각자 환경에 맞는 mysql정보를 넣는다.
 

환경변수를 사용하는경우

환경변수로 config를 채우는 경우 dotenv를 설치하고 아래 메소드를 mysql_pool.js에 추가합니다.

$ npm install dotenv

require("dotenv").config();
 

동작 구성

  • mysql2의 promise를 활용하여 커넥션 풀을 생성
  • getConnection 메소드에 쿼리를 던져서 비동기로 결과값을 받는다.
  • release()를 통해 pool을 반납한다.

 

📥 Api통신 모듈 작성

login에 관한 동작을 처리할 모듈을 작성한다.

시작하기전 필요한 모듈을 설치한다.

$ npm install express-session memorystore body-parser
  • express-session: express에서 제공하는 세션 관리 매니저
  • memorystore: 세션을 메모리에 저장하기 위해 같이 사용
  • body-parser: body로 전달받은 request데이터를 json으로 처리하기 위해 사용
     

📄/api/login.js

const getConnection = require("../db/mysql_pool");

exports.findOneUser = (userId) => {
    const sql = `select * from shin_games_user_table
    where user_id="${userId}"`;
    return getConnection(sql);
};

exports.findLoginUser = (userId, userPw) => {
    const sql = `select * from shin_games_user_table
    where user_id="${userId}" and user_pw="${userPw}"`;
    return getConnection(sql);
};

exports.Login = (req, res) => {
    const { userId, userPw } = req.body;
    this.findLoginUser(userId, userPw).then((result) => {
        if (result === false) {
            res.json({ msg: "fail" });
        } else {
            if (result.length > 0) {
                req.session.userId = result[0].user_id;
                res.json({ msg: "success" });
            } else {
                res.json({ msg: "fail" });
            }
        }
    });
};

exports.Logout = (req, res) => {
    let code = 200;
    if (req.session.userId) {
        req.session.destroy();
        res.json({ msg: "success", code });
    } else {
        res.json({ msg: "fail", msg: "권한이 없습니다.", code });
    }
};

exports.getUserInfo = (req, res) => {
    if (req.session.userId) {
        this.findOneUser(req.session.userId).then((result) => {
            if (!result) {
                res.json({ msg: "fail", code: 200 });
            } else {
                res.json({
                    data: result[0],
                    msg: "success",
                    code: 200,
                });
            }
        });
    } else {
        res.json({ msg: "fail" });
    }
};

api 모듈 작성이 완료되면 연결한다.

 

🔧 서버 모듈 적용하기

설치한 라이브러리 적용과 설정, 모듈을 적용한다.
 

📄index.js

const express = require("express");
const session = require("express-session");
const MemoryStore = require("memorystore")(session);
const bodyParser = require("body-parser");

const app = express();
const port = process.env.PORT || 3001;

app.listen(port, function () {
    console.log(`server running on ${port}`);
});

const sessionTime = 1000 * 60 * 30; // 30분
const sessionObj = {
    secret: "shin",
    resave: false,
    saveUninitialized: true,
    store: new MemoryStore({ checkPeriod: sessionTime }),
    cookie: {
        maxAge: sessionTime,
    },
};
app.use(session(sessionObj));
app.use(bodyParser.json());

// rest api callback module
const LoginApi = require("./api/login");

// 로그인 체크용
app.use("/login/login", LoginApi.Login);
// 로그아웃
app.use("/login/logout", LoginApi.Logout);
// 유저정보 조회
app.use("/login/getUserInfo", LoginApi.getUserInfo);

세션에 대한 설정처리와 rest api시 받은 데이터의 파싱을 위해 body-parser를 적용했다.
이후 각 rest api별 동작해야 할 모듈을 적용으로 서버에 대한 설정을 마친다.
이제 클라이언트단 작성을 하면 된다.

반응형
반응형

 

리액트를 학습하고 로그인, 로그아웃, 회원가입 페이지를 다뤄본 적이 없어서 경험을 해볼 겸 학습을 시작하였다.

먼저 리액트에선 프론트를 담당하고 db와 연결 및 데이터 검증을 해줄 서버 구성이 필요했다.

기존에 해보던 Java로 해도 괜찮지만 NodeJS를 적용해보면 간편하고 쉬울 것 같아서 시작하게 되었다.(새PC에 자바가 없어서 세팅하기가 귀찮다는 그런 이유도 있다...)

 

🚀 목표

  • 리액트로 로그인 페이지 구현
  • 로그아웃 처리
  • 회원가입 기능
  • 노드JS에서 REST API 처리
  • DB연결 및 쿼리 작성
  • 세션 처리

💡 생각해 볼 것

로그인 처리 후 인증 방식에 크게 SessionJWT가 있다.

아직 JWT를 경험하지 못해서 학습할 겸 적용해봐도 좋지만 노드JS도 다뤄본적이 없는데 너무 헤멜것 같아서 먼저 이번 목표를 수행하고 다음 과제로 수행 해 볼 예정이다. 고로 클래식한 Session처리로 한다.

 

🔧 설치할 모듈

  • express: nodeJS에서 서버를 구성하기 위한 라이브러리
  • nodemon: node기반에서 변경사항이 발생하면 자동으로 재시작해주는 라이브러리
  • cocurrently: 명령어를 동시에 실행시켜준다.(node, react동시 실행용)
  • react & typescript: 리액트(ts.ver) 설치
  • http-proxy-middleware: 프록시 설정을 위해 필요한 라이브러리(react -> node로 fetch때 사용예정)
  • mysql2: mysql접속을 위해 설치(설정 문제 때문에 2로 설치하였다.)

 

⚙️ 환경 설정

서버 구성하기(NodeJS )

먼저 서버로 작업할 디렉토리를 하나 파고 아래의 명령을 수행한다.

$ npm init -y
$ npm install express nodemon concurrently mysql2

이것만으로 가볍게 웹서버를 구성할 수 있다.
서버로 사용할 index페이지를 생성한다.

 

📄index.js

const express = require('express');
const app = express();
const port = process.env.PORT || 3001;

app.listen(port, function () {
  console.log(`server running on ${port}`);
});

nodemon 설정을 위해 package.json을 수정한다.
 

📄package.json

"scripts": {
  "start": "nodemon index.js"
}

프론트 생성(리액트 ts.ver)

$ npx create-react-app client --template typescript

리액트(client)에서 프록시 설정을 위해 아래 명령어로 라이브러리를 설치한다.

$ cd client
$ npm install http-proxy-middleware

설치가 끝나면 setupProxy파일을 생성하고 아래와 같이 입력한다.
 

📄/client/src/setupProxy.js

const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function (app) {
  app.use(
    createProxyMiddleware("/login/**", {
      target: "http://localhost:3001",
      changeOrigin: true,
    })
  );
};

 

개발 실행 환경 구성하기

package.json을 손봐야 vscode하나를 켜놓고 서버와 리액트를 동시에 실행하여 편하게 개발할 수 있다.

scripts부분을 아래와 같이 추가한다. (리액트쪽 package.json이 아님!!)
 

📄package.json

"scripts": {
  "start": "nodemon index.js",
  "dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"",
  "dev:server": "npm start",
  "dev:client": "cd client && npm start"
}

앞으로 개발시 npm run dev를 통해 개발 테스트를 할 수 있다.
 

🔥 임시 작성 테스트 결과

리액트 설정

📄/client/src/login/login.tsx

import { useEffect, useState } from "react";

export default function Login() {
    const [userId, setUserId] = useState("");
    const [userPw, setUserPw] = useState("");

    const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
        if (e.target.name === "userId") {
            setUserId(e.target.value);
        } else if (e.target.name === "userPw") {
            setUserPw(e.target.value);
        }
    };

    const goLogin = () => {
        console.log("click!");
    };

    useEffect(() => {
        fetch("/login/test")
            .then((res) => res.json())
            .then((data) => console.log(data));
    }, []);

    return (
        <div>
            <h2>Login Test</h2>
            <div>
                <label htmlFor="">아이디</label>
                <input
                    type="text"
                    name="userId"
                    value={userId}
                    onChange={handleInput}
                />
            </div>
            <div>
                <label htmlFor="">비밀번호</label>
                <input
                    type="password"
                    name="userPw"
                    value={userPw}
                    onChange={handleInput}
                />
            </div>
            <div>
                <button type="button" onClick={goLogin}>
                    로그인
                </button>
            </div>
        </div>
    );
}

 

📄src/App.tsx

import React from "react";
import Login from "./components/login/login";

function App() {
    return (
        <div>
            <Login />
        </div>
    );
}

export default App;

 

서버 설정

📄index.js

const express = require("express");
const app = express();
const port = process.env.PORT || 3001;

app.listen(port, function () {
    console.log(`server running on ${port}`);
});

app.use("/login/test", function (req, res) {
    res.json({ test: "success" });
});

 

테스트 캡처

login/test로 부터 응답을 받아서 클라이언트쪽에서 콘솔 로그에 결과값이 잘 찍히고 있다.

반응형
반응형

firebase에 개행처리된 데이터를 <br />태그로 치환 처리하여 개행을 처리하고 싶었다.

firebase에 저장된 데이터에는 개행을 처리하고자 하는부분에 "\n" 문자열을 입력하였다.

 

받는부분에선 아래와 같이 처리하여 <br/>태그입력에 성공하였다.

{data.comments.split("\\n").map((p: string, idx: number) => {
    return (
      <span key={`${idx}`}>
        {p}
        <br />
      </span>
    );
})}

key값은 알아서 원하는 값으로 처리하면 될듯하고 split()을 통해 개행으로 구분한 문자열로 나누고 map으로 재조합을 처리해주었다.

 

원하는 형태로 개행처리가 되었다.

반응형
반응형

로컬의 ttf, woff, woff2등 웹 폰트를 적용하는 방법은 아래 글을 참고해주세요.

https://myhappyman.tistory.com/287

 

React - Style component font 적용방법 알아보기(typescript.ver)

리액트에서 styled components를 활용하여 전역 공통 css부분을 적용할 수가 있는데요. 이때 폰트를 적용하는 방법을 알아보고자 합니다. 프로젝트 로컬에 위치한 폰트 연결하기 먼저 프로젝트 내부

myhappyman.tistory.com

 

구글 폰트 적용하기

이번엔 로컬이 아닌 자주 사용하는 구글폰트를 연결하는 방법을 알아보겠습니다.

 

기존의 로컬방식으로 styled-component부분에 import를 하여 간단히 사용하면 될 것으로 생각하였으나, 적용시 아래와 같은 경고메시지와 폰트 적용이 되지 않는 모습을 볼 수 있습니다.

createGlobalStyle인 전역 스타일링 부분에서 import구문을 활용한 적용방식을 추천하지 않으며 react-helmet의 라이브러리를 통해 적용하는 것을 추천한다는 내용입니다.

 

React-Helmet을 통해 폰트 적용하기

react-helmet은 어떤 컴포넌트에서든 사용하게되면 최상단 header에 접근하고 수정하거나 추가하는것을 도와주는 라이브러리 입니다.

 

먼저 설치를 진행해줍니다.

npm install react-helmet

 

 

설치가 완료되면, 원하는 위치에 파일을 생성합니다.

저는 src하위에 바로 생성하였습니다.

 

- src/HelmetComponents.tsx

import React from "react";
import { Helmet } from "react-helmet";

function HelmetComponent() {
  return (
    <Helmet>
      <link rel="preconnect" href="https://fonts.googleapis.com" />
      <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
      <link
        href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
        rel="stylesheet"
      />
    </Helmet>
  );
}

export default HelmetComponent;

이후 컴포넌트 작성하는 방식과 동일하며 Helmet 내부에 원하는 header태그에 해당할만한 내용을 작성해주시면 됩니다.

 

style태그, meta, link, title등 다양한 속성들이 전부 적용이 됩니다.

여기선 구글에서 제공하는 Poppins 폰트 정보를 적용하였습니다.

 

작성이 완료되면 저장을 한 후, index.tsx에서 해당 컴포넌트를 호출하여 적용해주면 됩니다.

 

- src/index.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import HelmetComponent from "./HelmetComponent";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <React.StrictMode>
  <RecoilRoot>
    <HelmetComponent />
    <App />
  </RecoilRoot>
  </React.StrictMode>
);

 

이것으로 준비는 끝입니다.

 

Global스타일링이나 원하는 위치에서 해당하는 구글 폰트를 font-family를 통해 적용시켜주면 끝입니다.

반응형
반응형

리액트에서 styled components를 활용하여 전역 공통 css부분을 적용할 수가 있는데요.

이때 폰트를 적용하는 방법을 알아보고자 합니다.

 

프로젝트 로컬에 위치한 폰트 연결하기

먼저 프로젝트 내부에 존재하는 ttf, woff, woff2등의 웹 폰트 파일을 연결하는 방법입니다.

 

1. 폰트 파일 준비

- src/assets/fonts/Pretendard 디렉토리에 원하는 파일들을 위치시켰습니다.

 

이후, 해당 파일을 import하여 전역 스타일링을 할 GlobalStyle에서 사용을 합니다.

 

2. 전역 스타일링 파일 GlobalStyle.ts

import { createGlobalStyle } from "styled-components";
import PretendardExtraLight from "./assets/fonts/Pretendard/PretendardExtraLight.woff2";
import PretendardRegular from "./assets/fonts/Pretendard/PretendardRegular.woff2";
import PretendardBold from "./assets/fonts/Pretendard/PretendardBold.woff2";
import PretendardExtraBold from "./assets/fonts/Pretendard/PretendardExtraBold.woff2";

const GlobalStyle = createGlobalStyle`
    @font-face {
        font-family: 'Pretendard';
        font-weight: 200;
        src: url(${PretendardExtraLight}) format('woff2');    
        unicode-range: U+AC00-D7A3;
    }

    @font-face {
        font-family: 'Pretendard';
        font-weight: 400;
        src: url(${PretendardRegular}) format('woff2');    
        unicode-range: U+AC00-D7A3;
    }

    @font-face {
        font-family: 'Pretendard';
        font-weight: 700;
        src: url(${PretendardBold}) format('woff2');    
        unicode-range: U+AC00-D7A3;
    }

    @font-face {
        font-family: 'Pretendard';
        font-weight: 800;
        src: url(${PretendardExtraBold}) format('woff2');    
        unicode-range: U+AC00-D7A3;
    }
    body {
    	font-family: 'Poppins', 'Pretendard', sans-serif;
    }
`;

export default GlobalStyle;

- src/GlobalStyle.ts

 

import를 통해 폰트 파일 위치의 woff2 파일들을 import하고 @font-face에 적용을 해줍니다.

저는 한글에만 pretendard가 동작하도록 unicode-range를 설정하였습니다.

여기서 그대로 따라하다보면 import부분에서 woff2확장자에 대해 알 수 없기에 오류가 발생할 것입니다.

typescript에게 woff2확장자에 대해 인식할 수 있도록 파일을 추가 생성합니다.

 

3. fonts.d.ts 생성

declare module "*.woff2";
declare module "*.ttf";
declare module "*.woff";

- src/fonts.d.ts

 

ttf, woff도 사용 할 수도 있기에 추가해두었습니다.

저장을 하면 import 오류부분이 사라지는 것을 볼 수 있습니다.

 

4. GlobalStyle.ts 적용하기

최상단 컴포넌트인 index.tsx또는 App.tsx부분에 적용을 해주면 폰트가 바로 적용되는 것을 확인 할 수 있습니다.

import React from "react";
import styled, { ThemeProvider } from "styled-components";
import GlobalStyle from "./GlobalStyle";
function App() {

  return (
    <ThemeProvider theme={"mars"}>
      <GlobalStyle />      
    </ThemeProvider>
  );
}

export default App;

- src/App.tsx

반응형