오상우
오상우

Categories

  • front-end
  • vue
  • web

1. 컴포넌트 개발 방식

기존의 MVC 패턴의 웹 개발 패턴은 페이지를 정적으로 불러오는 형태에는 적합했지만, 웹 어플리케이션이 사용자 경험을 더 중시하게 되면서 한계를 맞이한다. 특히 페이지 전체를 리 렌더링하지 않고 부분적으로 서버와 통신해 리렌더링하는 방식의 경우, 기존의 자바스크립트 코드만으로 DOM 을 수정하는 방식은 점점 문제가 생길 수 밖에 없었다.

그래서 등장한 것이 angular, react, vue 와 같은 자바스크립트 라이브러리이다. 이 라이브러리들의 공통점은 MVC 가 아닌 MVVM 패턴을 구현하기 위해 컴포넌트 단위의 개발 방식을 선택했다는 점이다. 웹 페이지를 하나의 커다란 페이지가 아닌, 작은 컴포넌트들의 조합으로 생각해, 컴포넌트들을 만들고 그것들을 합치는 방식의 개발이 컴포넌트 개발 방식이다. 재사용성, 유지 보수의 간결성 외에도 뷰 모델을 컴포넌트 수준에서 유지/조작할 수 있어 DOM 의 지속적 수정이 훨씬 직관적이고 용이해진다는 장점이 있다. 다만 그렇게 복잡하지 않고, 잦은 DOM 수정이 필요없는 페이지의 경우 얻는 이득에 비해 코드가 복잡해진다는 단점은 있다.

2. props 와 event emitting

vue 에서는 부모 컴포넌트가 자식에게 넘겨주는 것을 props 로 자식이 받을 수 있다.

    <div id="app">
        <ul>
            <students-box :students='students' />
        </ul>
    </div>

    <script>
        Vue.component('student-list', {
            props: {
                student: Object
            },
            template: `<li>{{student.name}}, {{student.age}}세</li>`
        })

        Vue.component('students-box', {
            props: {
                students: Array
            },
            template: `<div><student-list v-for='student in students' :student='student' :key='student.id' /></div>`
        });

        const app = new Vue({
            el: '#app',
            data: {
                students: [
                    {
                        id: '123',
                        name: '안웅장',
                        age: 25
                    }, {
                        id: '456',
                        name: '오상우',
                        age: 24
                    }
                ]
            }
        });
    </script>

위의 코드를 보면 id 가 app 인 최상위 컴포넌트에 students-box 라는 하위 컴포넌트가 있고, 그 밑에 student-list 라는 자식 컴포넌트가 있다. Data 자체는 최상위 컴포넌트가 갖고 있고, props 를 통해 app -> students-box -> 각각의 student-list 로 정보를 내려보내는 모양이다. 이렇게 데이터가 부모에서 자식으로, 단방향으로 흐르는 방식은 vue 뿐만 아니라 react 등의 다른 자바스크립트 라이브러리도 마찬가지이다. 이렇게 데이터가 한방향으로만 흐름으로서 데이터를 관리하기가 훨씬 쉬워지고, 수정 삭제 등이 간편해진다(데이터를 가지고 있는 최상위 컴포넌트만 관리해주면 그것을 전달받는 하위 컴포넌트들은 알아서 바뀐다!).

부모에게서 데이터를 전달받는 것은 이해가 되는데, 그럼 자식이 데이터를 바꾸고 싶어지면 어떡할까?? 리액트에서는 부모에서 정의된 핸들러 함수의 참조값을 props 로 내려주는 방법으로 해결하는데, 뷰에서는 이벤트 emitting 을 통해 이를 해결한다.

    <div id="app">
        <ul>
            <students-box :students='students' @removee='removeStudent' />
        </ul>
    </div>

    <script>
        Vue.component('student-list', {
            props: {
                student: Object
            },
            methods: {
                clickStudent(id) {
                    this.$emit('remove', id);
                }
            },
            template: `<li @click='() => clickStudent(student.id)' >{{student.name}}, {{student.age}}세</li>`
        })

        Vue.component('students-box', {
            props: {
                students: Array
            },
            methods: {
                clickHandler(id) {
                    console.log(id);
                    this.$emit('removee', id);
                }
            },
            template: `<div><student-list v-for='student in students' :student='student' :key='student.id' @remove='clickHandler' /></div>`
        });

        const app = new Vue({
            el: '#app',
            data: {
                students: [
                    {
                        id: '123',
                        name: '안웅장',
                        age: 25
                    }, {
                        id: '456',
                        name: '오상우',
                        age: 24
                    }
                ]
            },
            methods: {
                removeStudent(id) {
                    console.log(id);
                    this.students = this.students.filter(stu => stu.id !== id);
                }
            }
        });
    </script>

위의 코드를 보자. 제일 아래에 있는 student-list 컴포넌트에서 클릭 이벤트가 발생하면 그 카드에 해당하는 student 데이터를 지우려고 한다. 제일 먼저 student-list 컴포넌트에 on-click 이벤트에 bind 된 clickStudent 메소드를 보자. 해당 메소드는 실행된 student 객체의 id 를 인자로 받는다. 그 다음 ‘remove’ 라는 이름의 이벤트를 emit 하면서 id 라는 데이터를 전달한다.
그러면 그 부모인 students-box 컴포넌트가 on-remove 이벤트에 bind 된 clickHandler 메소드를 실행시킨다. 해당 메소드 역시 첫 번째 인자로 id 를 받아서 ‘removee’ 라는 이름의 이벤트를 emit 하며 id 를 전달한다.
마지막으로 루트 컴포넌트가 on-removee 이벤트에 bind 된 removeStudent 메소드를 실행해 자식 컴포넌트에서 전달받은 id 를 이용해 자신의 data 를 수정한다. 그러면 해당 데이터를 사용하던 자식 컴포넌트들이 모두 리렌더링 된다. 이것이 뷰의 데이터 흐름이다.