阿里云主机折上折
  • 微信号
Current Site:Index > Type checking in strict mode

Type checking in strict mode

Author:Chuan Chen 阅读数:4396人阅读 分类: TypeScript

TypeScript's strict type-checking mode is a collection of compiler options that significantly enhance code type safety by enabling these options. Under strict mode, the type system catches more potential errors, reduces the likelihood of runtime exceptions, and enforces stricter type definitions from developers.

Core Options of Strict Mode

The strict flag in tsconfig.json is essentially a shortcut for the following seven independent options:

{
  "compilerOptions": {
    "strict": true,
    /* Equivalent to enabling:
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true
    */
  }
}

In-Depth Analysis of noImplicitAny

When the compiler cannot infer a variable's type, it defaults to any. Enabling noImplicitAny triggers an error in such cases:

// Error example
function logValue(value) {  // Error: Parameter implicitly has 'any' type
  console.log(value.toFixed(2));
}

// Correct approach
function logValue(value: number) {
  console.log(value.toFixed(2));
}

This option is particularly useful for large-scale project migrations. Consider a legacy JavaScript file:

// legacy.js
export function calculateTotal(items) {  // Marked as an error during migration
  return items.reduce((sum, item) => sum + item.price, 0);
}

The step-by-step fix would be:

interface Item {
  price: number;
}

export function calculateTotal(items: Item[]) {
  return items.reduce((sum, item) => sum + item.price, 0);
}

Practical Applications of strictNullChecks

This option prevents null and undefined from being assigned to other types:

let userName: string = null;  // Error

// Correct approach
let userName: string | null = null;

// Real-world example: API response handling
interface ApiResponse<T> {
  data: T | null;
  error: string | null;
}

function processResponse(response: ApiResponse<string>) {
  if (response.data !== null) {
    console.log(response.data.toUpperCase());  // Safe access
  }
}

It is especially useful when working with DOM elements:

// Without strictNullChecks
const element = document.getElementById('not-exist');
element.addEventListener('click', () => {});  // Runtime error

// With strictNullChecks enabled
const element = document.getElementById('not-exist');
if (element) {  // Null check required
  element.addEventListener('click', () => {});
}

Contravariance Checks with strictFunctionTypes

This option enforces stricter contravariance checks for function parameter types:

type Handler = (request: { id: string }) => void;

const handler1: Handler = (req: {}) => {};  // Error
const handler2: Handler = (req: { id: string, extra: number }) => {};  // Correct

// Real-world scenario
class EventEmitter<T> {
  private handlers: ((payload: T) => void)[] = [];
  
  register(handler: (payload: T) => void) {
    this.handlers.push(handler);
  }
}

const stringEmitter = new EventEmitter<string>();
stringEmitter.register((data: string | number) => {});  // Error

Controlling Class Properties with strictPropertyInitialization

Forces class properties to be initialized in the constructor:

class User {
  name: string;  // Error: Not initialized
  
  constructor() {
    // Forgot to initialize 'name'
  }
}

// Solution 1: Explicit initialization
class User {
  name: string = '';
}

// Solution 2: Using definite assignment assertion
class User {
  name!: string;  // Tells the compiler we'll assign it later
}

Particularly useful in React components:

class Profile extends React.Component {
  state: UserState;  // Error
  
  constructor(props: Props) {
    super(props);
    // Must initialize state here
    this.state = { /*...*/ };
  }
}

Proper Usage of strictBindCallApply

Ensures call, apply, and bind methods use the correct parameter types:

function greet(name: string, age: number) {
  return `Hello ${name}, you are ${age} years old`;
}

const bound1 = greet.bind(null, "Alice");  // Error: Missing 'age' parameter
const bound2 = greet.bind(null, "Alice", 30);  // Correct

// Real-world application: Event handler binding
class ButtonComponent {
  handleClick(id: number, event: MouseEvent) {
    console.log(`Button ${id} clicked`);
  }
  
  setup() {
    const button = document.querySelector('button');
    // Error: Incomplete binding parameters
    button?.addEventListener('click', this.handleClick.bind(this, 1));
    
    // Correct: Using a wrapper function
    button?.addEventListener('click', (e) => this.handleClick(1, e));
  }
}

Context Control with noImplicitThis

Prevents implicit any typing for this:

class Rectangle {
  width = 10;
  height = 20;
  
  getArea() {
    return function() {
      return this.width * this.height;  // Error: 'this' implicitly has type 'any'
    }
  }
  
  // Correct approach 1: Arrow function
  getAreaCorrect() {
    return () => {
      return this.width * this.height;
    }
  }
  
  // Correct approach 2: Explicit typing
  getAreaCorrect2(this: Rectangle) {
    return this.width * this.height;
  }
}

Especially important in Vue's Options API:

export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++;  // Requires noImplicitThis
    }
  }
}

Parsing Mode with alwaysStrict

Forces strict mode parsing for all code, equivalent to adding "use strict" at the top of every file:

// The following code works in non-strict mode but errors with alwaysStrict
function leaky() {
  leakedVar = 10;  // Error: Undeclared variable
}

// Changes in strict mode
delete Object.prototype;  // Error: Deleting non-configurable properties is prohibited in strict mode

Gradual Adoption Strategy for Strict Mode

For existing projects, it's recommended to enable strict options incrementally:

  1. First enable noImplicitAny and strictNullChecks
  2. Then enable strictFunctionTypes and strictBindCallApply
  3. Finally enable strictPropertyInitialization and noImplicitThis

Example configuration:

{
  "compilerOptions": {
    "strict": false,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noImplicitThis": false,
    "strictPropertyInitialization": false
  }
}

Advanced Type Techniques in Strict Mode

Leverage strict mode for more precise type modeling:

// Using 'never' to represent impossible states
type Result<T, E> = 
  | { success: true; value: T }
  | { success: false; error: E }
  | never;  // Ensures all branches are handled

function processResult(result: Result<string, Error>) {
  if (result.success) {
    console.log(result.value);
  } else {
    console.error(result.error);
  }
  // If a new branch is added but not handled, type checking will error
}

Strict Mode and Third-Party Library Interactions

Handling libraries without type definitions:

// Using type assertions
const unsafeLib = require('untyped-lib') as {
  doSomething: (input: string) => number;
};

// Or declaring module types
declare module 'untyped-lib' {
  export function doSomething(input: string): number;
}

For potentially null callbacks:

interface LibraryOptions {
  onSuccess?: (data: string) => void;
  onError?: (err: Error) => void;
}

function useLibrary(options: LibraryOptions) {
  // In strict mode, must check if callbacks exist
  options.onSuccess?.("data");
}

Performance Considerations in Strict Mode

While strict checks increase compilation time, optimize with:

  1. Using relaxed configurations for test files
  2. Disabling strict mode for utility scripts
  3. Using project references to isolate strict checks

Example configuration:

// tsconfig.build.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "strict": true
  }
}

// tsconfig.tools.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "strict": false
  }
}

Strict Mode and Test Code

Test code can have relaxed restrictions:

// Allowing 'any' in test configurations
describe('API Client', () => {
  let mockResponse: any;  // 'any' is allowed in tests
  
  beforeEach(() => {
    mockResponse = { data: {}, status: 200 };
  });
});

// Or using type assertions
test('handles error', () => {
  const error = new Error() as any;
  error.code = 'CUSTOM_ERROR';
  // ...Test logic
});

Advanced Type Guards in Strict Mode

Using user-defined type guards for complex types:

interface Cat {
  meow(): void;
  climb(): void;
}

interface Dog {
  bark(): void;
  run(): void;
}

function isCat(animal: Cat | Dog): animal is Cat {
  return 'meow' in animal;
}

function handleAnimal(animal: Cat | Dog) {
  if (isCat(animal)) {
    animal.meow();  // Type narrowed to 'Cat'
  } else {
    animal.bark();  // Type narrowed to 'Dog'
  }
}

Strict Mode and Generic Constraints

Generic constraints become more precise in strict mode:

function mergeObjects<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

// Error example
mergeObjects(undefined, {});  // Violates 'object' constraint

// Proper handling of optional parameters
function safeMerge<T extends object, U extends object>(
  a?: T, 
  b?: U
): Partial<T & U> {
  return { ...a, ...b };
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.