【TypeScript】:functions、types

2021-02-26 16:11:29 浏览数 (1)

代码语言:javascript复制
目录
1. React 是怎么定义 useReducer 的?
2. TypeScript 必备知识
  2.1. Functions
    2.1.1. Typing the function
    2.1.2. Writing the function type
    2.1.3. Optional and Default Parameters
    2.1.4. Rest Parameters
    2.1.5. Overloads
  2.2. Unions and Intersection Types
    2.2.1. Union Types
  2.3. Conditional Types
  2.4. Type inference in conditional types    
3. useReducer 定义解析

本系列文章将从一些著名开源项目中找一些 TypeScript 代码,讲解如何应用 TypeScript。

1. React 是怎么定义 useReducer 的?

  • 以下代码节选自 React 的 useReducer 的 TypeScript 定义。
    • 如果能完全读懂,那这篇文章就不用看了...

node_modules@typesreactindex.d.ts:

代码语言:javascript复制
// this technically does accept a second argument, but it's already under a deprecation warning
// and it's not even released so probably better to not define it.
type Dispatch<A> = (value: A) => void;
// Since action _can_ be undefined, dispatch may be called without any parameters.
type DispatchWithoutAction = () => void;
// Unlike redux, the actions _can_ be anything
type Reducer<S, A> = (prevState: S, action: A) => S;
// If useReducer accepts a reducer without action, dispatch may be called without any parameters.
type ReducerWithoutAction<S> = (prevState: S) => S;
// types used to try and prevent the compiler from reducing S
// to a supertype common with the second argument to useReducer()
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any> ? S : never;
type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;
// The identity check is done with the SameValue algorithm (Object.is), which is stricter than ===
type ReducerStateWithoutAction<R extends ReducerWithoutAction<any>> =
    R extends ReducerWithoutAction<infer S> ? S : never;

/**
 * An alternative to `useState`.
 *
 * `useReducer` is usually preferable to `useState` when you have complex state logic that involves
 * multiple sub-values. It also lets you optimize performance for components that trigger deep
 * updates because you can pass `dispatch` down instead of callbacks.
 *
 * @version 16.8.0
 * @see https://reactjs.org/docs/hooks-reference.html#usereducer
 */
// overload where dispatch could accept 0 arguments.
function useReducer<R extends ReducerWithoutAction<any>, I>(
    reducer: R,
    initializerArg: I,
    initializer: (arg: I) => ReducerStateWithoutAction<R>
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];
/**
 * An alternative to `useState`.
 *
 * `useReducer` is usually preferable to `useState` when you have complex state logic that involves
 * multiple sub-values. It also lets you optimize performance for components that trigger deep
 * updates because you can pass `dispatch` down instead of callbacks.
 *
 * @version 16.8.0
 * @see https://reactjs.org/docs/hooks-reference.html#usereducer
 */
// overload where dispatch could accept 0 arguments.
function useReducer<R extends ReducerWithoutAction<any>>(
    reducer: R,
    initializerArg: ReducerStateWithoutAction<R>,
    initializer?: undefined
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];
/**
 * An alternative to `useState`.
 *
 * `useReducer` is usually preferable to `useState` when you have complex state logic that involves
 * multiple sub-values. It also lets you optimize performance for components that trigger deep
 * updates because you can pass `dispatch` down instead of callbacks.
 *
 * @version 16.8.0
 * @see https://reactjs.org/docs/hooks-reference.html#usereducer
 */
// overload where "I" may be a subset of ReducerState<R>; used to provide autocompletion.
// If "I" matches ReducerState<R> exactly then the last overload will allow initializer to be omitted.
// the last overload effectively behaves as if the identity function (x => x) is the initializer.
function useReducer<R extends Reducer<any, any>, I>(
    reducer: R,
    initializerArg: I & ReducerState<R>,
    initializer: (arg: I & ReducerState<R>) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
/**
 * An alternative to `useState`.
 *
 * `useReducer` is usually preferable to `useState` when you have complex state logic that involves
 * multiple sub-values. It also lets you optimize performance for components that trigger deep
 * updates because you can pass `dispatch` down instead of callbacks.
 *
 * @version 16.8.0
 * @see https://reactjs.org/docs/hooks-reference.html#usereducer
 */
// overload for free "I"; all goes as long as initializer converts it into "ReducerState<R>".
function useReducer<R extends Reducer<any, any>, I>(
    reducer: R,
    initializerArg: I,
    initializer: (arg: I) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
/**
 * An alternative to `useState`.
 *
 * `useReducer` is usually preferable to `useState` when you have complex state logic that involves
 * multiple sub-values. It also lets you optimize performance for components that trigger deep
 * updates because you can pass `dispatch` down instead of callbacks.
 *
 * @version 16.8.0
 * @see https://reactjs.org/docs/hooks-reference.html#usereducer
 */

// I'm not sure if I keep this 2-ary or if I make it (2,3)-ary; it's currently (2,3)-ary.
// The Flow types do have an overload for 3-ary invocation with undefined initializer.

// NOTE: without the ReducerState indirection, TypeScript would reduce S to be the most common
// supertype between the reducer's return type and the initialState (or the initializer's return type),
// which would prevent autocompletion from ever working.

// TODO: double-check if this weird overload logic is necessary. It is possible it's either a bug
// in older versions, or a regression in newer versions of the typescript completion service.
function useReducer<R extends Reducer<any, any>>(
    reducer: R,
    initialState: ReducerState<R>,
    initializer?: undefined
): [ReducerState<R>, Dispatch<ReducerAction<R>>];

2. TypeScript 必备知识

2.1. Functions

2.1.1. Typing the function

We can add types to each of the parameters and then to the function itself to add a return type.

代码语言:javascript复制
function add(x: number, y: number): number {
  return x   y;
}

let myAdd = function (x: number, y: number): number {
  return x   y;
};

2.1.2. Writing the function type

A function’s type has the same two parts: the type of the arguments and the return type.

  • When writing out the whole function type, both parts are required.
  • We write out the parameter types just like a parameter list, giving each parameter a name and a type.
    • This name is just to help with readability.
  • The second part is the return type. We make it clear which is the return type by using an arrow (=>) between the parameters and the return type.
    • As mentioned before, this is a required part of the function type, so if the function doesn’t return a value, you would use void instead of leaving it off.
代码语言:javascript复制
let myAdd: (x: number, y: number) => number = function (
  x: number,
  y: number
): number {
  return x   y;
};

2.1.3. Optional and Default Parameters

  • In TypeScript, every parameter is assumed to be required by the function.
  • In JavaScript, every parameter is optional, and users may leave them off as they see fit.
  • We can get this functionality in TypeScript by adding a ? to the end of parameters we want to be optional.
    • Any optional parameters must follow required parameters.
代码语言:javascript复制
function buildName(firstName: string, lastName?: string) {
  if (lastName) return firstName   " "   lastName;
  else return firstName;
}
  • In TypeScript, we can also set a value that a parameter will be assigned if the user does not provide one, or if the user passes undefined in its place. These are called default-initialized parameters.
    • Default-initialized parameters that come after all required parameters are treated as optional, and just like optional parameters, can be omitted when calling their respective function.
    • If a default-initialized parameter comes before a required parameter, users need to explicitly pass undefined to get the default initialized value.
代码语言:javascript复制
function buildName(firstName: string, lastName = "Smith") {
  // ...
}

2.1.4. Rest Parameters

Rest parameters are treated as a boundless number of optional parameters.The compiler will build an array of the arguments passed in with the name given after the ellipsis (...), allowing you to use it in your function.

代码语言:javascript复制
function buildName(firstName: string, ...restOfName: string[]) {
  return firstName   " "   restOfName.join(" ");
}

let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;

2.1.5. Overloads

JavaScript is inherently a very dynamic language. It’s not uncommon for a single JavaScript function to return different types of objects based on the shape of the arguments passed in.

The answer is to supply multiple function types for the same function as a list of overloads. This list is what the compiler will use to resolve function calls.

代码语言:javascript复制
let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x: any): any {
  // Check to see if we're working with an object/array
  // if so, they gave us the deck and we'll pick the card
  if (typeof x == "object") {
    let pickedCard = Math.floor(Math.random() * x.length);
    return pickedCard;
  }
  // Otherwise just let them pick the card
  else if (typeof x == "number") {
    let pickedSuit = Math.floor(x / 13);
    return { suit: suits[pickedSuit], card: x % 13 };
  }
}

let myDeck = [
  { suit: "diamonds", card: 2 },
  { suit: "spades", card: 10 },
  { suit: "hearts", card: 4 },
];

let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: "   pickedCard1.card   " of "   pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: "   pickedCard2.card   " of "   pickedCard2.suit);

With this change, the overloads now give us type checked calls to the pickCard function.

In order for the compiler to pick the correct type check, it follows a similar process to the underlying JavaScript. It looks at the overload list and, proceeding with the first overload, attempts to call the function with the provided parameters. If it finds a match, it picks this overload as the correct overload. For this reason, it’s customary to order overloads from most specific to least specific.

Note that the function pickCard(x): any piece is not part of the overload list, so it only has two overloads: one that takes an object and one that takes a number. Calling pickCard with any other parameter types would cause an error.

2.2. Unions and Intersection Types

Intersection and Union types are one of the ways in which you can compose types.

2.2.1. Union Types

A union type describes a value that can be one of several types.

代码语言:javascript复制
/**
 * Takes a string and adds "padding" to the left.
 * If 'padding' is a string, then 'padding' is appended to the left side.
 * If 'padding' is a number, then that number of spaces is added to the left side.
 */
function padLeft(value: string, padding: string | number) {
  // ...
}

If we have a value that is a union type, we can only access members that are common to all types in the union.

  • Union types can be a bit tricky here, but it just takes a bit of intuition to get used to. If a value has the type A | B, we only know for certain that it has members that both A and B have. In this example, Bird has a member named fly. We can’t be sure whether a variable typed as Bird | Fish has a fly method. If the variable is really a Fish at runtime, then calling pet.fly() will fail.
代码语言:javascript复制
interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}

declare function getSmallPet(): Fish | Bird;

let pet = getSmallPet();
pet.layEggs();

// Only available in one of the two possible types
pet.swim();
Property 'swim' does not exist on type 'Bird | Fish'.
  Property 'swim' does not exist on type 'Bird'.

2.2.1. Intersection Types

Intersection types are closely related to union types, but they are used very differently. An intersection type combines multiple types into one. This allows you to add together existing types to get a single type that has all the features you need.

代码语言:javascript复制
interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}

interface ArtworksData {
  artworks: { title: string }[];
}

interface ArtistsData {
  artists: { name: string }[];
}

// These interfaces are composed to have
// consistent error handling, and their own data.

type ArtworksResponse = ArtworksData & ErrorHandling;
type ArtistsResponse = ArtistsData & ErrorHandling;

const handleArtistsResponse = (response: ArtistsResponse) => {
  if (response.error) {
    console.error(response.error.message);
    return;
  }

  console.log(response.artists);
};

2.3. Conditional Types

A conditional type selects one of two possible types based on a condition expressed as a type relationship test:

代码语言:javascript复制
T extends U ? X : Y

The type above means when T is assignable to U the type is X, otherwise the type is Y.

A conditional type T extends U ? X : Y is either resolved to X or Y, or deferred because the condition depends on one or more type variables. When T or U contains type variables, whether to resolve to X or Y, or to defer, is determined by whether or not the type system has enough information to conclude that T is always assignable to U.

代码语言:javascript复制
declare function f<T extends boolean>(x: T): T extends true ? string : number;

2.4. Type inference in conditional types

Within the extends clause of a conditional type, it is now possible to have infer declarations that introduce a type variable to be inferred.

For example, the following extracts the return type of a function type:

代码语言:javascript复制
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

3. useReducer 定义解析

  • 注意它使用 infer 提取返回值的姿势

参考:

TypeScript——functions: https://www.typescriptlang.org/docs/handbook/functions.html Conditional Types: https://www.typescriptlang.org/docs/handbook/advanced-types.html#conditional-types Type inference in conditional types: https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-inference-in-conditional-types Unions and Intersection Types: https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html

0 人点赞