SuperAPI AI Directory

typescript-functional-style

Coding & development

View repository

Updated 10-16 14:54

About this skill

TypeScript functional style

This specification applies to the TypeScript, Vue script, composable, and shared helper implementations in this project.

Scope of application

  • File matching:**/*.{ts,tsx,js,jsx}, including .vue in files <script setup> TS logic within.
  • Applicable targets: business logic, composables, utility functions, type definitions, data transformations.
  • Not applicable to: configuration files, build scripts, and other non-logic code unrelated to the above targets.

Goals

  • Prefer functions, composition, and data transformations over classes and shared mutable state.
  • Keep code short, small, and explicit to reduce reading cost.
  • Ensure clarity first, then consider "more functional"; do not sacrifice readability for form.

Core rules

  1. Prefer pure functions
  • Logic that does not depend on lifecycle, DOM, or I/O should preferably be extracted into pure functions.
  • Inputs and outputs should be stable and predictable, avoiding hidden side effects.
  • Pure functions that serve only the current module should be placed in the same-level shared.ts。
  • Pure functions with cross-module reuse value should be placed in the project's global shared directory.
  1. Prefer composition over imperative stacking
  • Prefer small function composition, composables, and utility functions.
  • Do not introduce classes unless an external API mandates it.
  1. Keep functions short and small
  • A function should do only one thing.
  • If a function handles filtering, mapping, branching, and side effects at the same time, it should be split.
  1. Reduce nesting levels
  • Prefer guard clauses and early returns.
  • When conditional nesting exceeds two levels, prefer extracting a helper function or inverting the condition.
  1. Prefer declarative data transformations
  • When it can be expressed with map、filter、find、some、every , prefer not to use handwritten loops.
  • Only retain local imperative style when it is clearly clearer or more efficient.
  1. Limit mutability
  • Avoid shared mutable state.
  • Prefer derived values over mirrored state.
  • Local mutation is allowed only when the scope is very small and it is clearly more readable.
  1. Types must be precise
  • Exported functions, public utilities, and complex return values should declare clear parameter and return types.
  • Do not use any, broad assertions, or type escapes to conceal modeling problems.
  1. Concise but not showy
  • Do not introduce hard-to-read chained tricks for the sake of being "more functional".
  • If a shorter form is harder to understand, choose the clearer version.

Vue context supplement

  • Use computedfor derived state; do not store it repeatedly in ref.
  • Separate side effects from data transformations.
  • Pure logic unrelated to the template should preferably be extracted into a helper or composable.
  • Pure logic used only by the current component family should preferably be placed in the same-level shared.ts。
  • Pure logic reusable across modules should preferably be placed in the project's global shared directory.
  • Do not introduce a watcher when it is not necessary.
  • Before adding a helper, composable, or type, first check whether the project's existing implementation already satisfies the need.
  • If there is no suitable composable in the project, first check @vueuse/core, and do not rewrite common capabilities from scratch.

Refactoring Checklist

  • Can it be extracted into a pure function?
  • After extraction, should it be placed at the same level as shared.ts, or in the project's global shared directory?
  • Can early return be used to reduce indentation?
  • Can duplicate state be eliminated?
  • Can intermediate variables that are only passed through once be removed?
  • Are the parameters and return values of the exported API sufficiently clear?
  • Is the code still easier to read after being shorter?

Example

Worse:

export function collectEnabledIds(items: Item[]): string[] {
  const result: string[] = [];

  for (const item of items) {
    if (item.enabled) {
      result.push(item.id);
    }
  }

  return result;
}

Better:

export function collectEnabledIds(items: Item[]): string[] {
  return items.filter(item => item.enabled).map(item => item.id);
}

Worse:

export function resolveLabel(input?: string | null): string {
  let label = '';

  if (input) {
    label = input.trim();

    if (!label) {
      label = 'unknown';
    }
  } else {
    label = 'unknown';
  }

  return label;
}

Better:

export function resolveLabel(input?: string | null): string {
  const label = input?.trim();

  if (!label) {
    return 'unknown';
  }

  return label;
}

Worse (imperative stacking, mixed responsibilities):

export function getActiveAdminNames(users: User[]): string[] {
  const result: string[] = [];

  for (const user of users) {
    if (user.role === 'admin') {
      if (user.active) {
        if (!user.deleted) {
          result.push(user.name);
        }
      }
    }
  }

  return result;
}

Better (composing small functions + declarative transformation + guard clause):

const isActiveAdmin = (user: User): boolean =>
  user.role === 'admin' && user.active && !user.deleted;

export function getActiveAdminNames(users: User[]): string[] {
  return users.filter(isActiveAdmin).map(user => user.name);
}

Worse (type escape masking modeling problems):

export function parseConfig(raw: unknown): Config {
  return raw as Config;
}

Better (precise types + explicit validation, clear return value types):

export function parseConfig(raw: unknown): Config {
  if (!raw || typeof raw !== 'object') {
    return { entries: [] };
  }

  const { entries } = raw as Record<string, unknown>;

  return {
    entries: Array.isArray(entries) ? entries.filter(isConfigEntry) : []
  };
}