An overview of the
Since the project was a base service pack published via NPM, the goal of the TypeScript rework was to remove the Babel bucket, reduce the package size, and add strong typing constraints to avoid future development problems.
TypeScript V2.9.2 is packaged with Webpack V4.16.0. The development tool is VSCode, using the Chinese language pack. The expected goal is to compile TypeScript code directly into ES5 code via the Loader.
The issues covered in this article are partly TypeScript configuration and usage issues, and partly VSCode itself configuration issues.
Recording and analysis of modification problems
VSCode related
“Cannot find related module” error
In a project, if we use webpack.alias, we may be prompted that the module cannot be found.
The specific errors are as follows:
Error: TS2307: Cannot find Module'_utils/index'[ts] could not find module '_utils/index'.Copy the code
This is caused by the editor’s inability to read the corresponding alias information.
At this point, we need to check whether the corresponding module exists. If we confirm that the module exists and the terminal compiler does not report an error, but only an editor error, it is because the editor cannot read the WebPack configuration, and we need to add another configuration.
In addition to configuring webpack.alias, you also need to configure the corresponding tsconfig.json, as shown below:
"compilerOptions": {
"baseUrl": ".",
"paths": {
"_util/*": [
"src/core/utils/*"
]
}
}
Copy the code
Note: If configuredtsconfig.json
VSCode only reads configuration information when the project is loaded. In a JavaScript projectjsconfig.json
In the same way.
TypeScript related
Object attribute assignment error reported
In JavaScript, we often declare an empty object and then assign a value to the property. However, this operation will cause errors in TypeScript:
let a = {};
a.b = 1;
TS2339: Property 'b' does not exist on type '{}'.
[ts] type '{}' does not have attribute 'b'.
Copy the code
This is because TypeScript does not allow the addition of undeclared properties.
Therefore, we have two ways to resolve this error:
-
Add attribute definitions to an object (recommended). Let a = {b: void 0}; . This approach can fundamentally solve the current problem, but also can avoid the object is arbitrarily assigned to the problem.
-
Add a type definition to the object (recommended). The specific methods are as follows:
interface obj { [propName: string] :any }; let a: obj = {}; a.a = 1; Copy the code
This also avoids errors and does not introduce the full-object any case.
-
Add the any attribute to the a object (emergency). Let a: any = {}; . This method enables TypeScript type checking to ignore the object, allowing compilation to pass without error. This approach works well in cases where a lot of old code is reworked.
Error assignment of Window object property
As in the previous case, when we assign a nonexistent property to an object, we get an editor and compiler error:
window.a = 1;
TS2339: Property 'a' does not exist on type 'Window'.
[ts] type "Window" does not exist attribute "a".
Copy the code
This is also because TypeScript does not allow the addition of undeclared properties.
Since there is no way (or difficult) to declare the values of Windows properties, we need to do this in the following way:
- We added a type conversion for Windows use, i.e
(window as any).a = 1;
. This ensures that editor and compile time are error-free. However, this method is only recommended for older projects. We should try to avoid adding properties to the Window object and access data through a global data manager.
ES2015 Object added a method on the prototype chain that reported an error
In the project, some ES2015 new methods on the Object prototype chain are used, such as object. assign and object. values, etc. At this time, the compilation will fail, and VSCode will prompt an error:
Terminal compiler error: TS2339: Property'assign' does not exist on type 'ObjectConstructor'[ts] ObjectConstructor does not have attribute assign.Copy the code
This is because the target we specified in tsconfig.json is ES5, and TypeScript doesn’t have a polyfill, so we can’t use the new methods in ES2015.
Through the above analysis, we can use the following methods to solve the problem:
-
You can use the related methods in the LoDash toolset, which requires lodash.assign and @types/lodash.assign to be installed. And lodash.assign is a CMD specification package that requires importing _assign = require(‘lodash.assing’); Method introduction.
-
We can use rest writing, such as let a = {… b}; , can also achieve the effect of first-class shallow copy, the specific effects are as follows:
An error occurred during the initialization of the Map data structure added in ES2015
When converting ES2015 code to TypeScript code, if you use the new ES2015 Map type, you will get an error in both the editor and terminal compilation:
Error TS2693:'Map' only refers to a type, but is being used as a value here. [ts] "Map" only represents the type, but is used as a value here.Copy the code
This is because TypeScript does not provide a data type, nor does it have a polyfill.
Therefore, there are three ways to solve this problem:
- will
tsconfig.json
The configuration of thetarget
Attribute toes6
That is, output code that conforms to ES2015 specifications. Because ES2015 has a global Promise object, neither the compiler nor the editor will report an error. This method has the advantage of simple configuration and no code changes, but the disadvantage is that it requires advanced browser support or Babel whole-family bucket support. - Discard Map and replace it with Object. This kind of transformation is time-consuming and laborious, and is suitable for scenarios where the workload is small and you do not want to introduce other files.
- Implement or install a Map package. This approach is less costly to modify, with the disadvantage that additional code or packages are introduced, and code efficiency is not guaranteed. For example,
ts-map
andtypescript-map
, the search efficiency of both packages is O (n), which is lower than the Map of the native type. Therefore, it is recommended to use Object to achieve a simple Map, the specific implementation way can go to the Internet to find the relevant Map principle analysis and practice (the general principle is to use multiple objects, storage of different types of elements using different containers, to avoid type conversion problems).
An error is reported when using Promise added in ES2015
When converting ES2015 code to TypeScript code, if you use the new ES2015 Promise type, you will get an error in both editor and terminal compilation:
Error TS2693:'Promise' only refers to a type, but is being used as a value here. The editor reported an error: [ts] "Promise" only represents the type, but here it is used as a value.Copy the code
This is because TypeScript does not provide a Promise data type, nor does it have a polyfill.
So there are still three ways we can approach this problem:
-
Change the target property in the tsconfig.json configuration file configuration to ES6, that is, output ES2015 compliant code. Because ES2015 has a global Promise object, neither the compiler nor the editor will report an error. This method has the advantage of simple configuration and no code changes, but the disadvantage is that it requires advanced browser support or Babel whole-family bucket support.
-
Introduce a Promise library, such as Bluebird and other well-known Promise library. The @types/ Bluebird declaration file needs to be installed with the Bluebird installation. The disadvantage is that the introduced Promise libraries are large, and if your library is used as a base library, it may clash with other callers’ Promise libraries.
-
Add lib to tsconfig.json configuration file. The idea behind this approach is to make TypeScript compile with references to external Promise objects so that no errors are reported at compile time. The advantage of this approach is that it does not introduce any additional code, but the disadvantage is that you must ensure that a Promise object exists if you reference the library. The configuration is as follows:
"compilerOptions": { "lib": ["es2015.promise"]}Copy the code
Error when using SetTimeout
ES2015 code into a TypeScript code, if you use the setTimeout and setInterval function, may be unable to find the function of an error:
Error: TS2304: Cannot find name'setTimeout'[ts] could not find namesetThe Timeout ".Copy the code
This is caused by editors and compile-time not knowing where the code is currently running.
So there are two ways we can approach this problem:
-
Add lib to tsconfig.json configuration file. Make TypeScript aware of the current code container. The following is an example:
"compilerOptions": { "lib": ["dom"]}Copy the code
-
Install @ types/node. This approach works in a Node environment or you can introduce Node code when you are packaged with WebPack. NPM install @types/node NPM install @types/node
Module reference and export error
In the ES2015 code, we can directly export the imported files by using the @babel/plugin-proposal-export-default-from plug-in, as shown in the following example:
export Session from './session'; / / an error
export * from '_models/read-item'; / / is not an error
Copy the code
In TypeScript, this would cause an error:
TS1128: Declaration or statement expected. The editor reported an error: [ts] should be a declaration or statement.Copy the code
This is due to the different module syntax.
Therefore, we only need to solve the problem in the following way:
-
Adjust the syntax of export from above to fit the TypeScript syntax. Specific modifications are as follows:
export {default as Session} from '_models/session'; // No error is reported after adjustment export * from '_models/read-item';// No error was reported before and no adjustment is required Copy the code
The generic definition
We often encounter this situation in projects, we need to ensure that the type of the property passed in is the same as the parameter of a function, such as:
interface props {
value: number | string,
onChange: (v: string | number) = > void // The parameter type value must be consistent with value
}
Copy the code
To solve this problem, we need to use the generic definition:
interface Props<T extends string | number> {
value: T,
onChange: (v: T) = > void
}
Copy the code
At this point, when the type of value is determined, the type of the parameter becomes as determined as that of value.
The module reference
When we use TypeScript, it’s common to refer to other modules or even JavaScript packages. In TypeScript, there are several different export methods, and each export corresponds to a different reference method.
At present, I have encountered several modules in the project transformation:
- CMD specification.
- ES2015 Module specification.
For these modules, we also have different import methods:
import _assign = require('lodash.assign'); / / CMD specification
import constant from './constant'; // ES2015 Module specification
Copy the code
If the file you are importing is not a TypeScript file but a JavaScript file, you may also need to add a declaration file. We can add a declaration file by:
-
Add @types file. This way, some of the more well-known libraries can use this method.
-
Add a declaration to the.d.ts file. This declaration is valid globally. The specific way is as follows:
declare module 'promiz'; Copy the code
For JSON files, you need to use this declaration as well, as follows:
declare module "*.json" { const value: any; export const version: string; export default value; } Copy the code
In this way, we can handle different module specifications and different types of files.
TypeScript local substitutions
When refactoring, we may be able to replace only module by module at first. We need new TypeScript files and old JavaScript files to coexist peacefully to compile and run.
To meet this requirement, we only need to add the configuration of the relevant TS files in the Webpack-compiled Loader and add the support for the. Ts suffix in extension. Related configurations are as follows:
{
module: {
rules: [
{
test: /ts$/,
use: [{
loader: 'ts-loader',
options: {
silent: process.env.env === 'production' ? true : false
}
}]
}
]
},
extensions: ['.ts', '.js']
}
Copy the code
Then, we just need to add the.ts suffix when the file is imported in JavaScript, as shown in the following example:
// I used the CMD specification before, so importing the ES2015 module requires access to default
var EventEmitter = require('eventemitter3');
var Session = require('./session.ts').default;
Copy the code
In this way, we can do the module replacement and modification gradually, without the need for large-scale file replacement and rename.
conclusion
In the process of making TypeScript modifications for the project, I encountered many large and small pits. Many of these issues don’t have a solution or are spelled out on the web, so I hope this article will help you avoid wasting time on TypeScript migrations where I did.