프론트엔드 미션코스 3주차

white noise·2024년 12월 25일

2024 프로젝트 트랙

목록 보기
3/6

기초미션

Vite

빠르고 간결한 모던 웹 프로젝트 개발 경험에 초점을 맞춰 탄생한 빌드 도구이다.

Create React App은 속도가 느린 편이다. 처리해야 할 코드의 양이 많아질 수록 느린 속도가 체감된다. 이와 같은 단점을 해결하기 위해 Esbuild를 기반으로 만들어진 빌드툴인 Vite를 사용하게 된다.

# 현재 경로에 프로젝트 폴더 생성 및 구성
npm create vite@latest <프로젝트_폴더_이름>
  # > React 선택
  # > JavaScript + SWC 혹은 TypeScript + SWC 선택

# 프로젝트 경로로 이동
cd <프로젝트_폴더_이름>

# 의존성 패키지 설치
npm i

# 실행하기
npm run dev

메인미션 1

컴포넌트 합성

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

참고 https://ko.legacy.reactjs.org/docs/components-and-props.html
https://goddaehee.tistory.com/300

useState

useState는 컴포넌트에 state 변수를 추가할 수 있는 React Hook이다.

const [state, setState] = useState(initialState)

여기서 state현재 state이고, 처음에 제공한 초기 stateinitialState이다.
상호작용에 반응하여 다른 값으로 변경할 수 있는 setStateset 함수이다.

import { useState } from 'react';

function MyComponent() {
  const [age, setAge] = useState(28);
  const [name, setName] = useState('Taylor');
  const [todos, setTodos] = useState(() => createTodos());
  // ...

주의사항
set 함수를 호출해도 이미 실행 중인 코드의 현재 state는 변경되지 않는다.
set함수는 다음 렌더링에서 반환할 useState에만 영향을 준다.

마찬가지로 함수에 setAge(age + 1)을 세 번 넣는다고 +3이 되는 것이 아니다. (같은 함수에 있기 때문) 이미 실행중인 코드에서 state 변수가 업데이트 되지 않는다.

이런 경우에는 업데이터를 사용하면 된다.
setAge(a => a + 1);

객체 및 배열 state 업데이트하기

state에는 객체와 배열도 넣을 수 있다. React에서 state는 읽기 전용으로 간주되므로 기존 객체를 변경하지 않고, 교체해야 한다. 예를 들어, state에 form 객체가 있는 경우 변경하지 마라. 대신 새로운 객체를 생성하여 전체 객체를 교체하라.

초기 state 다시 생성하지 않기

React는 초기 state를 한 번 저장하고 다음 렌더링부터는 이를 무시한다.

function TodoList() {
  const [todos, setTodos] = useState(createInitialTodos());
  // ...

createInitialTodos()의 결과는 초기 렌더링에만 사용되지만, 여전히 모든 렌더링에서 이 함수를 호출한다. 이는 큰 배열을 생성하거나 값비싼 계산을 수행하는 경우 낭비일 수 있다.

이 문제를 해결하려면, useState에 초기화 함수로 전달해라.

function TodoList() {
  const [todos, setTodos] = useState(createInitialTodos);
  // ...

함수를 호출한 결과인 createInitialTodos()가 아니라 함수 자체인 createInitialTodos를 전달한다. 함수를 useState에 전달하면 React는 초기화 중에만 함수를 호출한다.

key로 state 초기화하기

컴포넌트에 다른 key를 전달하여 컴포넌으틔 state를 초기화할 수 있다. 이 예시에서는 Reset 버튼이 version state 변수를 변경하고, 이를 Formkey로 전달한다. key가 변경되면 React는 Form 컴포넌트(및 그 모든 자식)을 처음부터 다시 생성하므로 state가 초기화된다.

import { useState } from 'react';

export default function App() {
  const [version, setVersion] = useState(0);

  function handleReset() {
    setVersion(version + 1);
  }

  return (
    <>
      <button onClick={handleReset}>Reset</button>
      <Form key={version} />
    </>
  );
}

function Form() {
  const [name, setName] = useState('Taylor');

  return (
    <>
      <input
        value={name}
        onChange={e => setName(e.target.value)}
      />
      <p>Hello, {name}.</p>
    </>
  );
}

참고 https://ko.react.dev/reference/react/useState#avoiding-recreating-the-initial-state

메인미션 2

쿠키와 세션은 왜 등장했을까?

HTTP 통신은 요청(Request) -> 응답(Response) 이 종료되면 stateless(상태가 유지되지 않은)한 특징 때문에 연결을 끊는 처리 방식이다.
로그인과 같은 일을 할 때, '누가' 로그인 중인지 상태를 기억하기 위해 쿠키, 세션, 토큰을 사용한다.

  1. Connectionless 프로토콜(비연결 지향)
    클라이언트가 서버에 요청을 했을 때, 요청에 맞는 응답을 보낸 후 연결을 끊는 처리방식이다.
  2. Stateless 프로토콜(상태정보 유지 안함)
    클라이언트의 상태 정보를 가지지않는 서버 처리 방식이다. 클라이언트와 첫번째 통신에 데이터를 주고 받았다 해도, 두번째 통신에 이전 데이터를 유지하지 않는다.

쿠키

쿠키는 공개 가능한 정보를 사용자의 브라우저에 저장시킨다.

  • 서버는 클라이언트의 로그인 요청에 대한 응답을 작성할 때, 클라이언트 측에 저장하고 싶은 정보를 응답 헤더의 set-cookie에 담는다.
  • 이후 클라이언트가 재요청 할 때마다 저장된 쿠키를 요청 헤더의 cookie에 담아 보낸다.
  • 서버는 쿠키에 담긴 정보를 바탕으로 해당 요청의 클라이언트가 누군지 식별 할 수 있다.

세션

사용자의 로그인 상태를 유지하는 기능을 위해 쿠키에 아이디와 비밀번호를 담아두고 있을 때 개인 소유가 아닌 컴퓨터에서 사용할 경우 사용자의 개인정보가 털릴 것이고, http로 개인정보를 주고 받다보면 쿠키가 유출, 조작이 될 수 있는 보안상 큰 문제를 야기한다.

session은 비밀번호와 같은 인증 정보를 쿠키에 저장하지 않고 대신에 사용자의 식별자인 session id를 저장한다. 서버에는 인증 정보와 더불어 이 ID에 해당하는 사용자의 정보를 저장한다.

세션은 서버에 저장하기 때문에 사용자가 수백, 수천, 수만으로 늘어난다면해당 유저의 정보를 찾고 데이터 매칭에 오랜 시간이 걸리면서 서버에 부하가 가해지게 된다.

토큰(JWT)

Authentication: 인증. 내가 누구인지 증명해주는 것
Authorization: 인가. 너구나 ok 통과

단순 웹 토큰(SWT)및 SAML(Security Assertion Markup Language) 토큰과 비교할 때 JWT를 사용하면 이점이 있다.
1. More Compact: JSON으로 인코딩 하기 때문에, XML로 인코딩하는 SAML 토큰보다 작다.
2. More Secure: JWT는 공개키와 개인키를 나누어 서명이 가능하다. HMAC 암호 알고리즘을 사용해 암호화된 수정도 가능하다.
3. More Common: JSON의 object는 일반적인 프로그래밍 언어이기 때문에 접근성이 좋다.
4. Easier to process: JWT는 인터넷 규모로 사용된다. 모바일에서 처리하는 것이 더 쉽다는 걸 의미한다.

토큰의 구조

토큰은 헤더(Header), 페이로드(payload), 서명(signature) 세 파트를 .으로 구분하는 구조이다.

장점

  • header와 payload를 가지고 signature를 생성하므로 데이터 위변조를 막을 수 있다.
  • 인증 정보에 대한 별도의 저장소가 필요없다. (서버 부하 적음)
  • JWT는 토큰에 대한 기본 정보와 전달할 정보 및 토큰이 검증 되었다는 서명 등 필요한 모든 정보를 자체적으로 지니고 있다.
  • 토큰은 한 번 발급되면 유효기간이 만료될 때까지 계속 사용이 가능하다.

단점

  • 쿠키나 세션과 다르게 JWT는 토큰의 길이가 길어 인증 요청이 많아질수록 네트워크 부하가 심해진다.
  • payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보를 담으면 안된다.
  • 토큰을 탈취당하면 대처하기 어렵다.
  • 특정 사용자의 접속을 강제로 만료하기 어렵지만, 쿠키/세션 기반 인증은 서버 쪽에서 쉽게 세션을 삭제 할 수 있다.

보안 전략

  1. 짧은 만료 기한 설정
    토큰의 만료 기간이 짧으면 탈취되더라도 금방 만료되기 때문에 피해를 최소화 할 수 있다. 하지만 사용자가 자주 로그인 해야하는 불편함이 있다.
  2. Sliding Session
    예를 들어 로그인하고 글을 작성하는 도중 토큰이 만료 된다면 저장 작업이 정상적으로 작동하지 않고 작성한 글이 날아가는 일이 생기는 등 불편함이 존재한다. Sliding Session은 서비스를 지속적으로 이용한는 클라이언트에게 자동으로 토큰 만료 기한을 늘려주는 방법이다. 1번의 단점을 보완시켜준다.
  3. Refresh Token
    클라이언트가 로그인 요청을 보내면 서버는 Access Token과 그보다 만료기간이 긴 Refresh Token을 함께 내려준다. 클라이언트는 Access Token이 만료되었을 때, Refresh Token을 사용하여 Acess Token의 재발급을 요청한다. 서버는 DB에 저장된 Refresh Token과 비교하여 유효한 경우 새로운 Access Token을 발급하고, 만료된 경우 다시 로그인 시킨다.
    검증을 위해서는 서버에 Refresh Token을 별도로 저장시켜야 한다.

메인 미션3

API

API(Application Programming Interface)는 '서버와 클라이언트가 데이터를 주고 받을 수 있도록 도움을 주는 매개체'라고 정의할 수 있다.

API 명세서란 무엇인가?

API 명세서는 API의 동작 방식, 엔드포인트, 요청 및 응답 구조, 인증 방식 등을 설명하는 문서이다. API 명세서는 개발자와 사용자에게 API의 사용법을 명확히 전달하여, 올바른 API 사용을 가능하게 하고, API의 유지보수와 확장성에도 중요한 역할을 한다.

API 명세서 작성의 중요성

  • 명확한 소통: API 명세서를 통해 개발자 간의 소통이 원활해지고, API를 사용하는 외부 개발자도 이해하기 쉽다.
  • 일관성 유지: 명세서를 통해 API 설계와 구현에서 일관성을 유지할 수 있다.
  • 효율적인 유지보수: 잘 작성된 명세서는 코드 수정 시 API의 영향을 쉽게 파악할 수 있게 도와준다.

API 명세서 작성의 주요 요소

기본 정보

  • API 이름: API의 이름과 간단한 설명
  • 버전: API의 현재 버전 (예: v1, v2)
  • 기본 URL: API의 기본 URL

인증 방법

  • API 키 인증: Authorization: Api-Key {API_KEY}
  • OAuth2: Authorization: Bearer {ACCESS_TOKEN}

엔드포인트 및 HTTP 메서드

API 명세서의 핵심은 각 엔드포인트와 HTTP 메서드이다.

  • 엔드포인트 URL: (예: /users, /users/{id})
  • HTTP 메서드: (예: GET, POST, PUT, DELETE)
  • 설명: 해당 엔드포인트의 기능과 목적에 대한 설명

요청 파라미터

  • 경로 파라미터: (예: {id} in /users/{id})
  • 쿼리 파라미터: (예: ?sort=asc)
  • 헤더: (예: Content-Type: application/json)
  • 본문(body): JSON 형식의 요청 데이터 예시.

응답 구조

  • 응답 형식: 주로 JSON 형식으로 설명.
  • 상태 코드: 200 OK, 201 Created, 404 Not Found 등.
  • 응답 예시: 실제 응답 데이터의 예시(JSON).

오류 처리

  • 상태 코드: (예: 400 Bad Request, 401 Unauthorized, 500 Internal Server Error)
  • 오류 메시지 형식: 오류 발생 시 반환되는 JSON 메시지의 형식과 예시.

참고 https://rhgustmfrh.tistory.com/218

메인 미션4

이번에는 꽤나 애를 썼다. 제대로 한 건진 모르겠지만 일단 완성을 했다.

js

처음부터 어렵게 느껴진 이유는 form 태그 때문이었는데, form태그는 제출할 때 쓰는 태그인 것 같다.
만약 백엔드와 같이 작업을 할 때는 action을 이용해서 url을 연결하여 정보를 전달해주면 되는 듯 하다. 근데 나는 지금 협업 프로젝트를 하는게 아니라 프론트앤드만으로 로그인 화면을 구현하는 거라서 많이 헤맸다.

근데 form태그에서 onSubmit만 사용하면 되는 과제라서 onSubmit으로 잘 해결했다. onSubmit은 form 태그 안에 있는 요소의 type이 "submit"이라면 클릭했을 때 제출이 되는 것이다.

그리고 이외에 form태그 안에 input 태그를 이용해서 아이디와 비밀번호를 받았다. type에서 email이나 password를 설정하면 email은 흔히 말하는 골뱅이와 .com을 올바르게 써야지 제출이 되게 해주고, password는 비밀번호를 쓸 때 안 보이게 블러 처리를 해준다.

나는 User라는 객체 안에다가 유효한 email 하나랑 passsword 하나를 저장해놨는데, 이걸 input태그에 placeholder를 이용해서 빈칸에 쓰기 전까지 유효한 이메일과 비밀번호를 남겨놨다. 유효한 계정을 입력하지 않으면 로그인에 실패하고, 유효한 계정을 입력하면 로그인에 성공하여 다른 페이지로 넘어가게 만들었다.

useState를 이용해서 email과 password의 값이 변경되도 받아지게 설정하고, onChange를 이용해서 입력이 바뀔 때 email과 password가 바뀌도록 만들어줬다. 그리고 submit을 했을 때에는 onSubmit을 통해서 객체 속 유효한 유저정보와 일치했을 때 로그인이 성공하게 만들어줬다.

로그인에 성공했을 때 새로운 페이지로 넘어가는 건 저번 미션에서 배웠던 Router을 이용해서 페이지로 이동시켰다. 이번에는 눌렀을 때가 아니라 로그인을 했을때이기 때문에 Link to를 사용하지 않고, navigate를 사용했다.

서브 미션

useEffect

useEffect는 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 실행할 수 있도록 하는 Hook이다.
useEffect(setup(function), dependencies?)

  • setup(설정): Effect의 로직이 포함된 함수이다. React는 컴포넌트가 DOM에 추가된 이후에 설정 함수를 실행한다. 의존성의 변화에 따라 컴포넌트가 리렌더링이 되었을 경우, (설정 함수에 정리 함수를 추가했었다면) React는 이전 렌더링에 사용된 값으로 정리 함수를 실행한 후 새로운 값으로 설정 함수를 실행한다.

  • depedencies 선택사항: 설정 함수의 코드 내부에서 참조되는 모든 반응형 값들이 포함된 배열로 구성됐다. 반응형 값에는 props와 state, 모든 변수 및 컴포넌트 body에 직접적으로 선언된 함수들이 포함 된다.

사용법

useEffect 함수 불러오기

import React, { useEffect  from 'react';
  1. Component가 mount 됐을 때 (처음 나타났을 때)
    • 처음 렌더링 될 때 한 번만 실행하고 싶을 때에는 deps 위치에 빈 배열을 넣는다.
    • 만약 배열을 생략한다면 리렌더링 될 때마다 실행된다.
useEffect(() => {
  console.log('마운트 될 때 실행');
}, []);
  1. Component가 update 될 때 (특정 props, state가 바뀔 때)
    • 특정값이 업데이트 될 때 실행하고 싶을 때는 deps 위치의 배열 안에 검사하고 싶은 값을 넣어준다.
    • 업데이트 될 때만 실행하는 것이 아니라 마운트 될 때도 실행된다.
useEffect(() => {
  console.log(name);
  console.log('업데이트 될 때 실행');
}, [name]);
  1. Component가 unmount 될 때(사라질 때) & update 되기 직전에
    • clean up 함수 반환 (return 뒤에 나오는 함수이며 useEffect에 대한 뒷정리 함수라고 한다.)
    • 언마운트 될 때만 cleanup 함수를 실행하고 싶을 때: 두 번째 파라미터로 빈 배열을 넣는다.
    • 특정값이 업데이트 되기 직전에 cleanup 함수를 실행하고 싶을 때: deps 배열 안에 검사하고 싶은 값을 넣어준다.
useEffect(() => {
  console.log('effect');
  console.log(name);
  return () => {
    console.log('cleanup');
    console.log(name);
  };
}, []);

외부 시스템과 연결

몇몇 컴포넌트들은 페이지에 표시되는 동안 네트워크나 브라우저 API, 또는 서드파티 라이브러리와의 연결이 유지되어야 한다. React에 제어되지 않는 이러한 시스템들을 외부 시스템(external)이라 부른다.

import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
  	const connection = createConnection(serverUrl, roomId);
    connection.connect();
  	return () => {
      connection.disconnect();
  	};
  }, [serverUrl, roomId]);
  // ...
}

useEffect는 2개의 인수가 필요하다.

  1. 외부 시스템과 컴포넌트를 연결하는 설정 코드가 포함된 설정 함수
    • 외부 시스템과 연결을 해제하는 정리 코드가 포함된 정리 함수를 반환할 수 있다.
  2. 위 함수 내부에서 사용하는 컴포넌트에서 비롯된 반응형 값들을 포함하는 의존성 배열

React는 설정 함수와 저일 함수가 필요할 때마다 호출할 수 있으며, 이는 여러 번 호출될 수 있다.*

  1. 컴포넌트가 화면에 추가되었을 때 설정 코드가 동작한다 (마운트 시)
  2. 의존성이 변경된 컴포넌트가 리렌더링 될 때마다 아래 동작을 수행한다.
    • 먼저 정리 코드가 오래된 props와 state와 함께 실행된다.
    • 이후, 설정 코드가 새로운 props와 state와 함께 실행된다.
  3. 컴포넌트가 화면에서 제거된 이후에 정리 코드가 마지막으로 실행된다. (마운트 해제 시)

참고
https://ko.react.dev/reference/react/useEffect

https://xiubindev.tistory.com/100

profile
Hello World

2개의 댓글

comment-user-thumbnail
2024년 12월 26일

이번 주차 미션을 하시면서 생긴 고민의 흔적들이 잘 보이는 글이었어요,,👍
미션 완수하시느라 고생 많으셨어요! (서브 미션까지!)
앞으로의 프로젝트 개발도 응원합니다!

답글 달기
comment-user-thumbnail
2025년 1월 5일

확실히 미션들에서 배우는 개념들에 대해 깊게 공부한 흔적이 보이네요!! 이런 방식으로 코드 짜면서 개념 익히면 금방 익힐 수 있을거 같네여 질문 편하게 하셔도 되고 남은 개발 코스도 화이팅입니다~!

답글 달기