Вопрос: Каковы самые слабые места 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".
Комментарии (14)
 - edward_nsk Автор20.03.2018 01:50- Всё зависит от области применения. Ваши варианты в определенных условиях будут предпочтительнее. 
 
 Вариант с интерфейсами удобен, когда не сам проектируешь компоненты, а сдираешь чужой js-код.
 Кроме того, бывает полезно видеть результат компиляции из TypeScript в JavaScript без посредников. Декоратор несколько искажает картину.
 - XaveScor20.03.2018 11:43- Хорошо. Вы типизировали js код. А что делать с шаблонами? Ваша типизация никак не влияет на связи между компонентами. И это огромная проблема сейчас. Потому что это затрудняет рефакторинг, а так же просто может дать сломаться приложению в рантайме по недосмотру кого-то из разработчиков. 
 Да, лучше это, чем ничего. Но говорить, что это строгая типизация — это очень большая натяжка. - edward_nsk Автор20.03.2018 12:42- Надежная связь между ts-кодом компоненты и его представленим (шаблоном) — это частный случай общей проблемы поддержания взаимосвязей между моделью и представлением. 
 
 Например, те же проблемы есть в приложениях WPF или XamarinForms. XAML-файлы представления тоже можно ненароком сломать. И здесь тоже определенные ошибки проявятся только в рантайм.
 
 На 100% эту проблему никто ещё не решил нормально. Поэтому рефакторинг объектов, которые участвуют в биндинге — везде вызывает затруднения.
 
 На уровне привязки ts-кода Vue-компоненты к его шаблону особых проблем нет.
 Обычно это решается соглашением по именам (для AppGrid templateId=«app-grid-template», для DemoGrid templateId=«demo-grid-template»).
 
 На уровне привязки (биндинга) атрибутов компоненты к определенным элементам представления, естественно можно нарваться на ошибки в рантайм.
 
 Тут только грамотное тестирование сможет помочь. По-моему, Vue.js ругается вполне осмысленно в случае отсутствия нужных тэгов или сломанного биндинга.
 
 Но ведь строгая типизация, в любом случае, помогает улучшать код. Поэтому её надо применять в той части, в которой она работает. - DarthVictor20.03.2018 16:47- Вообще-то в TSX решили еще в первой версии TypeScript. И в шаблонах ангуляра вроде тоже.  - edward_nsk Автор20.03.2018 17:16- Про TSX и ангруляр ничего не скажу. 
 
 К этому ещё можно добавить Razor-рендеринг в приложениях Asp.Net Core MVC.
 Razor-синтаксис позволяет жестко связать модель и представление.
 Но здесь одно лечат, другое — калечат.
 
 
 
- JSmitty20.03.2018 13:01- Очень хорошо, что кто-то пишет на русском про TS применительно к Vue.js. Но остальное… 
 
 Про «строгую» типизацию в TS, где есть any и нет soundness — вам уже сказали.
 
 Построение — мне лично кажется что class MyComponent extends FrameworkName {… } — семантически кривой конструкт. Сравните с:
 class MyComponent extends React.Component {… }
 
 Извините, но от кода просто кровь из глаз. Как будто ES6 прошел мимо, к чему такая многословность?
 
 Сам Vue.js очень «волшебный» фреймворк, а вы ему еще магии добавляете. Официальный стайл-гайд требует, чтобы для фреймворка явно описывались типы свойств компонента. У вас — массив. Исправляем, делаем явно — и получаем дубликат описаний, то есть еще хуже.
 
 Вот так вообще писать нельзя, динамически меняющиеся свойства статикой упихиваем в data секцию:
 - data: function () { var sortOrders: any = {}; (this.$props as DemoGridProps).columns.forEach(function (key) { sortOrders[key] = 1 }) return { sortKey: '', sortOrders: sortOrders } as DemoGridData }
 
 Для этой цели есть секция computed. - edward_nsk Автор20.03.2018 14:28- При написании tutotial я не ставил себе задачу написать идеальный пример. 
 Я выбрал официальный пример известного автора: Evan You.
 Затем постарался минимизировать изменения в исходном коде.
 Чтобы не потерять узнаваемость.
 Так что: "так вообще писать нельзя" — это к автору :).
 - Что касается кривости кода — посмотрите на цитаты от создателя Vue.js в начале статьи. В них содержится ответ. 
 Vue.js изначально был плохо заточен под TypeScript.
 Например, дублирование определений props неизбежно даже при использовании vue-class-component.
 Поэтому и приходится использовать "костыли".
 Есть варианты "костылей", которые выглядят получше, чем использованный мной.
 - Описанный мной вариант с интерфейсами, мне лично, экономит массу времени и нервов при изучении потрохов самого Vue.js, а также "чужих" компонент, которые я подбираю для использования в своих приложениях. 
 Обычно сам пример мне не нужен, поэтому тратиться на него нет смысла.
 Главное — быстро заставить работать подходящий пример использования "нужной" компоненты.
 А потом ставишь точки остановки и шаришься по коду "нужной" компоненты и Vue.js.
 - В подобных применениях будет удобнее, как мне кажется, подход с явным определением интерфейсов для входных и выходных данных Vue-компоненты. 
 Не требуется "включать мозги" и разбираться во внутренней логике работы "чужого" примера.
 - Всё зависит от поставленных целей. 
  - edward_nsk Автор20.03.2018 14:54- Кстати, первый пример, который я использую в этой статье, тоже практически официальный. 
 Переход с официального сайта typescriptlang.org, выбрать Vue.js.
 Автор примера: DanielRosenwasser — Program Manager of TypeScript.
 - Развве может он писать на TypeScript криво? - JSmitty20.03.2018 16:30+1- А что, имя автора кода как-то гарантирует, что конкретный его код — хороший? Если он не очень знаком с фреймворком, ожидаемо что он может писать криво. Сразу из стартера — общепринято бить по рукам, если data является объектом, а не функцией, возвращающей объект. Впрочем остальное в стартере выглядит опрятно. 
 
 Образец на vuejs.org, куда вы ссылаетесь — лохматого года, и явно не Typescript. Вы его портируете, и остальные лохматости оставляете как есть. Причём там есть извиняющая причина (не факт, что браузер свежий), а у вас её нет (т.к. компилятор TS обязателен).
 
 К слову, Эван, при всём к нему уважении, зачастую пишет/выкладывает невменяемый код. С документированием кода во всей Vue.js экосистеме (включая сам фреймворк) очень плохо всё. Как пример, можно смотреть на vuex. И сравнить его с redux. Оцените например как реализован vuex хелпер mapGetters (то, что вчера пришлось смотреть). - edward_nsk Автор20.03.2018 16:54+1- Полностью с вами согласен. Имя автора ничего не гарантирует. Но зато, при сильных наездах, легче отмазаться :). 
 - Насчет "явно не TypeScript": 
 - Образец на vuejs.org, куда вы ссылаетесь — лохматого года, и явно не Typescript. Вы его портируете, и остальные лохматости оставляете как есть. - И я про тоже самое говорю: 
 - На широких просторах Интернета можно найти массу качественных примеров и готовых приложений, использующих Vue.js. Но подавляющее большинство этих примеров написано на JavaScript. - Зачастую, при попытке причесать "лохматости" что-нибудь отваливалось. Поэтому я и стараюсь оставлять "как есть". 
  - edward_nsk Автор21.03.2018 10:52- Официальный стайл-гайд требует, чтобы для фреймворка явно описывались типы свойств компонента. У вас — массив. Исправляем, делаем явно — и получаем дубликат описаний, то есть еще хуже. - Массив использовать опасно ещё по одной причине — у компилятора TypeScript частично отключается контроль типов. 
 - Теоретически оба варианта определения - props, которые приведены ниже, должны работать одинаково. И это действительно так. Даже получаемый на выходе JavaScript код одинаковый.
 - ... export default Vue.extend({ template: '#demo-grid-template', props: ['rows', 'columns', 'filterKey'], //props: { rows: Array, columns: Array, filterKey: String }, ... });
 - Но обнаруживается неприятный баг-фича в тайпинге Vue.js. 
 - Если - propsопределены как массив, то для компилятора TypeScript перечисленные свойства становятся легальными.
 Например, компилятор спокойно скушает использование- this.filterKey.
 Перечисленные в- propsсвойства даже в IntelliSense появляются при использовании VS2017.
 - А если определить - propsявно с указанием типов, то компилятор будет материться на тот же- this.filterKey.
 - Вот такой баг-фича. 
 
 
 
 - RouR20.03.2018 19:18- Была похожая проблема связать TS и Vue. Надо было написать свои однофайловые компоненты, и само приложение целиком на TS. 
 - Ваш способ мне не нравится. 
 Вот что у меня получилось (пример)- <template> <div> <hr/> MyDebug: <div class="row"> <div class="col-lg-6"> <pre>{{order }}</pre> </div> </div> </div> </template> <script lang="ts"> import Vue from "vue"; import {Component, Prop, Emit} from 'vue-property-decorator' import {Order} from "models/Order"; @Component export default class MyDebug extends Vue { @Prop() order: Order; mounted() { } // https://alligator.io/vuejs/typescript-class-components/ // https://github.com/kaorun343/vue-property-decorator } </script> <style lang="css" scoped> </style> - edward_nsk Автор20.03.2018 20:51- Меня тоже не устраивает описанный мной вариант для использования в продакшн. Только для тестирования и прототипирования. 
 
 В продакшн используем декоратор vue-class-component.
 
 
           
 
serf
Сразу скажу с Vue не работаю, но сказать что имею. Разве явный каст
во множестве мест не нивелирует преимущества строгой типизации? Вот подумайте, чтобы сделать каст, вам нужно знать во что кастить в том или ином случае, и так в каждом участке кода, ошибиться с таким кастом очень легко, создается видимость что типизация есть, когда в полноценном виде ее нет. То есть по хорошему типы для props и data нужно бы при их объявлении указать один раз и дальше никогда каст/as не делать. Вот vue-class-component пропаганирует подобный подход, но не ваш пример. И например, абстрактно, с дженериками это могло бы выглядеть как-то такРазве нельзя использовать интерфейсы совместно с vue-class-component (прямо в декораторе, вместо инлайн описания типа)?
PS если уж хочется делать явный каст, то почему не один раз, например так (и можно развить идею, вынести метод cast в базовый класс и сделать его generic, тогда DemoGridData/DemoGridProps фигурировали бы только одни раз в шапке класса как generic параметры):И далее использовать как-то так: