Машинописный код чистый

JavaScript TypeScript

За последние полгода я последовательно сдал код нескольким коллегам, и обнаружил, что хотя для стандартизации метода написания кода используется строгий eslint, а в проекте также полностью используется Typescript, но в процессе рассмотрения кода, там еще много неопрятных и нестандартных мест. . Хороший код легко читается, его приятно поддерживать и он менее подвержен рефакторингу. В этой статье мы объединим Typescript, чтобы рассказать о том, как очистить код:

  • базовая спецификация
  • функциональный

1. Основные характеристики

(1) Константы

Константы должны быть именованы, и прямое сравнение безымянных констант не допускается при вынесении логических суждений.

  • неправильное написание
  switch(num){
       case 1:
         ...
       case 3:
         ...
       case 7:
         ...
  }
    
  if(x === 0){
       ...
  }

В приведенном выше примере я не знаю, чему соответствует 1 3 7, и такой способ записи практически нечитаем.

  • Правильное написание
    enum DayEnum {
        oneDay = 1,
        threeDay = 3,
        oneWeek = 7,
    }
    let num  = 1;
    switch(num){
        case DayEnum.oneDay:
        ...
        case DayEnum.threeDay:
        ...
        case DayEnum.oneWeek:
        ...
    }


   const RightCode = 0;
   if(x === RightCode)

Из приведенного выше правильного написания видно, что константы имеют имена.При вынесении логических суждений, таких как переключатель или если, мы можем узнать конкретное значение констант из имен переменных, что повышает читабельность.

(2) Перечисление

В дополнение к постоянному перечислению, на этапе компиляции Typescript перечисление будет генерировать объект сопоставления,Если не строковое перечисление, будет сгенерировано даже двунаправленное сопоставление. Таким образом, в нашем бизнес-коде с перечислениями нет необходимости в массиве, связанном со значениями перечисления.

  • неправильное написание
enum FruitEnum {
       tomato = 1,
       banana =  2,
       apple = 3
}

const FruitList = [
  {
     key:1,
     value: 'tomato'
  },{
     key:2,
     value: 'banana'
  },{
     key:3,
     value: 'apple'
  }
]

Причина ошибки здесь - избыточность. Чтобы получить FruitList, нам не нужен новый. Вместо этого мы можем напрямую сгенерировать массив на основе перечисления FruitEnum. Принцип - это перечисление Typescript, о котором мы упоминали ранее, за исключением для постоянного перечисления.Кроме того, объект карты генерируется во время компиляции.

  • Правильное написание

enum FruitEnum {
    tomato = 1,
    banana =  2,
    apple = 3
}
const FruitList = Object.entries(FruitEnum)

Выше приведен правильный способ записи.Этот способ записи не только не является избыточным.Кроме того, если тип перечисления изменен, нам нужно только изменить перечисление напрямую, и производный массив также изменится.

Кроме того, значения и строки onum enum по существу различны в определении типового времени, пожалуйста, будьте осторожны, или остальное позволит вам написать код избыточным.

  • неправильное использование
enum GenderEnum{
  'male' = '男生',
  'female' = '女生'
}
interface IPerson{
   name:string
   gender:string
}
let bob:IPerson = {name:"bob",gender:'male'}

<span>{Gender[bob.gender as keyof typeof GenderEnum]}</span>  

Причина вышеуказанной ошибки в том, что в определении типа IPerson пол должен быть не строкой, а перечисляемым ключом.При преобразовании строки в значение перечисления вы должны добавить утверждение as keyof typeof GenderEnum.

  • Правильное написание
enum GenderEnum{
  'male' = '男生',
  'female' = '女生'
}
interface IPerson{
   name:string
   gender:keyof typeof GenderEnum
}
let bob:IPerson = {name:"bob",gender:'male'}

<span>{Gender[bob.gender]}</span>  

Выше правильное написание,Существует очевидная разница между строковым перечислением и типом строки. Когда переменная должна использовать перечисление, его нельзя определить как строку..

(3) ts-игнорировать и любой

Использование ts-ignore должно быть строго запрещено в Typescript, так как ts-ignore — это фактор, влияющий на качество кода Typescript больше, чем любой другой. Для любого я когда-то хотел запретить любое в своем проекте, но есть некоторые сценарии, где нужно использовать любое, поэтому нет грубого запрета на использование любого. Но в большинстве случаев вам может и не понадобиться их использовать.

  • Сценарии, в которых ts-ignore используется неправильно
 //@ts-ignore 
 import Plugin from 'someModule' //如果someModule的声明不存在
 Plugin.test("hello world")

Это самый классический сценарий использования ts-ignore, вышеприведенный метод использует ts-ignore, тогда Typescript будет думать, что тип плагина любой. Правильный метод — настроить используемый тип с помощью метода объявления модуля.

  • Правильный путь
import Plugin from 'someModule'
declare module 'someModule' {
    export type test = (arg: string) => void;
}

Объявления могут быть определены внутри модуля.Объявления с одинаковыми именами следуют определенным принципам слияния.Если вы хотите расширить трехсторонний модуль, объявление модуля очень удобно.

В большинстве же сценариев вам не нужно их использовать.В некоторых сценариях, если тип значения не может быть определен сразу, мы можем использовать unknown вместо any.

Any полностью потеряет суждение о типе, что на самом деле довольно опасно, и использование any эквивалентно отказу от проверки типов и, по сути, отказу от машинописи. Например:

let fish:any = {
       type:'animal',
       swim:()=> {
       
       }
}
fish.run()

В приведенном выше примере мы вызываем метод, который не существует, потому что используется любой, поэтому он пропускает проверку статического типа, поэтому он небезопасен. Во время выполнения возникает ошибка, и если тип значения не может быть определен сразу, мы можем использовать unknown вместо any.

let fish:unknown = {
      type:'animal',
      swim:()=> {
      
      }
}
fish.run() //会报错

unkonwn является подтипом любого типа, так что, как и любому, любой тип может быть назначен unkonwn. В отличие от других, переменная unkonwn должна указывать свой собственный тип.После сокращения типа или утверждения типа переменная unkonwn может использовать методы и переменные, определенные для нее, как обычно.

Проще говоря,unkonwn должен принудительно определить свой тип, прежде чем использовать его.

(4) пространство имен

В коде Typescript, особенно при частичной разработке бизнеса, вы принципиально не используете пространство имен. Кроме того, модуль естественным образом поддерживается в nodejs, а в es6 (далее) модуль es также стал спецификацией на уровне языка, поэтому Typescript официально рекомендует использовать модуль.

Пространство имен — это просто глобальный объект.Конечно, мы также можем поместить пространство имен в модуль, но также проблематично поместить пространство имен в модуль.

  • Неправильный способ
//在一个shapes.ts的模块中使用

export namespace Shapes {
    export class Triangle {
      /* ... */
    }
    export class Square {
      /* ... */
    }
}

//我们使用shapes.ts的时候
//shapeConsumer.ts

import * as shapes from "./shapes";
let t = new shapes.Shapes.Triangle(); // shapes.Shapes?

  • Правильный способ (используя модуль напрямую)
export class Triangle {
/* ... */
}
export class Square {
/* ... */
}

Вышеупомянутое прямое использование модуля является правильным методом.В модульной системе можно избежать дублирования имен переменных, поэтому пространство имен не имеет смысла.

(5) Ограничьте количество параметров функции

При определении функции количество параметров функции должно быть уменьшено, рекомендуется не более 3-х.

  • неправильное использование
function getList(searchName:string,pageNum:number,pageSize:number,key1:string,key2:string){
   ...
}

Не рекомендуется, чтобы параметры функции превышали 3. При наличии более 3 параметров следует использовать объект для агрегации.

  • правильное использование
interface ISearchParams{
   searchName:string;
   pageNum:number;
   pageSize:number;
   key1:string;
   key2:string;
}

function getList(params:ISearchParams){

}

То же самое распространяется на проект React, и то же самое верно для useState.

const [searchKey,setSearchKey] = useState('');
const [current,setCurrent] = useState(1)
const [pageSize,setPageSize] = useState(10)  //错误的写法

const [searchParams,setSearchParams] = useState({
   searchKey: '',
   current:1,
   pageSize:10
})  //正确的写法

(6) Модуль модуля должен попытаться убедиться, что нет побочных эффектов.

Пожалуйста, не используйте модули с побочными эффектами. Чтобы гарантировать, что использование модуля должно быть сначала импортировано, а затем использовано.

  • Неправильный способ
//Test.ts
window.x = 1;
class Test{

}
let test = new Test()


//index.ts
import from './test'
...

Вышеупомянутый модуль, импортированный в index.ts, вызывается внутри файла test.ts Этот метод заключается в импорте модуля с побочными эффектами.

Правильный метод должен заключаться в обеспечении чистоты неэкспортируемых переменных модуля, и вызывающая сторона должна сначала импортировать, а затем вызывать при использовании модуля.

  • Правильный путь
//test.ts
class Test{
   constructor(){
      window.x = 1
   }

}
export default Test

//index.ts
import Test from './test'
const t = new Test();

(7) Использование ненулевых утверждений !. запрещено.

Само ненулевое утверждение небезопасно, и в субъективных суждениях есть ошибки.С точки зрения защитного программирования не рекомендуется использовать ненулевые утверждения.

  • неправильное использование
let x:string|undefined = undefined
x!.toString()

Из-за использования non-void break он не будет сообщен при компиляции, но будет сообщен при запуске.

Рекомендуется использовать необязательную цепочку. в виде.

(8) Используйте встроенные функции машинописного текста

Многие встроенные функции typescript могут повторно использовать некоторые определения. Я не буду вводить их здесь один за другим.Обычными являются Partial, Pick, Omit, Record, extends, infer и т. д. Если вам нужно вывести новые типы из существующих типов, просто и удобно использовать встроенные функции. Вы также можете использовать типы объединения, типы пересечения и объединения типов.

  • тип союза
//基本类型
let x:number|string
x= 1;
x = "1"
//多字面量类型 
let type:'primary'|'danger'|'warning'|'error' =  'primary'

Обращает на себя внимание назначение литералов.

let type:'primary'|'danger'|'warning'|'error' =  'primary'

let test = 'error'
type = test  //报错

let test = 'error' as const 
type =  test //正确

  • перекрестный тип
interface ISpider{
   type:string
   swim:()=>void
}
interface IMan{
   name:string;
   age:number;
}
type ISpiderMan = ISpider & IMan
let bob:ISpiderMan  = {type:"11",swim:()=>{},name:"123",age:10}

  • тип слияния

Наконец, давайте поговорим о слиянии типов, которое крайне не рекомендуется. В бизнес-коде не рекомендуется использовать слияние типов, что увеличит сложность чтения кода. Слияние типов существует во многих местах. Типы могут быть объединены между классами, интерфейсами, пространствами имен и т. д. Возьмем интерфейс в качестве примера:

interface Box {
  height: number;
  width: number;
}

interface Box {
  scale: number;
}

let box: Box = { height: 5, width: 6, scale: 10 };

Блок интерфейса с тем же именем выше будет иметь слияние типов. Не только интерфейс и интерфейс могут быть объединены типами, но класс и интерфейс, класс и пространство имен и т. д. могут иметь объединение типов с одним и тем же именем.Лично объединение типов не рекомендуется в бизнес-коде.

(9) Инкапсулируйте условные операторы и типы защиты ts

  • неправильное написание
if (fsm.state === 'fetching' && isEmpty(listNode)) {
 // ...
}

  • Правильное написание
function shouldShowSpinner(fsm, listNode) {
     return fsm.state === 'fetching' && isEmpty(listNode);
}

   if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
     // ...
   }

При правильном написании мы инкапсулируем логику условного суждения в независимую функцию. Этот способ написания более удобочитаем, и мы можем узнать, какое суждение сделано из имени функции.

Кроме того, условные операторы пакета могут быть охраняются с пользовательским типом крючка TS. Пользовательский вид максимально простейшего типа условного утверждения охраны пакета.

function IsString (input: any): input is string { 
    return typeof input === 'string';
}
function foo (input: string | number) {
     if (IsString(input)) {
        input.toString() //被判断为string
     } else {
     
     }
}

Разумное использование пользовательских охранников в проекте может помочь нам уменьшить количество ненужных утверждений типов и улучшить читаемость кода.

(10) Не используйте непеременные

Будь то имя переменной или имя функции, пожалуйста, не используйте безымянный. Я столкнулся с этой проблемой в бизнесе. Бэкэнд определяет безымянную переменную isNotRefresh:


let isNotRefresh = false  //是否不刷新,否表示刷新

isNotRefresh означает не обновлять, поэтому переменная, определенная таким образом, вызовет обратную логику, связанную с этой переменной. Правильная форма должна состоять в том, чтобы определить переменную isRefresh, чтобы указать, нужно ли обновлять.


let isRefresh = false  //是否刷新,是表示刷新

2. Функциональный

Лично я очень рекомендую функциональное программирование, так как субъективно считаю, что цепные вызовы лучше обратных вызовов, а функциональные методы лучше цепных вызовов. В последние годы функциональное программирование становится все более популярным, и различные библиотеки с открытым исходным кодом, такие как Ramdajs, RxJS, cycleJS, lodashJS и т. д., используют функциональные возможности. В этой статье в основном рассказывается, как использовать ramdajs для упрощения кода.

(1) Декларативное и императивное

Лично я считаю декларативный вызов функции более лаконичным, чем императивный, например:

//命令式
let names:string[] = []
for(let i=0;i<persons.length;i++){
        names.push(person[i].name)
}

//声明式
let names = persons.map((item)=>item.name)

Из вышеприведенного примера видно, что декларативный способ вызова функции более лаконичен. Кроме того, для функций без побочных эффектов, таких как приведенная выше функция карты, вы можете сосредоточиться на написании бизнес-кода, не задумываясь о том, как функция реализована внутри. При оптимизации вашего кода вам нужно сосредоточиться только на внутренней части этих стабильных и надежных функций.

(2) Рамдайс

Рекомендуется использовать ramdajs, отличную библиотеку функционального программирования.По сравнению с другими библиотеками функционального программирования, ramdajs автоматически каррируется, а функции, предоставляемые ramdajs, никогда не изменяют существующие данные пользователей.

Простой пример из недавнего бизнес-кода:

   /**
    * 获取标签列表
    */
   const getList = async () => {
       pipeWithP([
           () => setLoading(true),
           async () =>
               request.get('', {
                   params: {action: API.getList},
               }),
           async (res: IServerRes) => {
               R.ifElse(
                 R.isEqual(res.message === 'success'),
                 () => setList(res.response.list);
               )();
           },
           () => setLoading(false)
       ])();
   };

Выше приведен пример бизнес-кода.Использование канала может сделать работу процесса более понятной, и нет необходимости определять промежуточные переменные.

Давайте посмотрим на другой пример:

let persons = [

      {username: 'bob', age: 30, tags: ['work', 'boring']},
      {username: 'jim', age: 25, tags: ['home', 'fun']},
      {username: 'jane', age: 30, tags: ['vacation', 'fun']}
      
]

Нам нужно узнать из этого массива объекты, теги которых содержат fun. При использовании императива:

let NAME = 'fun'
let person;
for(let i=0;i<persons.length;i++){
   let isFind = false
   let arr = persons[i].tags;
   for(let j = 0;j<arr.length;j++){
      if(arr[i] === NAME){
         isFind = true
         break;
      }
   }
   if(isFind){
      person = person[i]
      break;
   }

}

Мы можем упростить его с помощью функциональной записи:

  let person = R.filter(R.where({tags: R.includes('fun')}))

Объем кода значительно уменьшен, а смысл легче понять.

Наконец, давайте посмотрим на пример:

 const oldArr= [[[[[{name: 'yuxiaoliang'}]]]]];
 

Мы хотим преобразовать многомерный массив oldArr, самое внутреннее имя, из нижнего регистра в верхний регистр, который можно записать непосредственно в функциональном стиле.

 R.map(atem =>
      R.map(btem => R.map(ctem => R.map(dtem => R.map(etem => etem.name.toUpperCase())(dtem))(ctem))(btem))(atem),
  )(arr);