一個巧合,我把文件寫進了程式碼裡

飛灰同學發表於2021-02-22

最近因為公司業務的調整,專案需要開發大量的業務元件、高複用邏輯提供給客戶使用。當各類元件、程式碼多了以後,加上團隊內幾個成員書寫習慣、開發思想的不同,出現了好多問題。尤其兩個問題最嚴重:

  1. 大量的業務元件/業務邏輯需要通過查原始碼的方式,或者問寫元件的人,才能知道元件是否有自己需要的屬性/鉤子方法
  2. 有些元件因為產品需求 + 口頭溝通 + 需求妥協,只能應用於某一個特定的情況下,其他人看設計圖或者邏輯差不多相似就直接拿過來用,結果發現用不了/各種問題

為了解決這兩個問題,就開始要求組員在開發業務元件的同時,必須寫對應的開發文件/程式碼註釋。一開始還好,中後期開發文件的更新明顯跟不上元件的迭代,逐漸地又回到了靠嘴問的情況,第2個問題也是隨著時間推移又回到了起點。

某天通過VS Code除錯程式碼的時候忽然發現,用滑鼠在原生語法和react的方法上懸浮幾秒鐘,就會出現一個提示框,裡面有一些節點/元件/方法的簡單介紹,引數等。

對,這就是我想要的效果!

原生語法 (如document.getElementById):
document.getElementById

react的方法(如useState):
useState

通過ctrl + 滑鼠左鍵點開型別定義,發現提示框裡的內容其實是相關程式碼上方的註釋。
型別定義
按照型別定義裡面的註釋,我在程式碼裡輸入/**的時候出現瞭如下圖的提示。
JSDOC提示

拿著關鍵詞我去VS Code的官網搜尋了一番,在官網搜到了答案(點選此處)。

VS Code understands many standard JSDoc annotations, and uses these annotations to provide rich IntelliSense.

VS Code 可以理解標準的JSDoc程式碼註釋,並使用這些註釋提供豐富的智慧感知(如智慧程式碼完成,懸停資訊和簽名資訊)

JSDoc的語法也非常簡單,只需要保證註釋的開頭是/**即可,其他與多行註釋沒有什麼差別。(更多語法:點選此處

/** 這樣便建立了一個程式碼提醒 */
function remind() {}

上手寫個元件試試效果!

import React, { useEffect, useState } from 'react'

interface KeywordInterface {
  /**
   * 關鍵詞
   */
  keyword?: string;
  /**
   * 高亮顯示的顏色,支援hex、hsl、rgba、keywords
   */
  color?: string;
  children?: string;
}

/**
 * 關鍵詞高亮元件
 * 
 * @example <LightKeyword keyword="hello">Hello World</LightKeyword>
 * 
 * @param { string } keyword - 關鍵詞
 * @param { string } color - 高亮顯示的顏色
 */
const LightKeyword: React.FC<KeywordInterface> = ({
  color = '',
  keyword = '',
  children = ''
}) => {

  const [ context, setContext ] = useState('')

  useEffect(() => {
    // 當關鍵詞為空時,無需對內容做高亮顯示
    if( !keyword ) { 
      return setContext(children)
    }

    const pattern = new RegExp(keyword, 'gi')
    // 通過正則把關鍵詞過濾出來並增加HTML節點
    const allword = (children as string).replace(pattern, (word) => `<i class="light-keyword-item" ${ color && `style="color: ${ color }"` }>${ word }</i>`)

    setContext(allword)
  }, [ keyword, color, children ])

  return (
    <span className="light-keyword" dangerouslySetInnerHTML={{ __html: context }}></span>
  )
}

export default LightKeyword

效果展示:

當滑鼠懸浮在元件上時:
當滑鼠懸浮在元件上時

當資料懸浮在元件屬性上時:
當資料懸浮在元件屬性上時

完美!這樣只要按格式寫好註釋,就可以不用那麼麻煩地去查文件了。(前提是得寫)

那如果是業務邏輯呢?因此我寫了一段基於業務封裝的非同步請求程式碼。



import qs from 'qs'
import { message } from 'antd'
import axios, { AxiosRequestConfig } from 'axios'

interface configInterface {
  /**
   * 請求地址
   */
  url: string;
  /**
   * 請求方式
   */
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
  /**
   * 請求引數
   */
  data?: any;
  /**
   * 其他配置引數
   * 
   * @param { Object }  headers 請求頭配置
   * @param { boolean } errorMessage 是否啟用錯誤提醒
   * @param { string }  responseType 請求型別,預設為json
   * @param { boolean } withCredentials 是否攜帶跨域憑證
   */
  options?: {
    /**
     * 請求頭配置
     */
    headers?: any;
    /**
     * 是否啟用錯誤提醒
     */
    errorMessage?: boolean;
    /**
     * 請求型別,預設為json
     */
    responseType?: 'json' | 'arraybuffer' | 'blob' | 'document' | 'text' | 'stream';
    /**
     * 是否攜帶跨域憑證
     */
    withCredentials?: boolean
  }
}

// axios全域性配置
const $axios = axios.create({

  // 請求介面地址
  baseURL: 'https://demo.com',
  // 超時時間
  timeout: 60 * 1000
})

/**
 * 非同步請求
 * 
 * @description 基於現有業務封裝,自動處理GET請求序列化/錯誤碼處理反饋/跨域配置等操作
 * @example useRequest<T>({ url: 'api/weather', method: 'GET', data: { date: '2021-02-30' }, options: {} })
 * @typedef requestConfig 請求引數
 * @param   { string } requestConfig.url 請求地址
 * @param   { string } requestConfig.method 請求方式
 * @param   { any }    requestConfig.data 請求引數
 * @param   { object } requestConfig.options 其他配置引數
 */
const useRequest = async <T>(requestConfig: configInterface): Promise<T> => {

  const requestOptions = requestConfig.options || {}

  const axiosConfig: AxiosRequestConfig = {

    url: requestConfig.url,
    method: requestConfig.method || 'GET',
    headers: requestOptions.headers || {},
    responseType: requestOptions.responseType || 'json',
    withCredentials: requestOptions.withCredentials !== false
  }

  // 請求方式為GET時,對引數進行序列化處理
  if( axiosConfig.method === 'GET' ) {

    axiosConfig.params = requestConfig.data || {}
    axiosConfig.paramsSerializer = (params) => qs.stringify(params, { arrayFormat: 'brackets' })

  } else {

    axiosConfig.data = requestConfig.data || {}
  }

  try {

    const { data: response } = await $axios(axiosConfig)

    // 如後端返回錯誤碼,將錯誤推入catch控制程式碼執行
    if( response.code !== 0 ) {

      // 錯誤提醒
      if( requestOptions.errorMessage !== false ) {

        message.error(response.message || '未知錯誤')
      }
      
      return Promise.reject(response)
    }

    return Promise.resolve(response)

  } catch(e) {

    // 錯誤提醒
    if( requestOptions.errorMessage !== false ) {

      message.error('請求錯誤,請稍後重試')
    }

    return Promise.reject(e)
  }
}

export default useRequest

實際效果:
懸停提示
(基本用法及引數提醒)
額外配置提醒
(額外配置提醒)

配合Typescript,幾乎就是把文件寫進了程式碼裡!!!

然而當我興致勃勃地搭建vue 3的開發環境,想嘗試一下vue的智慧提示。經過多輪測試,JSDoc的智慧提示只支援在js/ts/tsx這幾類的檔案,並不支援.vue格式的檔案。
vue檔案不支援jsdoc的智慧提示
vue檔案不支援jsdoc的智慧提示)

如果希望在vue檔案中也有類似的智慧提示,可以通過VS Code安裝vetur外掛,然後在專案根目錄下建立名為vetur的資料夾,並新建tags.jsonattributes.json兩個檔案,然後在package.json中引入兩者的路徑 。

// package.json
{
  "name": "demo",
  "version": "0.1.0",
  "vetur": {
    "tags": "./vetur/tags.json",
    "attributes": "./vetur/attributes.json"
  }
}

// vetur/tags.json
{
  "light-keyword": {
    "attributes": ["keyword", "content", "color"],
    "description": "關鍵詞高亮元件"
  }
}

// vetur/attributes.json
{
  "color": {
    "type": "string",
    "description": "高亮顯示的顏色,支援hex、hsl、rgba、keywords"
  },
  "content": {
    "type": "string",
    "description": "文字內容"
  },
  "keyword": {
    "type": "string",
    "description": "關鍵詞"
  }
}

最後的實現效果
元件智慧提示
(元件智慧提示)
元件描述
(元件描述)
屬性描述
(屬性描述)

好處是不受vue版本的限制,2和3都可以用;壞處是json檔案的限制,沒有辦法像JSDoc一樣顯示豐富的格式和程式碼片段,希望vetur能夠加強這方面的優化吧。

相關文章