패캠 인강/React

[패캠 인강] React 기초 (4)

Je-chan 2021. 8. 23. 23:13

 

 

오늘 할 것을 설명하자면 옆의 사진들과 같다.

CSS 는 편의상 margin 값만 추가했다.

 

왼쪽 스크린샷은 최초 렌더링됐을 때의 화면이다.

 

1) 저 인물 리스트들을 객체를 요소로 하는 배열로 받은 후, 하드 코딩이 아니라 이 배열을 이용해서 여러 리스트가 나오도록 출력할 것이다

 

2) input 창도 두 개로 관리할 예정이다. 

3) 왼쪽에 있는 스크린샷처럼 input에서 이름과 전공을 사용자로부터 입력을 받은 후 등록 버튼을 누르면 상단의 리스트에 이름과 전공이 추가되는 화면을 넣을 것이다. (현재 화면에선 '추민하'가 추가됨)

 

4) 인물 부분은 <b> 태그로 감싸서 Bold 처리를 하고, 전공은 소괄호로 묶어서 표현한다.

 

5) input 창은 보이는 것처럼 placeholder 처리를 해줄 것이다.

 

6) 마지막으로 모든 목록에는 remove 버튼을 넣을 건데 Remove 를 누르면 그 내용이 사라진다.(현재 화면에선 '안정원'이 사라짐)

 

 

 

 

 

 

 

그럼 하나 하나 천천히 시작해보자

 

1) 해결

1) 을 해결하기 위해선 일단 인물의 정보를 담은 객체를 배열로 정렬해줘야 한다. 이걸 여러 태그로 표현하는 방법은 여러 가지가 있겠지만, 나는 각 컴포넌트는 한 가지의 기능을 한다는 리액트의 원칙을 받아들여서 저 태그를 만드는 Practice 컴포넌트를 따로 만들어 생성하고, 배열은 App에서 만들어 props로 Practice로 넘기로 한다.

 

먼저 배열을 만들자. 배열을 만들 때 주의할 점은 이 배열은 언제든지 바뀔 수 있다는 점이다. 현재 '등록' 버튼과 'Remove' 버튼을 누르면 리스트가 추가가 되고 제거가 됐다. 방금 위에서 언급했지만, 우리는 리스트를 만들 때 배열을 활용해서 만들 예정이다. 그러면 리스트가 추가되고 삭제 되는 것은 배열 안에 요소가 추가되고, 삭제된다는 것을 의미한다. 즉, 컴포넌트에 의해서 변화되는 상태 값, State 값이다. 이 State 는 동적으로 변화하기에 useState 를 통해서 State 관리를 해주기로 한다. 그러면 일단 첫 렌더링 했을 때 나오는 값들은 기본 값이 되므로 useState 의 기본값으로 넣어버린다.

 

// App.js 의 function App 함수 안에 작성

const [yuljeFam, setYuljeFam] = useState([
    {
      id: 1,
      userName: '이익준',
      position: 'GS'
    },
    {
      id: 2,
      userName: '채송화',
      position: 'NS'    
    },{
      id: 3,
      userName: '김준완',
      position: 'CS'
    },
    {
      id: 4,
      userName: '안정원',
      position: 'PDS'    
    },{
      id: 5,
      userName: '양석형',
      position: 'OB&GY'
    },
  ])

 

보면, id 값이 왜 들어갔지 할텐데, 지금 설명하긴 어렵고 조금 있다가 짚고 넘어가겠다. 일단, 이렇게 생성했으면 이제 Practice 컴포넌트에 어떻게 Props 로 전달해줄지 생각해보자. 아예 저 배열을 넘겨서 그걸로 조작할 수 있고, App에서 Props를 넘겨줄 때 아예 배열 안의 요소 하나씩만을 넘길 수 있다. 지금은 전자의 방법을 사용하겠다. props 는 yulje 라는 이름으로 넘겨주겠다.

 

Practice로 건너가서 function 을 하나 만들고 리턴으로 태그들을 만들자. 주의할 점은 태그를 만들 때 하드 코딩으로 작성하지 않겠다고 한 점이다. 이를 위해선 배열의 각각의 요소들을 태그로 변환해야 한다. 배열 각각의 요소를 변형시키는 방법을 우리는 map 을 통해서 할 수 있다. 그러면 다음과 같은 코드가 만들어진다.

 

// Practice.js 에서 작성
// 참고로 {/* */} 는 리액트에서 주석처리할 때 사용되는 것.

function Practice ({yuljeFam}) {
 return (
    <div>
      {yuljeFam.map(yulje => { {/* yuljeFam 안에 있는 요소인 객체 하나 하나를 return 문의 내용으로 바꾼다*/}
        return (
          <div>
            <b>{yulje.userName}</b> <span>({yulje.position})</span>
            {/* 배열의 요소인 객체에서 key가 userName인 것의 value를 <b> 태그 안에 넣는다 */}
            {/* 배열의 요소인 객체에서 key가 position인 것의 value를 <span> 태그 안에 넣는다 */}
            <button>Remove</button>
          </div>
        )
      } ) 
      }
    </div>
  )
 }

 

자연스럽게 4)의 내용을 수행했다. <b> 태그 안에는 이름을 넣어주었고, <span> 태그 안에는 소괄호로 묶어서 그 전공을 넣어주었다.

 

그런데 저 위의 코드도 너~무 복잡하다 싶으면 다른 방법이 하나 있다. 저 <div> 안에 있는 내용들을 따로 컴포넌트처럼 작성해서 넣는 것이다. 하나의 js 파일에는 한 개 이상의 컴포넌트가 들어가도 된다. 즉, 컴포넌트를 두개 만들 수 있단 얘기다. 그러면 저 <div> 태그 안의 내용을 따로 컴포넌트로 만는데 이 Practice.js 파일에 두 개의 컴포넌트가 들어가게 작성하겠다. 그때의 컴포넌트 이름은 임의상 Yulje 로 한다.

 

function Yulje ({yulje}) {
    
  return (
    <div>
      <b>{yulje.userName}</b> <span>({yulje.position})</span>
      <button>Remove</button>
    </div>
  )
  
}


function Practice ({yuljeFam}) {

  return (
    <div>
      {yuljeFam.map(yulje => <Yulje yulje={yulje} key={yulje.id}/>)}
    </div>
  )
  
}

 

자 이렇게 만들면 조금 더 가독성이 좋아진다. Practice 라는 컴포넌트는 yuljeFam 이라는 props를 받고 map으로 그 props의 내용 하나 하나(배열의 요소인 객체 하나씩)를 Yulje라는 컴포넌트에 props 로 전달해주는 역할을 하게 된다. 실질적으로 받은 객체를 태그를 만드는 컴포넌트는 Yulje 컴포넌트가 된다. 

 

자 여기에서 한 가지 주의할 점은 key 값을 내가 map 을 사용했을 때 전달해줬다는 점이다. key를 넣어주지 않아도 렌더링은 잘 되지만 콘솔창을 확인해보면 빨간 오류가 나있을 것이다. key에 고유한 값 id를 지정해준 이유는 나중에 추가, 삭제할 때 불필요한 연산을 방지하기 위해서다. 다음 예시를 통해서 만약 key 값을 안 주었을 때 배열에 있는 요소를 빼고 추가하는 과정이 얼마나 비효율적인지 확인해보자

 

// 배열에 요소 f를 b와 c사이에 넣고 c를 빼는 상황
// 정말 간략하게 보여주기 위해서 단순히 a~e를 넣었을 뿐 현 상황에선 map으로 만들어진 <Yulje /> 컴포넌트입니다.

// id 값이 없는 경우
[a, b, c, d, e] => [a, b, f, d] => [a, b, f, c] => [a, b, f, c, d, e]
=> [a, b, f, d, d, e] => [a, b, f, d, e, e] => [a, b, f, d, e]

// 굉장히 복잡하다


// 반면 id 값이 있는 경우
// 최대한 간략하게 보여주기 위해서 숫자뒤 괄호를 지정해준 key 값으로 가정합니다.

// id 값이 있으면 다음과 같이 명령하면 된다 => id 2와 3사이에 6을 넣어라
[ a(1), b(2), c(3), d(4), e(5)] 
=> [ a(1), b(2), f(6), c(3), d(4), e(5) ]
// id 3 제거
=> [ a(1), b(2), f(6), d(4), e(5) ]

// 즉 id 값이 있을 때 우리가 직관적으로 받아들이는대로 컴퓨터가 작동하게 된다.

 

6) 해결

순서는 바뀌지만 Practice 내부에서 수정할 수 있는 작업인 6) 을 진행하겠다. 6)은 우리가 만들어준 <button>Remove</button> 에 해당한다. 이 버튼을 눌렀을 때 삭제하는 함수를 만들 거다. 일단 버튼을 누른다는 건 마우스로 클릭한다는 말이므로 onClick 을 사용할 거다. 그러면 이 onClick을 눌렀을 때의 함수는 어디에 선언해줘야 할까? 그냥 보기에 <button> 태그가 있는 Yulje 에서 해야한다고 받아들일 수 있겠지만, 배열의 기원이자 배열을 useState 로 관리하고 있는 App.js 에서 선언할 것이다. 그 선언한 함수는 똑같이 props 를 통해서 저 Yulje 에 넣어줄 것이다. 그러면 App.js 에서 onClick 했을 때 실행되는 함수를 만들어보자. 일단, 우리는 바로 위에서 배열의 요소를 삭제할 때 id 값을 활용하면 좋다는 것을 확인했다. 이걸 활용해보도록 하겠다. 단, Yulje 컴포넌트에 사용한 key값을 props 처럼 활용해서 button에 값을 주지 않을 것이다. key는 그 자체로 고유해야 하기에 props로 사용해서는 안 된다.

 

// App.js 에서

return (
  <div className="yuljeInfo">
    <Practice 
      yuljeFam={yuljeFam}
      handleRemoveClick={handleRemoveClick}
    />
  </div>
)


// Practice.js 에서


function Yulje ({yulje, handleRemoveClickYulje}) {
  
  return (
    <div>
      <b>{yulje.userName}</b> <span>({yulje.position})</span>
      <button
        onClick = {() => handleRemoveClickYulje(yulje.id)}
      > 
     	Remove
      </button>
    </div>
  )
}

function Practice ({yuljeFam, handleRemoveClick}) {

  return (
    <div>
      {yuljeFam.map(yulje => 
      	<Yulje 
          yulje={yulje} 
          handleRemoveClickYulje={handleRemoveClick} 
          key={yulje.id}
        />)
      }
    </div>
  )
}

 

하나하나 따지고 들어가면 아직 작성은 하지 않았으나 onClick이 실행되면 App.js에서 실행할 함수의 이름은 handleRemoveClick 으로 지정해주었고 그 이름 그대로 Practice 에 Props 로 넘겨주었다. 여기서 Practice는 그 함수를 handleRemoveClickYulje 라는 이름으로 Yulje 컴포넌트에 Props 로 넘겨준 것이다. 물론 이때 props 로 넘겨줄 때의 이름은 임의적으로 지정할 수 있다. 그러니까 App.js 에서는 random={handleRemoveClick}  으로 props를 넘겨주고 Pracitce.js 에서는 normal={random} 으로 Yulje 에 props 를 넘겨줄 수 있다. 저렇게 넘긴다 한들 추적해 나가면 본질은 App.js 에서 선언한 handleRemoveClick 이라는 함수가 된다. 단, 가독성과 유지보수 측면에서 이름을 동일하게 해주는 것이 좋다. 그러니 지금은 이 개념을 확인해보기 위해 handleRemoveClickYulje 라고 이름을 지정해서 Yulje 컴포넌트에 props로 넘겨줬지만, 넘겨받은 props 이름과 똑같이 지정해주는 것이 더욱 좋을 것이다. (최종 코드에는 그렇게 작성할 예정) 

 

다시 본론으로 돌아가면 button에 onClick 이벤트가 발생하면 handleRemoveClick 함수는 인자로 Yulje 가 Practice로부터 Props로 넘겨받은 객체의 id 값을 갖는다. 우리는 배열의 각 객체 요소를 화면에 렌더링되는 태그로 바꿨다. 그렇다면 화면에서 렌더링되는 태그를 삭제한다는 건 그 태그가 참조하는 객체  요소를 배열에서 제거한다는 것과 같은 말이다. 그러면 우리는 그 배열에서 제거되는 요소를 어떻게하면 없앨 수 있을까? 우리는 현재 click을 통해 함수의 인자로 태그가 참조하는 객체 요소의 id 값을 가지고 왔다. 그러면 그 id 값을 가지는 객체의 요소만을 제거하면 된다. 우리는 id 값은 암묵적인 동의 하에 고유하게 설정해준 값이므로(암묵적 동의라 강제성은 없으나 사회적 약속으로 강제적이긴 함) 그 값에 해당하는 요소는 오로지 하나밖에 없을 것이다. 그러면 배열의 메소드인 filter를 통해서 그 id 값과 동일한 id 값을 지닌 객체를 제거해주면 될 것 같다. 그렇게 하면

 

 const handleRemoveClick = (id) => {
   setYuljeFam(yuljeFam.filter(rmYulje => rmYulje.id !== id))
 }

 

위의 코드를 해석하면 handleRemoveClick이 실행될 때 인자로 id 값을 받아온다. useState로 관리하고 있었던 yuljeFam이라는 이름의 배열은 setYuljeFam을 통해서 변화를 줄 건데, 그 요소(rmYulje) 가 만약에 인자로 받아온 id 값과 같지 않다면(같지 않은 게 true 값이라면) 그 요소는 남긴다. 이 말은 곧 id 값과 같다면 그 요소는 버린다는 의미다.

 

이렇게 해서 우리는 이제서야 1), 4), 6)을 끝낸 거다. 다음 시간에는 남은 과제들을 수행하겠다

'패캠 인강 > React' 카테고리의 다른 글

React 기초 다지기 (1) - JSX 기초  (0) 2022.01.06
[패캠 인강] React 기초 (5)  (0) 2021.08.27
[패캠 인강] React 기초 (3)  (0) 2021.08.11
[패캠 인강] React 기초 (2)  (0) 2021.08.07
[패캠 인강] React 기초 (1)  (0) 2021.08.03