Skip to content

[Tech Review] Plans for the Next Iteration of Vue.js

jykim edited this page Dec 5, 2018 · 1 revision

Plans for the Next Iteration of Vue.js

*은 아직 구현되지 않았거나, 알아보는 단계에 있습니다.
코드 펜스에 작성된 샘플 코드와 **은 이해를 돕기 위한 예제 및 추가 설명입니다.

다음 글은 Plans for the Next Iteration of Vue.js를 번역했습니다.

High-Level API Changes

Render Function API와 Scoped-slots 문법을 제외한 나머지는 동일하게 유지되거나 2.x와 호환이 가능하게 구현된다.

새로운 Major 업데이트이기 때문에, 몇 가지 큰 변화가 있을 것입니다. 하지만 우리는 이전 버전과의 호환성을 중요하게 여기므로, 가능한 빨리 이런 변화들을 공유하고자 합니다. 아래는 현재 계획 중인 public API의 변경 내용입니다.

  • <template> 문법은 99%가 기존과 동일할 것입니다. scoped-slot 문법에는 약간의 변화가 있을 수 있지만, 이외에 다른 것을 변경할 계획은 없습니다.
<!-- Scoped-Slot Sample Code 1 -->
<!-- ParentComponent.vue -->
<template>
  <div>
    <child-component>
      <p slot-scope="defaultSlotScope">
        {{ defaultSlotScope.text }}
        <!-- Renders <p>I'll get rendered inside the default slot.</p> -->
      </p>
      <p slot="literally-the-best" slot-scope="bestSlotScope">
        {{ bestSlotScope.text }}
         <!-- Renders <p>I'll get rendered inside the *best* slot.</p> -->
      </p>
    </child-component>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  }
}
</script>
<!-- Scoped-Slot Sample Code 2 -->
<!-- ChildComponent.vue -->
<template>
  <div>
    <p>Look, there's a slot below me!</p>
    <slot :text="defaultSlotText"></slot>
    <slot name="literally-the-best" :text="namedSlotText"></slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      defaultSlotText: "I'll get rendered inside the default slot.",
      namedSlotText: "I'll get rendered inside the *best* slot."
    }
  }
}
</script>
  • 3.0은 클래스 기반 컴포넌트를 기본적으로 지원합니다. 이는 Transpilation 또는 Stage-X 기능이 필요 없이 기본 ES6에서 사용하기 좋은 API를 제공하기 위함입니다. 대부분 현재 제공되는 옵션들은 클래스 기반 API와 적절한 매핑을 가질 것입니다. class fields와 decorators와 같은 Stage-X 기능을 선택적으로 사용하여 개발 환경을 향상시킬 수 있습니다. 또한, 3.0 API는 TypeScript의 Type 추구를 염두해두고 설계되었습니다. 3.X 코드베이스는 그 자체가 TypeScript로 작성될 것이며, 이는 Vue.JS의 TypeScript의 개선된 서포트를 제공할 것입니다.
  1. Transpile 어떤 특정 언어로 작성된 소스 코드를 다른 소스 코드로 변환하는 것을 의미합니다. EVUI 프로젝트는 Babel을 사용하고 있습니다.**
  2. Stage-X Babel에서 제공하는 Transpile Preset으로 es2015, stage-0, stage-1, stage-2, stage-3, react가 있습니다. es2015는 es6를 es5로 변환하는데 필요한 설정이 포함되어 있고, state-X는 ECMAScript 표준을 여러 단계로 나눠 놓은 것입니다. Stage 0 - Strawman, Stage 1 - Proposal, Stage 2 - Draft, Stage 3 - Candidate, Stage 4 - Finished로 나누어 지고 숫자가 적은 단계가 상위 단계를 모두 포함하고 있습니다. React는 JSX 변환을 의미합니다. 다만 이들을 모두 한 번에 사용하도록 처리하는 preset은 babel-preset-env입니다.**
  • 2.X의 객체 기반 컴포넌트 포맷은 내부적으로 해당 객체에 상응하는 클래스로 변환함으로써 계속 지원됩니다.
<!--Vue 2.X Object-based Component-->
<template>
  <div/>
</template>
<script>
  export default {
    name: 'ExemVue',
    data() {
      return {
        exem: 'exem',
      };
    },
  };
</script>
<style scoped>
</style>
  • Mixins는 계속 지원될 것입니다.*

  • 플러그인이 설치될 때 Vue runtime을 전역으로 변경되는 것을 피하기 위해 최상위 API들이 검사 받을 수 있습니다. 대신 플러그인들이 적용되고 컴포넌트 트리로 범위가 지정됩니다. 이렇게 하면 특정 플러그인에 의존하는 컴포넌트를 쉽게 테스트 할 수 있을 뿐만 아니라, 다른 플러그인이 설치된 Vue runtime을 사용하여 동일한 페이지에 여러 개의 Vue 애플리케이션을 마운트할 수 있습니다.*

  • Functional Components는 이제 기본적인 기능이 될 것입니다. 하지만 비동기 컴포넌트는 helper function을 통해 명시적으로 만들어야 합니다.

    Async Component는 대규모 애플리케이션에서 컴포넌트를 작게 나누고 필요할 때만 로드해야할 때 사용됩니다. 권장되는 방법은 Webpack의 Code Splitting 기능과 함께 사용하는 것입니다.**

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 컴포넌트 정의를 resolve 콜백에 전달합니다.
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
});
  • 가장 많이 변경될 부분은 render function에 사용되는 Virtual DOM 포맷입니다. 현재 주요 라이브러리 개발자들로부터 피드백을 받고 있습니다. 자세한 내용을 공유할 예정이지만, 여러분의 애플리케이션에서 따로 직접 작성된 render function에 크게 의존하지 않는 한 합리적이고 간단한 프로세스일겁니다. (JSX는 제외)
<div id="app"></div>
<script>
  new Vue({
    el: '#app',
    data: {
      name: "EXEM"
    },
    render(h) { 
      return h('div', {
        attrs: { id: 'people' },
        class: 'sideBar'
      }, "EXEM and EVUI") 
    }
    // Adding JSX with the help of Babel Plugin.
    // render(h) {
    //	return (
    //    <div id="people" class="sideBar">EXEM and EVUI</div>
    //  )
  	// }
  });
</script>

Source Code Architecture

더 좋게 분리된 내부 모듈, 타입스크립트, 그리고 기여하기 쉬워진 코드베이스

더 깨끗하고 유지보수가 용이한 구조를 위해 3.0 코드를 처음부터 다시 작성하고 있으며, 특히 쉽게 기여할 수 있도록 노력하고 있습니다. 복잡성의 범위를 분리시키기 위해 일부 내부 기능을 개별 패키지로 분리하고 있습니다. 예를 들어, observer 모듈은 자체 공통 API 및 테스트와 함께 자체 패키지가 될 것입니다. Vue를 사용하기 위해 여러 패키지로부터 개별적인 부분들을 수동으로 가져올 필요가 없을 것이며, 이것이 프레임워크 API에 영향을 주지 않을 것입니다. 대신에, 최종 Vue 패키지는 이런 내부 패키지들을 사용하여 만들어졌습니다.

코드베이스는 이제 TypeScript로 개발되었습니다. 비록 이러한 새로운 코드베이스에 기여하기 위한 전제조건으로 타입스크립트의 숙련도가 필요 하겟지만, 타입 정보와 IDE 지원이 새로운 기여자가 의미있는 기여를 하는 것을 더 쉽도록 도와줄 것이라 믿습니다.

observer와 scheduler를 별도의 패키지로 분리하면 이런 모듈들의 대체 구현을 더욱 쉽게 할 수 있게 됩니다. 예를 들어, 동일한 API로 IE11에 호환되는 observer 구현이 가능하거나, 긴 업데이트 중에 브라우저에게 전송할 수 있도록requestIdleCallback를 활용하여 scheduler를 대체합니다.*

Observation Mechanism

보다 완벽하고, 정밀하며, 효율적이고, 디버깅이 가능한 반응형 시스템의 트래킹과 관측 대상을 생성하기 위한 API.

Vue.js에서 사용되는 Reactivity System은 Observer Pattern을 이용하고 있습니다. Observer Pattern은 Youtube 채널의 구독하기와 비슷합니다. 채널의 정보가 변경될 때마다 Client App으로 알림이 오기 때문에 바로 확인할 수 있습니다. 채널 상태의 변경을 내가 직접 확인하는 것이 아니라, Youtube에서 구독자 리스트를 가지고 변경 사항이 있을 때, 구독자에게 Client App을 통해 전송하는 방식과 동일합니다.**

여기서 상태를 가지고 있는 Youtube 채널이 주체 객체 혹은 Observable이라 하고, 상태 정보를 필요로 하는 구독자를 Observer라고 합니다.**

3.0은 Proxy 기반 Observer 구현체와 함께 제공됩니다. 이는 자바스크립트 전체에 대한 Reactivity 트래킹을 제공합니다. Object.defineProperty 기반으로 구현된 Vue 2.X대의 제약이 사라집니다.

  • property의 추가 제거에 대한 탐지
  • Array의 index 값 변경과 .length 값 변경에 대한 탐지
  • Map, Set, WeakMap과 WeakSet에 대한 지원
/**** Reactivity System Example ****/
let data = { price: 5, quantity: 2 }
let target = null

// This is exactly the same Dep class
class Dep {
  constructor () {
    this.subscribers = [] 
  }
  depend() {  
    if (target && !this.subscribers.includes(target)) {
      // Only if there is a target & it's not already subscribed
      this.subscribers.push(target)
    } 
  }
  notify() {
    this.subscribers.forEach(sub => sub())
  }
}

// Go through each of our data properties
Object.keys(data).forEach(key => {
  let internalValue = data[key]

  // Each property gets a dependency instance
  const dep = new Dep()

  Object.defineProperty(data, key, {
    get() {
      dep.depend() // <-- Remember the target we're running
      return internalValue
    },
    set(newVal) {
      internalValue = newVal
      dep.notify() // <-- Re-run stored functions
    }
  })
})

// My watcher no longer calls dep.depend,
// since that gets called from inside our get method.
function watcher(myFunc) {
  target = myFunc
  target()
  target = null
}

watcher(() => {
  data.total = data.price * data.quantity
})

// Open a console window and input below code.
data.total // 10
data.price = 20
data.total // 40
data.quantity = 3
data.total // 60

위 코드에서 알 수 있듯, 기존 Vue Reactivity System은 Object.defineProperty기반으로 최초에 생성되는 data의 property에 대해 Dependency를 설정하기 때문에, 동적으로 들어오는 Property에 대한 Reactivity가 동작하지 않는다.**

let data_without_proxy = data; // Save old data object
data = new Proxy(data_without_proxy, {
	// Override data to have a proxy in the middle
	get(obj, key) {
		deps.get(key).depend(); // <-- Remember the target we're running
		return obj[key]; // call original data
	},
	set(obj, key, newVal) {
		obj[key] = newVal; // Set original data to new value
		deps.get(key).notify(); // <-- Re-run stored functions
		return true;
	}
});

위 코드는 Proxy를 사용한 대체코드이며, Proxy는 객체의 변화에 대해서 감지가 가능하며, 원본 데이터를 훼손하지 않는다는 장점이 있습니다.**

새로운 Observer는 다음과 같은 특징이 있습니다.

  • Observable을 생성하기 위한 공개된 API. 이는 중소 규모의 시나리오를 위해, 가볍고 간단한 cross-component state management 솔루션을 제공합니다.
  • Lazy observation이 기본으로 적용됩니다. 2.X에서는 반응형 데이터의 크기에 상관없이 Vue가 시작될 때 관찰을 했습니다. 이는 Dataset이 거대한 경우 App의 시작 시 눈에 띄는 오버헤드를 발생시킬 수 있습니다. 3.X에서는, App을 최초에 렌더링 하는데 사용되는 데이터만 관찰하며, 관찰 속도 자체 또한 훨씬 빨라집니다.
  • 보다 정확한 변경 알림. 2.X에서는 Vue.set을 사용하여 새로운 Property를 강제로 추가하면 모든 watcher가 객체를 재 평가하여 Dependency를 만듭니다. 3.X에서는 특정 Property에 대한 watcher만 변경을 알립니다.
  • 변경할 수 없는 Observebles. 시스템이 일시적으로 내부에서 Lock 해제할 때를 제외하고 중첩된 Property에서도 Mutation을 방지하는 "Immutable" 버전의 Observables를 만들 수 있습니다. 이 메카니즘은 전달된 Props 또는 Vuex의 State Tree를 밖에서 mutation 하는 것을 막는데 사용할 수 있습니다.
  • 더 좋아진 디버깅 기능: 새로운 renderTracked 및 renderTriggered Hook을 사용하여 컴포넌트의 Re-Render를 추적하거나 트리거 되는 시기와 이유를 정확하게 찾아낼 수 있습니다.

Other Runtime Improvements

더 작고, 빠르고, 트리쉐이킹이 가능해집니다. fragments & portals를 지원하며, custom render API가 개선됩니다.

  • Samller: 새로운 코드베이스는 처음부터 트리쉐이킹이 쉽도록 설계 되었습니다. 내장 컴포넌트(<transition>. <keep-alive>)와 디렉티브 런타임 헬퍼(v-model) 같은 기능은 이제 즉시 가져오거나 트리쉐이킹이 가능합니다. 새로운 Rumtime에 대한 기본 사이즈는 gzip 형태의 10kb 미만입니다. 게다가 트리쉐이크 기능이 활성화되고 또한 사용하지 않는 사용자에게 페이로드 패널티을 부과하지 않고도 더 많은 내장 기능을 제공할 수 있게 해줍니다.

  • Faster: 예비 밴치마크에서 우리는 Virtual DOM의 mouting과 patching, 컴포넌트 초기화와 데이터 관측을 포함하여 전반적으로 100% 향상된 성능을 확인했습니다. (우리는 Inferno에서 몇 가지 트릭을 배웠고, 그것은 가장 빠른 Virtual DOM 구현 방법입니다.) 3.0은 당신의 앱이 부팅될 때 자바스크립트에서 소모되었던 시간의 절반을 단축할 것입니다.

  • Fragments & Portals: 크기가 줄어들었음에도 불구하고 3.0에서는 Fragments(여러 개의 최상위 노드를 반환하는 컴포넌트)와 포탈(내부 컴포넌트 대신에 DOM의 또다른 부분에 하위 트리를 렌더링)이 기본적으로 지원됩니다.

<!-- fragments examples -->

<!-- vue 2.x: don't use -->
<template>
  <div>A</div>
  <div>B</div>
</template>

<!-- child.js -->
<script>
  export default {
    functional: true,
    render: h => [
      h('div', 'Maxgauge'),
      h('div', 'Intermax')
    ];
  });
</script>

<!-- main.js -->
<script>
  import Child from "child.js";
  new Vue({
    el: '#app',
    template: `<div id="app">
                  <child>
                  </child>
                </div>`,
    components: {
      Child
    }
  });
</script>

<!-- vue 3.0: can use -->
<template>
  <div>A</div>
  <div>B</div>
</template>
/*** portal examples***/
<!-- https://linusborg.github.io/portal-vue -->

<div id="app" v-cloak>
  <div class="container">
    <card class="item left">
      <p>We can easily fill this sidemenu from the components on the right</p>
      <portal-target name="sidebar"></portal-target>      
    </card>

    <card class="item right">
      <span><a href="#" @click.preven="show = 'A'">Show Component A</a></span> | 
      <span><a href="#" @click.preven="show = 'B'">Show component B</a></span>
      <component-a v-if="show === 'A'"></component-a>
      <component-b v-if="show === 'B'"></component-b>
    </card>
  </div>
</div>

<template id="A">
  <div>
    <p>This is the content for Componen A that should be displayed here, on the right side</p>
    <portal to="sidebar">
      <ul>
        <li>and this list</li>
        <li>came from Component A</li>
        <li>on the right side</li>
      </ul>
    </portal>
  </div>
</template>

<template id ="B">
  <div>
    <p>The content for Componen B will be displayed on the here as well...</p>
    <portal to="sidebar">
      <div>
        ...and Component B can send something different to the sidebar!
      </div>
    </portal>
  </div>
</template>
  • Improved slots mechanism: 컴파일러에서 생성되는 모든 slot은 이제 함수이며 자식 컴포넌트들의 render 호출 중에 호출됩니다. slot의 종속성 보장이 상위 대신 하위에 대한 종속성으로 수집됩니다.

이러한 의미는 다음과 같습니다

  1. slot의 내용이 변경되었을 때, 하위만 다시 렌더링됩니다.
  2. 상위가 다시 렌더링 되었을 때, 만약 slot의 내용이 변경되지 않았다면 하위는 렌더링되지 않아도 됩니다.

이 변경은 컴포넌트 트리 수준에서 훨씬 더 정확한 변경 감지를 제공하므로 쓸데없는 재 렌더링을 줄입니다.

  • Custom Renderer 생성을 위해 일류 API를 이용할 수 있으며, 더 이상 사용자 정의 수정과 함께 Vue 코드베이스 기반을 분리하지 않아도 됩니다. 이것은 WeexNativeScript Vue 같은 프로젝트가 업스트림 변경사항을 최신 상태로 유지하는 것이 훨씬 쉬워지게 되며, 또한 다양한 용도로 사용자 정의 렌더러를 만드는 것이 쉬워지게 해줍니다.

Compiler Improvement

tree-shaking에 친숙한 결과물, 더 좋은 AOT(Ahead-of-Time) 컴파일러 최적화, Source Map 지원과 더 나은 에러 정보를 제공하는 파서

  • tree-shaking이 가능한 번들러를 타겟으로 할 때, 선택적 기능을 사용하는 Template은 ES 모듈 구문을 사용하는 기능을 import하여 코드를 생성합니다. 사용되지 않는 선택적 기능은 최적 번들에서 제외됩니다.
  • 새로운 Virtual DOM 구현체의 개선으로 인해, 보다 효율적인 컴파일 타임 최적화를 수행할 수 있습니다. 예를 들어, 정적 트리 호이스팅, 정적 속성 호이스팅, 자식의 정규화를 스킵하기 위한 런타임 시 컴파일러 힌트, VNode 생성에 대한 빠른 경로 등이 있습니다.
/*** Current Render Function ***/
render(h) { 
	return h('div', {
		attrs: {
				id: 'company'
		}, 
		class: 'company-class'
	}, 'Exem')
}
<!-- Static Tree Hoisting -->
<template>
	<div>
    <span class="foo">Static</span>
    <span>{{ dynamic }}</span>
  </div>
</template>
<script>
	// compiler output
  // skip patching entire trees
  // works even with multiple occurrences
  const __static1 = h('span', { class: 'foo' }, 'static');
  render() {
    return h('div', [__static1, h('span', this.dynamic)])
  }
</script>
<!-- Static Props Hoisting -->
<template>
	<div id="foo" class="bar">
    {{ text }}
  </div>
</template>

<script>
	// compiler output
  // skip patching the node itself, but keep patching children
  const __props1 = {
    id: 'foo',
    class: 'bar',
  };
  
  render() {
    render h('div', __props1, this.next)
  }
</script>
  • 템플릿 컴파일 에러의 위치를 제공하도록 파서를 다시 개발했습니다. 이것은 또한 템플릿 소스 맵을 지원해야 하고, 새로운 파서는 eslint-plugin-vue 및 IDE 언어 서비스와 같은 써드파티 도구 통합의 기반이 될 수 있습니다.

IE 11 Support *

IE 11은 계속 지원되지만, Vue 2.X의 반응형에 대한 한계로 빌드가 분리됩니다.

How Do We Get There

아직 정확한 일정은 없습니다. 우리가 현재 정해둔 건 우리가 3.0에 가기 위해 취해야할 단계들입니다.

  1. 런타임 프로토타입을 위한 내부 피드백 (현재 단계)
  2. RFCs를 통한 공개 피드백
  3. 2.x 및 2.x-next에서 호환 가능한 기능의 소개 (https://github.com/vuejs/roadmap)
    2.x의 마지막 릴리즈는 LTS가 되어 3.0 릴리즈 이후 18개월 동안 버그 및 보안에 대한 수정을 계속 이뤄갑니다.
  4. Alpha Phase
    3.0 컴파일러와 서버 측 렌더링 부분을 마무리 한 뒤 안정성 테스트의 목적으로 알파 릴리즈를 개발합니다.
  5. Beta Phase
    Vue Router, Vuex, Vue CLI, Vue DevTools 등을 업데이트 하고 코어와의 동작을 확인합니다. 또한 주요 라이브러리 개발자들과 함께 3.0 개발을 준비합니다.
  6. RC Phase
    API와 코드베이스가 안정적이라고 판단되면 API 개발을 끝내고 RC에 진입합니다.
  7. IE11 build
    3.0 마지막 릴리즈 이전에 IE11 호환성 빌드를 만듭니다.
  8. Final Release
    솔직히 말해 이것이 언제 될 지 아직 알지 모르지만, 2019년에 완료될 가능성이 있습니다. 다시 말하지만, 특정 날짜를 맞추기보다는 견고하고 안정된 것을 배포하는 것에 더 신경을 많이 쓰고 있습니다. 끝내야 할 일이 많이 있지만, 우리는 하나 하나의 단계에 흥분하고 있습니다.

추가적으로 참고하시면 도움이 될 문서

Evan You Previews Vue.js 3.0

문서 작성에 참고한 URL

https://linusborg.github.io/portal-vue/#/guide
https://vuemastery.com
https://vuejsdevelopers.com/2018/09/11/vue-multiple-root-fragments/