Vue 3의 주문형 반응성
게시 됨: 2022-03-11놀라운 성능 개선 외에도 최근에 출시된 Vue 3에는 몇 가지 새로운 기능이 추가되었습니다. 틀림없이 가장 중요한 소개는 Composition API 입니다. 이 기사의 첫 번째 부분에서는 더 나은 코드 구성 및 재사용이라는 새로운 API에 대한 표준 동기를 요약합니다. 두 번째 부분에서는 Vue 2의 반응성 시스템에서 표현할 수 없는 반응성 기반 기능을 구현하는 것과 같이 새 API를 사용할 때 논의되지 않은 측면에 중점을 둘 것입니다.
이것을 주문형 반응성 이라고 합니다. 관련 새 기능을 도입한 후 Vue 반응성 시스템의 새로운 표현력을 보여주기 위해 간단한 스프레드시트 애플리케이션을 빌드합니다. 맨 끝에서 우리는 이러한 개선된 주문형 반응성이 실제로 어떤 용도로 사용되는지 논의할 것입니다.
Vue 3의 새로운 기능과 중요한 이유
Vue 3은 Vue 2의 주요 재작성 버전으로, 이전 API와 거의 완전히 이전 버전과의 호환성을 유지하면서 많은 개선 사항을 도입했습니다.
Vue 3의 가장 중요한 새 기능 중 하나는 Composition API 입니다. 그것의 도입은 처음 공개적으로 논의되었을 때 많은 논란을 불러일으켰습니다. 새로운 API에 대해 아직 익숙하지 않은 경우를 대비하여 먼저 그 이면에 있는 동기를 설명하겠습니다.
코드 구성의 일반적인 단위는 키가 구성 요소 조각의 다양한 가능한 유형을 나타내는 JavaScript 개체입니다. 따라서 객체는 반응 데이터( data
)에 대한 섹션 하나, 계산된 속성에 대한 다른 섹션( computed
), 구성 요소 메서드에 대한 섹션( methods
) 등을 가질 수 있습니다.
이 패러다임에서 구성 요소는 내부 작동이 앞서 언급한 구성 요소 섹션에 분산되어 있는 여러 관련되지 않거나 느슨하게 관련된 기능을 가질 수 있습니다. 예를 들어 파일 관리와 업로드 상태 애니메이션을 제어하는 시스템이라는 두 가지 본질적으로 별개의 기능을 구현하는 파일 업로드 구성 요소가 있을 수 있습니다.
<script>
부분에는 다음과 같은 내용이 포함될 수 있습니다.
export default { data () { return { animation_state: 'playing', animation_duration: 10, upload_filenames: [], upload_params: { target_directory: 'media', visibility: 'private', } } }, computed: { long_animation () { return this.animation_duration > 5; }, upload_requested () { return this.upload_filenames.length > 0; }, }, ... }
코드 구성에 대한 이 전통적인 접근 방식에는 이점이 있습니다. 주로 개발자는 새 코드를 작성할 위치에 대해 걱정할 필요가 없습니다. 반응 변수를 추가하는 경우 data
섹션에 삽입합니다. 기존 변수를 찾고 있다면 data
섹션에 있어야 한다는 것을 알고 있습니다.
기능 구현을 섹션( data
, computed
등)으로 분할하는 전통적인 접근 방식은 모든 상황에 적합하지 않습니다.
다음과 같은 예외가 자주 인용됩니다.
- 많은 기능을 가진 구성 요소 다루기. 예를 들어 애니메이션 시작을 지연시키는 기능으로 애니메이션 코드를 업그레이드하려면 코드 편집기에서 구성 요소의 모든 관련 섹션 간에 스크롤/점프해야 합니다. 파일 업로드 컴포넌트의 경우 컴포넌트 자체가 작고 구현하는 기능의 수도 적습니다. 따라서 이 경우 섹션 간 이동은 실제로 문제가 되지 않습니다. 이 코드 조각화 문제는 우리가 큰 구성 요소를 다룰 때 관련이 있습니다.
- 전통적인 접근 방식이 부족한 또 다른 상황은 코드 재사용입니다. 종종 우리는 하나 이상의 구성 요소에서 사용할 수 있는 반응 데이터, 계산된 속성, 메서드 등의 특정 조합을 만들어야 합니다.
Vue 2(및 이전 버전과 호환되는 Vue 3)는 대부분의 코드 구성 및 재사용 문제에 대한 솔루션을 제공합니다: mixins .
Vue 3에서 Mixin의 장단점
믹스인을 사용하면 구성 요소의 기능을 별도의 코드 단위로 추출할 수 있습니다. 각 기능은 별도의 믹스인에 배치되며 모든 구성 요소는 하나 이상의 믹스인을 사용할 수 있습니다. 믹스인에 정의된 조각은 마치 컴포넌트 자체에 정의된 것처럼 컴포넌트에서 사용할 수 있습니다. mixin은 주어진 기능과 관련된 코드를 수집한다는 점에서 객체 지향 언어의 클래스와 약간 비슷합니다. 클래스와 마찬가지로 믹스인은 다른 코드 단위에서 상속(사용)될 수 있습니다.
그러나 믹스인으로 추론하는 것은 클래스와 달리 캡슐화를 염두에 두고 설계할 필요가 없기 때문에 더 어렵습니다. 믹스인은 외부 세계에 대한 잘 정의된 인터페이스 없이 느슨하게 바인딩된 코드 조각 모음이 될 수 있습니다. 동일한 구성 요소에서 한 번에 둘 이상의 믹스인을 사용하면 이해하고 사용하기 어려운 구성 요소가 생성될 수 있습니다.
대부분의 객체 지향 언어(예: C# 및 Java)는 객체 지향 프로그래밍 패러다임에 이러한 복잡성을 처리할 수 있는 도구가 있음에도 불구하고 다중 상속을 권장하지 않거나 허용하지 않습니다. (C++와 같은 일부 언어는 다중 상속을 허용하지만 구성이 상속보다 여전히 선호됩니다.)
Vue에서 믹스인을 사용할 때 발생할 수 있는 보다 실용적인 문제는 공통 이름을 선언하는 둘 이상의 믹스인을 사용할 때 발생하는 이름 충돌 입니다. 이름 충돌을 처리하기 위한 Vue의 기본 전략이 주어진 상황에서 이상적이지 않은 경우 개발자가 전략을 조정할 수 있다는 점에 유의해야 합니다. 이는 더 많은 복잡성을 도입하는 대가를 치르게 됩니다.
또 다른 문제는 믹스인이 클래스 생성자와 유사한 것을 제공하지 않는다는 것입니다. 이것은 다른 구성 요소에 존재하기 위해 매우 유사하지만 정확히 동일 하지는 않은 기능이 종종 필요하기 때문에 문제입니다. 이것은 mixin 팩토리를 사용하여 몇 가지 간단한 경우에 우회할 수 있습니다.
따라서 믹스인은 코드 구성 및 재사용에 이상적인 솔루션이 아니며 프로젝트가 클수록 문제가 더 심각해집니다. Vue 3는 코드 구성 및 재사용과 관련된 동일한 문제를 해결하는 새로운 방법을 소개합니다.
구성 API: 코드 구성 및 재사용에 대한 Vue 3의 답변
구성 API를 사용하면 구성 요소의 조각을 완전히 분리할 수 있습니다(필수는 아님 ). 변수, 계산된 속성, 시계 등 모든 코드 조각은 독립적으로 정의할 수 있습니다.
예를 들어, (기본값) 값이 "playing"인 키 animation_state
를 포함하는 data
섹션을 포함하는 객체를 갖는 대신 이제 다음과 같이 작성할 수 있습니다(JavaScript 코드의 모든 위치).
const animation_state = ref('playing');
효과는 일부 구성 요소의 data
섹션에서 이 변수를 선언하는 것과 거의 같습니다. 유일한 본질적 차이점은 컴포넌트 외부에 정의된 ref
를 사용하려는 컴포넌트에서 사용할 수 있도록 만들어야 한다는 것입니다. 구성 요소가 정의된 위치로 모듈을 가져와 구성 요소의 setup
섹션에서 ref
를 반환하여 이를 수행합니다. 지금은 이 절차를 건너뛰고 잠시 동안 새 API에 집중하겠습니다. Vue 3의 반응성에는 구성 요소가 필요하지 않습니다. 그것은 실제로 독립적인 시스템입니다.
이 변수를 가져오는 모든 범위에서 animation_state
변수를 사용할 수 있습니다. ref
를 구성한 후 ref.value
를 사용하여 실제 값을 가져오고 설정합니다. 예를 들면 다음과 같습니다.
animation_state.value = 'paused'; console.log(animation_state.value);
할당 연산자가 (비반응성) 값 "일시 중지"를 변수 animation_state
에 할당하기 때문에 '.value' 접미사가 필요합니다. JavaScript의 반응성(Vue 2에서와 같이 defineProperty
를 통해 구현될 때와 Vue 3에서와 같이 Proxy
를 기반으로 하는 경우 모두)에는 반응형으로 작업할 수 있는 키를 가진 객체가 필요합니다.
이것은 Vue 2에서도 마찬가지였습니다. 거기에는 반응 데이터 멤버( component.data_member
)에 대한 접두사로 구성 요소가 있었습니다. JavaScript 언어 표준이 할당 연산자를 오버로드하는 기능을 도입하지 않는 한 반응 표현식은 개체와 키(예: 위와 같은 animation_state
및 value
)가 우리가 원하는 할당 작업의 왼쪽에 나타나도록 요구합니다. 반응성을 보존합니다.
Vue는 템플릿 코드를 사전 처리해야 하고 참조를 자동으로 감지할 수 있으므로 템플릿에서 .value
를 생략할 수 있습니다.
<animation :state='animation_state' />
이론적으로 Vue 컴파일러는 SFC(Single File Component)의 <script>
부분도 비슷한 방식으로 사전 처리하여 필요한 곳에 .value
를 삽입할 수 있습니다. 그러나 refs
의 사용은 SFC를 사용하는지 여부에 따라 다르므로 아마도 그러한 기능은 바람직하지 않을 수 있습니다.
때로는 완전히 다른 인스턴스로 대체할 의도가 없는 엔티티(예: Javascript 객체 또는 배열)가 있습니다. 대신 키 필드 수정에만 관심이 있을 수 있습니다. 이 경우 속기가 있습니다. ref
대신 .value
reactive
생략할 수 있습니다.
const upload_params = reactive({ target_directory: 'media', visibility: 'private', }); upload_params.visibility = 'public'; // no `.value` needed here // if we did not make `upload_params` constant, the following code would compile but we would lose reactivity after the assignment; it is thus a good idea to make reactive variables ```const``` explicitly: upload_params = { target_directory: 'static', visibility: 'public', };
ref
및 react를 사용하여 분리된 reactive
은 Vue 3의 완전히 새로운 기능이 아닙니다. Vue 2.6에서 부분적으로 도입되었으며, 여기서 반응성 데이터의 분리된 인스턴스를 "관찰 가능"이라고 합니다. 대부분의 경우 Vue.observable
을 reactive
로 대체할 수 있습니다. 차이점 중 하나는 Vue.observable
에 직접 전달된 객체에 액세스하고 변경하는 것은 반응적인 반면 새 API는 프록시 객체를 반환하므로 원래 객체를 변경해도 반응적인 효과가 없다는 것입니다.

Vue 3에서 완전히 새로운 것은 컴포넌트의 다른 반응성 부분이 이제 반응성 데이터 외에도 독립적으로 정의될 수 있다는 것입니다. 계산된 속성은 예상된 방식으로 구현됩니다.
const x = ref(5); const x_squared = computed(() => x.value * x.value); console.log(x_squared.value); // outputs 25
마찬가지로 다양한 유형의 시계, 수명 주기 메서드 및 종속성 주입을 구현할 수 있습니다. 간결함을 위해 여기에서는 다루지 않습니다.
Vue 개발에 표준 SFC 접근 방식을 사용한다고 가정합니다. 데이터, 계산된 속성 등에 대한 별도의 섹션이 있는 기존 API를 사용할 수도 있습니다. Composition API의 작은 반응성을 SFC와 통합하려면 어떻게 해야 합니까? Vue 3는 이를 위한 또 다른 섹션인 setup
을 소개합니다. 새 섹션은 새로운 수명 주기 메서드로 생각할 수 있습니다(다른 후크 이전, 특히 created
되기 전에 실행).
다음은 기존 접근 방식을 Composition API와 통합하는 완전한 구성 요소의 예입니다.
<template> <input v-model="x" /> <div>Squared: {{ x_squared }}, negative: {{ x_negative }}</div> </template> <script> import { ref, computed } from 'vue'; export default { name: "Demo", computed: { x_negative() { return -this.x; } }, setup() { const x = ref(0); const x_squared = computed(() => x.value * x.value); return {x, x_squared}; } } </script>
이 예에서 제거해야 할 사항:
- 이제 모든 구성 API 코드가
setup
되었습니다. 각 기능에 대해 별도의 파일을 만들고, 이 파일을 SFC로 가져오고,setup
에서 원하는 반응성 비트를 반환할 수 있습니다(나머지 구성 요소에서 사용할 수 있도록). - 동일한 파일에서 새로운 접근 방식과 기존 접근 방식을 혼합할 수 있습니다.
x
는 참조일지라도 템플릿 코드 또는computed
와 같은 구성 요소의 기존 섹션에서 참조할 때.value
가 필요하지 않습니다. - 마지막으로 중요한 것은 템플릿에 두 개의 루트 DOM 노드가 있다는 것입니다. 여러 루트 노드를 갖는 기능은 Vue 3의 또 다른 새로운 기능입니다.
Vue 3에서 반응성이 더 많이 표현됨
이 기사의 첫 번째 부분에서 우리는 코드 구성 및 재사용을 개선하는 Composition API에 대한 표준 동기를 다루었습니다. 사실, 새로운 API의 주요 판매 포인트는 그것의 힘이 아니라 그것이 가져오는 조직적 편리성, 즉 코드를 보다 명확하게 구조화하는 능력입니다. 컴포지션 API가 믹스인과 같은 기존 솔루션의 한계를 피하는 구성 요소를 구현하는 방법을 가능하게 하는 것이 전부인 것처럼 보일 수 있습니다.
그러나 새 API에는 더 많은 것이 있습니다. 구성 API는 실제로 더 잘 조직화될 뿐만 아니라 더 강력한 반응 시스템을 가능하게 합니다. 핵심 요소는 애플리케이션에 반응성을 동적으로 추가하는 기능입니다. 이전에는 구성 요소를 로드하기 전에 모든 데이터, 모든 계산된 속성 등을 정의해야 했습니다. 나중 단계에서 반응형 개체를 추가하는 것이 왜 유용한가요? 나머지 부분에서 더 복잡한 예인 스프레드시트를 살펴보겠습니다.
Vue 2에서 스프레드시트 만들기
Microsoft Excel, LibreOffice Calc 및 Google Sheets와 같은 스프레드시트 도구에는 모두 일종의 반응 시스템이 있습니다. 이러한 도구는 A–Z, AA–ZZ, AAA–ZZZ 등으로 인덱싱된 열과 숫자로 인덱싱된 행이 있는 테이블을 사용자에게 제공합니다.
각 셀에는 일반 값이나 수식이 포함될 수 있습니다. 수식이 있는 셀은 기본적으로 값이나 기타 계산된 속성에 따라 달라질 수 있는 계산된 속성입니다. 표준 스프레드시트를 사용하면(Vue의 반응성 시스템과 달리) 이러한 계산된 속성이 자체적으로 종속될 수도 있습니다! 이러한 자체 참조는 반복 근사를 통해 원하는 값을 얻는 일부 시나리오에서 유용합니다.
셀의 내용이 변경되면 해당 셀에 종속된 모든 셀이 업데이트를 트리거합니다. 추가 변경 사항이 발생하면 추가 업데이트가 예약될 수 있습니다.
Vue로 스프레드시트 애플리케이션을 구축한다면 Vue 자체 반응성 시스템을 사용하여 스프레드시트 앱의 엔진으로 만들 수 있는지 묻는 것이 당연할 것입니다. 각 셀에 대해 편집 가능한 원시 값과 해당 계산 값을 기억할 수 있습니다. 계산된 값은 일반 값이면 원시 값을 반영하고, 그렇지 않으면 계산된 값은 일반 값 대신 작성된 표현식(수식)의 결과입니다.
Vue 2에서 스프레드시트를 구현하는 방법은 raw_values
가 문자열의 2차원 배열이고 computed_values
가 셀 값의 (계산된) 2차원 배열입니다.
적절한 Vue 구성 요소가 로드되기 전에 셀 수가 적고 고정되어 있으면 구성 요소 정의에 있는 테이블의 모든 셀에 대해 하나의 원시 값과 하나의 계산 값을 가질 수 있습니다. 그러한 구현이 야기하는 미학적 기괴함을 제외하고, 컴파일 타임에 고정된 수의 셀이 있는 테이블은 아마도 스프레드시트로 계산되지 않을 것입니다.
2차원 배열 computed_values
에도 문제가 있습니다. 계산된 속성은 항상 이 경우 평가가 자체에 의존하는 함수입니다(일반적으로 셀 값을 계산하려면 이미 계산된 다른 값이 필요합니다). Vue가 자체 참조 계산 속성을 허용하더라도 단일 셀을 업데이트하면 모든 셀이 다시 계산됩니다(종속성 여부에 관계없이). 이것은 매우 비효율적일 것입니다. 따라서 Vue 2로 원시 데이터의 변경 사항을 감지하기 위해 반응성을 사용하게 될 수도 있지만 반응성 측면에서 다른 모든 것은 처음부터 구현해야 합니다.

Vue 3에서 계산된 값 모델링하기
Vue 3를 사용하면 모든 셀에 대해 새로운 계산 속성을 도입할 수 있습니다. 테이블이 커지면 새로운 계산 속성이 도입됩니다.
A1
및 A2
셀이 있고 A2
가 값이 숫자 5인 A1
의 제곱을 표시하기를 원한다고 가정합니다. 이 상황의 스케치는 다음과 같습니다.
let A1 = computed(() => 5); let A2 = computed(() => A1.value * A1.value); console.log(A2.value); // outputs 25
이 간단한 시나리오에 잠시 머물러 있다고 가정해 보겠습니다. 여기에 문제가 있습니다. 숫자 6을 포함하도록 A1
을 변경하려면 어떻게 해야 합니까? 다음과 같이 작성한다고 가정합니다.
A1 = computed(() => 6); console.log(A2.value); // outputs 25 if we already ran the code above
이것은 단순히 A1
의 값 5 를 6 으로 변경한 것이 아닙니다. 변수 A1
은 이제 완전히 다른 ID를 갖습니다. 계산된 속성은 숫자 6으로 해석됩니다. 그러나 변수 A2
는 여전히 변수 A1
의 이전 ID 변경에 반응합니다. 따라서 A2
는 A1
을 직접 참조해서는 안 되며 컨텍스트에서 항상 사용할 수 있고 현재 A1
이 무엇인지 알려주는 특별한 개체를 참조해야 합니다. 즉, 포인터와 같은 A1
에 액세스하기 전에 간접 참조 수준이 필요합니다. Javascript에는 1급 엔터티로서의 포인터가 없지만 시뮬레이션하기 쉽습니다. pointer
가 value
을 가리키도록 하려면 pointer = {points_to: value}
개체를 만들 수 있습니다. 포인터를 리디렉션하는 것은 pointer.points_to
에 할당하는 것과 같고 역참조(가리킨 값에 액세스하는 것)는 pointer.points_to
의 값을 검색하는 것과 같습니다. 우리의 경우 다음과 같이 진행합니다.
let A1 = reactive({points_to: computed(() => 5)}); let A2 = reactive({points_to: computed(() => A1.points_to * A1.points_to)}); console.log(A2.points_to); // outputs 25
이제 5를 6으로 대체할 수 있습니다.
A1.points_to = computed(() => 6); console.log(A2.points_to); // outputs 36
Vue의 Discord 서버에서 사용자 redblobgames 는 계산된 값을 사용하는 대신 일반 함수를 래핑하는 참조를 사용하는 또 다른 흥미로운 접근 방식을 제안했습니다. 이렇게 하면 참조 자체의 ID를 변경하지 않고 함수를 유사하게 교환할 수 있습니다.
스프레드시트 구현에는 일부 2차원 배열의 키로 참조되는 셀이 있습니다. 이 배열은 우리가 필요로 하는 간접적인 수준을 제공할 수 있습니다. 따라서 우리의 경우 추가 포인터 시뮬레이션이 필요하지 않습니다. 원시 값과 계산 값을 구분하지 않는 하나의 배열을 가질 수도 있습니다. 모든 것이 계산된 값일 수 있습니다.
const cells = reactive([ computed(() => 5), computed(() => cells[0].value * cells[0].value) ]); cells[0] = computed(() => 6); console.log(cells[1].value); // outputs 36
그러나 원시 값을 HTML 입력 요소에 바인딩할 수 있기를 원하기 때문에 원시 값과 계산된 값을 구별하고 싶습니다. 또한 원시 값에 대한 별도의 배열이 있는 경우 계산된 속성의 정의를 변경할 필요가 없습니다. 원시 데이터를 기반으로 자동으로 업데이트됩니다.
스프레드시트 구현
대부분의 경우 자체 설명이 가능한 몇 가지 기본 정의부터 시작하겠습니다.
const rows = ref(30), cols = ref(26); /* if a string codes a number, return the number, else return a string */ const as_number = raw_cell => /^[0-9]+(\.[0-9]+)?$/.test(raw_cell) ? Number.parseFloat(raw_cell) : raw_cell; const make_table = (val = '', _rows = rows.value, _cols = cols.value) => Array(_rows).fill(null).map(() => Array(_cols).fill(val)); const raw_values = reactive(make_table('', rows.value, cols.value)); const computed_values = reactive(make_table(undefined, rows.value, cols.value)); /* a useful metric for debugging: how many times did cell (re)computations occur? */ const calculations = ref(0);
계획은 모든 computed_values[row][column]
가 다음과 같이 계산되도록 하는 것입니다. raw_values[row][column]
이 =
로 시작하지 않으면 raw_values[row][column raw_values[row][column]
을 반환합니다. 그렇지 않으면 수식을 구문 분석하고 JavaScript로 컴파일하고 컴파일된 코드를 평가하고 값을 반환합니다. 간단히 말해서 구문 분석 공식으로 약간의 속임수를 사용하고 여기에서는 컴파일 캐시와 같은 몇 가지 명백한 최적화를 수행하지 않을 것입니다.
사용자가 수식으로 유효한 JavaScript 표현식을 입력할 수 있다고 가정합니다. A1, B5 등과 같이 사용자의 표현에 나타나는 셀 이름에 대한 참조를 실제 셀 값(계산된)에 대한 참조로 대체할 수 있습니다. 다음 함수는 셀 이름과 유사한 문자열이 실제로 항상 셀을 식별한다고 가정하고 이 작업을 수행합니다(그리고 일부 관련 없는 JavaScript 표현식의 일부가 아님). 단순화를 위해 열 인덱스가 단일 문자로 구성되어 있다고 가정합니다.
const letters = Array(26).fill(0) .map((_, i) => String.fromCharCode("A".charCodeAt(0) + i)); const transpile = str => { let cell_replacer = (match, prepend, col, row) => { col = letters.indexOf(col); row = Number.parseInt(row) - 1; return prepend + ` computed_values[${row}][${col}].value `; }; return str.replace(/(^|[^AZ])([AZ])([0-9]+)/g, cell_replacer); };
transpile
함수를 사용하여 셀 참조가 있는 JavaScript의 작은 "확장"으로 작성된 표현식에서 순수한 JavaScript 표현식을 얻을 수 있습니다.
다음 단계는 모든 셀에 대해 계산된 속성을 생성하는 것입니다. 이 절차는 모든 셀의 수명 동안 한 번 발생합니다. 원하는 계산 속성을 반환하는 팩토리를 만들 수 있습니다.
const computed_cell_generator = (i, j) => { const computed_cell = computed(() => { // we don't want Vue to think that the value of a computed_cell depends on the value of `calculations` nextTick(() => ++calculations.value); let raw_cell = raw_values[i][j].trim(); if (!raw_cell || raw_cell[0] != '=') return as_number(raw_cell); let user_code = raw_cell.substring(1); let code = transpile(user_code); try { // the constructor of a Function receives the body of a function as a string let fn = new Function(['computed_values'], `return ${code};`); return fn(computed_values); } catch (e) { return "ERROR"; } }); return computed_cell; }; for (let i = 0; i < rows.value; ++i) for (let j = 0; j < cols.value; ++j) computed_values[i][j] = computed_cell_generator(i, j);
위의 모든 코드를 setup
메소드에 {raw_values, computed_values, rows, cols, letters, calculations}
을 반환해야 합니다.
아래에서는 기본 사용자 인터페이스와 함께 전체 구성 요소를 제공합니다.
코드는 GitHub에서 사용할 수 있으며 라이브 데모도 확인할 수 있습니다.
<template> <div> <div>Calculations: {{ calculations }}</div> <table class="table" border="0"> <tr class="row"> <td></td> <td class="column" v-for="(_, j) in cols" :key="'header' + j" > {{ letters[j] }} </td> </tr> <tr class="row" v-for="(_, i) in rows" :key="i" > <td class="column"> {{ i + 1 }} </td> <td class="column" v-for="(__, j) in cols" :key="i + '-' + j" :class="{ column_selected: active(i, j), column_inactive: !active(i, j), }" @click="activate(i, j)" > <div v-if="active(i, j)"> <input :ref="'input' + i + '-' + j" v-model="raw_values[i][j]" @keydown.enter.prevent="ui_enter()" @keydown.esc="ui_esc()" /> </div> <div v-else v-html="computed_value_formatter(computed_values[i][j].value)"/> </td> </tr> </table> </div> </template> <script> import {ref, reactive, computed, watchEffect, toRefs, nextTick, onUpdated} from "vue"; export default { name: 'App', components: {}, data() { return { ui_editing_i: null, ui_editing_j: null, } }, methods: { get_dom_input(i, j) { return this.$refs['input' + i + '-' + j]; }, activate(i, j) { this.ui_editing_i = i; this.ui_editing_j = j; nextTick(() => this.get_dom_input(i, j).focus()); }, active(i, j) { return this.ui_editing_i === i && this.ui_editing_j === j; }, unselect() { this.ui_editing_i = null; this.ui_editing_j = null; }, computed_value_formatter(str) { if (str === undefined || str === null) return 'none'; return str; }, ui_enter() { if (this.ui_editing_i < this.rows - 1) this.activate(this.ui_editing_i + 1, this.ui_editing_j); else this.unselect(); }, ui_esc() { this.unselect(); }, }, setup() { /*** All the code we wrote above goes here. ***/ return {raw_values, computed_values, rows, cols, letters, calculations}; }, } </script> <style> .table { margin-left: auto; margin-right: auto; margin-top: 1ex; border-collapse: collapse; } .column { box-sizing: border-box; border: 1px lightgray solid; } .column:first-child { background: #f6f6f6; min-width: 3em; } .column:not(:first-child) { min-width: 4em; } .row:first-child { background: #f6f6f6; } #empty_first_cell { background: white; } .column_selected { border: 2px cornflowerblue solid !important; padding: 0px; } .column_selected input, .column_selected input:active, .column_selected input:focus { outline: none; border: none; } </style>
실제 사용은 어떻습니까?
Vue 3의 분리된 반응성 시스템이 Vue의 새로운 반응성 메커니즘을 기반으로 하여 더 깨끗한 코드를 가능하게 할 뿐만 아니라 더 복잡한 반응 시스템을 허용하는 방법을 보았습니다. Vue가 도입된 지 약 7년이 지났고 표현력의 향상은 분명히 큰 수요가 없었습니다.
스프레드시트 예제는 Vue가 현재 무엇을 할 수 있는지에 대한 간단한 데모이며 라이브 데모를 확인할 수도 있습니다.
그러나 실제적인 예로서, 그것은 다소 틈새입니다. 어떤 상황에서 새로운 시스템이 유용할 수 있습니까? 주문형 반응성의 가장 확실한 사용 사례는 복잡한 애플리케이션의 성능 향상일 수 있습니다.

많은 양의 데이터로 작업하는 프런트 엔드 응용 프로그램에서 제대로 고려되지 않은 반응성을 사용하는 오버헤드는 성능에 부정적인 영향을 미칠 수 있습니다. 회사의 비즈니스 활동에 대한 대화형 보고서를 생성하는 비즈니스 대시보드 애플리케이션이 있다고 가정합니다. 사용자는 시간 범위를 선택하고 보고서에서 성과 지표를 추가하거나 제거할 수 있습니다. 일부 지표는 다른 지표에 의존하는 값을 표시할 수 있습니다.
보고서 생성을 구현하는 한 가지 방법은 모놀리식 구조를 사용하는 것입니다. 사용자가 인터페이스에서 입력 매개변수를 변경하면 단일 계산 속성(예: report_data
)이 업데이트됩니다. 이 계산된 속성의 계산은 하드코딩된 계획에 따라 발생합니다. 먼저 모든 독립 성과 지표를 계산한 다음 이러한 독립 지표에만 의존하는 지표 등을 계산합니다.
더 나은 구현은 보고서의 비트를 분리하고 독립적으로 계산합니다. 다음과 같은 이점이 있습니다.
- 개발자는 지루하고 오류가 발생하기 쉬운 실행 계획을 하드코딩할 필요가 없습니다. Vue의 반응성 시스템은 종속성을 자동으로 감지합니다.
- 관련된 데이터의 양에 따라 수정된 입력 매개변수에 논리적으로 의존하는 보고서 데이터만 업데이트하므로 상당한 성능 향상을 얻을 수 있습니다.
Vue 구성 요소가 로드되기 전에 최종 보고서의 일부일 수 있는 모든 성능 지표가 알려진 경우 Vue 2로도 제안된 디커플링을 구현할 수 있습니다. 그렇지 않으면 백엔드가 단일 소스(즉, 일반적으로 데이터 기반 응용 프로그램의 경우) 또는 외부 데이터 공급자가 있는 경우 보고서의 모든 부분에 대해 주문형 계산 속성을 생성할 수 있습니다.
Vue 3 덕분에 이것은 이제 가능할 뿐만 아니라 쉽게 할 수 있습니다.