
* 이 글은 I created the exact same app in React and Vue. Here are the differences 번역하였습니다.
I created the exact same app in React and Vue. Here are the differences. [2020 Edition]
I created the exact same app in React and Vue. Here are the differences. [2020 Edition]: 2020 Edition: Now with React Hooks vs Vue 3 + Composition API! by Sunil Sandhu
sunilsandhu.com
Vue를 사용하여 일을 하면서, 정말로 많은 것들을 이해하였습니다. 그러나 울타리에 너머 있는 다른 라이브러리가 궁금하였고, 리액트에 대한 관심을 갖기 시작하였습니다.
그렇기에 리액트 공식문서를 읽고, 몇 가지 튜토리얼 비디오를 보앗습니다. 그러나 정말 알고 싶은 부분은 리액트와 뷰의 차이점이었습니다. 리액트와 뷰가 가지고 있는 virtual DOM, 렌더링 페이지가 어떻게 동작하는 지에 대한 부분은 차이점은 아니라고 생각했습니다. 그냥 단지 누군가가 시간을 들여 코드를 설명해 주었으면 했습니다. 뷰와 리액트의 차이점에 대한 글을 열심히 찾아보았지만, 찾지 못하였습니다.
그래서 제가 직접 하기로 마음을 먹고 리액트와 뷰의 비슷한 점과 차이점에 대해 글을 써보았습니다. 리액트와 뷰의 공식문서를 참고하였고, 결국에는 아래의 글이 탄생하였습니다.
두가지의 어플리케이션은 CLI (creact-react-app for React, Vue-cli for Vue)를 통해 만들었습니다. 참고해 주시면 좋을 것 같습니다.
리액트는 Hooks를 이용하였고, 뷰는 버전 3을 참고하여 작성하였습니다.
Let's take a quick look at how the two apps look

두 어플리케이션의 CSS 코드는 동일합니다. 다음은 파일 구조를 소개해드리겠습니다.

위를 보시면 구조도 거의 비슷합니다. 오직 다른점은 뷰는 CSS파일이 없는 반면, 리액트가 2개의 CSS파일을 가지고 있다는 점입니다. 그 이유는 뷰은 HTML, CSS, JavaScript를 뷰 컴포넌트 안에 생성하는 반면, create-react-app은 스타일을 위한 CSS파일을 자동으로 분리하여 만들기 때문입니다.
궁극적으로 리액트와 뷰는 같은 것을 달성하고, 파일을 동일하게 구성합니다. 단지 개인 취향의 차이일 뿐입니다. 스타일 컴포넌트 및 Emotion과 같은 CSS-in-JS 솔루션이 있기 때문에 리액트와 관련하여 CSS를 구조화하는 방법에 대한 부분은 항상 논란의 대상이 되어 왔습니다. 하지만 요즘은 리액트와 뷰는 CLI에 배치 된 구조를 따라 코드를 작성하는 것이 대부분입니다.
더 나아가기 전에 빠르게 리액트와 뷰의 컴포넌트를 살펴봅시다.
React-file-component

Vue-file-component

이제 핵심 세부사항들을 살펴 보겠습니다.
How do we mutate data?
먼저 mutate data라는 게 무엇인 줄 알아야 할 것 같습니다. 마치 기술과도 관련있는 것 같이 보이지만 기본적으로 우리가 저장해 놓은 데이터를 변경하는 것을 의미합니다. 만약 John 이라는 이름에서 Mark라는 사람 이름을 변경하길 원한다면 우리는 mutate data를 해야 합니다. 이것이 리액트와 뷰의 차이를 알아볼 수 있는 핵심 사항입니다. 뷰는 본질적으로 데이터를 자유롭게 업데이트 할 수 있는 데이터 객체를 생성하지만 리액트는 이를 상태 hooks를 통해 처리합니다.
아래의 코드를 보면서 확인해보도록 합시다.
React state

Vue state

보는 것과 같이 같은 데이터를 보내지만 구조는 현저하게 다릅니다.
2019년도 부터 리액트는 Hooks를 사용하여 state 값을 다룹니다. 보기에는 약간 이상하게 생겼다고 느낄 수 있습니다. 기본적으로 다음과 같이 작동합니다.
만약에 Todo 리스트를 만들어야 한다고 가정해 봅시다. list라는 변수를 생성해야 할 것이고 문자열이나 객체의 배열을 취할 것입니다. const [ list, setList] = useState([]); 를 작성하여 설정합니다. 여기서는 리액트 hooks라고 불리우는 useState를 사용합니다. 컴포넌트 내의 지역 state값을 지닙니다.
또한 useState() 내부에 빈 배열 []을 전달했음을 알 수 있습니다. 안에 넣은 것은 가장 처음에 list 내에 설정하고 싶은 것을 넣었습니다. 우리는 빈 배열을 넣은 것입니다. 하지만 만약 배열 안에 어떤 데이터에 이미지를 전달하고 싶다면 이미지 배열을 전달하면 됩니다. 자세한 사항은 추후 다시 설명 드리겠습니다.
이와는 반대로 뷰는 변경할 수 있는 모든 데이터는 외부에 노출시키고 싶은 함수와 데이터를 가지고 있는 객체를 리턴하는 setup 함수 내에 정의 되어있습니다. 위를 보면 어플리케이션의 각 state 데이터가 ref() 함수 내부에 래핑되어 있음을 알 수 있습니다. 여기서 ref() 함수는 뷰에서 import 하여 수정하고 싶은 데이터를 어플리케이션 어디에서나 수정할 수 있도록 만들어 줍니다. 간략하게 말하자면 뷰 안에서 변경하고 싶은 데이터를 만들고 싶다면 ref() 함수를 만들어 default 데이터를 내부에 위치시키면 됩니다.
So how would we reference mutable data in our app?
자, sunil이라는 이름을 가진 값이 있다고 가정해 봅시다.
리액트에서는 useState() hooks를 호출하여 const [name, setName] = useState('Sunil'); 과 같은 코드를 만듭니다. 어플리케이션에서는 단순히 이름을 호출하여 동일한 데이터를 참조합니다. 여기서 중요한 차이점은 단순히 name = 'John'이라고 쓸 수 없다는 것입니다. 왜나하면 React는 사이드 이펙트를 방지하기 위한 제한이 존재하기 때문입니다. 그래서 리액트에서는 setName('John'); 이라고 써야합니다. 여기서 setName이 작동합니다. 기본적으로 const [name, setName] = useState('Sunil'); 쓰는 이유는 두개의 변수를 쓰는 것과 같습니다. const name = 'Sunil' 과 const setName을 사용하여 새로운 이름을 재 할당하는 함수가 이에 해당합니다.
이와는 반대로 뷰에서는 setup() 함수 내부에서 데이터를 셋팅합니다. const name = ref('sunil'); 이렇게 말이죠, 어플리케이션에서 ref() 함수 내부에 데이터를 정의한 경우 name.value를 통하여 이름에 접근할 수 있습니다. 즉, 상태를 유지하는 변수의 값을 원하면 이름이 아닌 name.value를 찾아야 합니다. 이름을 수정하려면 name.value를 수정하면 됩니다. 예를 들어 John으로 이름을 수정하고 싶다면 name.value = 'John' 이라 작성하면 됩니다.
결론적으로 리액트와 뷰는 수정할 데이터를 생성하는 것은 같습니다. 단지 뷰는 ref() 함수 내부에 이름을 업데이트 할 setName과 name을 한 곳에 결합하였고, 리액트는 상태 값을 수정하기 위해 내부 값으로 setName()을 호출합니다. 하지만 뷰는 데이터를 수정하고 싶은 경우 이러한 작업을 수행하고 싶다고 가정합니다. 그렇다면 리액트는 데이터 수정을 하기 위하여 왜 함수를 분리하여 작성하였을까요? 본질적으로 리액트는 상태가 변경 될 때마다 특정 라이프 사이클 hooks가 다시 실행되기 때문입니다. 예를 들어 setName()을 호출하였을 때 리액트는 어떤 상태 값이 변경되었다는 것을 인지하고 라이프 사이클 hooks를 호출합니다. 상태를 직접 변경할 경우 리액트는 뷰보다 더 많은 일들 (라이프 사이클 hooks)을 수행합니다.
데이터를 참조하고 수정하는 것까지 살펴 보았으니 이제는 새로운 항목을 추가하는 것을 살펴보도록 하겠습니다.
How do we create new To Do Items?
React.
How did React do that?
const createNewToDoItem = () => {
const newId = generateId();
const newToDo = { id: newId, text: toDo };
setList([...list, newToDo]);
setToDo("");
};
리액트에서는 value라고 불리우는 속성이 input 필드가 가지고 있습니다. 이 value는 onChange 이벤트 리스너를 통해 데이터가 업데이트 될때마다 자동으로 value 값이 변경됩니다. HTML과 비슷한 JSX 문법은 아래와 같습니다.
<input
type="text"
placeholder="I need to..."
value="{toDo}"
onChange="{handleInput}"
onKeyPress="{handleKeyPress}"
/>
value가 바뀔때마다 state는 업데이트됩니다. handleInput 함수는 아래와 같습니다.
const handleInput = (e) => {
setToDo(e.target.value);
};
유저가 새로운 아이템을 추가하기 위해 "+" 버튼을 누르면, createNewToDoItem 함수가 발동합니다. 해당 함수는 아래와 같습니다.
const createNewToDoItem = () => {
const newId = generateId();
const newToDo = { id: newId, text: toDo };
setList([...list, newToDo]);
setToDo("");
};
보시다시피 newId 함수는 새로운 아이디를 만들어 새로운 ToDo 아이템에 넣어지게 됩니다. 새로운 ToDo 변수는 newId를 새로운 키 값을 가진 객체입니다. 또한 toDo 라는 값을 가진 text의 키를 갖게 됩니다. toDo는 input 값이 바뀔 때 함께 바뀌는 값입니다.
그 다음 setLIst 함수를 실행하고 전체 목록과 새로 생성된 newToDo를 포함하는 배열을 전달합니다.
만약 ...list 가 생소하게 느껴지신다면, ... 점은 spread operator라 불리우는 새로운 것입니다. 이는 list의 모든 값을 분해하여 전달합니다. 만약 혼란스러우시다면 spread operator를 찾아 읽어보시는 걸 추천드립니다.
어쨋든 마지막으로 setToDo 함수를 실행하고, 빈 문자열을 전달합니다. 이는 입력 값을 비워 줌으로써 새로운 할 일을 입력할 수 있도록 도와줍니다.
Vue.
How did Vue do that?
function createNewToDoItem() {
const newId = generateId();
list.value.push({ id: newId, text: todo.value });
todo.value = "";
}
뷰에서는 input 필드는 v-model을 가지고 있습니다. 이는 two-way binding 을 구현할 수 있도록 도와줍니다. 아래의 코드를 보고 다시 설명을 드리겠습니다.
<input type="text" placeholder="I need to..." v-model="todo" v-on:keyup.enter="createNewToDoItem" />
v-model은 이 필드의 입력을 setup() 함수의 맨 위에서 생성한 변수에 연결한 다음 반환된 객체 내부의 키로 노출됩니다. 우리는 지금까지 객체에서 반환된 내용을 많이 다루지 않았으므로 이를 위해 ToDo.vue 내부의 setup() 함수에서 반환된 내용을 소개해드리겠습니다.
return {
list,
todo,
showError,
generateId,
createNewToDoItem,
onDeleteItem,
displayError,
};
list, todo 그리고 showError는 상태 값이며, 반면 다른 것들은 우리의 어플리케이션 어디에서나 호출할 수 있는 함수들입니다. 다시 돌아와서 페이지가 로드되면 todo는 비어있는 값을 저장합니다. 다음과 같이 말이죠.
const todo = ref("").
만약 todo에 데이터가 존재한다면 다음과 같겠죠.
const todo = ref("add some text here")
그럼 input 필드에는 "add some text here" 이라는 값이 로드되겠죠. 어쨌든, 빈 문자열로 돌아가서 input 필드에 입력하는 텍스트는 무엇이든 todo.value에 바인딩됩니다. 이것이 효과적인 two-way binding입니다. input 필드는 ref() 값을 업데이트할 수 있고 ref() 값은 입력 필드를 업데이트할 수 있습니다.
그럼 다시 createNewToDoItem() 함수로 돌아가서 todo.value를 list.value로 푸시하여 todo.value의 내용을 목록 배열로 푸시한 다음 todo.value를 빈 문자열로 업데이트하는 것을 아래에서 볼 수 있습니다.
function createNewToDoItem() {
const newId = generateId();
list.value.push({ id: newId, text: todo.value });
todo.value = "";
}
How do we delete it from the list?
React.
How did React do that?
const deleteItem = (id) => {
setList(list.filter((item) => item.id !== id));
};
deleteItem() 함수는 ToDo.js 내부에 있지만 deleteItem() 함수를 전달하여 ToDoItem.js 내부에서 매우 쉽게 참조할 수 있습니다. 아래와 같이 말이죠
<ToDoItem key="{item.id}" item="{item}" deleteItem="{deleteItem}" />
먼저 자식으로 접근할 수 있도록 해당 함수를 아래로 내립니다. 그다음 TodoItem.js 에서 아래와 같이 작성합니다.
<button className="ToDoItem-Delete" onClick={() => deleteItem(item.id)}>
-
</button>
부모 구성 요소 내부에 있는 함수를 참조하기 위해 해야 하는 일은 props를 이용하여 deleteItem을 참조해야 합니다. 코드 예제에서는 props.deleteItem 대신 deleteItem을 작성했다는 것을 인지하셨을 것입니다. 그 이유는 Destructuring 이라는 문법을 사용하여 객체나 나 배열에 들어있는 값을 빼낼 수 있기 때문입니다. 아래와 같이 말이죠.
const ToDoItem = (props) => {
const { item, deleteItem } = props;
};
이것은 props.item과 동일한 값을 할당받는 item이라는 변수와 props.deleteItem에서 값을 할당받는 deleteItem이라는 두 개의 변수를 생성했습니다. 단순히 props.item과 props.deleteItem을 사용할 수도 있지만, Destructuring을 사용하여 코드를 줄일 수 있었습니다.
Vue.
How did vue do That?
function onDeleteItem(id) {
list.value = list.value.filter((item) => item.id !== id);
}
뷰에서는 다르게 여러가지 방향으로 접근할 수 있습니다. 총 3가지로 나누어 설명드리겠습니다.
첫번째로 element 안에서 우리가 원하는 함수를 호출하는 것입니다.
<button class="ToDoItem-Delete" @click="deleteItem(item.id)">-</button>
더 단순하게 작성하면 다음과 같습니다.
<button class="ToDoItem-Delete" @click="emit("delete", item.id)">
-
</button>
그 다음 emit 함수를 사용하여 자식 컴포넌트의 함수에서 호출하는 것입니다. 아래와 같이 말이죠
function deleteItem(id) {
emit("delete", id);
}
ToDo.vue 내부에 ToDoItem.vue를 추가할 때 실제로 해당 함수를 참조한다는 것을 알 수 있습니다. 아래와 같이 말이죠.
<ToDoItem v-for="item in list" :item="item" @delete="onDeleteItem" :key="item.id" />
이것은 커스텀 이벤트 리스너로 알려져 있습니다. 'delete' 가 발동되는 모든 경우를 계속 지켜봅니다. 리스너가 작동을 하면 onDeleteItem이라는 함수를 자동으로 발동시킵니다. 사실 아래의 함수는 ToDoItem.vue가 아니라 ToDo.vue 내부에 있습니다. 이 함수는 앞에서 나열한 것처럼 단순히 list.value 배열에서 id를 필터링합니다.
function onDeleteItem(id) {
list.value = list.value.filter((item) => item.id !== id);
}
간단히 말해서, React의 자식 구성 요소는 props를 통해 부모 기능에 접근 할 수 있는 반면, Vue에서는 부모 요소 내부에 속해있는 이벤트 함수를 자식 요소에서 호출을 시켜야 해당 함수에 접근할 수 있습니다.
How do we pass event listeners?
React.
클릭 이벤트와 같은 간단한 이벤트 리스너는 간단합니다. 다음은 새 할 일 항목을 생성하는 버튼에 대한 클릭 이벤트를 생성한 방법의 예입니다.
<button className="ToDo-Add" onClick="{createNewToDoItem}">+</button>
보시다시피 바닐라 JS로 인라인 onClick을 처리하는 방법과 거의 비슷합니다. keyPress 이벤트를 처리하는 것도 마찬가지입니다.
<input
type="text"
placeholder="I need to..."
value="{toDo}"
onChange="{handleInput}"
onKeyPress="{handleKeyPress}"
/>
함수는 아래와 같습니다. 해당 데이터를 입력하고 Enter를 누르면, createNewToDoItem()함수를 호출합니다.
const handleKeyPress = (e) => {
if (e.key === "Enter") {
createNewToDoItem();
}
};
Vue
Vue에서는 더 간단합니다. @ 기호를 사용한 다음 수행하려는 이벤트 유형을 사용하기만 하면 됩니다. 예를 들어 클릭 이벤트 리스너를 추가하려면 다음과 같이 작성할 수 있습니다.
<button class="ToDo-Add" @click="createNewToDoItem">+</button>
@click은 v-on:click의 축약입니다. Vue에서 이벤트 리스너의 좋은점은 모든 이벤트 함수를 체이닝할 수 있다는 점입니다. 예를 들어 Enter 버튼을 누를 때마다 새로운 ToDo 항목을 생성하기 위해 React에서는 이벤트 리스너를 생성하기 위해 여러가지를 처리해줘야 했던 것과 달리 Vue에서는 다음과 같이 간단하게 작성할 수 있습니다.
<input type=”text” v-on:keyup.enter=”createNewToDoItem”/>
How do we pass data through to a child component?
React
React에서는 아래와 같이 자식 컴포넌트로 해당 데이터를 전달할 수 있습니다.
<ToDoItem key="{item.id}" item="{item}" deleteItem="{deleteItem}" />
위에 코드에서는 2개의 props를 ToDoItem 컴포넌트로 전달하였습니다. 자식 컴포넌트에서는 this.props를 사용하여 해당 데이터에 접근할 수 있습니다. 예를 들어 item.todo에 접근하기 위해서는 props.item을 호출합니다. 동시에 key prop을 가져야 한다는 경고를 받게 될 것입니다. 이는 React의 내부를 위한 것입니다. 동일한 구성 요소간 업데이트를 하고 이를 추적할 때 작업을 더 쉽게 수행하도록 만들어줘야합니다. 여기서 key는 그 역할을 합니다. 만약 key prop을 무시한다면 React는 우리에게 경고 콘솔을 띄울 것입니다.
Vue
Vue에서는 생성된 지점에서 자식 컴포넌트에 props를 전달합니다.
<ToDoItem v-for="item in list" :item="item" @delete="onDeleteItem" :key="item.id" />
그 다음 props:["todo"] 와 같이 자식 컴포넌트의 props 배열에 전달합니다. 그런 다음 이름으로 해당 항목을 참조할 수 있습니다. 따라서 이 경우에는 todo입니다. 해당 prop 키를 어디에 배치해야 하는지 확실하지 않은 경우를 위해 export default를 사용하여 아래와 같이 정리하여 사용합니다.
export default {
name: "ToDoItem",
props: ["item"],
setup(props, { emit }) {
function deleteItem(id) {
emit("delete", id);
}
return {
deleteItem,
};
},
};
당신이 알아야 할 한가지는 Vue에서 데이터를 반복할 때 실제로는 list.value가 아닌 list를 반복한다는 것입니다. list.value를 통해 루프를 시도하면 작동하지 않습니다.
How do we emit data back to a parent component?
React.
리액트에서는 다음과 같습니다. 자식 컴포넌트에서는 부모의 함수에 접근하기 위해 props를 이용하였습니다. 그런 다음 props.someFunction 또는 someFunction를 참조하여 onClick과 같은 방법으로 자식에 대한 함수 호출을 추가합니다. 추가를 하게 되면 부모 컴포넌트에서 해당 함수를 트리거 할 것입니다. 예시를 보고 싶다면 'How do we delet from thd list' 목차를 참고해 주세요.
Vue.
뷰에서는 다음과 같습니다. 자식 컴포넌트에서 해당 함수에서 emit이라는 함수를 추가하여 부모 컴포넌트로 올려 보냅니다. 다음 부모함수에서는 emit 함수를 계속 바라보는 함수를 정의함으로써 자식 함수가 호출되면 지켜보는 부모함수에서 트리거 될 수 있도록 합니다. 예시를 보고 싶다면 'How do we delet from thd list' 목차를 참고해 주세요.
And There we have it
우리는 Vue와 React에서 추가, 삭제 그리고 데이터 수정에 대하여 살펴보고 부모 컴포넌트에서 어떻게 자식 컴포넌트로 데이터를 전달하는지, 반대로 자식 컴포넌트에서 부모 컴포넌트로 어떻게 데이터를 전달할 수 있는지에 대하여 살펴보았습니다. 물론 예상한 대로 Vue와 React의 문법은 달랐지만, 핵심적인 내용은 비슷하였습니다. 해당 글이 많은 도움이 되었으면 좋겠습니다.
긴 글 읽어주셔서 감사합니다.
< 참고자료 >
[사이트] #medium
https://sunilsandhu.com/posts/i-created-the-exact-same-app-in-react-and-vue-2020-edition
I created the exact same app in React and Vue. Here are the differences. [2020 Edition]
I created the exact same app in React and Vue. Here are the differences. [2020 Edition]: 2020 Edition: Now with React Hooks vs Vue 3 + Composition API! by Sunil Sandhu
sunilsandhu.com
I created the exact same app in React and Vue. Here are the differences end
'Language & Framework & Library > React' 카테고리의 다른 글
Design Patterns - Compound component pattern (0) | 2023.10.21 |
---|---|
Re-rendering과 memoization (2) | 2022.03.13 |
컴포넌트 Re-rendering을 피하는 5가지 방법 (0) | 2022.03.13 |
React의 컴포넌트 수명주기 (0) | 2021.02.12 |
Virtual DOM (0) | 2020.01.16 |

* 이 글은 I created the exact same app in React and Vue. Here are the differences 번역하였습니다.
I created the exact same app in React and Vue. Here are the differences. [2020 Edition]
I created the exact same app in React and Vue. Here are the differences. [2020 Edition]: 2020 Edition: Now with React Hooks vs Vue 3 + Composition API! by Sunil Sandhu
sunilsandhu.com
Vue를 사용하여 일을 하면서, 정말로 많은 것들을 이해하였습니다. 그러나 울타리에 너머 있는 다른 라이브러리가 궁금하였고, 리액트에 대한 관심을 갖기 시작하였습니다.
그렇기에 리액트 공식문서를 읽고, 몇 가지 튜토리얼 비디오를 보앗습니다. 그러나 정말 알고 싶은 부분은 리액트와 뷰의 차이점이었습니다. 리액트와 뷰가 가지고 있는 virtual DOM, 렌더링 페이지가 어떻게 동작하는 지에 대한 부분은 차이점은 아니라고 생각했습니다. 그냥 단지 누군가가 시간을 들여 코드를 설명해 주었으면 했습니다. 뷰와 리액트의 차이점에 대한 글을 열심히 찾아보았지만, 찾지 못하였습니다.
그래서 제가 직접 하기로 마음을 먹고 리액트와 뷰의 비슷한 점과 차이점에 대해 글을 써보았습니다. 리액트와 뷰의 공식문서를 참고하였고, 결국에는 아래의 글이 탄생하였습니다.
두가지의 어플리케이션은 CLI (creact-react-app for React, Vue-cli for Vue)를 통해 만들었습니다. 참고해 주시면 좋을 것 같습니다.
리액트는 Hooks를 이용하였고, 뷰는 버전 3을 참고하여 작성하였습니다.
Let's take a quick look at how the two apps look

두 어플리케이션의 CSS 코드는 동일합니다. 다음은 파일 구조를 소개해드리겠습니다.

위를 보시면 구조도 거의 비슷합니다. 오직 다른점은 뷰는 CSS파일이 없는 반면, 리액트가 2개의 CSS파일을 가지고 있다는 점입니다. 그 이유는 뷰은 HTML, CSS, JavaScript를 뷰 컴포넌트 안에 생성하는 반면, create-react-app은 스타일을 위한 CSS파일을 자동으로 분리하여 만들기 때문입니다.
궁극적으로 리액트와 뷰는 같은 것을 달성하고, 파일을 동일하게 구성합니다. 단지 개인 취향의 차이일 뿐입니다. 스타일 컴포넌트 및 Emotion과 같은 CSS-in-JS 솔루션이 있기 때문에 리액트와 관련하여 CSS를 구조화하는 방법에 대한 부분은 항상 논란의 대상이 되어 왔습니다. 하지만 요즘은 리액트와 뷰는 CLI에 배치 된 구조를 따라 코드를 작성하는 것이 대부분입니다.
더 나아가기 전에 빠르게 리액트와 뷰의 컴포넌트를 살펴봅시다.
React-file-component

Vue-file-component

이제 핵심 세부사항들을 살펴 보겠습니다.
How do we mutate data?
먼저 mutate data라는 게 무엇인 줄 알아야 할 것 같습니다. 마치 기술과도 관련있는 것 같이 보이지만 기본적으로 우리가 저장해 놓은 데이터를 변경하는 것을 의미합니다. 만약 John 이라는 이름에서 Mark라는 사람 이름을 변경하길 원한다면 우리는 mutate data를 해야 합니다. 이것이 리액트와 뷰의 차이를 알아볼 수 있는 핵심 사항입니다. 뷰는 본질적으로 데이터를 자유롭게 업데이트 할 수 있는 데이터 객체를 생성하지만 리액트는 이를 상태 hooks를 통해 처리합니다.
아래의 코드를 보면서 확인해보도록 합시다.
React state

Vue state

보는 것과 같이 같은 데이터를 보내지만 구조는 현저하게 다릅니다.
2019년도 부터 리액트는 Hooks를 사용하여 state 값을 다룹니다. 보기에는 약간 이상하게 생겼다고 느낄 수 있습니다. 기본적으로 다음과 같이 작동합니다.
만약에 Todo 리스트를 만들어야 한다고 가정해 봅시다. list라는 변수를 생성해야 할 것이고 문자열이나 객체의 배열을 취할 것입니다. const [ list, setList] = useState([]); 를 작성하여 설정합니다. 여기서는 리액트 hooks라고 불리우는 useState를 사용합니다. 컴포넌트 내의 지역 state값을 지닙니다.
또한 useState() 내부에 빈 배열 []을 전달했음을 알 수 있습니다. 안에 넣은 것은 가장 처음에 list 내에 설정하고 싶은 것을 넣었습니다. 우리는 빈 배열을 넣은 것입니다. 하지만 만약 배열 안에 어떤 데이터에 이미지를 전달하고 싶다면 이미지 배열을 전달하면 됩니다. 자세한 사항은 추후 다시 설명 드리겠습니다.
이와는 반대로 뷰는 변경할 수 있는 모든 데이터는 외부에 노출시키고 싶은 함수와 데이터를 가지고 있는 객체를 리턴하는 setup 함수 내에 정의 되어있습니다. 위를 보면 어플리케이션의 각 state 데이터가 ref() 함수 내부에 래핑되어 있음을 알 수 있습니다. 여기서 ref() 함수는 뷰에서 import 하여 수정하고 싶은 데이터를 어플리케이션 어디에서나 수정할 수 있도록 만들어 줍니다. 간략하게 말하자면 뷰 안에서 변경하고 싶은 데이터를 만들고 싶다면 ref() 함수를 만들어 default 데이터를 내부에 위치시키면 됩니다.
So how would we reference mutable data in our app?
자, sunil이라는 이름을 가진 값이 있다고 가정해 봅시다.
리액트에서는 useState() hooks를 호출하여 const [name, setName] = useState('Sunil'); 과 같은 코드를 만듭니다. 어플리케이션에서는 단순히 이름을 호출하여 동일한 데이터를 참조합니다. 여기서 중요한 차이점은 단순히 name = 'John'이라고 쓸 수 없다는 것입니다. 왜나하면 React는 사이드 이펙트를 방지하기 위한 제한이 존재하기 때문입니다. 그래서 리액트에서는 setName('John'); 이라고 써야합니다. 여기서 setName이 작동합니다. 기본적으로 const [name, setName] = useState('Sunil'); 쓰는 이유는 두개의 변수를 쓰는 것과 같습니다. const name = 'Sunil' 과 const setName을 사용하여 새로운 이름을 재 할당하는 함수가 이에 해당합니다.
이와는 반대로 뷰에서는 setup() 함수 내부에서 데이터를 셋팅합니다. const name = ref('sunil'); 이렇게 말이죠, 어플리케이션에서 ref() 함수 내부에 데이터를 정의한 경우 name.value를 통하여 이름에 접근할 수 있습니다. 즉, 상태를 유지하는 변수의 값을 원하면 이름이 아닌 name.value를 찾아야 합니다. 이름을 수정하려면 name.value를 수정하면 됩니다. 예를 들어 John으로 이름을 수정하고 싶다면 name.value = 'John' 이라 작성하면 됩니다.
결론적으로 리액트와 뷰는 수정할 데이터를 생성하는 것은 같습니다. 단지 뷰는 ref() 함수 내부에 이름을 업데이트 할 setName과 name을 한 곳에 결합하였고, 리액트는 상태 값을 수정하기 위해 내부 값으로 setName()을 호출합니다. 하지만 뷰는 데이터를 수정하고 싶은 경우 이러한 작업을 수행하고 싶다고 가정합니다. 그렇다면 리액트는 데이터 수정을 하기 위하여 왜 함수를 분리하여 작성하였을까요? 본질적으로 리액트는 상태가 변경 될 때마다 특정 라이프 사이클 hooks가 다시 실행되기 때문입니다. 예를 들어 setName()을 호출하였을 때 리액트는 어떤 상태 값이 변경되었다는 것을 인지하고 라이프 사이클 hooks를 호출합니다. 상태를 직접 변경할 경우 리액트는 뷰보다 더 많은 일들 (라이프 사이클 hooks)을 수행합니다.
데이터를 참조하고 수정하는 것까지 살펴 보았으니 이제는 새로운 항목을 추가하는 것을 살펴보도록 하겠습니다.
How do we create new To Do Items?
React.
How did React do that?
const createNewToDoItem = () => {
const newId = generateId();
const newToDo = { id: newId, text: toDo };
setList([...list, newToDo]);
setToDo("");
};
리액트에서는 value라고 불리우는 속성이 input 필드가 가지고 있습니다. 이 value는 onChange 이벤트 리스너를 통해 데이터가 업데이트 될때마다 자동으로 value 값이 변경됩니다. HTML과 비슷한 JSX 문법은 아래와 같습니다.
<input
type="text"
placeholder="I need to..."
value="{toDo}"
onChange="{handleInput}"
onKeyPress="{handleKeyPress}"
/>
value가 바뀔때마다 state는 업데이트됩니다. handleInput 함수는 아래와 같습니다.
const handleInput = (e) => {
setToDo(e.target.value);
};
유저가 새로운 아이템을 추가하기 위해 "+" 버튼을 누르면, createNewToDoItem 함수가 발동합니다. 해당 함수는 아래와 같습니다.
const createNewToDoItem = () => {
const newId = generateId();
const newToDo = { id: newId, text: toDo };
setList([...list, newToDo]);
setToDo("");
};
보시다시피 newId 함수는 새로운 아이디를 만들어 새로운 ToDo 아이템에 넣어지게 됩니다. 새로운 ToDo 변수는 newId를 새로운 키 값을 가진 객체입니다. 또한 toDo 라는 값을 가진 text의 키를 갖게 됩니다. toDo는 input 값이 바뀔 때 함께 바뀌는 값입니다.
그 다음 setLIst 함수를 실행하고 전체 목록과 새로 생성된 newToDo를 포함하는 배열을 전달합니다.
만약 ...list 가 생소하게 느껴지신다면, ... 점은 spread operator라 불리우는 새로운 것입니다. 이는 list의 모든 값을 분해하여 전달합니다. 만약 혼란스러우시다면 spread operator를 찾아 읽어보시는 걸 추천드립니다.
어쨋든 마지막으로 setToDo 함수를 실행하고, 빈 문자열을 전달합니다. 이는 입력 값을 비워 줌으로써 새로운 할 일을 입력할 수 있도록 도와줍니다.
Vue.
How did Vue do that?
function createNewToDoItem() {
const newId = generateId();
list.value.push({ id: newId, text: todo.value });
todo.value = "";
}
뷰에서는 input 필드는 v-model을 가지고 있습니다. 이는 two-way binding 을 구현할 수 있도록 도와줍니다. 아래의 코드를 보고 다시 설명을 드리겠습니다.
<input type="text" placeholder="I need to..." v-model="todo" v-on:keyup.enter="createNewToDoItem" />
v-model은 이 필드의 입력을 setup() 함수의 맨 위에서 생성한 변수에 연결한 다음 반환된 객체 내부의 키로 노출됩니다. 우리는 지금까지 객체에서 반환된 내용을 많이 다루지 않았으므로 이를 위해 ToDo.vue 내부의 setup() 함수에서 반환된 내용을 소개해드리겠습니다.
return {
list,
todo,
showError,
generateId,
createNewToDoItem,
onDeleteItem,
displayError,
};
list, todo 그리고 showError는 상태 값이며, 반면 다른 것들은 우리의 어플리케이션 어디에서나 호출할 수 있는 함수들입니다. 다시 돌아와서 페이지가 로드되면 todo는 비어있는 값을 저장합니다. 다음과 같이 말이죠.
const todo = ref("").
만약 todo에 데이터가 존재한다면 다음과 같겠죠.
const todo = ref("add some text here")
그럼 input 필드에는 "add some text here" 이라는 값이 로드되겠죠. 어쨌든, 빈 문자열로 돌아가서 input 필드에 입력하는 텍스트는 무엇이든 todo.value에 바인딩됩니다. 이것이 효과적인 two-way binding입니다. input 필드는 ref() 값을 업데이트할 수 있고 ref() 값은 입력 필드를 업데이트할 수 있습니다.
그럼 다시 createNewToDoItem() 함수로 돌아가서 todo.value를 list.value로 푸시하여 todo.value의 내용을 목록 배열로 푸시한 다음 todo.value를 빈 문자열로 업데이트하는 것을 아래에서 볼 수 있습니다.
function createNewToDoItem() {
const newId = generateId();
list.value.push({ id: newId, text: todo.value });
todo.value = "";
}
How do we delete it from the list?
React.
How did React do that?
const deleteItem = (id) => {
setList(list.filter((item) => item.id !== id));
};
deleteItem() 함수는 ToDo.js 내부에 있지만 deleteItem() 함수를 전달하여 ToDoItem.js 내부에서 매우 쉽게 참조할 수 있습니다. 아래와 같이 말이죠
<ToDoItem key="{item.id}" item="{item}" deleteItem="{deleteItem}" />
먼저 자식으로 접근할 수 있도록 해당 함수를 아래로 내립니다. 그다음 TodoItem.js 에서 아래와 같이 작성합니다.
<button className="ToDoItem-Delete" onClick={() => deleteItem(item.id)}>
-
</button>
부모 구성 요소 내부에 있는 함수를 참조하기 위해 해야 하는 일은 props를 이용하여 deleteItem을 참조해야 합니다. 코드 예제에서는 props.deleteItem 대신 deleteItem을 작성했다는 것을 인지하셨을 것입니다. 그 이유는 Destructuring 이라는 문법을 사용하여 객체나 나 배열에 들어있는 값을 빼낼 수 있기 때문입니다. 아래와 같이 말이죠.
const ToDoItem = (props) => {
const { item, deleteItem } = props;
};
이것은 props.item과 동일한 값을 할당받는 item이라는 변수와 props.deleteItem에서 값을 할당받는 deleteItem이라는 두 개의 변수를 생성했습니다. 단순히 props.item과 props.deleteItem을 사용할 수도 있지만, Destructuring을 사용하여 코드를 줄일 수 있었습니다.
Vue.
How did vue do That?
function onDeleteItem(id) {
list.value = list.value.filter((item) => item.id !== id);
}
뷰에서는 다르게 여러가지 방향으로 접근할 수 있습니다. 총 3가지로 나누어 설명드리겠습니다.
첫번째로 element 안에서 우리가 원하는 함수를 호출하는 것입니다.
<button class="ToDoItem-Delete" @click="deleteItem(item.id)">-</button>
더 단순하게 작성하면 다음과 같습니다.
<button class="ToDoItem-Delete" @click="emit("delete", item.id)">
-
</button>
그 다음 emit 함수를 사용하여 자식 컴포넌트의 함수에서 호출하는 것입니다. 아래와 같이 말이죠
function deleteItem(id) {
emit("delete", id);
}
ToDo.vue 내부에 ToDoItem.vue를 추가할 때 실제로 해당 함수를 참조한다는 것을 알 수 있습니다. 아래와 같이 말이죠.
<ToDoItem v-for="item in list" :item="item" @delete="onDeleteItem" :key="item.id" />
이것은 커스텀 이벤트 리스너로 알려져 있습니다. 'delete' 가 발동되는 모든 경우를 계속 지켜봅니다. 리스너가 작동을 하면 onDeleteItem이라는 함수를 자동으로 발동시킵니다. 사실 아래의 함수는 ToDoItem.vue가 아니라 ToDo.vue 내부에 있습니다. 이 함수는 앞에서 나열한 것처럼 단순히 list.value 배열에서 id를 필터링합니다.
function onDeleteItem(id) {
list.value = list.value.filter((item) => item.id !== id);
}
간단히 말해서, React의 자식 구성 요소는 props를 통해 부모 기능에 접근 할 수 있는 반면, Vue에서는 부모 요소 내부에 속해있는 이벤트 함수를 자식 요소에서 호출을 시켜야 해당 함수에 접근할 수 있습니다.
How do we pass event listeners?
React.
클릭 이벤트와 같은 간단한 이벤트 리스너는 간단합니다. 다음은 새 할 일 항목을 생성하는 버튼에 대한 클릭 이벤트를 생성한 방법의 예입니다.
<button className="ToDo-Add" onClick="{createNewToDoItem}">+</button>
보시다시피 바닐라 JS로 인라인 onClick을 처리하는 방법과 거의 비슷합니다. keyPress 이벤트를 처리하는 것도 마찬가지입니다.
<input
type="text"
placeholder="I need to..."
value="{toDo}"
onChange="{handleInput}"
onKeyPress="{handleKeyPress}"
/>
함수는 아래와 같습니다. 해당 데이터를 입력하고 Enter를 누르면, createNewToDoItem()함수를 호출합니다.
const handleKeyPress = (e) => {
if (e.key === "Enter") {
createNewToDoItem();
}
};
Vue
Vue에서는 더 간단합니다. @ 기호를 사용한 다음 수행하려는 이벤트 유형을 사용하기만 하면 됩니다. 예를 들어 클릭 이벤트 리스너를 추가하려면 다음과 같이 작성할 수 있습니다.
<button class="ToDo-Add" @click="createNewToDoItem">+</button>
@click은 v-on:click의 축약입니다. Vue에서 이벤트 리스너의 좋은점은 모든 이벤트 함수를 체이닝할 수 있다는 점입니다. 예를 들어 Enter 버튼을 누를 때마다 새로운 ToDo 항목을 생성하기 위해 React에서는 이벤트 리스너를 생성하기 위해 여러가지를 처리해줘야 했던 것과 달리 Vue에서는 다음과 같이 간단하게 작성할 수 있습니다.
<input type=”text” v-on:keyup.enter=”createNewToDoItem”/>
How do we pass data through to a child component?
React
React에서는 아래와 같이 자식 컴포넌트로 해당 데이터를 전달할 수 있습니다.
<ToDoItem key="{item.id}" item="{item}" deleteItem="{deleteItem}" />
위에 코드에서는 2개의 props를 ToDoItem 컴포넌트로 전달하였습니다. 자식 컴포넌트에서는 this.props를 사용하여 해당 데이터에 접근할 수 있습니다. 예를 들어 item.todo에 접근하기 위해서는 props.item을 호출합니다. 동시에 key prop을 가져야 한다는 경고를 받게 될 것입니다. 이는 React의 내부를 위한 것입니다. 동일한 구성 요소간 업데이트를 하고 이를 추적할 때 작업을 더 쉽게 수행하도록 만들어줘야합니다. 여기서 key는 그 역할을 합니다. 만약 key prop을 무시한다면 React는 우리에게 경고 콘솔을 띄울 것입니다.
Vue
Vue에서는 생성된 지점에서 자식 컴포넌트에 props를 전달합니다.
<ToDoItem v-for="item in list" :item="item" @delete="onDeleteItem" :key="item.id" />
그 다음 props:["todo"] 와 같이 자식 컴포넌트의 props 배열에 전달합니다. 그런 다음 이름으로 해당 항목을 참조할 수 있습니다. 따라서 이 경우에는 todo입니다. 해당 prop 키를 어디에 배치해야 하는지 확실하지 않은 경우를 위해 export default를 사용하여 아래와 같이 정리하여 사용합니다.
export default {
name: "ToDoItem",
props: ["item"],
setup(props, { emit }) {
function deleteItem(id) {
emit("delete", id);
}
return {
deleteItem,
};
},
};
당신이 알아야 할 한가지는 Vue에서 데이터를 반복할 때 실제로는 list.value가 아닌 list를 반복한다는 것입니다. list.value를 통해 루프를 시도하면 작동하지 않습니다.
How do we emit data back to a parent component?
React.
리액트에서는 다음과 같습니다. 자식 컴포넌트에서는 부모의 함수에 접근하기 위해 props를 이용하였습니다. 그런 다음 props.someFunction 또는 someFunction를 참조하여 onClick과 같은 방법으로 자식에 대한 함수 호출을 추가합니다. 추가를 하게 되면 부모 컴포넌트에서 해당 함수를 트리거 할 것입니다. 예시를 보고 싶다면 'How do we delet from thd list' 목차를 참고해 주세요.
Vue.
뷰에서는 다음과 같습니다. 자식 컴포넌트에서 해당 함수에서 emit이라는 함수를 추가하여 부모 컴포넌트로 올려 보냅니다. 다음 부모함수에서는 emit 함수를 계속 바라보는 함수를 정의함으로써 자식 함수가 호출되면 지켜보는 부모함수에서 트리거 될 수 있도록 합니다. 예시를 보고 싶다면 'How do we delet from thd list' 목차를 참고해 주세요.
And There we have it
우리는 Vue와 React에서 추가, 삭제 그리고 데이터 수정에 대하여 살펴보고 부모 컴포넌트에서 어떻게 자식 컴포넌트로 데이터를 전달하는지, 반대로 자식 컴포넌트에서 부모 컴포넌트로 어떻게 데이터를 전달할 수 있는지에 대하여 살펴보았습니다. 물론 예상한 대로 Vue와 React의 문법은 달랐지만, 핵심적인 내용은 비슷하였습니다. 해당 글이 많은 도움이 되었으면 좋겠습니다.
긴 글 읽어주셔서 감사합니다.
< 참고자료 >
[사이트] #medium
https://sunilsandhu.com/posts/i-created-the-exact-same-app-in-react-and-vue-2020-edition
I created the exact same app in React and Vue. Here are the differences. [2020 Edition]
I created the exact same app in React and Vue. Here are the differences. [2020 Edition]: 2020 Edition: Now with React Hooks vs Vue 3 + Composition API! by Sunil Sandhu
sunilsandhu.com
I created the exact same app in React and Vue. Here are the differences end
'Language & Framework & Library > React' 카테고리의 다른 글
Design Patterns - Compound component pattern (0) | 2023.10.21 |
---|---|
Re-rendering과 memoization (2) | 2022.03.13 |
컴포넌트 Re-rendering을 피하는 5가지 방법 (0) | 2022.03.13 |
React의 컴포넌트 수명주기 (0) | 2021.02.12 |
Virtual DOM (0) | 2020.01.16 |