TypeScript. Начало работы, часть 3 из 7

27.02.2023

Теги: JavaScriptTypeScriptWeb-разработкаТеорияТипыДанных

Настройка проекта в VS Code

1. Настройка проекта на TypeScript

Теперь настроим проект на TypeScript в редакторе VS Code. Директория проекта в корне будет содержать файлы конфигурации + директорию src для исходников и директорию build для скомпилированных файлов. Сначала нужно установить расширение ESlint для форматирования и линтинга кода. Вообще, это расширение для линтинга, но мы можем его настроить и для форматирования с использованием Prettier.

Для начала создаем файл package.json и устанавливаем необходимые пакеты.

$ npm init -y
{
    "name": "typescript-project-one",
    "version": "1.0.0",
    "description": "Настройка проекта на TypeScript в редакторе VS Code",
    "private": true,
    "scripts": {
        "build": "tsc"
    },
    "keywords": [],
    "author": "Evgeniy Tokmakov"
}

Устанавливаем необходимые для проекта пакеты

$ npm install typescript --save-dev # компилятор typescript
$ npm install eslint --save-dev # eslint для линтинга кода
$ npm install prettier --save-dev # prettier для форматирования кода
$ npm install prettier eslint-plugin-prettier --save-dev # чтобы запускать prettier как правило eslint
$ npm install @typescript-eslint/parser --save-dev # чтобы eslint мог анализировать код typescript
$ npm install @typescript-eslint/eslint-plugin --save-dev # правила eslint для линтинга кода typescript
{
    "name": "typescript-project-one",
    "version": "1.0.0",
    "description": "Настройка проекта на TypeScript в редакторе VS Code",
    "private": true,
    "scripts": {
        "build": "tsc"
    },
    "keywords": [],
    "author": "Evgeniy Tokmakov",
    "devDependencies": {
        "@typescript-eslint/eslint-plugin": "^5.53.0",
        "@typescript-eslint/parser": "^5.53.0",
        "eslint": "^8.35.0",
        "eslint-plugin-prettier": "^4.2.1",
        "prettier": "^2.8.4",
        "typescript": "^4.9.5"
    }
}

Создаем файл конфигурации Prettier .prettierrc.json

{
    "arrowParens": "always",
    "bracketSpacing": true,
    "endOfLine": "lf",
    "htmlWhitespaceSensitivity": "css",
    "insertPragma": false,
    "jsxBracketSameLine": false,
    "jsxSingleQuote": false,
    "printWidth": 80,
    "proseWrap": "preserve",
    "quoteProps": "as-needed",
    "requirePragma": false,
    "semi": true,
    "singleQuote": true,
    "tabWidth": 4,
    "trailingComma": "es5",
    "useTabs": false,
    "vueIndentScriptAndStyle": true,
    "embeddedLanguageFormatting": "auto"
}

Создаем файл конфигурации ESLint .eslintrc.json

{
    "root": true,
    "env": {
        "browser": true,
        "es2021": true,
        "node": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@typescript-eslint/recommended-requiring-type-checking",
        "plugin:@typescript-eslint/strict"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "project": "./tsconfig.json"
    },
    "plugins": [
        "@typescript-eslint",
        "prettier"
    ],
    "rules": {
        "prettier/prettier": "warn"
    }
}
Набор правил eslint/recommended не содержит правил, связанных с форматированием кода. Но другой набор правил, например Standard, может содержать их в большом количестве. Чтобы отключить все правила из такого набора, связанные с форматированием — нужно установить eslint-plugin-prettier, см. здесь.

Создаем файл конфигурации TypeScript tsconfig.json

{
    "compilerOptions": {
        // Общие настройки компиляции
        "target": "es2015",
        "moduleResolution": "node16",
        "module": "es2015",
        "outDir": "build",
        "sourceMap": true,
        "removeComments": true,
        "newLine": "lf",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        // Группа Strict Checks
        "alwaysStrict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "strictPropertyInitialization": true,
        "strictFunctionTypes": true,
        "noImplicitThis": true,
        "strictBindCallApply": true,
        // Группа Linter Checks
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": false,
        "noUnusedLocals": false,
        "noUnusedParameters": false,
        // Группа Advanced
        "allowUnreachableCode": false,
        "allowUnusedLabels": true,
        "suppressExcessPropertyErrors": false,
        "suppressImplicitAnyIndexErrors": false,
        "noStrictGenericChecks": false
    },
    "include": [
        "src"
    ]
}

Редактируем файл конфигурации VS Code settings.json

{
    ..........
    "files.associations": {
        "*.js": "javascript",
        "*.jsx": "javascriptreact",
        "*.ts": "typescript",
        "*.tsx": "typescriptreact",
    },
    "javascript.validate.enable": true,
    "typescript.validate.enable": true,
    "workbench.colorTheme": "Monokai Dimmed",
    "workbench.colorCustomizations": {
        "editor.foreground": "#bbbbbb",
        "editor.background": "#222222",
        // Цвет волнистой линии для ошибок линтигна и форматирования
        "editorWarning.foreground": "#ffff0055",
        "editorError.foreground": "#ff777777",
        "editorInfo.foreground": "#00ff0055",
    },
    // По умолчанию форматирование кода запрещено
    "editor.formatOnSave": false, // форматировать код при сохранении файла
    "editor.formatOnPaste": false, // форматировать при вставке фрагмента кода
    "editor.codeActionsOnSave": [], // набор действий при сохранении файла
    "editor.defaultFormatter": null,
    // Разрешаем ESLint форматирование и линтинг
    "eslint.enable": true, // разрешить работу расширения eslint
    "eslint.format.enable": true, // запретить или разрешить форматирование
    "eslint.run": "onType", // запускать проверку кода по мере печати кода
    "eslint.probe": ["javascript", "javascriptreact", "typescript", "typescriptreact"], // что проверять
    "eslint.rules.customizations": [ // для проблем форматирования кода уровень info
        {"rule": "prettier/prettier", "severity": "info"}
    ],
    "[javascript]": {
        "editor.formatOnPaste": true,
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "dbaeumer.vscode-eslint",
    },
    "[javascriptreact]": {
        "editor.formatOnPaste": true,
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "dbaeumer.vscode-eslint",
    },
    "[typescript]": {
        "editor.formatOnPaste": true,
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "dbaeumer.vscode-eslint",
    },
    "[typescriptreact]": {
        "editor.formatOnPaste": true,
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "dbaeumer.vscode-eslint",
    },
    ..........
}

2. Простой проект на TypeScript

Создаем в директории src файлы index.ts и User.class.ts

import User from './User.class';

const user: User = new User();
user.hello();
class User {
    name = 'Аноним';
    hello() {
        console.log(`Привет, ${this.name}!`);
    }
}

export default User;

Хорошо, все готово — можно компилировать typescript в javascript

$ npm run build

В директории build теперь скомпилированные файлы javascript

import User from './User.class';
const user = new User();
user.hello();
class User {
    constructor() {
        this.name = 'Аноним';
    }
    hello() {
        console.log(`Привет, ${this.name}!`);
    }
}
export default User;

Давайте изменим в файле tsconfig.json значения target:es5 и module:umd

$ npm run build

Теперь скомпилированные файлы javascript могут работать в старых браузерах

var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./User.class"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var User_class_1 = __importDefault(require("./User.class"));
    var user = new User_class_1.default();
    user.hello();
});
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var User = (function () {
        function User() {
            this.name = 'Аноним';
        }
        User.prototype.hello = function () {
            console.log("\u041F\u0440\u0438\u0432\u0435\u0442, ".concat(this.name, "!"));
        };
        return User;
    }());
    exports.default = User;
});

Все готово, можно запускать на выполнение build/index.js

$ node build/index.js
Привет, Аноним!

3. Настройка проекта React + TypeScript

Давайте настроим еще один проект — нужно будет установить такой же набор пакетов и скопировать все файлы конфигурации. После этого установим еще два пакета, которые позволят ESLint работать с React приложением.

$ npm install eslint-plugin-react eslint-plugin-react-hooks --save-dev
{
    "name": "typescript-project-two",
    "version": "1.0.0",
    "description": "Настройка проекта на TypeScript в редакторе VS Code",
    "private": true,
    "scripts": {
        "build": "tsc"
    },
    "keywords": [],
    "author": "Evgeniy Tokmakov",
    "devDependencies": {
        "@typescript-eslint/eslint-plugin": "^5.53.0",
        "@typescript-eslint/parser": "^5.53.0",
        "eslint": "^8.35.0",
        "eslint-plugin-prettier": "^4.2.1",
        "eslint-plugin-react": "^7.32.2",
        "eslint-plugin-react-hooks": "^4.6.0",
        "prettier": "^2.8.4",
        "typescript": "^4.9.5"
    }
}

Редактируем файл конфигурации ESLint .eslintrc.json

{
    "root": true,
    "env": {
        "browser": true,
        "es2021": true,
        "node": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:react-hooks/recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@typescript-eslint/recommended-requiring-type-checking",
        "plugin:@typescript-eslint/strict"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "project": "./tsconfig.json"
    },
    "plugins": [
        "react",
        "react-hooks",
        "@typescript-eslint",
        "prettier"
    ],
    "rules": {
        "prettier/prettier": "warn"
    }
}

Редактируем файл конфигурации TypeScript tsconfig.json

{
    "compilerOptions": {
        // Общие настройки компиляции
        "target": "es2015",
        "moduleResolution": "node16",
        "module": "es2015",
        "outDir": "build",
        "sourceMap": true,
        "removeComments": true,
        "newLine": "lf",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "jsx": "react", // NEW включить поддержку jsx
        // Группа Strict Checks
        "alwaysStrict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "strictPropertyInitialization": true,
        "strictFunctionTypes": true,
        "noImplicitThis": true,
        "strictBindCallApply": true,
        // Группа Linter Checks
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": false,
        "noUnusedLocals": false,
        "noUnusedParameters": false,
        // Группа Advanced
        "allowUnreachableCode": false,
        "allowUnusedLabels": true,
        "suppressExcessPropertyErrors": false,
        "suppressImplicitAnyIndexErrors": false,
        "noStrictGenericChecks": false
    },
    "include": [
        "src"
    ]
}

4. Особенности React 17-ой версии

Начиная с 17-ой версии React — в коде компонентов можно опускать импорт React. Чтобы учесть эту особенность, отредактируем файл tsconfig.json.

{
    "compilerOptions": {
        // Общие настройки компиляции
        "target": "es2015",
        "moduleResolution": "node16",
        "module": "es2015",
        "outDir": "build",
        "sourceMap": true,
        "removeComments": true,
        "newLine": "lf",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "jsx": "react-jsx", // NEW включить поддержку jsx
        // Группа Strict Checks
        "alwaysStrict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "strictPropertyInitialization": true,
        "strictFunctionTypes": true,
        "noImplicitThis": true,
        "strictBindCallApply": true,
        // Группа Linter Checks
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": false,
        "noUnusedLocals": false,
        "noUnusedParameters": false,
        // Группа Advanced
        "allowUnreachableCode": false,
        "allowUnusedLabels": true,
        "suppressExcessPropertyErrors": false,
        "suppressImplicitAnyIndexErrors": false,
        "noStrictGenericChecks": false
    },
    "include": [
        "src"
    ]
}

В наборе правил plugin:react/recommended есть два правила, связанных с импортом React:

  • react/jsx-uses-react при использовании jsx-кода запрещает сообщения, что React объявлен, но не используется
  • react/react-in-jsx-scope при использовании jsx-кода выдает сообщения, что React должен быть в области видимости

Теперь эти правила будут мешать, так что их нужно отключить. Для этого либо добавить эти два правила в секцию rules и там отключить, либо добавить набор plugin:react/jsx-runtime в секцию extends — который сделает это за нас. Обратите внимание — одно из двух, не все сразу — нет смысла отключать правила два раза.

{
    "root": true,
    "env": {
        "browser": true,
        "es2021": true,
        "node": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        // первый способ отключить два правила
        "plugin:react/jsx-runtime",
        "plugin:react-hooks/recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@typescript-eslint/recommended-requiring-type-checking",
        "plugin:@typescript-eslint/strict"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "project": "./tsconfig.json",
        "ecmaFeatures": {
            "jsx": true
        }
    },
    "plugins": [
        "react",
        "react-hooks",
        "@typescript-eslint",
        "prettier"
    ],
    "rules": {
        "prettier/prettier": "warn",
        // второй способ отключить два правила
        "react/jsx-uses-react": "off",
        "react/react-in-jsx-scope": "off"
    }
}

5. Простой проект React + TypeScript

Чтобы проверить, как все работает — создадим небольшое React приложение на TypeScript. У нас будет директория src с исходниками и директория build для готовой сборки.

$ npm install react react-dom --save-prod
$ npm install @types/react @types/react-dom --save-prod

Babel нужен для компиляции ES6+ в ES5 и для преобразования jsx-кода в javascript

$ npm install @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript --save-dev

Для сборки проекта установим и настроим webpack — компилятора tsc будет недостаточно

$ npm install webpack webpack-cli webpack-dev-server --save-dev
$ npm install babel-loader css-loader style-loader html-webpack-plugin --save-dev

Создадим файл конфигурации webpack, это webpack.config.js (см. подробности здесь)

const path = require('path');
const HtmlWebPackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: 'development',
    entry: './src/index.tsx', // точка входа
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: 'bundle.js',
    },
    resolve: {
        extensions: ['.ts', '.tsx', '...'],
    },
    module: {
        rules: [
            {
                test: /\.(png|jpe?g|gif|webp|svg)$/,
                type: 'asset/resource',
                generator: {
                    filename: '[hash][ext][query]',
                },
            },
            {
                test: /\.tsx?$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                }
            },
            {
                test: /\.css$/,
                use: [
                    {loader: 'style-loader'},
                    {loader: 'css-loader'}
                ],
            },
        ],
    },
    plugins: [
        new HtmlWebPackPlugin({
            template: path.resolve(__dirname, 'public/index.html'), // файл шаблона
            filename: 'index.html', // выходной файл
        }),
    ],
};

Создаем файл конфигурации Babel, где указываем presets, это .babelrc.json

{
    "presets": [
        "@babel/preset-env",
        ["@babel/preset-react", {"runtime": "automatic"}],
        "@babel/preset-typescript"
    ]
}
Опция runtime может иметь значение classic — для версий React до 17-ой или automatic — для версий React от 17-ой. Начиная с Babel v7.9.0, automatic является значением по умолчанию для @babel/preset-react — так что опцию runtime можно не задавать вовсе.

Можно не создавать файл .babelrc.json, а задать значения presets в файле конфигурации webpack.config.js.

module.exports = {
    // ..........,
    module: {
        rules: [
            // ..........
            {
                test: /\.tsx?$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript']
                    }
                }
            },
            // ..........
        ],
    },
    // ..........
};

В секцию scripts добавим три команды для запуска сервера и сборки проекта

{
    ..........
    "scripts": {
        "start": "webpack serve", // запустить сервер разработки
        "build": "webpack build --node-env=production" // выполнить сборку проекта
    },
    ..........
}

Чтобы минимизировать скомпилированный javascript код — желательно указать, в каких браузерах будет работать этот код. Это можно сделать, если создать файл .browserslistrc или добавить опцию browserslist в файл package.json.

{
    // ..........
    "browserslist": [
        "defaults"
    ]
}

Теперь создадим в директории src файлы index.tsx и App.tsx.

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

const root = ReactDOM.createRoot(
    document.getElementById('root') ?? document.body
);
root.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>
);
// import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
    return (
        <div className="App">
            <header className="App-header">
                <img src={logo} className="App-logo" alt="logo" />
                <p>
                    Edit <code>src/App.tsx</code> and save to reload.
                </p>
                <a
                    className="App-link"
                    href="https://reactjs.org"
                    target="_blank"
                    rel="noopener noreferrer"
                >
                    Learn React
                </a>
            </header>
        </div>
    );
}

export default App;

Все готово, запускаем сервер разработки и открываем в браузере http://localhost:8080. Посмотреть файлы сборки можно в браузере http://localhost:8080/webpack-dev-server.

$ npm run start

Хорошо, давайте теперь соберем проект в директорию build — и посмотрим, что получилось с помощью Live Server (см. здесь). Нужно изменить настройки Live Server, потому что по умолчанию сервер обслуживает директорию dist.

Правила ESlint для TypeScript

Набор правил plugin:@typescript-eslint/recommended в основном отключает правила из набора eslint:recommended, потому что TypeScript сам находит такие ошибки.

/**
 * This is a compatibility ruleset that:
 * - disables rules from eslint:recommended which are already handled by TypeScript.
 * - enables rules that make sense due to TS's typechecking / transpilation.
 */
export = {
    overrides: [
        {
            files: ['*.ts', '*.tsx', '*.mts', '*.cts'],
            rules: {
                'constructor-super': 'off', // ts(2335) & ts(2377)
                'getter-return': 'off', // ts(2378)
                'no-const-assign': 'off', // ts(2588)
                'no-dupe-args': 'off', // ts(2300)
                'no-dupe-class-members': 'off', // ts(2393) & ts(2300)
                'no-dupe-keys': 'off', // ts(1117)
                'no-func-assign': 'off', // ts(2539)
                'no-import-assign': 'off', // ts(2539) & ts(2540)
                'no-new-symbol': 'off', // ts(7009)
                'no-obj-calls': 'off', // ts(2349)
                'no-redeclare': 'off', // ts(2451)
                'no-setter-return': 'off', // ts(2408)
                'no-this-before-super': 'off', // ts(2376)
                'no-undef': 'off', // ts(2304)
                'no-unreachable': 'off', // ts(7027)
                'no-unsafe-negation': 'off', // ts(2365) & ts(2360) & ts(2358)
                'no-var': 'error', // ts transpiles let/const to var, so no need for vars any more
                'prefer-const': 'error', // ts provides better types with const
                'prefer-rest-params': 'error', // ts provides better types with rest args over arguments
                'prefer-spread': 'error', // ts transpiles spread to apply, so no need for manual apply
                'valid-typeof': 'off', // ts(2367)
            },
        },
    ],
};

Дополнительный рекомендуемый набор правил plugin:@typescript-eslint/recommended-requiring-type-checking.

export = {
    extends: ['./configs/base', './configs/eslint-recommended'],
    rules: {
        '@typescript-eslint/await-thenable': 'error',
        '@typescript-eslint/no-floating-promises': 'error',
        '@typescript-eslint/no-for-in-array': 'error',
        'no-implied-eval': 'off',
        '@typescript-eslint/no-implied-eval': 'error',
        '@typescript-eslint/no-misused-promises': 'error',
        '@typescript-eslint/no-unnecessary-type-assertion': 'error',
        '@typescript-eslint/no-unsafe-argument': 'error',
        '@typescript-eslint/no-unsafe-assignment': 'error',
        '@typescript-eslint/no-unsafe-call': 'error',
        '@typescript-eslint/no-unsafe-member-access': 'error',
        '@typescript-eslint/no-unsafe-return': 'error',
        'require-await': 'off',
        '@typescript-eslint/require-await': 'error',
        '@typescript-eslint/restrict-plus-operands': 'error',
        '@typescript-eslint/restrict-template-expressions': 'error',
        '@typescript-eslint/unbound-method': 'error',
    },
};

Набор правил plugin:@typescript-eslint/strict — для тех, кто хорошо разбирается в TypeScript, на первых порах не рекомендуется.

export = {
    extends: ['./configs/base', './configs/eslint-recommended'],
    rules: {
        '@typescript-eslint/array-type': 'warn',
        '@typescript-eslint/ban-tslint-comment': 'warn',
        '@typescript-eslint/class-literal-property-style': 'warn',
        '@typescript-eslint/consistent-generic-constructors': 'warn',
        '@typescript-eslint/consistent-indexed-object-style': 'warn',
        '@typescript-eslint/consistent-type-assertions': 'warn',
        '@typescript-eslint/consistent-type-definitions': 'warn',
        'dot-notation': 'off',
        '@typescript-eslint/dot-notation': 'warn',
        '@typescript-eslint/no-base-to-string': 'warn',
        '@typescript-eslint/no-confusing-non-null-assertion': 'warn',
        '@typescript-eslint/no-duplicate-enum-values': 'warn',
        '@typescript-eslint/no-dynamic-delete': 'warn',
        '@typescript-eslint/no-extraneous-class': 'warn',
        '@typescript-eslint/no-invalid-void-type': 'warn',
        '@typescript-eslint/no-meaningless-void-operator': 'warn',
        '@typescript-eslint/no-mixed-enums': 'warn',
        '@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'warn',
        'no-throw-literal': 'off',
        '@typescript-eslint/no-throw-literal': 'warn',
        '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'warn',
        '@typescript-eslint/no-unnecessary-condition': 'warn',
        '@typescript-eslint/no-unnecessary-type-arguments': 'warn',
        '@typescript-eslint/no-unsafe-declaration-merging': 'warn',
        'no-useless-constructor': 'off',
        '@typescript-eslint/no-useless-constructor': 'warn',
        '@typescript-eslint/non-nullable-type-assertion-style': 'warn',
        '@typescript-eslint/prefer-for-of': 'warn',
        '@typescript-eslint/prefer-function-type': 'warn',
        '@typescript-eslint/prefer-includes': 'warn',
        '@typescript-eslint/prefer-literal-enum-member': 'warn',
        '@typescript-eslint/prefer-nullish-coalescing': 'warn',
        '@typescript-eslint/prefer-optional-chain': 'warn',
        '@typescript-eslint/prefer-reduce-type-parameter': 'warn',
        '@typescript-eslint/prefer-return-this-type': 'warn',
        '@typescript-eslint/prefer-string-starts-ends-with': 'warn',
        '@typescript-eslint/prefer-ts-expect-error': 'warn',
        '@typescript-eslint/unified-signatures': 'warn',
    },
};

Поиск: JavaScript • TypeScript • Web-разработка • Теория • Типы данных

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.