Udemy - React완벽가이드
양식 입력 추가하기
Hanachoi
2023. 2. 19. 10:11
- 지금 현재 만들고 있는 비용추적기에서 또 한 가지 중요한 것은 사용자가 비용을 직접 입력할 수 있게 해주는 것이다.
- 이를 만들기 위해 또 하나의 컴포넌트를 추가한다.
- 유저 인풋을 가져오기 위한 NewExpense 컴포넌트를 만든다. 그 안에는 사용자들이 그들의 비용 데이터를 입력할 수 있는 입력창을 렌더링 한다.
- 이 컴포넌트의 목표는 결국 폼을 반환하는 것인데, 이건 입력값을 위한 폼이다.
- 그 폼에 스타일을 적용하려고 <div>를 만들어 준뒤 거기에 className을 넣어준다.

- 스타일링을 위해서 NewExpense.css 파일을 만들고 스타일링을 해준다. 또한 NewExpense.js 파일로 넘어가 css파일을 import 해준다.
- <form></form>부분을 별도의 컴포넌트로 다시 분리해준다. 폼 로직이 그 컴포넌트 안에만 있도록 하기 위해서다. ExpenseForm.js 파일을 만들어 폼 형식을 다른 컴포넌트로 분리해주자.
- ExpenseForm.js 컴포넌트 안에서는 사용자가 날짜설정 / 타이틀 설정 / 금액설정 이 세 가지가 가능할 수 있도록 기능을 만들어 준다.

- 폼을 형성했으면 이 폼을 넘겨줄 버튼도 필요하다. 버튼을 또 밑에 만들어 준다.
- 그 다음 다시 NewExpense.js로 넘어가 <ExepnseForm /> 컴포넌트를 넣어준다.

- 그리고 다시 App.js에서 이 모든 컴포넌트들을 랜더링 할 수 있도록 연결해줘야 한다.
- App.js 에서 NewExpense.js 를 import 해준다.

- 그리고 return 부분에 컴포넌트를 넣어준다

- 그럼 페이지 상단 부분에 우리가 만든 컴포넌트가 랜더링 되는걸 볼 수 있다.

#사용자 입력 리스닝 (listening)
- 이제 유저 인풋을 모아보자.
- 입력에서 발샐하는 모든 변화들을 받아와서 마치 콘솔에 찍는것처럼 값을 저장해보자.
- 이렇게 만들기 위해서는 리스너를 추가해줘야 한다. on으로 시작하는 props를 넣어주자.
- onInput과 onChange는 거의 비슷하게 작동한다. 여기서는 onChange를 사용하자
* onInput 과 onChange 차이
- onInput은 사용자가 데이터를 입력할때마다 바로 데이터 확인 가능
- onChange는 input태그에 데이터를 입력하고 다른곳을 클릭하면 작동, 모든 입력타입에 같은 이벤트를 사용 가능함. 예를 들면, 드롭다운 메뉴도 사용가능하다.
- onChange 부분에는 역시 포인터역할처럼 함수의 이름만 넣어주고, 함수 정의는 return 앞 부분에 넣어준다. 이게 훨씬 깔끔한 관리법.
- titleChangeHandler를 만들어서 console.log를 찍어보면, input박스에 값이 들어올때마다 console에 값이 찍히는걸 확인할 수 있다.


- 그렇다면 사용자가 입력한 값은 어떻게 얻어올 수 있을까?
- 바닐라 자바스크립트에서 우리는 어떤 요소에 이벤트 리스너를 추가하고자 한다면 addEventListener를 사용하게 되는데, 두번째 인자로 값을 전달하는 함수를 사용할 수 있다.
- 이 두번째 함수에서는 자동적으로 그 이벤트를 설명하는 이벤트 객체를 얻을 수 있다. 이 이벤트 객체는 기본적인 자바스크립트 동작으로 이벤트를 수신할 때 브라우저에서 벌어지는 일이다.
document.getElementById('').addEventListener('click', ()=>{}) // 두번째는 값 전달하는 함수
- 이벤트를 onChange props를 통해서 리액트에 전달하기 때문에 리액트 혹은 브라우저에서 변경 이벤트가 발생했을 때 이벤트 객체를 얻을 수 있도록 해주는 것이다.
- console.log(event)를 찍어보면 이 객체안에 무엇이 있는지를 볼 수 있다.

- 이벤트 객체안에 target이라는 부분을 살펴보면 그 안에 input으로 처리되어있고 그 안에 있는 value값도 확인해 볼 수 있다.

- 이 value는 이벤트가 벌어졌을 시점의 현재 입력값을 가진다.
- 이 부분을 사용하면 우리가 수신하는 요소의 값을 가져올 수 있게 된다.
- titleChangeHandler함수에 매개변수로 event를 넣어주고 event.target.value를 해주면 사용자가 입력하는값을 그대로 얻어올 수 있다.


#여러 state 다루기
- 그럼 이걸로 무엇을 하고 싶은 것일까? 우리는 값을 받아서 나중에 폼이 넘겨졌을 때 그 값을 사용할 수 있도록 하고 싶다. 폼이 제출 되었을 때 모든 값을 객체와 결합해 사용하고 싶다.
- 함수가 실행되고 컴포넌트가 재 실행되는 상황에서도 값을 저장해서 살리고 싶다면 useState를 다시 사용할 수 있다.
- useState를 사용하면서 처음에는 아무 값도입력되지 않은 상황이니 빈문자열을 넣어준다. 그리고 배열디스트럭처링을 사용해 useState를 작성해준다.

- 그리고 titleChangeHandler함수에서 이벤트에 반응할때 setEnteredTitle을 호출해서 event.target.value를 전달해줄 수 있다. 이렇게 되면 우리의 상태로 저장이 된다.

- 하지만 여기서 이 컴포넌트를 업데이트하기 위해서 작업을 한 것은 아니다. 물론 여기까지의 작업으로 업데이트는 진행된다.
- 이 컴포넌트의 life cycle과 별개인 변수에 저장한다는 점이 중요하다. 컴포넌트의 함수가 얼마나 자주 다시 실행되는지에 상관없이 이 상태는 저장되고 살아 있다.
- 이렇게 title의 상태를 업데이트 해주는 useState를 사용해봤다. Amount와 Date도 동일한 방법으로 변화를 줄 수 있다.
- 각각 useState를 사용해주고 함수를 만들어 준 뒤 , onChange로 포인터를 넣어주면 된다

- 하나 이상의 상태를 다루고 싶으면 useState를 여러번 사용할 수 있다.
- useState('')를 보면 초기상태를 문자열로 넣어줬따. 기본적으로 입력에 대한 변경 이벤트를 수신할때 마다 입력 요소값을 읽는다면 그건 항상 문자열이기 때문이다. 숫자를 저장해도 문자로 들어온다.
- 이처럼 컴포넌트들은 각각의 state를 가지고 별개로 관리될 수 있다.
# state 대신 사용하기(그리고 더 나은 방법)
- 위에서 우리는 3개의 다른 state를 각각의 useState를 만들어서 관리했다.
- 하지만 결론적으로 세개의 기능은 모두 동일하다. 따로 관리를 해도 되지만 세 가지를 모두 합칠 수도 있다.
- 합치는 방법은 useState()에 인자로 {} 객체를 넣어주는 것이다. 객체를 만들어 각각의 초기값을 한꺼번에 넣어준다.
- []안에 들어가는 값들은 통합된 [userInput, setUserInput]으로 만들어 준다.

- 그리고 아래에 만든 함수에 가서도 업데이트를 해준다. 이때 주의해야 할 것은 이 때도 setUserInput안에 객체로 값이 들어가줘야 한다는 것이다.
- 하나의 객체로 관리가 된다면 모든 데이터가 사라지지 않도록 해줘야 한다.
- 이때 스프레드 연산자를 사용해서 나머지 값들을 가져와주고 메인이 되는걸 오버라이드해준다.

- 각각 따로 정리하거나 이렇게 하나로 통합하거나 모두 괜찮은 방식이다. 선호도의 차이일 뿐이다.
# 이전 state에 의존하는 state 업데이트

- 위에서는 이런 식으로 업데이트를 진행했었는데, 기술적으로는 문제가 없이 작동했지만 이런식으로 업데이트하는 건 좋지 못하다. 몇몇의 케이스에서는 실패할 수 있기 때문이다.
- 문제는 무엇일까? 위의 케이스는 이전 state에 따라 상태를 업데이트하고 있다. 세개의 상태로 접근하는것이 아니라 하나로 접근하는 방식을 사용하고 있기 때문이다. 그래서 우리는 다른 값도 복사해서 잃어버리지 않도록 주의해야 한다.
- 값을 잃어버리지 않도록 이전 state의 스냅샷에 의존하면서 그런 다음 새로운 값으로 오버라이드 한다 (enteredDate를 다른 값으로 넣어주는것. 여기서는 빈문자열에서 event.target.value로 넣어줬다 )
- state를 업데이트 할때마다 기억해야 하는 중요한 규칙은 상태를 업데이트하는 대체폼을 사용해줘야 한다는 것이다.
- 이렇게 객체를 사용하지 않고 setUserInput안에 또다른 함수를 넣어줘야 한다.
- 이 함수는 리액트에 의해서 실행될 것이다.
- 업데이트할 함수의 state를 위해서 이전 state를 받아와야 하는데, 그걸 prevState로 받아준다.

- 그리고 return 부분에서는 다시 객체로 이전 state들을 불러오고 메인이 되는 애를 오버라이드 해준다. 여기서는 prevState가 바로 위에서 useState 부분에 넣은 객체가 될 것이다.
- 대부분의 경우에는 두 가지 방식이 모두 괜찮다. 하지만 리액트가 상태 업데이트 스케줄을 가지고 있어서 바로 실행하지 않는다는 점을 기억해야 한다.
- 위의 방식을 사용하게 되면 이론적으로 많은 수의 state 업데이트가 일어난다고 할 때, 오래되었거나 잘못된 스냅샷에 의존하게 될 수도 있다.
- 하지만 이렇게 함수를 넣어주는 두번째 방식을 사용하게 되면 이 함수안에 있는 스냅샷(prevState)r가 항상 최신상태의 스냅샷이고 항상 계획된상태의 업데이트를 염두에두고 있다는 것을 보장할 것이다.
- 그래서 만약 상태업데이트가 기존의 state에 의존해서 업데이트 된다면 이렇게 함수형을 쓰자!!