1. package.json 파일이 없는 경우

 

npm start 명령을 입력했지만, 아래와 같은 오류가 떴다.

club> npm start
npm ERR! code ENOENT
npm ERR! syscall open
npm ERR! path C:\Users\...\WebPrograming TeamProject\club\package.json
npm ERR! errno -4058
npm ERR! enoent Could not read package.json: Error: ENOENT: no such file or directory, open 'C:\Users\...\WebPrograming TeamProject\club\package.json'
npm ERR! enoent This is related to npm not being able to find a file.
npm ERR! enoent

npm ERR! A complete log of this run can be found in: C:\Users\clala\AppData\Local\npm-cache\_logs\2024-10-12T08_25_52_763Z-debug-0.log

 

  • package.json 파일 확인: 폴더에 package.json 파일이 있는지 확인해보자. 이 파일이 없으면 npm 명령을 실행할 수 없다.
  • package.json 파일 생성 (없을 경우): 폴더에 package.json 파일이 없으면 npm init 명령을 실행해서 파일을 생성한다. 기본 설정을 완료한 후 다시 npm install을 실행해서 의존성을 설치하고, 이후 npm start를 입력해본다.
    npm init -> npm install - npm start

 

 

2. start 스크립트 에러

아래 에러메세지는 현재 package.json 파일의 scripts 섹션에 start 스크립트가 정의되어 있지 않아 발생하는 문제이다.  

 

package.json 파일을 보았을 때, start에 대한 정의가 되어있지 않음을 확인할 수 있었다.

 

아래와 같이 start 정의 부분을 추가하여 주면, 문제가 해결됨을 확인할 수 있었다.

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "react-scripts start" // 이부분 추가
  },

 

개발도중, API에 접근하려하자 CORS 정책에 의해 막혔다는 에러가 발생했다. 

 

 

시작하기에 앞서, CORS에 대해 학습하고자 한다.

 

🔺 CORS(Cross-Origin Resource Sharing)

  • 교차 출처 리소스 공유
  • 도메인이 다른 서버끼리 리소스를 주고 받을 때 보안을 위해 설정된 정책

 

일반적으로, 프론트엔드와 백엔드가 협업하면서 각자 따로 서버를 띄우게 되었을 경우에 발생한다. 서로 다른 React 서버(3000포트)와 Springboot(8080포트) 서버가 리소스를 주고 받으려 한다면 포트번호가 달라 서로 다른 출처로 판단되어 CORS 위반 에러가 발생한다.

 

📌 Origin(출처)

그렇다면 같은 출처와 다른 출처를 어떻게 구분할 수 있을까?

Protocal + Host + Port 가 같으면, 동일 출처(Origin)이라고 한다.

 

URL 구성요소

 

✅ 각 웹에서 사용하는 HTTP(80포트), HTTPS(443포트) 프로토콜의 기본포트번호가 정해져있기 때문에 출처 내의 포트 번호는 생략 가능한데, 출처에 포트 번호가 명시적으로 포함되어 있다면 이 포트번호까지 모두 일치해야 같은 출처라고 인정된다.

 

 

🔺 CORS 정책은 언제 검사할까?

출처 : https://evan-moon.github.io/2020/05/21/about-cors/#출처origin가-무엇인가요

 

  • 여기서 중요한 점은 출처를 비교하는 로직이 서버에 구현된 것이 아니라 브러우저에 구현되어있다는 것이다.
  • 우리가 CORS 정책을 위반하는 리소스 요청을 하더라도 해당 서버가 같은 출처에서 보낸 요청만 받겠다는 로직을 가지고 있는 경우가 아니라면 서버는 정상적으로 응답을 하고, 이후 브라우저가 이 응답을 분석해서 CORS 정책 위반이라고 판단되면 그 응답을 사용하지 않고 그냥 버린다.
  • CORS는 브라우저의 구현 스펙에 포함되는 정책이기 때문에, 브라우저를 통하지 않고 서버 간 통신을 할 때는 이 정책이 적용되지 않는다.

 

🔺 CORS의 동작 시나리오 3가지

 

CORS는 세 가지의 시나리오에 따라 변경되기 때문에 내 요청이 어떤 시나리오에 해당되는지 잘 파악한다면 CORS 정책 위반으로 인한 에러를 고치기 쉬워질 것이다.

 

✅ Preflight Request

  • 일반적으로 우리가 웹 어플리케이션을 개발할 때 가장 많이 마주치는 시나리오
  • 브러우저는 요청을 한번에 보내지 않고 예비 요청(Preflight)과 본 요청으로 나누어서 서버에 전송한다.
    • 예비 요청의 역할 → 본 요청을 보내기 전에 브라우저 스스로 이 요청을 보내는 것이 안전한지 확인한다.
우리가 자바스크립트의 fetch API를 사용하여 브라우저에게 리소스를 받아오라는 명령을 내리면 브라우저는 서버에게 예비 요청을 먼저 보내고, 서버는 이 예비 요청에 대한 응답으로 현재 자신이 어떤 것들을 허용하고, 어떤 것들을 금지하고 있는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 다시 보내주게 된다.
이후 브라우저는 자신이 보낸 예비 요청과 서버가 응답에 담아준 허용 정책을 비교한 후, 이 요청을 보내는 것이 안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보내게 된다. 이후 서버가 이 본 요청에 대한 응답을 하면 브라우저는 최종적으로 이 응답 데이터를 자바스크립트에게 넘겨준다.
  • CORS 정책 위반으로 인한 에러는 예비 요청의 성공 여부와 관계 없다.
  • 예비 요청의 성공/실패 여부가 중요한 것이 아니라, “응답 헤더에 유효한 Access-Control-Allow-Origin값이 존재하는지”가 중요하다.

 

 

✅ Simple Request

 

Simple Request(단순 요청)은 예비 요청 없이 본 요청만을 보내고, 서버가 이에 대한 응답의 헤더에 Access-Control-Allow-Origin 과 같은 값을 보내주면 그때 브라우저가 CORS 정책 위반 여부를 검사하는 방식이다.

  • Preflight Request와 전체적인 로직 자체는 같되, 예비 요청의 존재 유무만 다르다.
  • Credential이 없는 요청의 경우 를 통해 브라우저의 Origin에 상관없이 모든 Origin 을 허용할 수 있다.

특정 조건을 만족하는 경우에만 예비 요청을 생략할 수 있다.

(까다로운 조건이기 때문에, 실제로 모두 충족시키기에는 어려움이 있다.)

 

단순 요청을 사용할 수 있는 경우
1. 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
2. Request Header에는 다음 속성만 허용 Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width
3. 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용된다.
4. 요청에 사용된 XMLHttpRequestUpload 객체에는 이벤트 리스너가 등록되어 있지 않다. 이들은 XMLHttpRequest.upload 프로퍼티를 사용하여 접근한다.
5. 요청에 ReadableStream 객체가 사용되지 않는다.

 

✅ Credentialed Request

  • 인증된 요청을 사용하는 방법
  • 다른 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법이다.
  • credentials 옵션을 사용해 요청에 인증과 관련된 HTTP Cookie와 HTTP AUthentication 정보를 담을 수 있게 해준다.
  • Access-Control-Allow-Origin에는 모든 요청을 허용하는 *를 사용할 수 없으며, 명시적인 URL이어야 한다.

 

 

🔺 CORS를 해결할 수 있는 방법

 

방법 1️⃣.  Access-Control-Allow-Origin 세팅하기

  • CORS 정책 위반으로 인한 문제를 해결하는 가장 대표적인 방법
  • 정석대로 서버에서 Access-Control-Allow-Origin 헤더에 알맞은 값을 세팅해주는 것
  • *을 사용하여 이 헤더를 세팅하게 되면, 모든 출처에서 오는 요청을 다 받겠다는 의미이므로 당장은 편할 수 있지만, 보안적으로 심각한 이슈가 발생할 수도 있다.

방법 2️⃣. Webpack Dev Server로 리버스 프록싱하기

  • 프록싱을 통해 CORS 정책을 우회하는 방법
    • 마치 CORS 정책을 지킨 것처럼 브라우저를 속여 우리가 원하는 서버와 자유롭게 통신할 수 있다.
  • 실제 프로덕션 환경에서도 클라이언트 어플리케이션의 소스를 서빙하는 출처와 API 서버의 출처가 같은 경우에 사용하는 것이 좋다.
    • 어플리케이션을 빌드하고 서버에 올리고 나면 더 이상 webpack-dev-server가 구동하는 환경이 아니기 때문에 이상한 곳으로 API 요청을 보내게 된다.

결론

  • webpack-dev-server 방법은 로컬 개발 환경에서만 통하는 방법인데다가, 근본적인 문제 해결 방법이 아니기 때문에,
  • 결국 운영 환경에서 CORS 정책 위반 문제를 해결하기 위해서는 백엔드 개발자가 서버 어플리케이션의 응답 헤더에 올바른 Access-Control-Allow-Origin이 내려오도록 세팅해줄 수밖에 없다.
  • 실제 배포 단계에서는 같은 origin을 사용하기 때문에 문제가 되지 않기 때문에, 일단락 개발단계에서는 백엔드 개발자가 cors를 모두 허용하여 문제를 해결하였다.

 


참고

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

 

Cross-Origin Resource Sharing (CORS) - HTTP | MDN

Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources. CORS also relies on a mechanism by which

developer.mozilla.org

https://evan-moon.github.io/2020/05/21/about-cors/#%EC%B6%9C%EC%B2%98origin%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94

 

CORS는 왜 이렇게 우리를 힘들게 하는걸까?

이번 포스팅에서는 웹 개발자라면 한번쯤은 얻어맞아 봤을 법한 정책에 대한 이야기를 해보려고 한다. 사실 웹 개발을 하다보면 CORS 정책 위반으로 인해 에러가 발생하는 상황은 굉장히 흔해서

evan-moon.github.io

https://velog.io/@effirin/CORS%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80

 

CORS란 무엇인가?

CORS가 무엇인지 알기 전에, 이 CORS가 등장하게 된 배경을 먼저 알아보자.SOP는 2011년 RFC 6454에서 등장한 보안 정책으로 "같은 출처에서만 리소스를 공유할 수 있다"라는 규칙을 가진 정책이다.그러

velog.io

https://kk-programming.tistory.com/63

 

[Web] CORS (Cross Origin Resource Sharing)

* 아래 Source들을 참고하여 작성했습니다. 🔥 CORS (Cross Origin Resource Sharing) 사용자가 가져오는 리소스들이 안전한지 검사하는 관문 프론트 애플리케이션과 다른 origin(출처)를 가진 서버 애플리케

kk-programming.tistory.com

 

 

글자 사이에 삽입되어 있는 이미지 태그는 어디에 이미지가 들어가는지 표기하기 위한 '이미지 마킹'이다. 이를 라이브러리를 통해서 json파일을 리액트로 렌더링 해서 한번에 처리하면 좋겠지만 우리가 생각한 것처럼 되는 라이브러리를 찾지 못했다. 그래서 이를 정규 표현식으로 직접 파싱해서 우리가 원하는 img 태그로 전환하려 한다. 그렇게 만들게 된 함수는 아래 코드와 같다.

//HTML 문자열에 포함된 <img> 태그를 실제 이미지로 변경하는 함수
  const parseImageTag = (question) => {
    // 정규식을 사용하여 <img src=1> 문자열을 찾는다.
    const imgRegex = /<img\\s+src=1\\s*\\/?>/g;
    // 대체할 이미지 태그로 교체한다.
    return question.replace(imgRegex, '<img src= "~img_url"   
/>');
};

 

imgRegex변수에는 json파일에 <img src = 1/>태그를 찾을 수 있는 정규 표현식 '/<img\s+src=1\s*\/?>/g'인 문자를 문자열로 기입 해주고 변환이 이루어질 json의 question문자열을 파라매터로 가지고 와 replace를 통해서 imRegex와 내가 윈하는 img 태그로 전환을 해준다.

 

<ol>
        {data.map((item, index) => (
          <li key={index}>
            {/* 질문 */}
             <p dangerouslySetInnerHTML={{ __html: parseImageTag(item.question) }} />
            {/* 4선지 */}
            {item.options.map((option, index) => (
              <div key={index}>{option}</div>
            ))}
          </li>
        ))}
      </ol>

 

여기서 'dangerouslySetInnerHTML'와 ' __html:' 문법은 문자열을 html로 렌더링 할 수 있게 전환 문법이다. dangerouslySetInnerHTML을 찾아보니 React 문법이며, HTML을 동적으로 렌더링하기 위한 용도로 사용된다. 주로 사용되는 상황은 서버에서 가져온 텍스트 데이터에 HTML 태그가 포함되어 있는 경우 그렇다. 이 문법을 사용하지 않으면 다른 문제 사항을 야기 할 수 있는데 React에서는 HTML 태그를 직접 렌더링할 경우 보안상의 이유로 기본적으로 허용하지 않는다. 하지만 dangerouslySetInnerHTML 속성을 사용하면 React가 HTML을 안전하게 렌더링하도록 허용할 수 있다.

따라서 주의해야 할 것은 dangerouslySetInnerHTML을 사용할 때 사전에 신뢰할 수 있는 소스에서 받은 HTML 코드를 렌더링할 때에만 사용해야 한다. 사용자 입력이나 외부에서 가져온 데이터를 이를 통해 렌더링하는 것은 보안상의 위험이 있을 수 있다. 사용 시에는 꼭 취약점에 대한 검토를 거쳐야 한다.

dangerouslySetInnerHTML을 사용하지 않고 html을 어떻게 렌더링 할 수 있는지 고민하다 찾은 라이브러리가 html-react-parser이다. 적용된 코드는 아래와 같다.

 

import parse from 'html-react-parser'; // html-react-parser 모듈 사용
  ...
//HTML 문자열에 포함된 <img> 태그를 실제 이미지로 변경하는 함수
  const parseImageTag = (question, question_img_url) => {
    // 정규식을 사용하여 <img src=1> 문자열을 찾는다.
    const imgRegex = /<img.*?\\/?>/g;
    // 대체할 이미지 태그로 교체한다.
    return question.replace(imgRegex, `<img src="${question_img_url} />`);
  };
 
<ol>
     {data.map((item, index) => (
          <li key={index}>
            {/* 질문 */}
            <p>{parse(parseImageTag(item.question, item.image))}</p>
            {/* 4선지 */}
            {item.options.map((option, index) => (
              <div key={index}>{option}</div>
            ))}
          </li>
        ))}
</ol>

 

기존과 달리 <p dangerouslySetInnerHTML={{ __html: … }} />를 삭제하고 <p>{parse(parseImageTag(item.question, item.image))}</p> 이와 같은 코드를 작성하면 안전하게 html을 렌더링 하게 된다.

 

📚 PDF 전환 라이브러리

프로젝트 진행 중, 자체 어플리케이션에서 문서를 직접 PDF 형식으로 다운로드하는 기능을 제공하기 위해 PDF 전환 라이브러리를 조사하게 되었다.

 

1️⃣ jsPDF 라이브러리 + html2canvas 라이브러리

  • jsPDF
    • html, 이미지, canvas 객체를 PDF로 변환해주는 라이브러리
    • HTML 요소를 직접적으로 PDF로 변환하는 기능은 제공하지 않고
    • 텍스트, 이미지 등의 기본적인 요소를 PDF에 추가하는 기능만을 제공하기 때문에 html2canvas 라이브러리와 함께 사용된다.
  • html2canvas
    • html 객체를 canvas로 변환해주는 라이브러리
    • HTML 요소를 캔버스 요소로 변환하는 기능을 제공한다.
    • 캔버스 요소는 그 자체로 이미지 데이터를 가지고 있기 때문에, 이를 jsPDF가 처리할 수 있는 이미지 형태로 변환하는 것이 가능하다.
  • 먼저 html2canvas를 이용해 HTML 요소를 캔버스로 변환하고, 그 결과를 jsPDF에 전달하여 PDF 문서를 생성한다.
  • HTML 페이지의 레이아웃과 스타일을 그대로 유지하면서 페이지를 PDF로 변환하는 데 매우 유용하다.
  • 장점
    • 간단하고 직관적인 API 제공
    • 브라우저와 서버측에서 모두 사용 가능해 플랫폼에 제약이 없다.
  • 단점
    • 복잡한 레이아웃이나 많은 양의 데이터를 처리하기에는 적합하지 않다.
    • 스타일링 기능이 제한적일 수 있다.

 

 

2️⃣ pdfmake

  • 순수 자바스크립트에서 서버 측 및 클라이언트 측 사용을 위한 PDF 문서 생성 라이브러리
  • 특징
    • 라인 래핑,
    • 텍스트 정렬 (왼쪽, 오른쪽, 중앙, 정당화),
    • 번호가 매겨진 글머리 기호 목록,
    • 표와 기둥
      • 자동/고정/별 크기 너비,
      • 콜 스팬과 로우 스팬,
      • 페이지 나누기의 경우 자동으로 반복되는 헤더,
    • 이미지와 벡터 그래픽,
    • 편리한 스타일링과 스타일 상속,
    • 페이지 머리글과 바닥글:
      • 정적 또는 동적 콘텐츠,
      • 현재 페이지 번호와 페이지 수에 대한 접근,
    • 배경 레이어,
    • 페이지 크기와 방향,
    • 여백,
    • 사용자 지정 페이지 나누기,
    • 글꼴 삽입,
    • 복잡하고 다단계(중첩된) 구조에 대한 지원,
    • 목차,
    • 생성된 PDF를 열기/인쇄/다운로드하는 도우미 방법,
    • PDF 메타데이터 설정(예: 저자, 주제).
  • 장점
    • JSON형식이여서 구조화된 문서 생성이 쉽다.
    • 다양한 기능을 제공하여 고급 PDF 생성이 가능하다. (복잡한 문서 구조를 다루기에 적합하다.)
    • 클라이언트와 서버 모두 사용 가능하다.
  • 단점

 

 

3️⃣ puppeteer

  • 장점
    • 웹 페이지를 그대로 캡쳐하여 PDF로 변환할 수 있어, 복잡한 레이아웃이나 스타일링을 쉽게 다룰 수 있다.
    • 브라우저 자동화 도구로서, 웹 페이지를 조작하거나 스크래핑하는 등의 다양한 기능을 제공한다.
  • 단점
    • Node.js 환경에서만 사용할 수 있다.
    • 크롬 브라우저를 내부적으로 사용하므로, 라이브러리의 용량이 크고 메모리 사용량이 많다.
    • 페이지 로딩이나 렌더링 시간이 추가로 필요하므로, 대량의 PDF를 빠르게 생성하는 데에는 한계가 있을 수 있다.
    • 대량의 페이지를 처리할 때 성능 문제 발생 가능

 

 

4️⃣ PDFKit

  • 최초의 PDF 라이브러리
  • 장점
    • 고급 PDF 생성을 위한 다양한 기능 제공
    • 서버 측, 브라우저 측에서 모두 실행 가능하다.
  • 단점
    • 복잡한 API

 

5️⃣ react-pdf

  • 리액트 컴포넌트를 PDF로 변환할 수 있다.
  • 복잡한 문서 구조와 이미지, 표 등을 다루기에 적합한 라이브러리
  • PDFDownloadLink 컴포넌트를 사용하여 PDF 파일을 다운로드 할 수 있는 링크를 생성 가능
  • 장점
    • 리액트와의 호환성이 뛰어나다.
    • PDF 문서를 생성하기 위한 컴포넌트 기반 접근 방식이여서 쉽게 구성하고 제어할 수 있다.
    • 애플리케이션 내에서 동적으로 PDF를 생성할 수 있다.
    • 서버와 클라이언트 양쪽에서 모두 사용할 수 있다.
  • 단점
    • 브라우저에서 실행되기 때문에 특정 브라우저에서 제한될 수 있다.
    • 많은 양의 이미지나 텍스트를 포함하는 PDF를 생성할 때 렌더링 속도가 느릴 수 있다.
    • PDF 생성을 위한 간단한 도구이기 때문에 고급기능이나 복잡한 레이아웃을 구현하기에는 적합하지 않을 수 있다.
    • 기존의 PDF 파일을 읽거나 수정할 수 없다.

 

 

 


참고

https://devlifetestcase.tistory.com/62

 

[Spring Legacy / Javascript / jsPDF] PDF 다운로드 기능 구현 - 1. 텍스트, Footer 생성 (feat. jsPDF-autotable, error)

프로젝트를 진행하면서 PDF 다운로드(PDF 작성) 기능을 구현하게 되었다. 처음에는 간단하게 jsPDF와 html2canvas 라이브러리의 조합으로 해결해보려고 했지만, 역시 간단하기만 한 것은 의도대로 사

devlifetestcase.tistory.com

https://velog.io/@mjhyp88/ReactTypscript-%ED%8C%8C%EC%9D%BC%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84With.-Springboot

 

[React+Typscript] 파일다운로드 기능 구현(With. Springboot)

저의 포스팅에서는 자세한 지식보다는 사용법 위주로 작성되며, 개인적인 내용을 포함하고 있습니다!이번 포스팅은 파일 업로드/다운로드 중 파일 다운로드를 할 예정이다.업로드 할 파일 선택

velog.io

 

https://devmemory.tistory.com/98

 

React - make html element to pdf(Feat. jspdf, html2canvas)

이번에는 특정 html element를 pdf로 만들어서 활용하는 예제를 만들어봤습니다 먼저 사용한 라이브러리는 2개로 jspdf : pdf를 만들 수 있는 라이브러리 (링크: https://www.npmjs.com/package/jspdf) html2canvas : ht

devmemory.tistory.com

 

https://dev.to/handdot/generate-a-pdf-in-js-summary-and-comparison-of-libraries-3k0p

 

A full comparison of 6 JS libraries for generating PDFs

This article will introduce you to a collection of libraries for creating PDFs in Javascript, comparing their uses and how they are used. Also, compared to the following aspects is it easy to handle in a modern front-end? works in Node and browser? Is it a

dev.to

 

'React' 카테고리의 다른 글

CORS 에러  (0) 2024.08.06
dangerouslySetInnerHTML/html-react-parser  (0) 2024.08.06
useHistory 그리고 useNavigate  (0) 2024.07.23
react-router-dom 오류  (2) 2024.07.23
fetch와 axios  (1) 2024.07.23

 

⚠️ useHistory는 react-router 버전5까지 사용할 수 있다. react-router-dom 버전6로 업데이트 되면서 useHistory가 useNavigate로 바뀌었다.

 

useHistory훅

리액트에서 URL 주소를 변경할 때 사용하는 훅이다.

리액트 특성상, URL변경없이 내부 컴포넌트만 변경시켜 화면을 바꿔줄수 있지만, 핵심 컴포넌트들이 변경될 때 URL 주소를 같이 변경시켜주면 사용자 친화적인 페이지가 될 수 있다.

 

useHistory() 사용하기

import { useHistory } from "react-router-dom";

export default function SelectExam() {
  const history = useHistory();
}

const handleSubmit = (e) => {
    e.preventDefault();
      const format_info = {
        '시험 종류': examType,
        'tagType': checkType,
        'include' : keywords,
        'limit': select,
      }

      history.push("/lab"); // 페이지 이동
  }

return (
	<form action="" onSubmit={handleSubmit}>
		<input type="submit" value="Submit"/>
  </form>
);

 


참고

https://phsun102.tistory.com/91

 

React - useHistory 사용법

1. useHistory란? useHistory는 리액트에서 URL주소를 변경할 때 사용하는 Hook이다. 예를 들어, 로그인 버튼 또는 여러 목록 중에서 하나를 선택하여 클릭했을 때, URL주소를 변경시켜 URL주소와 일치하는

phsun102.tistory.com

 

 

react-router-dom를 설치하니, 

npm install react-router-dom

 

아래와 같은 오류가 발생하였다.

 

 

 

 

 

아래 명령어로 버전을 확인했더니, 버전6을 사용하고 있는 것을 확인할 수 있었다.

 

npm list react-router-dom

 

 

 

 

"react-router" 패키지에는 여러 기능과 함수들이 내보내기 되어 있다. 하지만 여기에 나열된 오류 메시지들은 "react-router-dom" 패키지에서 일부 내보내기를 찾지 못했다는 것을 나타낸다.

예를 들어, 이 오류 메시지에서는 'react-router' 패키지에서 AbortedDeferredError, NavigationType, createPath등의 내보내기를 찾지 못했다고 말하고 있다. 하지만 이러한 내보내기들은 'react-router' 패키지에 포함되어 있지 않다. 대신에 'react-router-dom' 패키지에 있는 일부 내보내기로 보인다.

이러한 오류는 주로 버전 충돌이나 잘못된 의존성 관리로 인해 발생할 수 있다. 보통은 'react-router'와 'react-router-dom' 버전 간의 호환성 문제일 가능성이 높다. 따라서 이를 해결하기 위해 두 패키지의 버전을 맞춰주는 것이 중요하다.

 

 

따라서, 아래 명령을 입력하여 다운그레이드를 하였더니 오류가 사라지는 것을 확인할 수 있었다.

npm install react-router-dom@5

 

 

하지만, 모듈을 빌드하는데 실패했다는 새로운 오류가 발생했다.

 

 

react-router와 react-router-dom의 버전은 동일했기 때문에 버전 문제는 아니었다.

 

 

 

https://stackoverflow.com/questions/74837774/react-router-enoent-no-such-file-or-directory

 

React Router - ENOENT: no such file or directory

I'm a beginner to React and I'm having issues which I have no idea how they even arose on a React project I've been playing with. Obviously I'm assuming the error is to do with the React Router pac...

stackoverflow.com

 

 

 

 

module 폴더를 삭제하고 다시 npm install 을 하니 오류가 완전히 사라진 것을 확인할 수 있었다.

 

하지만 버전에 따라 <Switch> 태그와 <Routes>태그를 다르게 사용해야하는 등의 문제가 발생할 수 있으니, 프로젝트를 진행하는 과정에서 어떤 버전을 사용할 것인지 논의해볼 필요가 있을 것 같다.


v6 사용

팀원과 논의해본 결과, 최신 버전인 6 버전을 사용하기로 결정하였다. 따라서

React-Router를 최신 버전으로 업데이트 하는 명령어를 사용해 업데이트 해주었다.

npm install react-router-dom@latest

 

v5와 차이점으로 아래와 같이 변경해주었다.

  • <Switch> → <Routes>
  • useHistory() → useNavigate()

 

axois와 fetch 모두 HTTP 요청을 보내는 데 사용되는 도구이다. axios는 fetch보다 편리한 API와 기능을 제공하며, 프로미스 기반으로 비동기 작업을 처리하는 데 있어서 더 직관적이고 편리한 방법을 제공한다.

✅ axios

  • Node.js, 브라우저를 위한 Promise API를 활용하는 HTTP 통신 라이브러리
  • 비동기로 HTTP 통신을 할 수 있으며 return을 promise 객체로 해주기 때문에 response 데이터를 다루기 쉽다.
  • HTTP 요청 취소 및 요청과 응답을 JSON 형태로 자동 변환해준다.

[장점]

  1. response timeout 처리 방법이 존재한다.
    • 요청을 중도 취소, 응답시간 초과 설정 등의 기능
  2. Promise 기반으로 만들어졌기 때문에 데이터 다루기 편리하다.
  3. 크로스 브라우징 최적화로 브라우저 호환성이 뛰어나다. (구형 브라우저 지원)
  4. JSON 데이터를 자동 변환해준다.
  5. Error 발생 시 reject로 response를 전달해 catch로 잡아낼 수 있다.

[단점]

  1. 사용을 위해 모듈 설치가 필요하다. npm install axios

 

✅ fetch

  • ES6부터 들어온 JavaScript 내장 라이브러리
  • Promise 기반으로 만들어졌기 때문에 데이터를 다루기 편하다.
  • 내장 라이브러리이기 때문에 업데이트에 따른 에러 방지가 가능하다.

[장점]

  1. 자바스크립트의 내장 라이브러리 이므로 별도로 import 할 필요가 없다.
  2. Promise 기반으로 만들어졌기 때문에 데이터 다루기 편리하다.
  3. 내장 라이브러리이기 때문에 업데이트에 따른 에러 방지가 가능하다.

[단점]

  1. 지원하지 않는 브라우저가 존재 (IE11..)
  2. 네트워크 에러 발생 시 response timeout이 없어 기다려야 한다.
  3. JSON으로 변환해주는 과정 필요하다.
  4. 상대적으로 axios에 비해 기능이 부족하다

 

✅ GET 요청

axios

axios.get('/api/users')
  .then(response => {
    // 응답 데이터 처리
    console.log(response.data);
  })
  .catch(error => {
    // 에러 처리
    console.error(error);
  });
  • .get() 메서드를 사용해 간단하게 GET 요청을 보낼 수 있다.
  • 응답 데이터는 response.data로 접근할 수 있다.
  • 에러 핸들링은 .catch() 메서드를 통해 처리할 수 있다.

fetch

fetch('/api/users')
  .then(response => response.json())
  .then(data => {
    // 응답 데이터 처리
    console.log(data);
  })
  .catch(error => {
    // 에러 처리
    console.error(error);
  });
  • .json() 메서드를 사용하여 응답 데이터를 JSON 형식으로 파싱해야한다.
  • 응답 데이터는 체이닝된 다음 .then() 블록에서 접근할 수 있다.
  • 에러 핸들링은 동일하게 .catch() 메서드를 통해 처리할 수 있다.

✅ POST 요청

axios

axois

axios.post('/api/users', { name: 'John', age: 25 })
  .then(response => {
    // 응답 데이터 처리
    console.log(response.data);
  })
  .catch(error => {
    // 에러 처리
    console.error(error);
  });
  • .post() 메서드를 사용하여 POST 요청을 보낼 수 있으며, 두번째 인자로 데이터가 전달된다.
  • 응답 데이터는 response.data로 접근할 수 있다.

fetch

fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'John', age: 25 })
})
  .then(response => response.json())
  .then(data => {
    // 응답 데이터 처리
    console.log(data);
  })
  .catch(error => {
    // 에러 처리
    console.error(error);
  });
  • 요청 설정 객체에 method, headers, body 등을 명시하여 POST 요청을 구성해야 한다.
  • 응답 데이터는 response.json() 을 사용하여 JSON 형식으로 파싱해야 한다.

결론

fetch를 사용하는 경우에는 요청 설정 객체에 더 많은 구성을 해주어야 하고, 응답 데이터에 접근하기 위해 .json() 메서드를 호출하여 데이터를 파싱해야하지만, axios는 코드를 간결하고 직관적으로 유지하는 데 도움이 된다. 하지만 fetch와 axios 중 어느 것을 선택할 지는 프로젝트의 요구 사항에 맞춰야 할 것 같다. 간단한 요청을 보낼 때는 fetch가 편리하며, 좀 더 복잡한 요청 및 에러 처리가 필요한 경우 axios를 사용할 수 있을 것 같다.

→ 이번 프로젝트에서는 axios를 활용하도록 하겠다.

 


참고

 

https://kindjjee.tistory.com/145

 

fetch 보다 axios를 쓰는 이유

메인 프로젝트에서 템플릿을 사용할때, 왜 사용하는지 정리하는 중에 알게 된 사실이다. 왜 fetch 보다 axios를 쓰는 것이 좋을까? axios와 fetch는 모두 HTTP 요청을 보내는 데 사용되는 도구입니다. 하

kindjjee.tistory.com

 

https://tlsdnjs12.tistory.com/26

 

fetch와 axios 차이점과 비교

저번에 API에 대해서 알아봤습니다. 자바스크립트에서 HTTP Requests를 위한 방법이 두가지가 존재하는데 🎁 오늘은 어떤것이 존재하고 어떤 차이점이 존재하는지 알아보겠습니다. 🎁 💖 Fetch ES6

tlsdnjs12.tistory.com

 

'React' 카테고리의 다른 글

useHistory 그리고 useNavigate  (0) 2024.07.23
react-router-dom 오류  (2) 2024.07.23
텍스트 편집기 직접 만들기  (0) 2024.03.21
텍스트 입력 시, 한글 마지막 글자가 추가되는 문제  (0) 2024.03.12
axios와 fetch  (1) 2024.02.14

 

아래와 같은 document.execCommand는 더 이상 권장되지 않는다.

 document.execCommand("bold");

 

 

따라서, 아래와 같은 형식으로 제작하였다. 

import React, { useState } from "react";

export default function EditExam() {

  const [fontSize, setFontSize] = useState("12px");
  const [uploadedImage, setUploadedImage] = useState(null); 

  const wrapTextWithSpan = (action, value) => {
    const selection = window.getSelection();
    if (!selection.rangeCount) return false;

    const range = selection.getRangeAt(0);
    const selectedText = range.extractContents();
    const span = document.createElement("span");

    switch (action) {
      case "bold":
        span.style.fontWeight = "bold";
        break;
      case "underline":
        span.style.textDecoration = "underline";
        break;
      case "italic":
        span.style.fontStyle = "italic";
        break;
      case "fontSize":
        span.style.fontSize = value;
        break;
      default:
        break;
    }

    span.appendChild(selectedText);
    range.insertNode(span);

    // 선택 해제
    selection.removeAllRanges();
  };

  // 정렬 기능 추가
  const applyTextAlignment = (alignment) => {
    const selection = window.getSelection();
    if (!selection.rangeCount) return false;

    const range = selection.getRangeAt(0);
    const contentEditableDiv = range.commonAncestorContainer.parentNode.closest('[contenteditable="true"]');

    if (contentEditableDiv) {
      contentEditableDiv.style.textAlign = alignment;
    }
  };

  const handleFontSizeChange = (e) => {
    setFontSize(e.target.value);
    wrapTextWithSpan("fontSize", e.target.value);
  };

  // 이미지 파일을 업로드하고 미리 보기를 설정하는 함수
  const handleImageUpload = (e) => {
    const file = e.target.files[0];
    if (file && file.type.startsWith("image/")) {
      const reader = new FileReader();
      reader.onload = (e) => {
        setUploadedImage(e.target.result); // 업로드된 이미지의 URL을 상태에 저장
      };
      reader.readAsDataURL(file);
    } else {
      alert("이미지 파일만 업로드 가능합니다.");
    }
  };

  return (
    <div>
      
      
      <button onClick={() => wrapTextWithSpan("bold")} style={{ fontWeight: "bold", marginRight: "10px" }}>
        볼드
      </button>
      <button onClick={() => wrapTextWithSpan("underline")} style={{ textDecoration: "underline" }}>
        밑줄
      </button>
      <button onClick={() => wrapTextWithSpan("italic")} style={{ fontStyle: "italic" }}>
        기울기
      </button>

      {/* 폰트 사이즈 조절 */}
      <select value={fontSize} onChange={handleFontSizeChange}>
        <option value="12px">12px</option>
        <option value="14px">14px</option>
        <option value="16px">16px</option>
        <option value="18px">18px</option>
        <option value="20px">20px</option>
        {/* 추가 폰트 사이즈 옵션 가능 */}
      </select>
      {/* 텍스트 정렬 */}
      <button onClick={() => applyTextAlignment("left")} style={{ marginRight: "10px" }}>
        왼쪽 정렬
      </button>
      <button onClick={() => applyTextAlignment("center")} style={{ marginRight: "10px" }}>
        가운데 정렬
      </button>
      <button onClick={() => applyTextAlignment("right")} style={{ marginRight: "10px" }}>
        오른쪽 정렬
      </button>
      {/* 이미지 업로드 버튼 */}
      <input type="file" accept="image/*" onChange={handleImageUpload} />
      
      {/* 업로드된 이미지 미리 보기 */}
      {uploadedImage && (
        <div style={{ marginTop: "10px" }}>
          <img src={uploadedImage} alt="Uploaded" style={{ maxWidth: "300px", maxHeight: "300px" }} />
        </div>
      )}

      {/* 여러 contentEditable div */}
      <div
        contentEditable={true}
        style={{ marginBottom: "10px", width: "300px", height: "100px", border: "1px solid black", padding: "5px" }}
      ></div>
      <div
        contentEditable={true}
        style={{ marginBottom: "10px", width: "300px", height: "100px", border: "1px solid black", padding: "5px" }}
      ></div>
      {/* 추가 contentEditable div 가능 */}

    </div>
  );
}

 

 

아직 미흡한 부분

- 다시한번 클릭했을 때, 기능(볼드/기울기/밑줄 등)이 없어지지 않는다.

'React' 카테고리의 다른 글

react-router-dom 오류  (2) 2024.07.23
fetch와 axios  (1) 2024.07.23
텍스트 입력 시, 한글 마지막 글자가 추가되는 문제  (0) 2024.03.12
axios와 fetch  (1) 2024.02.14
dependencies와 devDependencies  (2) 2024.02.14

키보드 이벤트의 isComposing

키워드를 입력받을 때, 한글을 입력하면 검색어의 마지막 글자가 딸려오는 문제가 발생했다. 영어를 입력했을 때에는 문제가 되지 않지만, 한글을 입력했을 때에만 문제가 발생했다.

 

 

한글은 자음과 모음의 조합으로 한 음절이 만들어지기 때문에 조합문자이고, 영어는 조합문자가 아니다.

따라서 한글을 입력할 때 이 글자가 조합중인건지, 조합이 끝난 상태인지 파악하기 어렵기 때문에, Enter키를 입력하면 이벤트가 2번 발생하게 되며, 영어를 입력할 때에는 발생하지 않고, 한글을 입력할 때에만 발생하게 된다.

 

 

📌 또한 이 문제는 크롬 브라우저에서 한글을 사용하는 경우에만 문제가 발생한다.

 

 

키보드 이벤트에는 isComposing이라는 입력문자가 조합문자인지 아닌지를 boolean값으로 반환하는 프로퍼티가 있는데,

React에서는 isComposing 프로퍼티를 제공하지 않기 때문에 nativeEvent의

e.nativeEvent.isComposing 을 사용해야한다.

// 키워드 추가
  const handlePushKeyword = (e) => {
    if (e.key === 'Enter') {
      e.preventDefault();
      if (!e.nativeEvent.isComposing) { 
        setKeywords([...keywords, search.trim()]); // 입력된 단어를 키워드 배열에 추가
        setSearch(''); // 검색어를 초기화
      }
    }
  };

위와 같이 코드를 변경하였고, 문제가 해결되는 것을 확인할 수 있었다.

 

 

참고

https://velog.io/@o1_choi/isComposing

 

키보드 이벤트의 isComposing (Feat: React )

자동완성 기능을 구현하던 중 추천 키워드로 이동 시 기존 입력한 검색어의 마지막 글자가 포함되어 출력되는 문제**가 발생했다. 테스트를 해보니 크롬 브라우저에서 한글을 사용하는 경우만

velog.io

https://always-hyeppy.tistory.com/36

'React' 카테고리의 다른 글

fetch와 axios  (1) 2024.07.23
텍스트 편집기 직접 만들기  (0) 2024.03.21
axios와 fetch  (1) 2024.02.14
dependencies와 devDependencies  (2) 2024.02.14
리액트 상태 관리  (0) 2023.11.05

 

axois와 fetch 모두 HTTP 요청을 보내는 데 사용되는 도구이다. axios는 fetch보다 편리한 API와 기능을 제공하며, 프로미스 기반으로 비동기 작업을 처리하는 데 있어서 더 직관적이고 편리한 방법을 제공한다.

✅ axios

  • Node.js, 브라우저를 위한 Promise API를 활용하는 HTTP 통신 라이브러리
  • 비동기로 HTTP 통신을 할 수 있으며 return을 promise 객체로 해주기 때문에 response 데이터를 다루기 쉽다.
  • HTTP 요청 취소 및 요청과 응답을 JSON 형태로 자동 변환해준다.

[장점]

  1. response timeout 처리 방법이 존재한다.
    • 요청을 중도 취소, 응답시간 초과 설정 등의 기능
  2. Promise 기반으로 만들어졌기 때문에 데이터 다루기 편리하다.
  3. 크로스 브라우징 최적화로 브라우저 호환성이 뛰어나다. (구형 브라우저 지원)
  4. JSON 데이터를 자동 변환해준다.
  5. Error 발생 시 reject로 response를 전달해 catch로 잡아낼 수 있다.

[단점]

  1. 사용을 위해 모듈 설치가 필요하다. npm install axios

✅ fetch

  • ES6부터 들어온 JavaScript 내장 라이브러리
  • Promise 기반으로 만들어졌기 때문에 데이터를 다루기 편하다.
  • 내장 라이브러리이기 때문에 업데이트에 따른 에러 방지가 가능하다.

[장점]

  1. 자바스크립트의 내장 라이브러리 이므로 별도로 import 할 필요가 없다.
  2. Promise 기반으로 만들어졌기 때문에 데이터 다루기 편리하다.
  3. 내장 라이브러리이기 때문에 업데이트에 따른 에러 방지가 가능하다.

[단점]

  1. 지원하지 않는 브라우저가 존재 (IE11..)
  2. 네트워크 에러 발생 시 response timeout이 없어 기다려야 한다.
  3. JSON으로 변환해주는 과정 필요하다.
  4. 상대적으로 axios에 비해 기능이 부족하다

✅ GET 요청

axios

axios.get('/api/users')
  .then(response => {
    // 응답 데이터 처리
    console.log(response.data);
  })
  .catch(error => {
    // 에러 처리
    console.error(error);
  });
  • .get() 메서드를 사용해 간단하게 GET 요청을 보낼 수 있다.
  • 응답 데이터는 response.data로 접근할 수 있다.
  • 에러 핸들링은 .catch() 메서드를 통해 처리할 수 있다.

fetch

fetch('/api/users')
  .then(response => response.json())
  .then(data => {
    // 응답 데이터 처리
    console.log(data);
  })
  .catch(error => {
    // 에러 처리
    console.error(error);
  });
  • .json() 메서드를 사용하여 응답 데이터를 JSON 형식으로 파싱해야한다.
  • 응답 데이터는 체이닝된 다음 .then() 블록에서 접근할 수 있다.
  • 에러 핸들링은 동일하게 .catch() 메서드를 통해 처리할 수 있다.

✅ POST 요청

axios

axios.post('/api/users', { name: 'John', age: 25 })
  .then(response => {
    // 응답 데이터 처리
    console.log(response.data);
  })
  .catch(error => {
    // 에러 처리
    console.error(error);
  });
  • .post() 메서드를 사용하여 POST 요청을 보낼 수 있으며, 두번째 인자로 데이터가 전달된다.
  • 응답 데이터는 response.data로 접근할 수 있다.

fetch

fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'John', age: 25 })
})
  .then(response => response.json())
  .then(data => {
    // 응답 데이터 처리
    console.log(data);
  })
  .catch(error => {
    // 에러 처리
    console.error(error);
  });
  • 요청 설정 객체에 method, headers, body 등을 명시하여 POST 요청을 구성해야 한다.
  • 응답 데이터는 response.json() 을 사용하여 JSON 형식으로 파싱해야 한다.

결론

fetch를 사용하는 경우에는 요청 설정 객체에 더 많은 구성을 해주어야 하고, 응답 데이터에 접근하기 위해 .json() 메서드를 호출하여 데이터를 파싱해야하지만, axios는 코드를 간결하고 직관적으로 유지하는 데 도움이 된다. 하지만 fetch와 axios 중 어느 것을 선택할 지는 프로젝트의 요구 사항에 맞춰야 할 것 같다. 간단한 요청을 보낼 때는 fetch가 편리하며, 좀 더 복잡한 요청 및 에러 처리가 필요한 경우 axios를 사용할 수 있을 것 같다.


참고

https://kindjjee.tistory.com/145

 

fetch 보다 axios를 쓰는 이유

메인 프로젝트에서 템플릿을 사용할때, 왜 사용하는지 정리하는 중에 알게 된 사실이다. 왜 fetch 보다 axios를 쓰는 것이 좋을까? axios와 fetch는 모두 HTTP 요청을 보내는 데 사용되는 도구입니다. 하

kindjjee.tistory.com

 

https://tlsdnjs12.tistory.com/26

 

fetch와 axios 차이점과 비교

저번에 API에 대해서 알아봤습니다. 자바스크립트에서 HTTP Requests를 위한 방법이 두가지가 존재하는데 🎁 오늘은 어떤것이 존재하고 어떤 차이점이 존재하는지 알아보겠습니다. 🎁 💖 Fetch ES6

tlsdnjs12.tistory.com

 

+ Recent posts