Вопрос: Каковы самые слабые места 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
, а можно просто открыть файл wwwrootindex.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&MinifierUpdate 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", позволяет меньше включать
Кроме того, интерфейсы дают возможность повторного использования определений типов для входных и выходных данных Vue-компонент. Ведь тип входных данных одной компоненты часто совпадает с выходными данными другой.
Благодарности
- Заглавные цитаты взяты из статьи на Хабре: "Создатель Vue.js отвечает Хабру"
- Фото кота с костылем взято здесь.
- При создании примеров частично использовался: "TypeScript Vue Starter".
- При создании примеров использовался: "Grid Component Example".
Автор: Эдуард Щавелев