Вопрос: Каковы самые слабые места Vue?
Oтвет: На данный момент, наверное, недружественность к типизации. Наш API разрабатывался без планирования поддержки типизированных языков (типа TypeScript), но мы сделали большие улучшения в 2.5.
Вопрос: Тони Хор (Tony Hoare) назвал null ошибкой на миллиард долларов. Какое было самое неудачное техническое решение в твоей карьере?
Oтвет: Было бы неплохо использовать TypeScript изначально, еще когда я начал переписывать код для Vue 2.x.
из интервью "Создатель Vue.js отвечает Хабру"
Недружественность Vue.js к типизации вынуждает применять "костыли", чтобы использовать преимущества TypeScript. Один из предлагаемых в официальной документации Vue.js вариантов — это применение декораторов вместе с библиотекой "vue-class-component".
Я применяю другой вариант "костылей" для решения проблемы строгой типизации в приложениях Vue.js (без декораторов и vue-class-component). Через явное определение интерфейсов для опций "data" и "props", используемых в конструкторе экземпляров Vue-компоненты. В ряде случаев это проще и удобнее.
В данном tutorial, для иллюстрации обоих подходов к типизации (с декораторами и без) используется решение Visual Studio 2017 с приложениями Vue.js + Asp.Net Core MVC + TypeScript. Хотя приведенные здесь примеры можно поместить и в другое окружение (Node.js + Webpack).
Попутно демонстрируется, как компоненту на JavaScript быстро переделать под «полноценный» TypeScript с включенной строгой типизацией.
Содержание
Введение
Используемые механизмы
— Включение опций строгой типизации
— Типизация через декораторы
— Типизация через интерфейсы входных и выходных данных
Проект TryVueMvcDecorator
— Тестовое приложение
— Корректировка конфигурации
— Корректировка Index.cshtml
— Переход на декораторы
— Сборка и запуск проекта
Проект TryVueMvcGrid
— Тестовое приложение
— Создание заготовки AppGrid
— Сборка и запуск проекта
— Адаптация под строгую типизацию
Заключение
Введение
Данная статья является продолжением серии статей:
- Приложение Vue.js + Asp.NETCore + TypeScript без Webpack
- RequireJS для приложений Vue.js + Asp.NETCore + TypeScript
- Vue.js + Asp.Net Core MVC + TypeScript и ещё Bootstrap4
В примерах, которые приводились в этих статьях, TypeScript использовался только наполовину — строгая типизация была сознательно отключена. Теперь попробуем перейти к полноценному использованию TypeScript.
На широких просторах Интернета можно найти массу качественных примеров и готовых приложений, использующих Vue.js. Но подавляющее большинство этих примеров написано на JavaScript. Поэтому заталкивание этих примеров в "прокрустово ложе" TypeScript требует некоторых усилий.
API, который предлагается в официальной документации Vue.js, позволяет определить Vue-компоненту на основе классов при помощи официально поддерживаемого декоратора vue-class-component. Использование декораторов требует установки опции компилятора {"experimentalDecorators": true}, что несколько напрягает (есть вероятность существенных изменений в будущих версиях TypeScript). Кроме того, требуется использовать дополнительную библиотеку.
Параноидальное стремление избавляться от "лишних" библиотек привело меня к использованию явного определения интерфейсов для свойств и данных Vue-компонент при решении проблемы строгой типизации в приложениях Vue.js + TypeScript.

В данном tutorial сначала опишем механизмы использования обоих вариантов "костылей", затем создадим 2 проекта: TryVueMvcDecorator, TryVueMvcGrid.
Используемые механизмы
Если исходный код Vue-компонеты, который загоняем в модуль TypeScript, написан на JavaScript, то сначала можно попытаться его откомпилировать, просто отключив все опции компилятора, отвечающие за контроль (по умолчанию они отключены). Затем в работающем коде приложения "закручиваем гайки", путем включения нужных опций с устранением причин ругани компилятора TypeScript.
После включения ряда опций компилятора код Vue-компонент может перестать компилироваться. Т.к. отсутствует явное определение переменных, перечисленных в "data" и "props". Ниже опишем способ решения этой проблемы при помощи декораторов и без них.
Включение опций строгой типизации
Опция {"strict": true} сразу включает множество проверок (noImplicitAny, noImplicitThis, alwaysStrict, strictNullChecks, strictFunctionTypes, strictPropertyInitialization), поэтому бывает полезно включать эти проверки последовательно. Затем можно дополнительно ужесточить контроль, например, включив проверку на наличие неиспользуемых переменных и параметров.
{ "compilerOptions": { ... "experimentalDecorators": true, //"noImplicitAny": true, //"noImplicitThis": true, //"alwaysStrict": true, //"strictNullChecks": true, //"strictFunctionTypes": true, //"strictPropertyInitialization": true, "strict": true, "noUnusedLocals": true, "noUnusedParameters": true }, "include": [ "./ClientApp/**/*.ts" ] }
Постепенное ужесточение контроля компилятора TypeScript ("закручивание гаек") позволяет достаточно быстро включить строгую типизацию, не особенно вникая в логику работы Vue-компоненты.
Типизация через декораторы
Определение Vue-компоненты выглядит похожим на определение класса, но на самом деле — это вызов функции Vue.extend(), которая создает и регистрирует экземпляр объекта Vue с определенными свойствами и методами. Так как определение свойств и методов задаются в параметре вызова функции Vue.extend(), то компилятор TypeScript не всё о них знает.
В приведенном примере подразумевается, что у экземпляра Vue есть свойства: name, initialEnthusiasm, enthusiasm, а также методы: increment(), decrement(), exclamationMarks(). Естественно, компилятор TypeScript может начать ругаться благим матом при попытке включить соответствующие опции контроля типов.
Декоратор vue-class-component позволяет использовать определение Vue-компоненты в виде полноценного класса. Соответственно, появляется возможность определения всех свойств и методов Vue-компоненты в явном виде. А такое компилятор TypeScript вполне нормально переваривает.
// Исходный текст определения Vue-компоненты export default Vue.extend({ template:'#hello-template', props: ['name', 'initialEnthusiasm'], data() { return { enthusiasm: this.initialEnthusiasm } }, methods: { increment() { this.enthusiasm++; }, decrement() { if (this.enthusiasm > 1) { this.enthusiasm--; } } }, computed: { exclamationMarks(): string { return Array(this.enthusiasm + 1).join('!'); } } });
// Текст определения Vue-компоненты с использованием декоратора @Component({ template: '#hello-template', props: ['name', 'initialEnthusiasm'] }) export default class HelloComponent extends Vue { enthusiasm!: number; initialEnthusiasm!: number; data() { return { enthusiasm: this.initialEnthusiasm } }; // methods: increment() { this.enthusiasm++; }; decrement() { if (this.enthusiasm > 1) { this.enthusiasm--; } }; // computed: get exclamationMarks() { return Array(this.enthusiasm + 1).join('!'); } };
Типизация через интерфейсы входных и выходных данных
Применение строгой типизации через определение интерфейсов для свойств и данных основано на следующем моменте: у экземпляров Vue есть соответствующие прокси (this.$props, this.$data).
vm.$data
Объект с данными, над которым экземпляр Vue осуществляет наблюдение. Экземпляр проксирует сюда вызовы своих полей. (Например, vm.a будет указывать на vm.$data.a)
vm.$props
Объект, предоставляющий доступ к текущим входным данным компонента. Экземпляр Vue проксирует доступ к свойствам своего объекта входных данных.
Подробнее смотрите в официальной документации.
Благодаря этому, в приведенном примере для Vue-компоненты получаем: this.initialEnthusiasm эквивалентно this.$props.initialEnthusiasm, а также this.enthusiasm эквивалентно this.$data.enthusiasm. Остается в явном виде определить интерфейсы для свойств и данных, а также обеспечить явное приведение типов при использовании this.$props, this.$data.
// Пример явного определения интерфейсов interface HelloProps { name: string; initialEnthusiasm: number; } interface HelloData { enthusiasm: number; } // Примеры приведения типов при использовании свойств экземпляра Vue ... enthusiasm = (this.$props as HelloProps).initialEnthusiasm; ... var thisData = this.$data as HelloData; if (thisData.enthusiasm > 1) { thisData.enthusiasm--; } ...
Для лучшего понимания применяемого здесь подхода приводим более сложный пример использования интерфейсов для строгой типизации:
// Фрагмент ClientApp/components/DemoGrid.ts interface DemoGridProps { rows: Array<any>; columns: Array<string>; filterKey: string; } interface DemoGridData { sortKey: string; sortOrders: { [index: string]: number }; } export default Vue.extend({ ... computed: { filteredData: function () { var thisData = (this.$data as DemoGridData); var thisProps = (this.$props as DemoGridProps); var sortKey = thisData.sortKey; var filterKey = thisProps.filterKey && thisProps.filterKey.toLowerCase(); var order = thisData.sortOrders[sortKey] || 1; var rows = thisProps.rows; if (filterKey) { rows = rows.filter(function (row) { return Object.keys(row).some(function (key) { return String(row[key]).toLowerCase().indexOf(filterKey) > -1 }) }) } if (sortKey) { rows = rows.slice().sort(function (a, b) { a = a[sortKey] b = b[sortKey] return (a === b ? 0 : a > b ? 1 : -1) * order }) } return rows; } }, ... methods: { sortBy: function (key: string) { var thisData = (this.$data as DemoGridData); thisData.sortKey = key thisData.sortOrders[key] = thisData.sortOrders[key] * -1 } } });
В результате получаем простой способ перехода к строгой типизации — после явного определения интерфейсов свойств и данных, тупо ищем this.someProperty и применяем в этих местах явное приведение типов. Например, this.columns превратится в (this.$props as DemoGridProps).columns.
Проект TryVueMvcDecorator
В данном разделе tutorial создаем приложение Vue.js на TypeScript с вариантом решения проблемы строгой типизации при помощи декторатора "vue-class-component".
Тестовое приложение
В качестве отправной точки для тестового приложения берём на github проект TryVueMvc для Visual Studio 2017. Либо создаем этот проект "с нуля" по предыдущему tutorial Vue.js + Asp.Net Core MVC + TypeScript и ещё Bootstrap4. Сборку и запуск проекта можно произвести в среде VS2017 либо через командную строку в каталоге проекта:
npm install dotnet build dotnet bundle dotnet run
В браузере открываем страницу, адрес которой dotnet сообщает в консоли, например, http://localhost:52643.
Для предпочитающих однофайловые Vue-компонеты и сборку при помощи Webpack, в качестве отправной точки для тестового приложения можно использовать проект TryVueWebpack. Для сборки и запуска приложения, через командную строку в каталоге проекта выполняем следующее:
npm install npm run build
Далее можно также воспользоваться dotnet run, а можно просто открыть файл wwwroot\index.html.
Корректировка конфигурации
В файле tsconfig.json добавить опцию компилятора {"experimentalDecorators": true}.
Добавляем в файл package.json установку NPM-пакета "vue-class-component".
{ "version": "1.0.0", "name": "asp.net", "private": true, "dependencies": { "jquery": "^3.3.1", "popper.js": "^1.12.9", "bootstrap": "^4.0.0", "vue": "^2.5.13", "systemjs": "^0.21.0", "vue-class-component": "^6.2.0" } }
Корректируем bundleconfig.json для обеспечения возможности копирования vue.js и vue-class-component.js из каталога node_modules в wwwroot/vendor.
[ { "outputFileName": "wwwroot/dist/vendor1.js", "inputFiles": [ "node_modules/jquery/dist/jquery.js", "node_modules/popper.js/dist/umd/popper.js", "node_modules/bootstrap/dist/js/bootstrap.js", "node_modules/systemjs/dist/system.src.js" ], "minify": { "enabled": true, "renameLocals": true }, "sourceMap": true }, { "outputFileName": "wwwroot/dist/vendor1.css", "inputFiles": [ "node_modules/bootstrap/dist/css/bootstrap.css" ], "minify": { "enabled": false } }, { "outputFileName": "wwwroot/dist/vendor1.min.css", "inputFiles": [ "node_modules/bootstrap/dist/css/bootstrap.min.css" ], "minify": { "enabled": false } }, { "outputFileName": "wwwroot/vendor/vue.js", "inputFiles": [ "node_modules/vue/dist/vue.js" ], "minify": { "enabled": true, "renameLocals": true }, "sourceMap": true }, { "outputFileName": "wwwroot/vendor/vue-class-component.js", "inputFiles": [ "node_modules/vue-class-component/dist/vue-class-component.js" ], "minify": { "enabled": true, "renameLocals": true }, "sourceMap": true }, { "outputFileName": "wwwroot/dist/main.css", "inputFiles": [ "ClientApp/**/*.css" ], "minify": { "enabled": true } }, { "outputFileName": "wwwroot/dist/app-bandle.min.js", "inputFiles": [ "wwwroot/dist/app-bandle.js" ], "minify": { "enabled": true, "renameLocals": true } }, { "outputFileName": "wwwroot/dist/app-templates.html", "inputFiles": [ "ClientApp/**/*.html" ], "minify": { "enabled": false, "renameLocals": false } } ]
Корректировка Index.cshtml
Так как у нас появилось использование vue-class-component, необходимо сообщить SystemJS откуда грузить эту библиотеку. Для этого модифицируем код Razor-рендеринга в Views/Home/Index.cshtml.
@* Views/Home/Index.cshtml *@ @using Microsoft.AspNetCore.Hosting @inject IHostingEnvironment hostingEnv @{ var suffix = hostingEnv.IsDevelopment() ? "" : ".min"; var vueUrl = $"vendor/vue{suffix}.js"; var vueClassComponentUrl = $"vendor/vue-class-component{suffix}.js"; var mainUrl = $"dist/app-bandle{suffix}.js"; ViewData["Title"] = "TryVueMvc Sample"; } <section id="app-templates"></section> <div id="app-root">loading..</div> @section Scripts{ <script> System.config({ map: { "vue": "@vueUrl", "vue-class-component": "@vueClassComponentUrl" } }); $.get("dist/app-templates.html").done(function (data) { $('#app-templates').append(data); SystemJS.import('@mainUrl').then(function (m) { SystemJS.import('index'); }); }); </script> }
Переход на декораторы
Для перехода на декораторы в нашем приложении достаточно поменять код модулей AppHello.ts и Hello.ts.
// ClientApp/components/AppHello.ts import Vue from "vue"; import Component from "vue-class-component"; import HelloComponent from "./Hello"; @Component({ template: '#app-hello-template', components: { HelloComponent } }) export default class AppHelloComponent extends Vue { data() { return { name: "World" } } };
// ClientApp/components/Hello.ts import Vue from "vue"; import Component from "vue-class-component"; @Component({ template: '#hello-template', props: ['name', 'initialEnthusiasm'] }) export default class HelloComponent extends Vue { enthusiasm!: number; initialEnthusiasm!: number; data() { return { enthusiasm: this.initialEnthusiasm } }; // methods: increment() { this.enthusiasm++; }; decrement() { if (this.enthusiasm > 1) { this.enthusiasm--; } }; // computed: get exclamationMarks() { return Array(this.enthusiasm + 1).join('!'); } };
Если в качестве отправной точки использовался проект TryVueWebpack, то код модулей AppHello.ts и Hello.ts будет немного отличаться.
// ClientApp/components/AppHello.ts import Vue from "vue"; import Component from "vue-class-component"; import HelloComponent from "./Hello.vue"; @Component({ components: { HelloComponent } }) export default class AppHelloComponent extends Vue { data() { return { name: "World" } } };
// ClientApp/components/Hello.ts import Vue from "vue"; import Component from "vue-class-component"; @Component({ props: ['name', 'initialEnthusiasm'] }) export default class HelloComponent extends Vue { enthusiasm!: number; initialEnthusiasm!: number; data() { return { enthusiasm: this.initialEnthusiasm } }; // methods: increment() { this.enthusiasm++; }; decrement() { if (this.enthusiasm > 1) { this.enthusiasm--; } }; // computed: get exclamationMarks() { return Array(this.enthusiasm + 1).join('!'); } };
Сборка и запуск проекта
Сборка и запуск приложения — традиционные для среды VS2017. Бандлинг производится через команду "Bundler&Minifier\Update Bundles" контексного меню на файле bundleconfig.json. Также сборку и запуск можно произвести через командную строку в каталоге проекта. Должны получить что-то подобное изображенному на скриншоте.

Свой результат выполнения описанных действий можете сравнить с проектом TryVueMvcDecorator на github.
Проект TryVueMvcGrid
Теперь создаем приложение Vue.js на TypeScript с вариантом решения проблемы строгой типизации путем явного определения типов для входных (this.$props) и выходных (this.$data) данных Vue-компоненты. На этот раз обходимся без декоратора и дополнительной библиотеки.
Приложение немного усложним, встроив в него пример с официального сайта Vue.js Grid Component Example. Можете посмотреть этот же пример на jsfiddle.
Идем от простого к сложному. Для облегчения понимания разобьём создание AppGrid на четыре этапа:
- подготовка тестового приложения (клонирование TryVueMvc);
- создание скелета приложения AppGrid;
- перенос основного исходного кода примера с официального сайта Vue.js;
- включение опций строго типизации с адаптацией кода приложения.
Тестовое приложение
В качестве отправной точки для тестового приложения, также, как и в предыдущем случае, берём на github проект TryVueMvc для Visual Studio 2017.
Создание заготовки AppGrid
Заменяем приложение AppHello на заготовку (скелет) приложения AppGrid. Для этого меняем содержимое файла ClientApp/index.ts, а вместо старых файлов в папке ClientApp/components создаем заготовки новых компонент: AppGrid, DemoGrid.
// ClientApp/index.ts import Vue from "vue"; import AppGrid from "./components/AppGrid"; new Vue({ el: "#app-root", render: h => h(AppGrid), components: { AppGrid } });
// ClientApp/components/AppGrid.ts import Vue from "vue"; import DemoGrid from "./DemoGrid"; export default Vue.extend({ template: '#app-grid-template', components: { DemoGrid }, data: function () { return { foo: 42 } } });
<!-- ClientApp/components/AppGrid.html --> <template id="app-grid-template"> <div> <h2>AppGrid component</h2> <demo-grid /> </div> </template>
// ClientApp/components/DemoGrid.ts import Vue from "vue"; export default Vue.extend({ template: '#demo-grid-template', props: ['foo'], data: function () { return { bar: 42 } } });
<!-- ClientApp/components/DemoGrid.html --> <template id="demo-grid-template"> <h4>DemoGrid component</h4> </template>
После пересборки и запуска приложения в браузере должно получиться что-то подобное изображенному на скриншоте.

Встраивание примера DemoGrid
Переносим код AppGrid.ts и содержимое шаблона. Производим замену возвращаемого свойства 'gridData' -> 'gridRows', чтобы не путать с data(). Компиляция ts-кода должна пройти нормально даже после включения опций контроля типов, т.к. здесь строгая типизация не требуется.
// ClientApp/components/AppGrid.ts import Vue from "vue"; import DemoGrid from "./DemoGrid"; export default Vue.extend({ template: '#app-grid-template', components: { DemoGrid }, data: function() { return { searchQuery: '', gridColumns: ['name', 'power'], gridRows: [ { name: 'Chuck Norris', power: Infinity }, { name: 'Bruce Lee', power: 9000 }, { name: 'Jackie Chan', power: 7000 }, { name: 'Jet Li', power: 8000 } ] } } });
<!-- ClientApp/components/AppGrid.html --> <template id="app-grid-template"> <div> <form id="search"> Search <input name="query" v-model="searchQuery"> </form> <demo-grid :rows="gridRows" :columns="gridColumns" :filter-key="searchQuery"> </demo-grid> </div> </template>
Переносим код DemoGrid.ts и содержимое шаблона. Производим замену входного свойства 'data' -> 'rows', чтобы не путать с data(). Определение свойств Vue-компоненты переделываем в массив имен (props: ['rows', 'columns', 'filterKey']).
// ClientApp/components/DemoGrid.ts import Vue from "vue"; export default Vue.extend({ template: '#demo-grid-template', props: ['rows', 'columns', 'filterKey'], data: function () { var sortOrders = {} this.columns.forEach(function (key) { sortOrders[key] = 1 }) return { sortKey: '', sortOrders: sortOrders } }, computed: { filteredData: function () { var sortKey = this.sortKey var filterKey = this.filterKey && this.filterKey.toLowerCase() var order = this.sortOrders[sortKey] || 1 var rows = this.rows if (filterKey) { rows = rows.filter(function (row) { return Object.keys(row).some(function (key) { return String(row[key]).toLowerCase().indexOf(filterKey) > -1 }) }) } if (sortKey) { rows = rows.slice().sort(function (a, b) { a = a[sortKey] b = b[sortKey] return (a === b ? 0 : a > b ? 1 : -1) * order }) } return rows } }, filters: { capitalize: function (str) { return str.charAt(0).toUpperCase() + str.slice(1) } }, methods: { sortBy: function (key) { this.sortKey = key this.sortOrders[key] = this.sortOrders[key] * -1 } } });
<!-- ClientApp/components/DemoGrid.html --> <template id="demo-grid-template"> <table> <thead> <tr> <th v-for="key in columns" @click="sortBy(key)" :class="{ active: sortKey == key }"> {{ key | capitalize }} <span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'"> </span> </th> </tr> </thead> <tbody> <tr v-for="entry in filteredData"> <td v-for="key in columns"> {{entry[key]}} </td> </tr> </tbody> </table> </template>
Создаем файл ClientApp/css/demo-grid.css на основе стилей компоненты DemoGrid.
/* ClientApp/css/demo-grid.css */ body { font-family: Helvetica Neue, Arial, sans-serif; font-size: 14px; color: #444; } table { border: 2px solid #42b983; border-radius: 3px; background-color: #fff; margin-top: .5rem; } th { background-color: #42b983; color: rgba(255,255,255,0.66); cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } td { background-color: #f9f9f9; } th, td { min-width: 120px; padding: 10px 20px; } th.active { color: #fff; } th.active .arrow { opacity: 1; } .arrow { display: inline-block; vertical-align: middle; width: 0; height: 0; margin-left: 5px; opacity: 0.66; } .arrow.asc { border-left: 4px solid transparent; border-right: 4px solid transparent; border-bottom: 4px solid #fff; } .arrow.dsc { border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 4px solid #fff; }
Сборка и запуск проекта
Сборка и запуск приложения производится также, как и для проекта TryVueMvcDecorator, описанного ранее. После пересборки и запуска приложения в браузере должно получиться что-то подобное изображенному на скриншоте.

Адаптация под строгую типизацию
Теперь начинаем закручивать гайки. Если попробовать сразу поставить опцию компилятора {"strict": true}, то получим кучу ошибок TypeScript при компиляции.
Как правило, включать контроль лучше поэтапно: включаем одну опцию, устраняем возникшие ошибки, затем делаем тоже самое для следующей опции и т.д.
Для адаптации существующего кода Vue-компоненты под строгую типизацию, в первую очерередь, определяем интерфейсы для входных (props) и выходных данных (data) компоненты.
interface DemoGridProps { rows: Array<any>; columns: Array<string>; filterKey: string; } interface DemoGridData { sortKey: string; sortOrders: { [index: string]: number }; }
Затем ставим опцию компилятора {"noImplicitThis": true} и устраняем ошибки способом, описанным ранее в пункте Типизация через интерфейсы входных и выходных данных.
После установки опции компилятора {"noImplicitAny": true} разбираемся с остальными неопределенными типами. После этого включение {"strict": true} уже ошибок не дает (для нашего примера). Результат адаптации модуля DemoGrid.ts приведен под спойлером.
// ClientApp/components/DemoGrid.ts import Vue from "vue"; interface DemoGridProps { rows: Array<any>; columns: Array<string>; filterKey: string; } interface DemoGridData { sortKey: string; sortOrders: { [index: string]: number }; } export default Vue.extend({ template: '#demo-grid-template', props: ['rows', 'columns', 'filterKey'], //props: { rows: Array, columns: Array, filterKey: String }, data: function () { var sortOrders: any = {}; (this.$props as DemoGridProps).columns.forEach(function (key) { sortOrders[key] = 1 }) return { sortKey: '', sortOrders: sortOrders } as DemoGridData }, computed: { filteredData: function () { var thisData = (this.$data as DemoGridData); var thisProps = (this.$props as DemoGridProps); var sortKey = thisData.sortKey var filterKey = thisProps.filterKey && thisProps.filterKey.toLowerCase() var order = thisData.sortOrders[sortKey] || 1 var rows = thisProps.rows if (filterKey) { rows = rows.filter(function (row) { return Object.keys(row).some(function (key) { return String(row[key]).toLowerCase().indexOf(filterKey) > -1 }) }) } if (sortKey) { rows = rows.slice().sort(function (a, b) { a = a[sortKey] b = b[sortKey] return (a === b ? 0 : a > b ? 1 : -1) * order }) } return rows } }, filters: { capitalize: function (str: string) { return str.charAt(0).toUpperCase() + str.slice(1) } }, methods: { sortBy: function (key: string) { var thisData = (this.$data as DemoGridData); thisData.sortKey = key thisData.sortOrders[key] = thisData.sortOrders[key] * -1 } } });
Свой результат выполнения описанных действий можете сравнить с проектом TryVueMvcGrid на github.
Заключение
У способа определения Vue-компонент через декоратор есть свои преимущества и недостатки. Один из недостатков — необходимость реструктуризации кода, когда работающий пример написан на JavaScript. Что требует большей аккуратности.
Вариант строгой типизации через явное определение интерфейсов для опций "data" и "props", позволяет меньше включать мозги на этапе переноса JavaScript-кода Vue-компонент.
Кроме того, интерфейсы дают возможность повторного использования определений типов для входных и выходных данных Vue-компонент. Ведь тип входных данных одной компоненты часто совпадает с выходными данными другой.
Благодарности
- Заглавные цитаты взяты из статьи на Хабре: "Создатель Vue.js отвечает Хабру"
- Фото кота с костылем взято здесь.
- При создании примеров частично использовался: "TypeScript Vue Starter".
- При создании примеров использовался: "Grid Component Example".
Update 05.06.2019:
На данный момент исходный код примеров на на github немного отличается от приведенного в статье. Изменения вызваны обновлением версий используемых компонент (переход на Asp.NETCore 2.2 и т.д.).
