몇 번의 시도 끝에 'Hello React!'를 웹에 표시해냈다

 

 

 

중간에 막혔던 부분들 기록...

 

1. 설치 경로 문제

윈도우 바탕화면에 리액트 개발환경 폴더를 만들었더니 리액트 설치가 되지 않았음. 아마 'OneDrive'나 '바탕 화면'이라는 파일명 때문에 그러지 않았을까 싶음. 서브 SSD에 폴더를 이동해서 설치했더니 다음 단계로 넘어감.

 

2. 설치 중 오류 발생 : 리액트 버전 문제

npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: react-app@0.1.0
npm error Found: react@19.0.0
npm error node_modules/react
npm error   react@"^19.0.0" from the root project
npm error
npm error Could not resolve dependency:
npm error peer react@"^18.0.0" from @testing-library/react@13.4.0
npm error node_modules/@testing-library/react
npm error   @testing-library/react@"^13.0.0" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
npm error to accept an incorrect (and potentially broken) dependency resolution.
npm error
npm error

 

ChatGPT 사용하여 원인 찾아보니 리액트 버전 문제일 수 있다고 하여 아래 명령어 사용하여 다운그레이드.

npm install react@18 react-dom@18

 

3. 설치 후 'npm start' 후 에러 발생 : Node.js 호환성 문제

Error: error:0308010C:digital envelope routines::unsupported
    at new Hash (node:internal/crypto/hash:79:19)
    at Object.createHash (node:crypto:139:10)
    at module.exports

 

위 에러 메시지 원인을 GPT로 찾아보니 Node.js의 최신 버전(22.12.0)에서 OpenSSL 3과 Webpack의 호환성 문제 때문에 발생했다고 했다. NODE_OPTIONS 환경 변수를 설정하여 OpenSSL 3의 새로운 요구사항을 우회하라고 해서 아래 명령어 적용.

$env:NODE_OPTIONS="--openssl-legacy-provider"
npm start

 

 

4. 패키지가 설치되지 않아 오류 발생

ERROR in ./src/reportWebVitals.js 5:4-24
Module not found: Error: Can't resolve 'web-vitals' in...

 

'web-vitals' 재설치하여 해결

npm install web-vitals

마우스 호버링 시 파스텔 톤의 녹색으로 강조되는 효과 추가

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>표 생성기</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      text-align: center;
      margin: 20px;
    }

    .input-container {
      margin-bottom: 20px;
    }

    input[type="number"] {
      padding: 10px;
      font-size: 1rem;
      width: 80px;
      margin: 5px;
      border: 1px solid #ccc;
      border-radius: 5px;
    }

    button {
      padding: 10px 20px;
      font-size: 1rem;
      background-color: #007aff;
      color: white;
      border: none;
      border-radius: 5px;
      cursor: pointer;
    }

    button:hover {
      background-color: #005ecb;
    }

    table {
      margin: 20px auto;
      border-collapse: collapse;
      width: auto;
    }

    td {
      border: 1px solid #ddd;
      padding: 10px;
      text-align: center;
      transition: background-color 0.3s ease; /* 부드러운 강조 효과 */
    }

    td:hover {
      background-color: #b2f2bb; /* 파스텔 톤의 녹색 */
    }
  </style>
</head>
<body>
  <h1>표 생성기</h1>

  <div class="input-container">
    <label for="rows">행:</label>
    <input type="number" id="rows" min="1" placeholder="행 수 입력">
    <label for="columns">열:</label>
    <input type="number" id="columns" min="1" placeholder="열 수 입력">
    <button onclick="generateTable()">표 생성</button>
  </div>

  <div id="table-container"></div>

  <script>
    function generateTable() {
      // 입력 값 가져오기
      const rows = parseInt(document.getElementById("rows").value);
      const columns = parseInt(document.getElementById("columns").value);

      // 유효성 검사
      if (isNaN(rows) || isNaN(columns) || rows <= 0 || columns <= 0) {
        alert("유효한 숫자를 입력하세요.");
        return;
      }

      // 기존 표 제거
      const tableContainer = document.getElementById("table-container");
      tableContainer.innerHTML = "";

      // 새 표 생성
      const table = document.createElement("table");
      for (let i = 0; i < rows; i++) {
        const tr = document.createElement("tr");
        for (let j = 0; j < columns; j++) {
          const cell = document.createElement("td");
          cell.textContent = `셀 ${i + 1}-${j + 1}`;
          tr.appendChild(cell);
        }
        table.appendChild(tr);
      }

      // 표 추가
      tableContainer.appendChild(table);
    }
  </script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>할 일 목록</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 20px;
      padding: 0;
      line-height: 1.6;
    }

    h1 {
      text-align: center;
      margin-bottom: 20px;
    }

    .input-container {
      display: flex;
      justify-content: center;
      margin-bottom: 20px;
    }

    input[type="text"] {
      padding: 10px;
      font-size: 1rem;
      border: 1px solid #ccc;
      border-radius: 5px;
      width: 300px;
    }

    button {
      padding: 10px 20px;
      font-size: 1rem;
      background-color: #007aff;
      color: white;
      border: none;
      border-radius: 5px;
      cursor: pointer;
      margin-left: 10px;
    }

    button:hover {
      background-color: #005ecb;
    }

    ul {
      list-style-type: none;
      padding: 0;
      margin: 0;
    }

    li {
      display: flex;
      align-items: center;
      background-color: #f5f5f5;
      margin: 5px 0;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 5px;
      justify-content: space-between;
    }

    .task-text {
      flex-grow: 1;
      margin-left: 10px;
    }

    .completed {
      text-decoration: line-through;
      color: #ccc;
    }

    .check-btn {
      background-color: #4caf50;
      color: white;
      border: none;
      border-radius: 50%;
      cursor: pointer;
      width: 30px;
      height: 30px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 1.2rem;
      line-height: 1;
    }

    .check-btn:hover {
      background-color: #45a049;
    }

    .particle {
      position: absolute;
      border-radius: 50%; /* 원형 */
      opacity: 1;
      animation: explode 1s ease-out forwards, fade-out 1.5s ease-out forwards;
    }

    @keyframes explode {
      0% {
        transform: translate(0, 0) scale(1);
      }
      50% {
        transform: scale(1.5); /* 크기 확장 */
      }
      100% {
        transform: translate(var(--x), var(--y)) scale(0.5); /* 최종 위치 */
      }
    }

    @keyframes fade-out {
      0% {
        opacity: 1;
      }
      100% {
        opacity: 0;
      }
    }
  </style>
</head>
<body>
  <h1>할 일 목록</h1>

  <div class="input-container">
    <input type="text" id="todoInput" placeholder="할 일을 입력하세요">
    <button onclick="addTask()">할 일 추가</button>
  </div>

  <ul id="todoList"></ul>

  <script>
    function addTask() {
      const input = document.getElementById("todoInput");
      const task = input.value.trim();

      if (task === "") {
        alert("할 일을 입력하세요!");
        return;
      }

      // 새로운 할 일 생성
      const listItem = document.createElement("li");

      // 체크 버튼
      const checkButton = document.createElement("button");
      checkButton.textContent = "✔";
      checkButton.className = "check-btn";
      checkButton.onclick = function () {
        completeTask(listItem, taskSpan);
      };

      // 할 일 텍스트
      const taskSpan = document.createElement("span");
      taskSpan.textContent = task;
      taskSpan.className = "task-text";

      // 구성
      listItem.appendChild(checkButton);
      listItem.appendChild(taskSpan);
      document.getElementById("todoList").appendChild(listItem);

      // 입력 필드 초기화
      input.value = "";
    }

    function completeTask(listItem, taskSpan) {
      taskSpan.classList.add("completed");

      // 파티클 애니메이션 생성
      const particleCount = 50; // 파티클 수
      const rect = listItem.getBoundingClientRect();
      const centerX = rect.left + rect.width / 2;
      const centerY = rect.top + rect.height / 2;

      for (let i = 0; i < particleCount; i++) {
        const particle = document.createElement("div");
        particle.className = "particle";

        // 초기 위치 설정
        particle.style.left = `${centerX}px`;
        particle.style.top = `${centerY}px`;

        // 무작위 크기, 색상 설정
        const size = Math.random() * 8 + 4; // 크기 (4~12px)
        particle.style.width = `${size}px`;
        particle.style.height = `${size}px`;
        particle.style.backgroundColor = getRandomColor();

        // 애니메이션 속성 설정
        const angle = Math.random() * 2 * Math.PI; // 방향 (라디안)
        const distance = Math.random() * 120 + 50; // 거리
        particle.style.setProperty("--x", `${distance * Math.cos(angle)}px`);
        particle.style.setProperty("--y", `${distance * Math.sin(angle)}px`);

        // 파티클 제거
        setTimeout(() => {
          particle.remove();
        }, 1500);

        document.body.appendChild(particle);
      }
    }

    function getRandomColor() {
      const colors = ["#FF5722", "#FF9800", "#FFC107", "#4CAF50", "#03A9F4", "#9C27B0", "#E91E63"];
      return colors[Math.floor(Math.random() * colors.length)];
    }

    document.getElementById("todoInput").addEventListener("keypress", function (e) {
      if (e.key === "Enter") {
        addTask();
      }
    });
  </script>
</body>
</html>

+ Recent posts