패스트캠퍼스 환급챌린지 28일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기

2025. 7. 28. 22:11·패스트캠퍼스/김민태의 프론트엔드 아카데미: 제1강

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다

 

패스트캠퍼스 환급챌린지란?

 

(~6/20) 50일의 기적 AI 환급반💫 | 패스트캠퍼스

초간단 미션! 하루 20분 공부하고 수강료 전액 환급에 AI 스킬 장착까지!

fastcampus.co.kr

 

https://fastcampus.info/4n8ztzq

 

 

매복사랑니 발치한 곳에 이상한게 생겼지만 재미있게 강의를 들을 수 있게 해주는 환급챌린지 최공!

 

28일차 공부 시작!

 

오늘 공부 시작 시간 (07.28 19:19)

 

오늘의 강의 주제는 '회원 가입 폼-오류 정보 ux 선택하기'이다.

 

학습 인증샷 - 1

 

(오늘도 마찬가지로 모자이크 처리..)

 

오늘의 학습 내용

- 회원 가입 폼-오류 정보 ux 선택하기 -


* 회원가입 폼과 사용자 경험: 즉각적인 피드백을 위한 설계


이번 강의에선 사용자 입력 폼, 특히 비밀번호 입력 필드에 대한 UX 설계를 어떻게 고려해야 하는지를 실습을 통해 다뤘다. 사용자에게 실시간으로 피드백을 주는 방식은 단순한 편의를 넘어서 전체 서비스 경험의 품질을 좌우한다. 하지만 모든 피드백이 항상 유익한 것은 아니다. 너무 이른 에러 메시지는 오히려 사용자의 혼란을 유발한다. 예를 들어, 사용자가 아직 아무 글자도 입력하지 않았는데도 이미 "필수 입력 항목입니다"라는 메시지가 뜬다면, 이건 잘못된 UX다.


따라서 입력값의 상태에 따라 벨리데이션 피드백이 언제 작용할지를 명확히 정의해야 한다. 이를테면 첫 입력이 이루어진 시점 이후부터 에러 메시지를 노출하기 시작하거나, 사용자가 한번이라도 해당 입력 필드를 수정했는지 여부에 따라 다른 동작을 유도하는 전략이 필요하다. 이번 실습에서는 이런 로직을 플래그(updated)를 기준으로 처리하고 있다.

 

* 실습 코드 일부

import { nextTick } from '../utils';
import { ValidateRule } from '../types';
import template from './password-field.template';
import { RequireRule } from '../constant';

enum StrongLevel {
  None = 0,
  Light,
  Medium,
  Havey,
}

type Props = {
  id: string;
  label: string;
  text?: string;
  require?: boolean;
  placeholder?: string;
  strong?: StrongLevel;
}

const StrongMessage: [string, string, string, string] = [
  '금지된 수준',
  '심각한 수준',
  '보통 수준',
  '강력한 암호',
];

const DefaultProps: Props = {
  id: '',
  label: 'label',
  text: '',
  require: true,
  placeholder: '',
  strong: StrongLevel.None,
};

export default class PasswordField {
  private template = template;
  private container: string;
  private data: Props;
  private updated: boolean = false;
  private validateRules: ValidateRule[] = [];

  constructor(container: string, data: Props) {
    this.container = container;
    this.data = { ...DefaultProps, ...data };

    if (this.data.require) {
      this.addValidateRule(RequireRule);
    }

    nextTick(this.attachEventHandler);
  }

  private onChange = (e: Event) => {
    const { value, id } = e.target as HTMLInputElement;
  
    if (id === this.data.id) {
      this.updated = true;
      this.data.text = value;
      this.update();
    }
  }

  private attachEventHandler = () => {
    document.querySelector(this.container)?.addEventListener('change', this.onChange);
  }

  private buildData = () => { 
    let strongLevel = -1;
    const isInvalid: ValidateRule | null = this.validate();

    if (this.data.text!.length > 0) {
      strongLevel++;
    }

    if (this.data.text!.length > 12) {
      strongLevel++;
    }

    if (/[!@#$%^&*()]/.test(this.data.text!)) {
      strongLevel++;
    }

    if (/\d/.test(this.data.text!)) {
      strongLevel++;
    }

    return {
      ...this.data, 
      updated: this.updated,
      valid: this.updated ? !isInvalid : true,
      strongMessage: strongLevel < 0 ? '' : StrongMessage[strongLevel],
      strongLevel0: strongLevel >= 1,
      strongLevel1: strongLevel >= 2,
      strongLevel2: strongLevel >= 3,
      strongLevel3: strongLevel >= 4,
    };
  }

  private validate = (): ValidateRule | null => {
    const target = this.data.text ? this.data.text.trim() : '';

    const invalidateRules = this.validateRules
      .filter(validateRule => validateRule.rule.test(target) !== validateRule.match);

    return (invalidateRules.length > 0) ? invalidateRules[0] : null;
  }

  private update = () => {
    const container = document.querySelector(`#field-${this.data.id}`) as HTMLElement;
    const docFrag = document.createElement('div');

    docFrag.innerHTML = this.template(this.buildData());

    container.innerHTML = docFrag.children[0].innerHTML;
  }

  public get name(): string {
    return this.data.id;
  }

  public get value(): string {
    return this.data.text || '';
  }

  public get isValid(): boolean {
    return !this.validate();
  }

  public addValidateRule = (rule:ValidateRule) => {
    this.validateRules.push(rule);
  }

  public render = (append: boolean = false) => {
    const container = document.querySelector(this.container) as HTMLElement;

    if (append) {
      const divFragment = document.createElement('div');
      divFragment.innerHTML = this.template(this.buildData());

      container.appendChild(divFragment.firstElementChild as HTMLElement);
    } else {
      container.innerHTML = this.template(this.buildData());
    }
  }
}


* 비밀번호 유효성 검사 로직 설계


이번 코드 실습에선 `PasswordField` 클래스를 만들었다. 이 컴포넌트는 사용자의 비밀번호 입력을 처리하며, 입력값을 실시간으로 점검하여 강도 수준과 유효 여부를 판단해준다. `RequireRule` 같은 벨리데이션 규칙은 조건에 맞게 동적으로 적용되며, 입력값의 길이, 특수 문자 사용 여부, 숫자 포함 여부를 토대로 비밀번호 강도를 총 네 단계로 분류했다.

이 강도는 다음과 같은 조건으로 결정된다:
1. 비밀번호의 길이가 1자 이상
2. 비밀번호의 길이가 12자 초과
3. 특수문자 포함 여부
4. 숫자 포함 여부

이를 통해 금지된 수준부터 강력한 암호까지 순차적으로 피드백이 제공된다. 사용자가 입력을 거듭할수록 실시간으로 강도 메시지도 갱신되며, 그 시점의 상태에 맞는 시각적인 도움을 줄 수 있다.


* TypeScript 적용에서의 고민: any 대신 명확한 타입 지정


또 하나의 중요한 포인트는 주소 API 등 외부 데이터를 사용할 때의 타입 전략이다. 흔히 외부 API의 모든 필드를 타입 정의하려다 보면 지나치게 복잡해지고, 실제 사용하는 필드만 추려 타입핑하는 것이 이상적이다. 이때 `any` 타입을 사용하는 것은 피해야 한다. 타입스크립트의 이점을 제대로 살리지 못하게 되고, 코드 안정성과 예측 가능성에서도 손해를 보게 된다.

강의에서는 꼭 필요한 데이터 구조만 type이나 interface로 정의해서 사용하는 것이 더 현실적인 접근이라고 강조했다. 이는 협업 상황이나 리팩토링 상황에서도 많은 이점을 준다. 예를 들어, API가 변경되었을 때 모든 타입에 대해 점검할 필요 없이, 핵심 데이터만 빠르게 수정이 가능하기 때문이다.

 

 

30강 수강 완료

 

오늘 공부 종료 시간 (07.28 19:34)

 

30강 강의 내용 정리 필기

 

학습 인증샷 - 2

마치며

 

오늘도 환급 챌린지 30강 공부 완료!!

 

이번 강의에서는 단순히 동작하는 코드만 구현하는 것을 넘어서 사용자 경험(UX)을 고려한 입력 컴포넌트를 어떻게 설계할지에 대한 고민과 설계가 주된 내용이었다. 특히 입력 전과 후의 상태를 구분하여 시각적 피드백을 제공하고, 유효성 검사와 시점 제어를 통해 사용자가 불필요한 혼란을 겪지 않도록 하는 실질적인 로직이 중요했다.

'패스트캠퍼스 > 김민태의 프론트엔드 아카데미: 제1강' 카테고리의 다른 글

패스트캠퍼스 환급챌린지 30일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기  (2) 2025.07.30
패스트캠퍼스 환급챌린지 29일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기  (2) 2025.07.29
패스트캠퍼스 환급챌린지 27일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기  (2) 2025.07.27
패스트캠퍼스 환급챌린지 26일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기  (0) 2025.07.26
패스트캠퍼스 환급챌린지 25일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기  (2) 2025.07.25
'패스트캠퍼스/김민태의 프론트엔드 아카데미: 제1강' 카테고리의 다른 글
  • 패스트캠퍼스 환급챌린지 30일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기
  • 패스트캠퍼스 환급챌린지 29일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기
  • 패스트캠퍼스 환급챌린지 27일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기
  • 패스트캠퍼스 환급챌린지 26일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기
hangyeoldora
hangyeoldora
hangyeoldora 님의 블로그 입니다.
  • hangyeoldora
    한결의 개발로그
    hangyeoldora
  • 글쓰기 관리자
  • 전체
    오늘
    어제
    • 분류 전체보기 (65)
      • Azure (2)
      • NestJS (1)
      • Python (1)
        • 자연어 처리 (0)
        • 머신러닝 (0)
      • Java (1)
      • Javascript (1)
      • LLM (1)
        • 벡터 DB (1)
      • Electron (0)
      • Nsis (0)
      • 자격증 (0)
      • 패스트캠퍼스 (51)
        • 김민태의 프론트엔드 아카데미: 제1강 (51)
      • 혼공단 (7)
        • 혼자 공부하는 머신러닝 (7)
      • 목표 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 최근 댓글

  • 최근 글

  • 태그

    오공완
    직장인자기계발
    혼공학습단
    혼공단
    습관형성
    패스트캠퍼스
    패스트캠퍼스후기
    딥러닝
    혼공머신
    환급챌린지
  • hELLO· Designed By정상우.v4.10.3
hangyeoldora
패스트캠퍼스 환급챌린지 28일차: '김민태의 프론트엔드 아카데미 - 제 1강 JavaScript & TypeScript Essential' 강의 후기
상단으로

티스토리툴바