最近因為公司業務的調整,專案需要開發大量的業務元件、高複用邏輯提供給客戶使用。當各類元件、程式碼多了以後,加上團隊內幾個成員書寫習慣、開發思想的不同,出現了好多問題。尤其兩個問題最嚴重:
- 大量的業務元件/業務邏輯需要通過查原始碼的方式,或者問寫元件的人,才能知道元件是否有自己需要的屬性/鉤子方法
- 有些元件因為產品需求 + 口頭溝通 + 需求妥協,只能應用於某一個特定的情況下,其他人看設計圖或者邏輯差不多相似就直接拿過來用,結果發現用不了/各種問題
為了解決這兩個問題,就開始要求組員在開發業務元件的同時,必須寫對應的開發文件/程式碼註釋。一開始還好,中後期開發文件的更新明顯跟不上元件的迭代,逐漸地又回到了靠嘴問的情況,第2個問題也是隨著時間推移又回到了起點。
某天通過VS Code
除錯程式碼的時候忽然發現,用滑鼠在原生語法和react
的方法上懸浮幾秒鐘,就會出現一個提示框,裡面有一些節點/元件/方法的簡單介紹,引數等。
對,這就是我想要的效果!
原生語法 (如document.getElementById
):
react
的方法(如useState
):
通過ctrl + 滑鼠左鍵
點開型別定義,發現提示框裡的內容其實是相關程式碼上方的註釋。
按照型別定義裡面的註釋,我在程式碼裡輸入/**
的時候出現瞭如下圖的提示。
拿著關鍵詞我去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
檔案中也有類似的智慧提示,可以通過VS Code
安裝vetur
外掛,然後在專案根目錄下建立名為vetur
的資料夾,並新建tags.json
和attributes.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
能夠加強這方面的優化吧。