Practice | Calculator

Eunji Park·2022년 7월 27일
0

TypeScript

목록 보기
16/18
post-thumbnail
post-custom-banner

✓ 프로젝트 생성

Vite 프로젝트를 생성한다.

✓ Type Assertion

DOM Type & Event Type 지정하기

DOM 요소를 null 로 인식
DOM 에 대한 타입 지정 필요

사용자들과 상호작용할 수 있는 DOM 요소(HTMLDivElement), 마우스 이벤트(MouseEvent) 타입을 지정해주어야 한다.

그리고 이 요소들이 없을 때 null 로 인식될 수 있는 값 (빈 값이 될 수 있는 값)들을 tpescript로 다 잡아내서 꼼꼼하게 체크

개발자의 입장에서는 번거롭고 불편한 작업이지만 조금 더 안전한 결과물을 만들어낼 수 있다는 장정이 있다.

✓ 계산기

render

입력창에 render

const Calculator = {
  render() {
    const resultEl = < HTMLDivElement > document.querySelector('#result');

    if (resultEl) {
      resultEl.innerText = String(9999);
    }
  }
}

Calculator.render();

값 0으로 설정

const Calculator = {
  value: 0,
  render() {
    const resultEl = < HTMLDivElement > document.querySelector('#result');

    if (resultEl) {
      resultEl.innerText = String(this.value);
    }
  }
}

Calculator.render();

initEvent ( onClickButton )

클릭 시 입력창에 값 표기

1. Click 시 버튼 정보 콘솔에 찍기

? 기호 : 옵셔널 체이닝 | 선택적 프로퍼티

function onClickButton() {
  const buttonContainerEl = document.querySelector('.contents');
  // ? (선택적 프로퍼티 | 옵셔널 체이닝) : https://developer-talk.tistory.com/193
  buttonContainerEl?.addEventListener('click', console.log); 
} 

const Calculator = {
  value: 0,
  render() {
    const resultEl = < HTMLDivElement > document.querySelector('#result');

    if (resultEl) {
      resultEl.innerText = String(this.value);
    }
  },
  initEvent() {
    onClickButton();
  }
}

Calculator.render();
Calculator.initEvent();

1-2. Click 시 버튼 값 콘솔에 찍기

function onClickButton() {
  const buttonContainerEl = document.querySelector('.contents');
  buttonContainerEl?.addEventListener('click', function({ target }) {
    console.log((target as HTMLButtonElement).innerText);
  }); 
}

const Calculator = {
  value: 0,
  render() {
    const resultEl = < HTMLDivElement > document.querySelector('#result');

    if (resultEl) {
      resultEl.innerText = String(this.value);
    }
  },
  initEvent() {
    onClickButton();
  }
}

Calculator.render();
Calculator.initEvent();

this Error ( this 회피 )

2. initEvent 정리 + this 에러

const Calculator = {
  value: 0,
  render() {
    const resultEl = < HTMLDivElement > document.querySelector('#result');

    if (resultEl) {
      resultEl.innerText = String(this.value);
    }
  },
  reset() {
    this.value = 0;
  },
  initEvent() {
    const buttonContainerEl = document.querySelector('.contents');

    buttonContainerEl?.addEventListener('click', function ({ target }) {
    
      const buttonText = (target as HTMLButtonElement).innerText;
      // console.log((target as HTMLButtonElement).innerText);

      if (buttonText === 'AC') {
        // 여기서 this는 Event target 을 가리킴
        // 이 함수호 바인딩된 this가 지정되지 않았기 때문에, typescript에서 이 this는 any 에 가까움
        this.reset();  // --> this Error : 'this'에는 형식 주석이 없으므로 암시적으로 'any' 형식이 포함됩니다.ts(2683)
main.ts(39, 50): 'this'의 외부 값은 이 컨테이너에서 섀도 처리됩니다.
      } else {

      }
    });
  }
}

Calculator.render();
Calculator.initEvent();

3. this 에러

const Calculator = {
  value: 0,
  render() {
    const resultEl = < HTMLDivElement > document.querySelector('#result');

    if (resultEl) {
      resultEl.innerText = String(this.value);
    }
  },
  reset() {
    this.value = 0;
  },
  initEvent() {
    const buttonContainerEl = document.querySelector('.contents');

    // buttonContainerEl?.addEventListener('click', function ({ target }) {
    buttonContainerEl?.addEventListener('click', ({ target }) => {
      const buttonText = (target as HTMLButtonElement).innerText;
      // console.log((target as HTMLButtonElement).innerText);

      if (buttonText === 'AC') {
        // 여기서 this는 Event target 을 가리킴
        // 이 함수호 바인딩된 this가 지정되지 않았기 때문에, typescript에서 이 this는 any 에 가까움
        /* [ 해결 방법 ]
         * 1. tsconfig.json 파일의 compilerOtions 에 "noImplicitThis" : false 로 설정한다.
              noImplicitThis 는 this를 더 까다롭게 본다는 설정 ( 기본값  true)
              이 방법은 권장하지 않음
           2. 화살표 함수로 변경
              화살표 함수의 this는 일반함수의 this와 동작이 다르기 때문에 Error가 생기지 X
         */
        this.reset();
      } else {

      }
    });
  }
}

Calculator.render();
Calculator.initEvent();

operator

inputNumber 전달하기

const Calculator = {
  value: 0,
  render() {
    const resultEl = < HTMLDivElement > document.querySelector('#result');

    if (resultEl) {
      resultEl.innerText = String(this.value);
    }
  },
  reset() {
    this.value = 0;
  },
  operator(inputNumber: number) {
    alert(inputNumber);
  },
  initEvent() {
    const buttonContainerEl = document.querySelector('.contents');

    // buttonContainerEl?.addEventListener('click', function ({ target }) {
    buttonContainerEl?.addEventListener('click', ({ target }) => {
      const buttonText = (target as HTMLButtonElement).innerText;
      // console.log((target as HTMLButtonElement).innerText);

      if (buttonText === 'AC') {
        // 여기서 this는 Event target 을 가리킴
        // 이 함수호 바인딩된 this가 지정되지 않았기 때문에, typescript에서 이 this는 any 에 가까움
        /* [ 해결 방법 ]
         * 1. tsconfig.json 파일의 compilerOtions 에 "noImplicitThis" : false 로 설정한다.
              noImplicitThis 는 this를 더 까다롭게 본다는 설정 ( 기본값  true)
              이 방법은 권장하지 않음
           2. 화살표 함수로 변경
              화살표 함수의 this는 일반함수의 this와 동작이 다르기 때문에 Error가 생기지 X
         */
        this.reset();
      } else {
        // this.operator(buttonText);  // --> buttonText 는 innerText. 그리고 innerText 는 string ( Number형으로 변환 필요 )
        this.operator(Number(buttonText));
      }
    });
  }
}

Calculator.render();
Calculator.initEvent();

inputNumber 그리기 ( 1자리 )
문제 > 숫자가 3자리까지 입력되도록 하고 싶은데, 이 코드는 한 자리만 그려짐

const Calculator = {
  value: 0,  // 초기값
  // render(inputValue: string | number) {
  render(inputValue?: string | number) {
    const resultEl = < HTMLDivElement > document.querySelector('#result');

    if (resultEl) {
      resultEl.innerText = String(inputValue || this.value);  // inputValue가 없을 경우 초기값 넣기
    }
  },
  reset() {
    this.value = 0;
  },
  operator(inputNumber: number) {
    // alert(inputNumber);
    this.render(inputNumber)
  },
  initEvent() {
    const buttonContainerEl = document.querySelector('.contents');

    // buttonContainerEl?.addEventListener('click', function ({ target }) {
    buttonContainerEl?.addEventListener('click', ({ target }) => {
      const buttonText = (target as HTMLButtonElement).innerText;
      // console.log((target as HTMLButtonElement).innerText);

      if (buttonText === 'AC') {
        // 여기서 this는 Event target 을 가리킴
        // 이 함수호 바인딩된 this가 지정되지 않았기 때문에, typescript에서 이 this는 any 에 가까움
        /* [ 해결 방법 ]
         * 1. tsconfig.json 파일의 compilerOtions 에 "noImplicitThis" : false 로 설정한다.
              noImplicitThis 는 this를 더 까다롭게 본다는 설정 ( 기본값  true)
              이 방법은 권장하지 않음
           2. 화살표 함수로 변경
              화살표 함수의 this는 일반함수의 this와 동작이 다르기 때문에 Error가 생기지 X
         */
        this.reset();
      } else {
        // this.operator(buttonText);  // --> buttonText 는 innerText. 그리고 innerText 는 string ( Number형으로 변환 필요 )
        this.operator(Number(buttonText));
      }
    });
  }
}

Calculator.render();
Calculator.initEvent();

validateNumberLength (3자리 설정)

inputNumber 그리기 ( 3자리 )

import "./style.css";

const VALID_NUMBER_OF_DIGITS = 3;
const BASE_DIGIT = 10;

const validateNumberLength = (value: string | number) => {
  return String(value).length < VALID_NUMBER_OF_DIGITS;
}


const Calculator = {
  value: 0,  // 초기값
  // render(inputValue: string | number) {
  render(inputValue?: string | number) {
    const resultEl = < HTMLDivElement > document.querySelector('#result');

    if (resultEl) {
      resultEl.innerText = String(inputValue || this.value);  // inputValue가 없을 경우 초기값 넣기
    }
  },
  reset() {
    this.value = 0;
  },
  operator(inputNumber: number) {
    // alert(inputNumber);
    this.render(inputNumber)
  },
  initEvent() {
    const buttonContainerEl = document.querySelector('.contents');

    // buttonContainerEl?.addEventListener('click', function ({ target }) {
    buttonContainerEl?.addEventListener('click', ({ target }) => {
      const buttonText = (target as HTMLButtonElement).innerText;
      // console.log((target as HTMLButtonElement).innerText);

      if (buttonText === 'AC') {
        // 여기서 this는 Event target 을 가리킴
        // 이 함수호 바인딩된 this가 지정되지 않았기 때문에, typescript에서 이 this는 any 에 가까움
        /* [ 해결 방법 ]
         * 1. tsconfig.json 파일의 compilerOtions 에 "noImplicitThis" : false 로 설정한다.
              noImplicitThis 는 this를 더 까다롭게 본다는 설정 ( 기본값  true)
              이 방법은 권장하지 않음
           2. 화살표 함수로 변경
              화살표 함수의 this는 일반함수의 this와 동작이 다르기 때문에 Error가 생기지 X
         */
        this.reset();
      } else {
        // this.operator(buttonText);  // --> buttonText 는 innerText. 그리고 innerText 는 string ( Number형으로 변환 필요 )
        this.operator(Number(buttonText));
      }
    });
  }
}

Calculator.render();
Calculator.initEvent();

숫자 연속적으로 입력(렌더링)하기

STEP 1

import "./style.css";

const VALID_NUMBER_OF_DIGITS = 3;
const BASE_DIGIT = 10;


const validateNumberLength = (value: string | number) => {
  return String(value).length < VALID_NUMBER_OF_DIGITS;
}


const Calculator = {
  value: 0,  // 초기값
  // render(inputValue: string | number) {
  render(inputValue?: string | number) {
    const resultEl = < HTMLDivElement > document.querySelector('#result');
    const prevValue = Number(resultEl.innerText);

    if (resultEl) {
      // resultEl.innerText = String(prevValue + inputValue || this.value);  // inputValue가 없을 경우 초기값 넣기
      resultEl.innerText = isNaN(prevValue) ? String(inputValue) : String(prevValue);
    }
  },
  reset() {
    this.value = 0;
  },
  operator(inputNumber: number) {
    // alert(inputNumber);
    this.render(inputNumber)
  },
  initEvent() {
    const buttonContainerEl = document.querySelector('.contents');

    // buttonContainerEl?.addEventListener('click', function ({ target }) {
    buttonContainerEl?.addEventListener('click', ({ target }) => {
      const buttonText = (target as HTMLButtonElement).innerText;
      // console.log((target as HTMLButtonElement).innerText);

      if (buttonText === 'AC') {
        // 여기서 this는 Event target 을 가리킴
        // 이 함수호 바인딩된 this가 지정되지 않았기 때문에, typescript에서 이 this는 any 에 가까움
        /* [ 해결 방법 ]
         * 1. tsconfig.json 파일의 compilerOtions 에 "noImplicitThis" : false 로 설정한다.
              noImplicitThis 는 this를 더 까다롭게 본다는 설정 ( 기본값  true)
              이 방법은 권장하지 않음
           2. 화살표 함수로 변경
              화살표 함수의 this는 일반함수의 this와 동작이 다르기 때문에 Error가 생기지 X
         */
        this.reset();
      } else {
        // this.operator(buttonText);  // --> buttonText 는 innerText. 그리고 innerText 는 string ( Number형으로 변환 필요 )
        this.operator(Number(buttonText));
      }
    });
  }
}

Calculator.render();
Calculator.initEvent();

STEP 2

import "./style.css";

const VALID_NUMBER_OF_DIGITS = 3;
const BASE_DIGIT = 10;
const INIT_VALUE = 0;

const validateNumberLength = (value: string | number) => {
  return String(value).length < VALID_NUMBER_OF_DIGITS;
}


const Calculator = {
  value: 0,  // 초기값
  render(inputValue?: string | number) {
    const resultEl = < HTMLDivElement > document.querySelector('#result');
    const prevValue = resultEl.innerText;

    if (resultEl) {
      resultEl.innerText = Number(prevValue) === 0 ? String(inputValue) : String(prevValue + inputValue)
    }
  },
  reset() {
    this.value = 0;
  },
  operator(inputNumber: number) {
    this.render(inputNumber)
  },
  initEvent() {
    const buttonContainerEl = document.querySelector('.contents');

    buttonContainerEl?.addEventListener('click', ({ target }) => {
      const buttonText = (target as HTMLButtonElement).innerText;

      if (buttonText === 'AC') {
        this.reset();
      } else {
        this.render(Number(buttonText));
      }
    });
  }
}

Calculator.render(INIT_VALUE);
Calculator.initEvent();

조금 더 간결하게 isZero

import "./style.css";

const VALID_NUMBER_OF_DIGITS = 3;
const BASE_DIGIT = 10;
const INIT_VALUE = 0;

const validateNumberLength = (value: string | number) => {
  return String(value).length < VALID_NUMBER_OF_DIGITS;
}

const isZero = (value: string) => Number(value) === 0;

const Calculator = {
  value: 0,  // 초기값
  render(inputValue?: string | number) {
    const resultEl = < HTMLDivElement > document.querySelector('#result');
    const prevValue = resultEl.innerText;

    if (resultEl) {
      resultEl.innerText = isZero(prevValue) ? String(inputValue) : String(prevValue + inputValue)
    }
  },
  reset() {
    this.value = 0;
  },
  operator(inputNumber: number) {
    this.render(inputNumber)
  },
  initEvent() {
    const buttonContainerEl = document.querySelector('.contents');

    buttonContainerEl?.addEventListener('click', ({ target }) => {
      const buttonText = (target as HTMLButtonElement).innerText;

      if (buttonText === 'AC') {
        this.reset();
      } else {
        this.render(Number(buttonText));
      }
    });
  }
}

Calculator.render(INIT_VALUE);
Calculator.initEvent();

3자리 제한 적용

import "./style.css";

const VALID_NUMBER_OF_DIGITS = 3;
const BASE_DIGIT = 10;
const INIT_VALUE = 0;

const validateNumberLength = (value: string | number) => {  // 3자리 여부 검사
  return String(value).length < VALID_NUMBER_OF_DIGITS;
}

const isZero = (value: string) => Number(value) === 0;  // 0 인지 검사


const Calculator = {
  value: 0,  // 초기값
  render(inputValue?: string | number) {
    const resultEl = < HTMLDivElement > document.querySelector('#result');
    const prevValue = resultEl.innerText;

    if (!validateNumberLength(prevValue)) {  // 3자리 입력 제한하기
      alert('3자리 이상의 숫자를 입력할 수 없습니다.');
      return;
    }

    if (resultEl) {
      resultEl.innerText = isZero(prevValue) ? String(inputValue) : String(prevValue + inputValue)
    }
  },
  reset() {
    this.value = 0;
  },
  operator(inputNumber: number) {
    this.render(inputNumber)
  },
  initEvent() {
    const buttonContainerEl = document.querySelector('.contents');

    buttonContainerEl?.addEventListener('click', ({ target }) => {
      const buttonText = (target as HTMLButtonElement).innerText;

      if (buttonText === 'AC') {
        this.reset();
      } else {
        this.render(Number(buttonText));
      }
    });
  }
}

Calculator.render(INIT_VALUE);
Calculator.initEvent();

연산자 바인딩

최종 연산 구현

리택터링 및 추가 구현

post-custom-banner

0개의 댓글