공부하는 블로그

리액트와 뷰로 같은 앱을 만들어 보았다. 본문

자바스크립트

리액트와 뷰로 같은 앱을 만들어 보았다.

devtimothy 2018. 9. 12. 10:43

리액트와 뷰로 같은 앱을 만들어 보았다.

이 글의 원문은 여기이며 원작자가 번역을 허가하여 글을 번역했습니다.

번역을 다 하고 나니 이미 다른 한국어 번역이 있더군요. (링크)

그러나 번역한 것이 아까워서 포스팅을 합니다. (ㅠ_ㅠ)

저는 직장에서 Vue를 잘 사용해오고 있었습니다. 그러다가 저는 이 울타리의 다른쪽 뜰은 어떤지 궁금해졌습니다. 그 뜰은 바로 리액트입니다.

리액트 문서를 읽고 튜토리얼 동영상을 몇 편 보았습니다. 좋은 동영상이었지만 실제로 알고 싶은 것은 Vue와 리액트가 얼마나 다른지였습니다. "다르다"는 말은 가상 DOM을 가지고 있는지, 또는 렌더링 페이지를 어떻게 읽는지와 같은 것은 아닙니다. 누군가가 내게 시간을 들여가며 코드를 설명해주면서 무슨 일이 일어나는지 알려주면 좋겠다 싶었습니다. 이 Vue 또는 리액트 (또는 웹 개발 전체)에 익숙하지 않은 사람이 두 프레임워크의 차이점을 보다 잘 이해할 수 있도록 차이점을 설명하는 글을 찾고 싶었습니다.

그러나 이러한 부분을 다룬 글을 찾지는 못했습니다. 그래서 제가 유사점과 차이점을 비교하는 글을 써야겠다고 마음먹게 되었습니다.

나는 사용자가 목록에서 항목을 추가하고 삭제할 수 있게 해주는 평범한 To Do 앱을 만들기로 결정했습니다. 두 앱 모두 기본 CLI (리액트는 create-react-app, Vue는 vue-cli)를 사용하여 빌드되었습니다. (CLI는 Command Line Interface의 약자입니다. 🤓)

잡설이 길었네요. 먼저 두 앱의 모양을 간략히 살펴 보겠습니다.

Vue vs 리액트

두 앱의 CSS 코드는 완전히 동일하지만, 위치한 곳이 다릅니다. 이를 염두에 두고 다음 두 앱의 파일 구조를 살펴 보겠습니다.

뭐가 더 나을까요?

두 앱의 구조가 거의 동일하다는 것을 알 수 있습니다. 여기서 유일한 차이점은 리액트 앱에는 세 개의 CSS 파일이 있지만, Vue 앱에는 CSS 파일이 없다는 것입니다. 그 이유는 create-react-app에서 리액트 컴포넌트는 스타일을 유지하는 파일을 포함하지만 Vue CLI는 스타일을 실제 컴포넌트 파일 내에서 선언하는 포괄적인 접근 방식을 채택하기 때문입니다.

궁극적으로는 둘 다 똑같은 것을 구현합니다. 그리고 리액트 나 Vue에서 CSS를 다르게 구조화 할 수 없다고 말할 수도 없습니다. 이는 정말로 개인적인 취향에 달려 있습니다. 개발자 커뮤니티에서 CSS를 구성하는 방법에 대해 많은 토론을 듣게 될 것입니다. 지금은 두 CLI에서 모두 제안한 구조를 따릅니다.

하지만 더 자세히 살펴보기 전에 일반적인 Vue 및 리액트 컴포넌트의 모습을 간단히 살펴 보겠습니다.

왼쪽이 Vue, 오른쪽이 리액트입니다.

자, 좀더 자세히 들여다보죠.

데이터 변이(mutate)는 어떻게 하나요?

먼저 "데이터 변이(mutate)"란 무엇을 의미할까요? 조금 기술적인 용어 같죠? 기본적으로 우리가 저장한 데이터를 변경한다는 의미입니다. 그래서 사람 이름의 값(value)을 John에서 Mark로 바꾸고 싶다면 우리는 '데이터를 변이(mutate)시킬 것'입니다. 이것이 바로 리액트와 Vue의 큰 차이점입니다. Vue는 데이터를 자유롭게 업데이트 할 수 있는 data 오브젝트를 기본적으로 생성하지만 리액트는 state 오브젝트를 만듭니다. 리액트의 state 오브젝트는 업데이트를 수행하는 데 약간의 추가적인 작업이 더 필요로 합니다. 먼저 Vue의 data 오브젝트와 리액트의 state 오브젝트를 살펴 보겠습니다.

Vue의 데이터 오브젝트는 왼쪽. 리액트의 state 오브젝트는 오른쪽입니다.

우리는 양쪽 다 같은 데이터를 전달했음을 알 수 있습니다. 단순히 다르게 라벨링 되어 있을 뿐입니다. 초기 데이터를 컴포넌트로 전달하는 것은 매우 유사합니다. 그러나 앞서 언급했듯이 두 프레임워크에서 데이터를 변경하는 방식이 각각 다릅니다.

name : 'Sunil'이라는 데이터 엘리먼트가 있다고 가정해 보겠습니다.

Vue에서는 this.name을 호출하여 이를 참조합니다. this.name = ‘John’ 을 호출하여 업데이트 할수도 있습니다. 내 이름을 John으로 바꿀 것입니다. 나는 John이 호출된 것을 모르지만, 변이가 발생합니다.

리액트에서는 this.state.name을 호출하여 동일한 데이터를 참조합니다. 이제 핵심적인 차이는 this.state.name = 'John'으로 간단히 작성할 수 없다는 것입니다. 왜냐하면 리액트가 이러한 쉽게 발생할 수 있는 부주의한 변이를 방지하기 위해 제한을 두고 있기 때문입니다. 그래서 리액트에서는 this.setState ({name : 'John'}) 와 같이 작성합니다.

기본적으로 Vue에서와 동일한 작업을 수행하지만, 리액트는 데이터가 업데이트 될 때마다 기본적으로 setState의 자체 버전을 기본적으로 결합하기 때문에 추가 코드 작성이 필요합니다. 간단히 말해서 리액트는 setState와 그 내부의 업데이트 된 데이터를 요구하는 반면, Vue는 데이터 오브젝트 내부의 값을 업데이트하는 경우 이를 실행하리라 가정합니다. 그렇다면 리액트는 왜 이 문제에 신경을 써야하며 왜 setState가 필요한 것일까요? Revanth Kumar의 설명을 들어봅시다.

"리액트가 상태가 바뀔 때마다 리액트가 특정 라이프 사이클 훅 (예 : componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate)를 다시 실행하려고 하기 때문입니다. setState 함수를 호출하면 상태가 변경되었음을 알 수 있습니다. 당신이 직접적으로 상태를 변이시킨다면, 리액트는 변경 사항을 추적하고 라이프 사이클이 훅 등을 추적하기 위해 더 많은 작업을 해야 할 것입니다. 그래서 간단하게 만들기 위해 setState를 사용합니다."

<this.state를 직접 변경하지 않습니다.>

중요한 걸 알게 되었네요.

이제 변이를 알아봤으므로 To Do Apps에 새로운 항목을 추가하는 방법을 살펴봄으로써 좀 더 어려운 이야기에 빠져 들어 봅시다.

To Do Items을 새로 만드는 방법

리액트:

createNewToDoItem = () => {
   this.setState( ({ list, todo }) => ({
     list: [
         ...list,
      {
         todo
      }
    ],
     todo: ''
  })
);
};

리액트는 어떻게 했나요?

input 필드에는 value라는 속성이 있습니다. 이 값은 양방향 바인딩과 매우 유사한 무언가를 만들기 위해 몇 가지 함수를 함께 묶어 사용하여 자동으로 업데이트합니다.(양방향 바인딩에 대해 들어 보지 못했다면, Vue는 어떻게 했나요? 섹션에 대한 자세한 설명이 있습니다). onChange 이벤트 리스너input 필드에 추가함으로서 양방향 바인딩을 구현합니다. 무슨 일이 일어나고 있는지 볼 수 있도록 input 필드를 살펴 보겠습니다.

<input type="text" 
      value={this.state.todo}
      onChange={this.handleInput}/>

handleInput 함수는 입력 필드의 값이 변경 될 때마다 실행됩니다. state 오브젝트 안에 있는 todo를 입력 필드에 있는 항목으로 설정하여 업데이트합니다. 이 함수는 다음과 같습니다.

handleInput = e => {
 this.setState({
   todo: e.target.value
});
};

이제 사용자가 페이지의 + 버튼을 눌러 새 항목을 추가 할 때마다 createNewToDoItem 함수는 기본적으로 this.setState를 실행하고 함수에 전달합니다. 이 함수는 두 개의 매개 변수를 취합니다. 첫 번째는 상태 오브젝트의 전체 list 배열이고 두 번째는 todo입니다 (handleInput 함수에 의해 업데이트 됨). 그런 다음 이 함수는 이전에 전체 list가 포함 된 새 오브젝트를 반환한 다음 끝에 todo를 추가합니다. 전체 목록은 스프레드 연산자를 사용하여 추가됩니다 (이전에 본 적이없는 경우 구글링해서 찾아보세요, ES6 구문입니다).

마지막으로 todo를 빈 문자열로 설정하면 input 필드에서 value가 자동으로 업데이트됩니다.

Vue:

createNewToDoItem() {
   this.list.push(
      {
           'todo': this.todo
      }
  );
   this.todo = '';
}

Vue는 어떻게 하나요?

Vue에서 input 필드에는 v-model이라는 핸들이 있습니다. 이것은 양방향 바인딩으로 알려진 것을 할 수 있게 해줍니다. input 필드를 간단히 살펴 보겠습니다.

<input type="text" v-model="todo"/>

v-model은 필드의 입력을 toDoItem이라는 data 오브젝트에 있는 key와 연결합니다. 페이지가 로드되면 toDoItem에 빈 문자열이 설정됩니다. 만약 todo에 '여기에 텍스트를 추가하세요' 와 같은 값이 있다면 input 필드에서는 이미 해당 문자열을 로딩해 놓을 것입니다. 어찌됐건 입력 필드에 입력한 텍스트가 todo 값에 바인딩됩니다. 이는 실제로 양방향 바인딩입니다. (입력 필드는 데이터 오브젝트를 업데이트 할 수 있고, 데이터 오브젝트는 입력 필드를 업데이트 할 수 있습니다.)

따라서 이전의 createNewToDoItem() 코드 블록을 살펴보면, todo의 내용을 list 배열로 푸시 한 다음 todo를 빈 문자열로 업데이트한다는 것을 알 수 있습니다.

리스트에서는 어떻게 삭제하나요?

리액트:

deleteItem = indexToDelete => {
   this.setState(({ list }) => ({
     list: list.filter((toDo, index) => index !== indexToDelete)
  }));
};

리액트는 어떻게 하나요?

deleteItem 함수가 ToDo.js 안에 있고, ToDoItem.js<ToDoItem/> 에서 먼저 deleteItem() 함수를 prop으로 전달하여 참조 할 수 있었습니다.

<ToDoItem deleteItem={this.deleteItem.bind(this, key)}/>

먼저 함수를 전달하여 자식(TodoItem)이 액세스 할 수 있게 만듭니다. TodoItem을 보면 this를 바인딩하면서 key라는 매개변수를 함께 전달하는 것을 볼 수 있습니다. key는 TodoItem에서 삭제 버튼이 클릭될 때 어떤 데이터를 삭제하는지 구분하는 데 쓰입니다.

<div className=”ToDoItem-Delete” onClick={this.props.deleteItem}>-</div> 

상위 컴포넌트 내부에 있는 함수를 참조하기 위해 해야 할 일은 this.props.deleteItem을 참조하는 것이었습니다.

Vue:

onDeleteItem(todo){
 this.list = this.list.filter(item => item !== todo);
}

Vue에서는 어떻게 하나요?

Vue에서는 약간 다른 접근 방식이 필요합니다. 우리는 기본적으로 다음 세 가지를 수행해야합니다.

첫째로, 우리는 함수를 호출하기를 원합니다.

<div class=”ToDoItem-Delete” @click=”deleteItem(todo)”>-</div>

그런 다음 자식 컴포넌트 (이 경우 ToDoItem.vue) 내부의 메소드로 emit 함수를 만들어야 합니다. 함수는 다음과 같습니다.

deleteItem(todo) {
   this.$emit('delete', todo)
}

이와 함께 ToDo.vue 안에 ToDoItem.vue를 추가하면 함수를 실제로 참조하고 있음을 알 수 있습니다:

<ToDoItem v-for="todo in list" 
        :todo="todo"
        @delete="onDeleteItem" // <-- 여기 :)
        :key="todo.id" />

이것이 사용자 정의 이벤트 리스너입니다. emit이 'delete' 문자열로 트리거 되는 모든 상황을 리스닝합니다. 이 메시지가 감지되면 onDeleteItem 이라는 함수를 트리거합니다. 이 함수는 ToDoItem.vue 가 아닌 ToDo.vue 내부에 있습니다. 앞에서 설명한 것처럼 이 함수는 단순히 data 오브젝트 내부의 todo 배열을 필터링하여 클릭한 항목을 제거합니다.

또한 Vue 예제에서 @click 리스너 내부에 $emit 부분을 작성하는 방법도 있습니다.

 <div class=”ToDoItem-Delete” @click=”$emit(‘delete’, todo)”>-</div> 

이것은 3 단계에서 2 단계로 작업을 줄였습니다. 이것은 개인적인 취향에 코드가 달라집니다.

간단히 말해서, 리액트의 자식 컴포넌트는 this.props를 통해 부모 함수에 액세스 할 수 있습니다. (prop을 통해 하위로 전달하는 방식은 표준적인 방식으로 자리잡았으며 다른 리액트 예제를 살펴보아도 비슷한 경우를 많이 보실 수 있을 겁니다). Vue에서는 부모 컴포넌트 내부에서 자식 이벤트로 emit 해야 합니다.

어떻게 이벤트 리스너를 전달합니까?

리액트 :

클릭 이벤트와 같은 단순한 이벤트 리스너는 간단합니다. 다음은 ToDo 항목을 새로 만드는 버튼의 클릭 이벤트를 작성한 예입니다.

<div className=”ToDo-Add” onClick={this.createNewToDoItem}>+</div>.

vanilla JS로 인라인 onClick을 처리하는 방법처럼 쉽고 예뻐보입니다. Vue 섹션에서 언급했듯이, 엔터 버튼을 누를 때마다 처리 할 이벤트 리스너를 설정하는 데 약간 시간이 걸렸습니다. 이것은 필수적으로 onKeyPress 이벤트가 입력 태그에 의해 처리되도록 하였습니다.

<input type=”text” onKeyPress={this.handleKeyPress}/>.

이 함수는 다음과 같이 ‘enter’키를 눌렀을 때마다 createNewToDoItem 함수를 실행합니다.

handleKeyPress = (e) => {
   if (e.key === ‘Enter’) {
  this.createNewToDoItem();
  }
};

Vue :

Vue에서는 매우 명확합니다.. 우리는 단순히 @ 기호를 사용하고 우리가 하고 싶은 이벤트 리스너의 유형을 사용합니다. 예를 들어 클릭 이벤트 리스너를 추가하기 위해 다음과 같이 작성할 수 있습니다.

<div class=”ToDo-Add” @click=”createNewToDoItem()”>+</div> 

참고 : @click은 실제로 v-on:click의 단축형입니다. Vue 이벤트 리스너의 장점은 이벤트 리스너가 두 번 이상 트리거되지 않도록하는 .once와 같이 사용자가 연결할 수있는 많은 것들이 있다는 것입니다. 키 스트로크를 처리하기 위한 특정 이벤트 리스너를 작성할 때 많은 단축키가 있습니다. Enter 버튼을 누를 때마다 새로운 ToDo 항목을 만들기 위해 리액트에서 이벤트 리스너를 만드는 것이 꽤 오래 걸리는 것을 알게되었습니다. Vue에서는 다음과 같이 간단하게 쓸 수있었습니다.

<input type=”text” v-on:keyup.enter=”createNewToDoItem”/>

데이터를 자식 컴포넌트로 전달하는 방법은 무엇입니까?

리액트 :

리액트에서는 props로 자식 컴포넌트에게 생성된 것을 전달했습니다.

<ToDoItem key = {key} item = {todo} />

여기서 ToDoItem 컴포넌트로 전달 된 두 개의 prop을 봅니다. 이제부터는 this.props를 통해 자식 컴포넌트에서 참조할 수 있습니다. 따라서 item.todo prop에 액세스하려면 this.props.item을 호출하면 됩니다.

Vue :

Vue에서 우리는 자식 컴포넌트를 생성 한 지점에서 prop을 전달합니다.

<ToDoItem v-for="todo in list" 
          :todo="todo"
          :key="todo.id"
          @delete="onDeleteItem" />

이 작업이 끝나면 자식 컴포넌트의 props 배열에 다음과 같이 전달합니다. props : [ 'todo']. 그런 다음 이들을 이름으로 참조 할 수 있습니다. 여기서는 todo입니다.

부모 컴포넌트로 데이터를 다시 보내려면 어떻게 해야하나요?

리액트 :

먼저 자식 컴포넌트를 호출하는 곳에서 props를 참조하여 자식 컴포넌트로 함수를 전달합니다. 그런 다음 onClick과 같은 수단으로 this.props.whateverTheFunctionIsCalled를 참조하여 자식에게 함수 호출을 추가합니다. 그러면 상위 컴포넌트에있는 함수가 트리거됩니다. 이 전체 프로세스의 예제를 '리스트에서는 어떻게 삭제하나요?' 섹션에서 볼 수 있습니다.

Vue :

우리의 자식 컴포넌트에서 우리는 단순히 부모 함수에 값을 emit하는 함수를 작성합니다. 우리의 부모 컴포넌트에서, 우리는 그 값이 언제 나오는지를 기다리는 함수를 작성합니다. 그러면 함수를 트리거 할 수 있습니다. 이 전체 프로세스의 예를 '리스트에서는 어떻게 삭제하나요?' 섹션에서 볼 수 있습니다.

이제 다 했습니다! 🎉

데이터를 추가, 제거 및 변경하고, 부모로부터 자식으로 prop 형태로 데이터를 전달하고, 이벤트 리스너의 형태로 부모로부터 자식에게 데이터를 보내는 방법을 살펴 보았습니다. 물론 리액트와 Vue 사이에는 약간의 차이점과 단점이 있지만 이 글의 내용은 두 프레임워크가 어떻게 데이터를 처리하는지 이해할 수 있는 토대가 될 것입니다.

Github 링크:

Vue ToDo: https://github.com/sunil-sandhu/vue-todo

React ToDo: https://github.com/sunil-sandhu/react-todo

앵귤러는요?

질문 받아서 좋네요. Sam Borick이 이 글의 Part 2를 작성했거든요!

Comments