☁️ goormTIL | JavaScript #14

매루·2025년 9월 19일

goormTIL

목록 보기
12/67
post-thumbnail

📅 2025-09-19

➡️ JavaScript fetch와 localStorage에 대해 새롭게 알게 된 것 또는 헷갈리는 부분 정리 + 🤔❓


🔎 학습 리마인드

📌 JSON 타입 (JavaScript Object Notation)

  • JSON이란?

    • 데이터를 문자열로 표현하는 방식
    • 주로 서버 ↔ 클라이언트 간의 데이터를 주고받을 때 쓰임
  • 형태

    • 객체(Object) : { “key”: value }
    • 배열(Array) : [ value1, value2, … ]
    • 값(value) : 문자열, 숫자, 불리언(true, false), null, 객체, 배열
    • 문자열과 키는 반드시 쌍따옴표(")로 묶어야 함
{ "title": "책 제목", "price": 12000 }

💡 객체 → JSON 문자열 변환

  • JSON.stringify() 사용
    • 주로 서버 전송이나 브라우저 저장소(localStorage)에 데이터를 넣을 때 필요

      const user = { name: "홍길동", age: 25 };
      const jsonStr = JSON.stringify(user);
      
      console.log(jsonStr);
      // 출력: {"name":"홍길동","age":25}

💡 JSON 문자열 → 객체 복원

  • JSON.parse() 사용
    • 서버 응답이나 파일에서 읽어온 JSON 문자열을 실제로 다룰 수 있게 객체로 바꿈

      const jsonStr = '{"name":"홍길동","age":25}';
      const user = JSON.parse(jsonStr);
      
      console.log(user.name); // "홍길동"
      console.log(user.age);  // 25

📌 fetch

  • 브라우저가 제공하는 내장 함수
  • 서버에 http 요청을 보내고, 그 결과를 Promise 객체로 돌려줌
    • 우리가 .then()이나 async/await을 사용해서 응답을 처리할 수 있도록 도와주는 비동기 통신 도구

💡 작성법

fetch(주소).then((파라미터이름) => { 
	return 파라미터이름.json();
}).then((필요한데이터) => { // 데이터 사용
		console.log(필요한데이터)})
  • fetch의 인자로 주소 작성
  • 뒤에 .then(함수) 작성
  • then의 콜백함수에 파라미터 및 return 파라미터 .json() 작성
  • .then을 한 번 더 작성 및 파라미터 사용

💡 .then()으로 원하는 데이터 받기

  • Promise 객체는 바로 값이 나오지 않으니, 결과를 활용하려면 .then()을 써야 함
  • .then() 안의 콜백 함수 파라미터로 실제 결과를 받을 수 있음
fetch("https://jsonplaceholder.typicode.com/todos/1")
  .then((response) => {
    console.log(response); // Response 객체
  });

💡 catch

  • 에러가 발생할 때 어떻게 처리할지 조작하는 용도
fetch("https://jsonplaceholder.typicode.com/todos/1")
	.then((response) => {
		return response.json() // Promise
	})
  .then((data) => {
    console.log(data);
  })
   .catch((err) => console.error(err));

💡 원하는 데이터 추출하기

//fetch(주소).then((파라미터이름) => {return 파라미터이름.json();})

fetch("https://jsonplaceholder.typicode.com/todos/1")
  .then((response) => {
    return response.json(); // JSON 변환도 Promise
  })
  .then((data) => {
    console.log(data); 
});
  • .json()으로 호출하면 body을 JSON을 반환할 수 있음. 다만 이 메서드도 Promise를 반환하니 .then() 을 이어 붙여야 해요

왜 이렇게 동작할까?

  • fetch는 외부에서 데이터를 가져오는 시간이 오래 걸리는 작업
  • 오래 걸릴 수 있는 코드는 비동기로 실행하여 다음 코드가 계속 진행하도록 만든 것이 더 좋음
  • 대신 오래 걸리는 작업이 끝나면 .then() 함수로 결과가 전달하는 방식으로 작동

💡 fetch 연습

🔗 https://jsonplaceholder.typicode.com/

  • 사용자(user) 10개 가져오기
<script>
	fetch("https://jsonplaceholder.typicode.com/users")
	.then((response) => {
		console.log(response); // Response 객체
		return response.json();
	}).then((data) => {
		console.log(data);
	});
</script>

💡 options

  • 기본 구조
    // fetch(주소, 설정값) 
    fetch(url, options)
  • options객체(Object) 형태로 작성
    • 여기에 요청 방식, 인증 정보, 요청 헤더 등을 담음

      const options = {
        method: "GET",   // 어떤 방식으로 요청할지
        headers: {       // 요청 헤더(추가 정보)
          accept: "application/json", // 응답을 JSON으로 받고 싶다
          Authorization: "Bearer 토큰값", // 인증이 필요한 경우
        },
      };

왜 필요한가?

  • 서버에서(백앤드 개발자) 추가적인 정보를 요청한 경우
  • method
    • GET, POST, PUT, PATCH, DELETE
  • headers
    - accept: "application/json": 응답을 JSON으로 받고 싶다
    - Authorization: 인증이 필요한 경우

📌 HTTP 메서드와 프로토콜

  • HTTP(HyperText Transfer Protocol)는 클라이언트(브라우저)와 서버가 데이터를 주고 받는 통신 규칙

💡 HTTP 요청의 구조

GET /posts/1 HTTP/1.1
Host: jsonplaceholder.typicode.com
Accept: application/json
  • 첫 줄
    • GET → 메서드
    • /posts/1 → 요청 대상 리소스 (URL path)
    • HTTP/1.1 → 프로토콜 버전
  • 헤더(Header) → 요청에 대한 부가 정보 (ex: 인증, 응답 포맷)
  • 본문(Body) → 데이터가 필요한 요청에서만 사용 (주로 POST, PUT, PATCH)

💡 주요 HTTP 메서드

  • GET (조회)
    • 서버에 리소스 조회 요청
    • body없음
    • 예) /users/1 → id가 1인 유저 가져오기
      fetch("https://jsonplaceholder.typicode.com/users", { method: "GET" })
      // method 생략 가능
      
      fetch("https://jsonplaceholder.typicode.com/users")
      
      // 의미: 유저 데이터 여러개를 조회(GET)한다
      // GET 메소드는 생략 가능 기본 default값
  • POST (생성)
    • 서버에 새로운 리소스 생성 요청
    • body에 JSON, FormData 같은 실제 데이터가 들어감
    • 예) /posts에 새로운 게시물 등록
      fetch('https://jsonplaceholder.typicode.com/posts', {
        method: 'POST',
        body: JSON.stringify({
          title: '자바스크립트',
          category: 'front',
          userId: 1,
        }),
        headers: {
          'Content-type': 'application/json; charset=UTF-8',
        },
      })
        .then((response) => response.json())
        .then((json) => console.log(json));
  • PUT (전체 수정)
    • 서버의 리소스를 전체 수정
    • 요청 Body에 모든 필드를 넣어서, 리소스를 교체한다는 의미
    • 예: /posts/1에 들어 있는 글 전체를 다른 내용으로 바꾸기
      fetch('https://jsonplaceholder.typicode.com/posts/1', {
        method: 'PUT',
        body: JSON.stringify({
          id: 1,
          title: 'HTML ',
          body: 'front 기초',
          userId: 1,
        }),
        headers: {
          'Content-type': 'application/json; charset=UTF-8',
        },
      })
        .then((response) => response.json())
        .then((json) => console.log(json));
  • PATCH (부분 수정)
    • 서버 리소스의 일부 수정
    • Body에 바꾸고 싶은 필드만 전달
    • 예: /posts/1의 title만 고치기
      fetch('https://jsonplaceholder.typicode.com/posts/1', {
        method: 'PATCH',
        body: JSON.stringify({
          title: 'React JS',
        }),
        headers: {
          'Content-type': 'application/json; charset=UTF-8',
        },
      })
        .then((response) => response.json())
        .then((json) => console.log(json));
  • DELETE (삭제)
    • 서버의 리소스를 삭제 요청
    • 보통 Body는 없음
    • 예: /posts/1 삭제
      fetch('https://jsonplaceholder.typicode.com/posts/1', {
        method: 'DELETE',
      });

💡 응답 상태 코드

  • 200 OK (성공)
  • 201 Created (POST 성공)

===============================

  • 400 Bad Request (내가 이상하게 보냄)
  • 401/403 인증/권한 문제
  • 404 Not Found (주소/리소스 없음)
  • 500 Server Error (서버 쪽 문제)

왜 메서드가 중요한가?

HTTP 프로토콜에서는 같은 URL을 사용해도, 메서드에 따라 전혀 다른 요청이 됨

GET /users/1   → 1번 유저 조회
DELETE /users/1 → 1번 유저 삭제

메서드 + URL 조합이 요청의 의미를 정의하는 규칙

이를 RESTful API 디자인이라고 함

💡 REST란?

  • 서버와 클라이언트가 지원을 주고받는 방식에 대한 규칙
  • 주소를 보고 무슨 자원을 다루는 알 수 있게 하자

💡 RESTful 디자인?

  • REST 원칙을 잘 지켜서 API를 설계한 것
  • 명확하고 일관성 있는 API를 만드는 방법

📌 await/async

  • Promise.then() 체인으로 쓰면 결과 값을 오직 .then() 내부에서만 다룰 수 있어 불편하고, 가독성이 떨어짐
  • 이를 더 직관적으로 표현하기 위해 await/async 문법을 사용
    // 1. .then을 제거 await를 앞에 붙입니다.
    await fetch("https://jsonplaceholder.typicode.com/posts");
    
    // 2. .then의 콜백함수 파라미터에 들어가는 데이터를 변수로 받습니다.
    const response = await fetch("https://jsonplaceholder.typicode.com/posts");
    
    // 3. .then을 날리고 이전의 return 에 있는 코드 앞에 await를 붙입니다.
    await response.json();
    
    // 4. 다음 .then의 콜백함수 파라미터에 들어가는 데이터를 변수로 받습니다.
    const data = await response.json();
    
    // 결과
    const response = await fetch("https://jsonplaceholder.typicode.com/posts");
    const data = await response.json();
    
    // await/async 일반적으로 함수 안에서만 사용함
    async function getPosts() {
    	const response = await fetch("https://jsonplaceholder.typicode.com/posts");
    	const data = await response.json();
    	console.log(data)
    }
    
    getPosts();

💡 try/catch

try {
	const response = await fetch("https://jsonplaceholder.typicode.com/comments");
	const data = await response.json();
} catch(error) {
	console.log(error);
}
  • 실제 사용 예시
    1. response의 객체를 먼저 확인

      • 네트워크가 성공(200번대) 이어도 서버 응답 자체가 실패 상태일 수 있음
      async function safeFetch(url, options) {
        const res = await fetch(url, options);
        if (!res.ok) {
          // 상태코드 포함한 에러로 던지기
          throw new Error(`HTTP ${res.status}: ${res.statusText}`);
        }
        return res.json();
      }
    2. 에러는 throw, UI는 바깥에서 처리

      async function load() {
        try {
          const data = await safeFetch("/api/posts");
          // UI 업데이트
          renderPosts(data); // renderPosts 함수로 화면 업데이트 
        } catch (e) {
          // 여기서 사용자에게 안내
        }

📌 로컬 스토리지(LocalStorage)

  • 브라우저(Client)에 영구적으로 데이터를 저장할 수 있는 저장소
  • 서버에 요청하지 않고도 저장된 데이터를 다시 꺼내 쓸 수 있음
  • 서버(DB)에 저장할 정도로 중요하진 않지만, 있으면 편리한 정보
    • 오늘 하루, 4시간 그만보기
    • 최근 본 상품 등

💡 특징

  1. 문자열(String)로만 저장 가능

    • 배열, 객체는 반드시 JSON.stringify() 해서 저장
    • 꺼내올 때는 JSON.parse()로 다시 객체화
  2. 영구적 저장

    • 사용자가 직접 삭제하지 않는 한, 브라우저를 껐다 켜도 유지
  3. 브라우저 전용 저장소

    • 서버 요청 없이 클라이언트에서만 접근 가능

💡 주요 메서드

  1. localStorage.setItem(key, value)저장

    // 상품 리스트 저장
    const products = [
      { id: 1, brand: "아디다스", name: "멋진 아디다스 반팔티", price: 32000 },
      { id: 2, brand: "나이키", name: "멋진 나이키 바지", price: 30000 },
    ];
    
    localStorage.setItem("products", JSON.stringify(products));
  1. localStorage.getItem(key)불러오기

    const newProduct = { id: 3, brand: "뉴발", name: "멋진 뉴발 신발", price: 78000 };
    
    const products = JSON.parse(localStorage.getItem("products"));
    localStorage.setItem("products", JSON.stringify([...products, newProduct]));
  1. localStorage.removeItem(key)특정 데이터 삭제

    const products = JSON.parse(localStorage.getItem("products"));
    const filtered = products.filter((p) => p.id !== 1);
    
    localStorage.setItem("products", JSON.stringify(filtered));
  1. localStorage.clear()전체 삭제

    localStorage.clear();
<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8" />
        <title>localStorage 실습</title>
    </head>
    <body>
        <h2>📦 localStorage 기본 예제</h2>

        <button id="btnSave">1. 상품 저장 (setItem)</button>
        <button id="btnAdd">2. 상품 추가 (getItem + setItem)</button>
        <button id="btnDeleteOne">3. 상품 삭제 (removeItem / filter)</button>
        <button id="btnClear">4. 전체 삭제 (clear)</button>
        <button id="btnShow">현재 상태 확인</button>

        <pre id="output">결과가 여기에 표시됩니다.</pre>

        <script>
            const output = document.getElementById('output');

            // 1. 상품 저장
            document.querySelector('#btnSave').addEventListener('click', () => {
                const products = [
                    {
                        id: 1,
                        brand: '아디다스',
                        name: '멋진 아디다스 반팔티',
                        price: 32000,
                    },
                    {
                        id: 2,
                        brand: '나이키',
                        name: '멋진 나이키 반팔티',
                        price: 30000,
                    },
                ];

                localStorage.setItem('products', JSON.stringify(products));
                output.innerHTML = '상품 저장 완료';
            });

            // 2. 상품 추가
            document.querySelector('#btnAdd').addEventListener('click', () => {
                console.log('add Btn');
                const newProduct = {
                    id: 3,
                    brand: '뉴발',
                    name: '멋진 뉴발 신발',
                    price: 70000,
                };
                const products = JSON.parse(localStorage.getItem('products'));
                localStorage.setItem('products', JSON.stringify([...products, newProduct]));
                output.innerHTML = '상품 추가 완료';
            });

            // 3. 상품 삭제 (특정 상품)
            document.querySelector('#btnDeleteOne').addEventListener('click', () => {
                const products = JSON.parse(localStorage.getItem('products'));
                const filteredList = products.filter((product) => product.id !== 1);
                console.log(filteredList);

                localStorage.setItem('products', JSON.stringify(filteredList));
                output.innerHTML = '상품 삭제 완료 id = 1';
            });

            // 4. 상품 삭제
            document.querySelector('#btnClear').addEventListener('click', () => {
                localStorage.clear();
                output.innerHTML = '상품 전체 삭제';
            });

            // 5. 현재 상태 확인
            document.querySelector('#btnShow').addEventListener('click', () => {
                const product = localStorage.getItem('products');
                output.innerHTML = product;
            });
        </script>
    </body>
</html>

🌊 Deep Dive

➡️ EXP 미션 중 궁금해진 내용 정리

🤔 innerHTML vs. createElement

  • innerHTML은 DOM을 직접적으로 수정하기 때문에, innerHTML은 실행될 때마다 기존 DOM을 모두 다시 파싱 및 렌더링하므로, 반복 사용 시 성능 저하 발생
  • createElement()처럼 요소를 새롭게 생성하고 DOM 트리에 추가하는 방식이 더 효율적임

💡 Element.innerHTML

  • HTML 마크업이 포함된 문자열을 그대로 반환

❗ 사용자로부터 입력 받은 데이터를 그대로 innerHTML 프로퍼티에 할당하는 것은 크로스 사이트 스크립팅 공격(XSS)에 취약하므로 위험

💡 Document.createElement()

let element = document.createElement(tagName[, options]);
  • 지정한 tagNameHTML 요소를 만들어 반환

    ❗ 요소 노드를 생성할 뿐 DOM에 추가하지는 않음 → 별도 처리 필요
    append()/appendChild()


🔗 https://developer.mozilla.org/ko/docs/Web/API/Document/createElement

🔗 https://www.javascripttutorial.net/javascript-dom/javascript-innerhtml-vs-createelement/


🤔 append() vs. appendChild()

  • DOM 트리에 새로운 자식 노드를 추가하는 메서드
  • 지정된 부모 노드의 마지막 자식으로 새로운 노드를 추가

💡 append()

  • Node 객체문자열 모두 인수로 받을 수 있음
  • 여러 개의 인수를 한 번에 추가할 수 있음
  • 반환값: 없음 (undefined)

💡 appendChild()

  • 하나의 Node 객체만을 인수로 받을 수 있음
  • 이미 존재하는 노드를 추가하면 해당 노드는 기존 위치에서 제거되고 새로운 위치에 추가됨
  • 반환값: 추가된 Node 객체 반환
const parent = document.getElementById('posts');
const div1 = document.createElement('div');
const div2 = document.createElement('div');

parent.append(div1, div2, '텍스트도 가능');  // append
parent.appendChild(div1);                   // appendChild

📎 https://developer.mozilla.org/ko/docs/Web/API/Document/append

📎 https://developer.mozilla.org/ko/docs/Web/API/Node/appendChild


0개의 댓글