

저번 시간에 이어서 남은 과제를 수행하겠다.
1), 4), 6)은 했고 이제 input창 관리인 2), 3), 5) 남았다
왼쪽 스크린샷은 최초 렌더링됐을 때의 화면이다.
1) 저 인물 리스트들을 객체를 요소로 하는 배열로 받은 후, 하드 코딩이 아니라 이 배열을 이용해서 여러 리스트가 나오도록 출력할 것이다
2) input 창도 두 개로 관리할 예정이다.

3) 왼쪽에 있는 스크린샷처럼 input에서 이름과 전공을 사용자로부터 입력을 받은 후 등록 버튼을 누르면 상단의 리스트에 이름과 전공이 추가되는 화면을 넣을 것이다. (현재 화면에선 '추민하'가 추가됨)
4) 인물 부분은 <b> 태그로 감싸서 Bold 처리를 하고, 전공은 소괄호로 묶어서 표현한다.
5) input 창은 보이는 것처럼 placeholder 처리를 해줄 것이다.

6) 마지막으로 모든 목록에는 remove 버튼을 넣을 건데 Remove 를 누르면 그 내용이 사라진다.(현재 화면에선 '안정원'이 사라짐)
2) 해결
input 창은 두 개로 관리된다. input 창은 그 의미부터 사용자로부터 입력을 받아 변하는 State다. 그렇기에 두 input창은 useState 를 통해서 관리해줄 것이다. 텍스트를 입력하는 input 창에서 사용자가 직관적으로 기대할 수 있는 것은 개인 정보가 아닌 이상 자신이 적는 그대로 input 창에서 나타나는 것이다. input 창에 사용자가 입력하는 행위는 onChange 로 받아주면 되고, onChange 가 됐을 때 input창의 상태를 변화시켜줄 함수를 실행시킬 것이다. 여기서, input 창의 컴포넌트는 PracticeInput.js 라는 이름으로 만들고 App.js 의 자식 컴포넌트로 만들어 준다. 하지만, 동적 상태 관리나 함수는 전 시간에 작성했던 것과 마찬가지로 App.js 에서 만들고 Props 로 넘겨주고자 한다. 일단, 간단하게 PracticeInput 컴포넌트를 만들면 밑에처럼 만들 수 있을 것이다.
// Practice.js 에서 작성
return (
<div>
<input
name = "userName"
placeholder = "이름"
value = ''
/>
<input
name = "position"
placeholder = "전공"
value = ''
/>
<button>등록</button>
</div>
)
// App.js 에서 작성
return (
<div>
<div className="yuljeInfo">
<Practice
yuljeFam={yuljeFam}
handleRemoveClick={handleRemoveClick}/>
</div>
<div className="addInfo">
<PracticeInput />
</div>
</div>
);
}
5) 해결
5) 는 HTML 에서 했던 것과 마찬가지로 <input/> 안에 placeholder 라는 속성을 넣어준 후 값을 지정해주면 쉽게 해결할 수 있다.
자, 다시 2) 번 해결로 넘어가자. input창이 두 개지만 각자 자기만의 역할이 있다. 나는 그것을 name 으로써 각 input 창이 어떤 정보를 담는 내용인지 적었다. 실제로 첫 번째 input에 작성한 내용은 우리가 <Practice /> 창에서 <b> 태그로 감싸준 이름이 될 것이고, 두 번째 input에 작성한 나용은 <span> 태그에서 소괄호로 묶어준 내용이 될 것이다.
<Practice /> 는 App.js return 문에 넣어주고 이제 이 컴포넌트에 넘겨줄 Props들을 작성해보자.
먼저, 두 input 창을 useState 로 관리를 해주자. 이때, 명심할 점은 input 창에서 동적으로 변하는 건 input의 value 값이다. name은 두 input을 구별하는 용도고, 실질적으로 useState를 통해서 값이 변하는 것은 input 태그의 value 값이다. 두 개의 input을 동시에 관리해주므로 객체를 통해 기본값을 지정해줄 예정이다.
// App.js 에서 작성 (물론 당연히 App() 함수 안에서)
const [inputs, setInputs] = useState ({
userName: '',
position: ''
})
const { userName, position } = inputs
// 사실 위의 두 방식은 밑에 있는 방식과 동일함
const [userName, setUserName] = useState('')
const [position, setPosition] = useState('')
// 그런데 굳이 객체로 사용하는 이유는 나중에 프로젝트가 복잡해질 때를 대비하기 위함.
// useState 100개를 만들어줄 바엔 useState 하나에 100개의 내용을 객체로 담아주면 됨
// 그래서 이전 시간에 useState 로 관리해준 배열도 여기에서 함께 해줄 수 있으나 너무 길어지고 관심사 분리를 위해 따로 작성
// PracticeInput.js 에서 작성
function PracticeInput ({userName, position}) {
return (
<div>
<input
name = "userName"
placeholder = "이름"
value = {userName}
/>
<input
name = "position"
placeholder = "전공"
value = {position}
/>
<button>등록</button>
</div>
)
}
// userName 과 position은 inputs로 구조분해 할당된 내용들을 props 각각 받아와서 input에 지정
// userName 과 position 의 기본값은 빈 문자열 '' 이고
// 이후 setInputs를 통해 값이 변하면 input 창의 내용도 그에 맞춰 변화
input 창은 두 개 있으니 inputs의 기본값들을 객체로 지정해주고, 구조 분해 할당을 통해서 각각의 내용들을 하나씩 가져왔다. 이제 객체의 value 값(현재 빈 문자열 '')들은 나중에 setInputs 를 통해 변화될 것이고 그 변화된 것은 input 태그의 value 값이 될 것이다. userName과 position 이라는 이미 input 태그에 name으로 사용한 이유가 있다. 일단, 저 두 이름을 사용함으로써 각기 어떤 input 창의 기본값인지를 설명해주고 있으며 그 input 창의 value의 기본 값을 빈 문자열로 쓰겠다는 의미를 가시적으로 내포하고 있는 것이다. 꼭 저 이름이 아니도록 작성할 수 있느나 저렇게 작성해야 유지보수도 편하고 불필요한 로직을 추가하지 않아도 된다. 정리하면 객체에서 각각의 key를 담당하고 있는 userName과 position은 input 태그의 name 값이고, 이후 setInputs 를 통해 변화되는 객체의 value 값은 input의 value 값인 것이다.
input 창에서 이제 input 창이 onChange 됐을 때 실행될 함수 handleChange 를 만들어보자. handleChange 함수가 해야하는 일을 수도 코드로 작성하면 다음과 같다.
1. 두 input 창 중 어떤 input 창인지 식별
2. 그 input 창의 name 과 value(input 창에 나타날 내용) 를 가져온다
3. 그 name과 value를 setInputs를 통해 value 값이 바뀐다.
input을 구별해주는 용도로 name 을 사용해 userName 과 position 이라는 이름을 지정해줬다. 이 내용은 이벤트 onChange 가 실행된 input 태그의 name, value 값이므로 event 를 인자로 받아와서 even.target 의 name 과 value 로 구분해주면 될 것 같다. (1과 2 해결) 마지막으로 현재 name 값(userName, position) 은 input 객체의 key값이다. 그러면 그 key 값의 value 를 event.target 으로 가져온 value 값의 내용으로 바꿔준다는 코드를 작성하면 3번도 해결될 것 같다.
// App.js 에서 작성
const handleChange = (event) => {
const {name, value} = event.target
setInputs({
...inputs,
[name]: value
// [name]에서 [] 은 객체의 bracket notation
// name 은 우리가 event.target 에서 가져온 값 (userName, position 등)
// 그 이름들을 inputs에서 key 값으로 우리가 지정해줬음
// 그리고 그 key에 해당하는 값을 event.target 의 value 값으로 바꿔준다는 의미다.
// event.target 은 여기서 input 태그라 생각하면 되고
// input 태그의 value 는 handleChange input 창에서 사용자로부터 입력받은 값이라고 생각하면 된다
// setInputs를 통해 원래 input창에 나타나는 값을 사용자로부터 입력받은 값으로 바꾸는 것
})
}
// App.js return 문 안
<PracticeInput
userName = {userName}
position = {position}
handleChange = {handleChange}
/>
event.target 의 속성에는 여러가지가 있으므로 두 번째 줄처럼 구조 분해할당으로 각각의 name과 value 값을 가져온다 setInputs 에서 spread 문법을 사용해 inputs 객체의 모든 내용을 가져온 다음, [name]: value 를 통해서 [name] (input 창의 name 값과 동일한 key) 의 value 를 event.target 에서 가져온 value 로 바꾼다. (객체에서 같은 key 가 존재하면 하나로 합쳐지는데 이때 value 값은 둘 다가 아닌 더 아래에 있는 key의 value 값으로 사용한다.)
3) 해결
이제 마지막으로 PracticeInput에서 작성한 <button> 태그를 눌러주면 list 가 화면에 뜨게 만들 것이다. 화면에 렌더링 되고 있는 건 HTML 태그들이지만, 그 HTML 태그들은 Practice에서 yuljeFam 배열에서 유래됐다. 즉, 우리가 작성한 inputs의 내용들이 yuljeFam 배열에 추가가 된다면, 그 내용도 Practice 에서 태그로 변환해 화면에 렌더링해줄 것이다. 그러면 이제 <button> 태그가 클릭 됐을 때의 함수를 구현해보자. 함수의 이름은 handleCreateClick이다.
우리가 배열에 객체 요소를 넣는다고 할 때 사전에 있던 객체 요소들이 동일한 형식을 띄고 있다면 추가할 객체 요소 또한 동일한 형식을 띄고 있어야 한다.
{
id: 1,
userName: '이익준',
position: 'GS'
}
yuljeFam 배열 안에 있는 객체의 형태를 보면 Id 값, userName, position key 값으로 주어진다. 우리는 input 창 두 개를 통해서 사용자로부터 입력 받는 값을 userName과 position 이라 지정해주었다. 그러면 배열을 추가할 때 inputs 로 관리해주고 있는 userName과 position 을 객체의 value 값으로 넣어주면 될 것 같다. 그러나, 문제는 id 값이다. id는 저번 시간에도 봤듯이 굉장히 중요하게 사용하고 있다. 이것을 해결하기 위해서 여러 방법이 있지만 나는 그 중에서 React 의 Hook 중 하나인 useRef 를 사용하고자 한다. useRef는 어떤 값의 변화로 인해서 컴포넌트가 리렌더링된다 하더라도 기억하고 유지하고 싶은 값을 만들 때 사용한다. 컴포넌트들은 서로 연관이 돼있다. 우리가 만드는 작업물을 예시로 들면, PracticeInput 컴포넌트에서 등록 버튼을 누르면 App 컴포넌트에 있는 useState로 관리해주는 배열 내용이 변하게 되고 그 값은 Practice 컴포넌트로 전달되어 결과적으로 렌더링되는 내용이 추가가 된다. 이렇듯 한 컴포넌트에서 변화를 주면 다른 컴포넌트에서도 변화가 생기고 리렌더링될 수 있다. 이때, 다른 컴포넌트에 의해 혹은 자기 자신으로 인해 리렌더링된다 하더라도 컴포넌트가 이전에 있던 값을 그대로 기억하게 하고 싶은 데이터가 있을 수 있다. 지금의 경우라면 id 값이 그렇다. 그 때 사용해주는 Hook 이 useRef고 useRef를 통해서 그 값을 기억시켜 줄 거다.
// App.js 에서 작성
// 최상단 import 과정
import React, {useState, useRef} from 'react';
// App 함수 내부
const nextId = useRef(6)
// 배열 다음에 추가할 id 값이라는 의미에서 nextId 라 임의로 변수 지정
// 리렌더링되더라도 6이란 값은 계속 기억할 것
const handleCreateClick = () => {
const addYulje = {
id: nextId.current,
// useRef로 관리하는 변수에 .current 사용하면 지금 현재의 값을 의미함
userName, // userName: userName 의 의미다
position // position: position 의 의미다
}
setYuljeFam([...yuljeFam, addYulje]) // 혹은 yuljeFam.concat(addYulje)
setInputs({
userName: '',
position: ''
})
nextId.current++
// 6을 사용했으니 숫자 1을 더해서 다음 배열에 올 숫자는 6보다 1큰 수를 넣어줄 것
// 그 수는 useRef를 통해 다시 관리될 거고 리렌더링돼도 그 값은 기억
}
주석은 useRef 와 관련된 내용을 자세하게 적었다. 그러면 이제 다른 내용들을 설명해보겠다. addYulje는 배열에 추가해줄 객체 내용이다. id 값 잘 지정해주고, userName과 position 을 추가한다. 우리는 배열에 addYulje 내용을 추가할 것이다. 이때, push를 쓰면 에러가 난다. 대신에 위 방법 처럼 spread 문법을 사용해줄 수도 있고 concat을 사용해줄 수도 있다. 이후, 놓치면 안 되는 게 Inputs를 초기 상태로 되돌리는 것이다. userName과 position을 바꿔주지 않으면 등록돼도 input 창에 있는 값이 사라지지 않는다.
자 이제 구현은 다 했고 이걸 PracticeInput 에 props로 넣어주고, button에 넣어주자.
// App.js return 문 안
<div className="addInfo">
<PracticeInput
userName = {userName}
position = {position}
handleChange = {handleChange}
handleCreateClick = {handleCreateClick}
/>
</div>
// Practice.js return 문 안
<button onClick={handleCreateClick}>등록</button>
이것으로 모든 기능을 구현했다. 코드 최종본은 다음과 같다
지금은 App.js



다음은 Practice.js

다음은 PracticeInput.js

이것으로 모두 끝났다...
진짜 이렇게 길게 작성할 줄은 생각도 못했다.
당분간은 React 말고 JS 에 좀더 신경쓰면서 블로깅을 할 예정이다.
그리고 이제 이 강의를 참조한 블로깅은 당분간 하지 않을 예정이다. 이미 패캠에서도 강의 판매 목록에 강의가 사라졌고, 지금 최신 문법과는 거리가 먼 문법들 소개가 좀 있어서, 최신에 업데이트된 강의를 위주로 블로깅을 할 예정이다. 그래도 벨로퍼트님이 너무 강의를 잘해주셔서 지금도 사용하고 있는 문법들은 가끔씩 올릴 예정이다.
'패캠 인강 > React' 카테고리의 다른 글
React 기초 다지기 (2) - 리액트의 리렌더링(SPA), VirtualDOM (0) | 2022.01.12 |
---|---|
React 기초 다지기 (1) - JSX 기초 (0) | 2022.01.06 |
[패캠 인강] React 기초 (4) (0) | 2021.08.23 |
[패캠 인강] React 기초 (3) (0) | 2021.08.11 |
[패캠 인강] React 기초 (2) (0) | 2021.08.07 |