리액트로 숫자만 입력 가능한 input 만들기

이나리·2022년 7월 24일
2

사용자로부터 숫자를 입력받기 전에, input 요소와 관련된 키보드 이벤트에 관해 먼저 알아보겠습니다.

input 요소와 관련된 이벤트

focus

  • input 요소에 마우스 혹은 키보드로 커서를 활성화시켰을 때, 발생하는 이벤트입니다.
  • 다른 이벤트들보다 가장 먼저 실행됩니다.

keydown

  • 사용자가 키를 누를 때 발생하는 이벤트입니다.
  • 키 값이 input 요소에 타이핑 되기 전에 발생합니다.

keypress (deprecated)

  • keyDown 이벤트와 동일하게 동작하지만, keyDown 이벤트가 먼저 발생합니다.
    키를 입력했을 때 지원되지 않는 키도 있습니다. (shift, alt, ctrl, esc)
  • 이 이벤트는 현재 deprecated 되어 사용이 권장되지 않습니다.

input

  • input 요소의 value 속성값, 즉 입력값이 변경될 때마다 실행되는 이벤트입니다.
    즉, input 요소에 커서가 활성화된 상태에서 입력값이 바뀌면 항상 실행됩니다.

keyup

  • 사용자가 키를 누른 후, 손을 뗄 때 발생하는 이벤트입니다.

change

  • 포커스가 해제됐을 때 (blur 이벤트가 실행되기 전에), value 값에 변화가 발생하면 실행됩니다.
    이 이벤트는 값이 변경되지 않았다면 실행되지 않을 수 있습니다.
  • 리액트의 change 이벤트는 DOM의 change 이벤트와 동작 방식이 다르며, 오히려 input 이벤트와 비슷하게 동작합니다.

blur

  • input 요소에 포커스가 해제되었을 때 발생합니다.
    예를 들면, input 요소에 포커스가 된 상태에서 다른 요소를 클릭하는 등 다른 동작이 발생하게 되면 blur 이벤트가 실행됩니다.

위 이벤트의 실행 순서는 나열한 순서대로 실행됩니다.
focus → keydown → keypress → input → keyup → change → blur

사용자의 입력 키 제한하기

input 요소가 text type 인 경우, 모든 텍스트 문자열 입력이 가능합니다.
우리는 숫자값만 입력받는 input을 만들어야 합니다.
그럴러면, 사용자로부터 숫자 이외의 값을 입력받는 것은 최대한 제한하는 것이 좋을 것 같습니다.

이때 사용 가능한 이벤트로는, keydown, input, change 이벤트가 적당할 것 같습니다.

리액트에서 input 이벤트보다 change 이벤트를 사용할 것을 권장하고 있으니 change 이벤트를 이용해 사용자의 입력값을 제한해보도록 하겠습니다.

(keydown 이벤트의 경우에는 입력받은 key 의 값을 통해 걸러내야 하는 경우의 수가 change 이벤트보다 더 존재하다보니 사용하지 않게 되었습니다.)

change Event

일반적으로 text input 의 입력값에 전달되는 값의 타입은 string 입니다.
입력 받은 숫자를 저장해야 할 때, 문자열이 아닌 숫자형으로 저장해야 한다면 변형이 필요합니다.

문자열을 숫자형으로 변환하기

문자열을 숫자(정수) 로 변환할 수 있는 방법에는 여러가지가 있습니다.

  1. Number
  2. + 단항 연산자
  3. parseInt

1. Number

Number 메서드는 문자열을 숫자형으로 변환할 때, 아래의 규칙이 적용됩니다.

문자열의 처음과 끝 공백이 제거됩니다. 공백 제거 후 남아있는 문자열이 없다면 0, 그렇지 않다면 문자열에서 숫자를 읽습니다. 변환에 실패하면 NaN 이 됩니다. 모던 javascript 튜토리얼 참고

정리해보면,

숫자로만 구성된 문자열은 숫자로 변형되고,
문자열의 앞뒤 공백을 제거하고 난 후, 빈 문자열인 경우에는 0 이 되지만,
숫자가 아닌 문자열이 하나라도 포함되어 있을 경우 NaN 값이 됩니다.

const one = Number('123'); // 123
const two = Number(''); // 0
const three = Number('123a'); // NaN
const four = Number(' 123 '); // 123
const five = Number(' 12 3'); // NaN

2. + 단항 연산자

+ 단항 연산자는 Number 메서드와 같은 방식으로 작동합니다.
+ 단항 연산자는 + 뒤에 오는 값이 숫자가 아닌 경우에만, 숫자형으로 변환 됩니다.

3. parseInt

parseInt 메서드는 앞의 메서드들과 조금 다릅니다.
이 메서드는 앞에서부터 숫자를 읽을 수 있을 때까지 읽고, 값을 변형합니다.

숫자가 아닌 문자열이 숫자와 함께 포함되어 있어도, 숫자로 변형됩니다.
단, 숫자가 문자열보다 앞에 있을 경우에만 가능합니다. 이때, 빈 문자열은 숫자 앞에 있어도 상관없습니다.

하지만, 빈 문자열만 있는 경우에는 NaN 값을 표시합니다. 숫자가 없기 때문입니다.

const one = parseInt('123'); // 123
const two = parseInt(''); // NaN
const three = parseInt('123a'); // 123
const four = parseInt(' 12a3px'); // 12
const five = parseInt(' 12 3'); // 12

사용자가 입력된 값에 숫자를 덧붙여서 입력할 수도 있지만, 기존 값을 지울 경우에는 빈 문자열로 평가됩니다. 이때, 화면에 NaN 값이 표시되면 안되겠죠?
따라서, 빈 문자열이 0 으로 평가되는 Number 메서드를 이용해 문자열을 숫자형으로 변경합니다.

const [value, setValue = React.useState<number>(1);

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const value = Number(e.target.value);
  setValue(value);
}

하지만, 위의 방법만으로는 문제가 해결되지 않습니다.

기존의 값을 모두 지운 경우에는 0 으로 표시되도록 했지만, 숫자가 아닌 문자열을 입력하면 NaN 으로 표기되기 때문인데요.

NaN 값을 제어하기 위해 관련 메서드에 대해 알아보겠습니다.

isNaN(value)

isNaN 메서드는 매개변수로 받은 값을 Number 타입으로 변경하는 과정을 거치는데, 그 과정에서 01 로 변경되는 것들이 있기 때문에 완전히 NaN 값을 판별하지 못합니다.

더 엄격하게 NaN 값을 검사하려면, Number.isNaN 메서드를 사용합니다.

이 메서드의 경우, 인자로 들어온 값을 Number 타입으로 변경하지 않고 값을 평가합니다.

타입스크립트에서 이를 사용할 때, 매개변수에 지정된 타입을 확인해보면 그 차이를 확인할 수 있습니다.

isNaN 의 매개변수 타입은 number 타입으로 지정되어 있기 때문에, 매개변수 값이 숫자가 아니라면 isNaN(Number(value)) 와 같이 사용해야 합니다.

이와 달리, Number.isNaN 의 매개변수 타입은 unknown 으로 지정되어 있기 때문에, Number 타입을 전달하지 않아도 오류가 발생하지 않습니다.

isNaN(''); // 0 => false
isNaN('hello'); // NaN => true
isNaN(false); // 0 => false
isNaN(true); // 1 => false
isNaN([]); // 0 => false
isNaN({}); isNaN(undefined); // NaN => true

앞서, Number 메서드는 인자로 들어온 값을 숫자형으로 바꾸는 과정에서, 문자열이 1개라도 포함되어 있다면, NaN 값을 리턴한다고 했습니다. 이때, isNaN 메서드를 사용하면 숫자가 아닌 문자열을 입력했을 때 그 값을 걸러낼 수 있습니다.

const [value, setValue = React.useState<number>(1);

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const value = Number(e.target.value);
  if (isNaN(value)) return;
  setValue(value);
}

이제 숫자와 빈 문자열을 제외한 다른 문자열 입력이 되지 않도록 했으니, setValue 를 이용해 input 의 값을 설정해주면 숫자만 입력이 가능해집니다.

사용자의 잘못된 입력값 조정하기

여기서부터는 필요한 과정일 수도 있고 아닐 수도 있습니다.
보통 사용자로부터 숫자를 입력받는 경우에는, +, - 버튼과 함께 수량을 입력 받는 쇼핑몰 사이트에서 많이 볼 수 있습니다.

이때, 수량이 0 으로 입력됐다면 이 상태로 장바구니에 추가하거나, 주문할 수 없기 때문에 조정 과정이 필요합니다.

그런데 이를 change 이벤트에서 조정하면 어떤 일이 발생할까요?

const onChange = (e: React.ChanveEvent<HTMLInputElement>) => {
  const value = Number(e.target.value);
  if (Number.isNaN(value)) return;
  if (value === 0) return;
  setValue(value);
}

바로 backspace, delete 키와 같은 기존 입력값을 지우기 위해 사용되는 키의 입력이 허용되지 않는 문제가 발생합니다.

backspace 키가 입력될 때 value 값이 0으로 평가되어 value === 0 구문에서 빠져 나오므로 더이상 코드의 4번째 줄인 setValue 가 실행되지 않아서 새로운 값으로 값을 변경할 수 없는 것이죠.

따라서, 기존에 '3'이 입력된 값에 '1'을 추가하여 13 혹은 31을 만들 수는 있어도, '3'을 따로 지울 수 없는 문제가 발생합니다.

이때, blur 이벤트를 사용하여 이 문제를 해결할 수 있습니다.

blur Event

blur 이벤트는 사용자의 입력이 완료되고 input 엘리먼트에 대한 포커스가 해제됐을 때 발생합니다. 따라서, 사용자가 0 을 입력하더라도 입력값을 초기화할 수 있습니다.

const onBlur = (e: React.FocusEvent<HTMLInputElement>) => {
  if (e.target.value === '0') {
    setValue(1); // 초기화할 수량 입력
  }
}

1개의 댓글

comment-user-thumbnail
2023년 10월 27일

정리 주셔서 감사합니다~!

답글 달기