TypeScript. Начало работы, часть 3 из 7
27.02.2023
Теги: JavaScript • TypeScript • Web-разработка • Теория • ТипыДанных
Настройка проекта в 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-разработка • Теория • Типы данных