第七期:前端九條啟發分享
一、 ts陣列型別推導莫名被干擾
事情是這樣的, 週五的時候需要做一個小優化, 某個檔案裡面原本匯出3個 "json物件
", 我需要改成匯出三個function
方便根據傳參改變匯出內容, 這時就發生了奇怪的型別報錯, 假設當前有如下的程式碼:
下面程式碼不會報錯, 可以正常執行
export interface Dog {
name: string;
age?: 1 | 2 | 3;
}
export const a = (): Dog => ({
name: "金毛",
});
export const b = () => ({
name: "比熊",
age: 2,
});
export const c = () => ({
name: "拉布拉多",
});
const cc: Dog[] = [a(), b(), c()];
但是奇怪的是, 下面這段程式碼就會報錯
export interface Dog {
name: string;
age?: 1 | 2 | 3;
}
export const a = (): Dog => ({
name: "金毛",
});
export const b = () => ({
name: "比熊",
age: 2,
});
export const c = (): Dog => ({
name: "拉布拉多",
});
const cc: Dog[] = [a(), b(), c()];
我只是為第三個方法指定了返回值, 第二個方法居然報錯了, 我不為第三個方法指定返回值反而第三個方法不會報錯, 接下來還有奇怪的現象, 但我為第三個方法指定為返回any型別的時候, 居然第二個方法也不報錯了:
經過研究發現, 當陣列內某個值被指定為any
的時候, 陣列型別會變成any[]
, 所以才有了c()
被指定為返回any不報錯。
當a與c
兩個方法都被指定為Dog
型別時, ts
發現b方法
的返回值並不一定符合Dog
的推演, 因為Dog
型別裡面的age
只能含有1|2|3
所以報錯, 但是如果你完全不給a b c
指定返回值型別也不會報錯, ts會自動推匯出2在範圍內, 真神奇!
二、 如何設計 undefined | bl 這種結構的元件
舉個例子假設tip提示框
元件, 他有一個是否顯示的引數showTip
為布林值, 當這個值為true
的時候就算使用者沒有用滑鼠懸停這個彈框也會出現, 為false
就是怎麼也無法讓它顯示。
當前有一個需求要求進入頁面後這個框主動彈出停留3s
後消失, 那其實我們很容易就想到將showTip
賦予為true
3秒然後變為undefined
即可, 因為只存在兩種情況, 要麼true要麼undefined, 就好比如下的程式碼編寫方式:
const obj = {};
if(isFirstOpenPage){
obj.showTip = true;
}
return <tip {...obj}>
但是當我使用某個元件庫的時候出現了只要定義過showTip
屬性就無法置為undefined
的情況, 導致我彈出tip
之後就無法恢復到讓使用者去控制顯隱了。
這其實就是程式碼設計時候要考慮的問題了, 當我們設計一個值為布林
的屬性時, 應該同時也考慮到undefined
也可能是一種傳值情況。
三、 react如何重新整理元件自身
我遇到的情況是, 有a, b, c
三個元件, 這三個元件分別對應三種不同的特殊情況, 每個情況都有可能出現, 但這三個元件同一時間只能出現一個, 比如當前出現了a
元件, 使用者點選了關閉才會出現b
元件, 這就導致每次當使用者點選關閉時我要判斷是否另外兩個元件需要被渲染出來。
問題點就在於比如移除a元件
, 元件庫的做法是直接把a的dom結構
移除了, 導致react監控不到。
const [_, forceUpdate] = useReducer(x => x + 1, 0);
if (showA) {
return <A forceUpdate={forceUpdate} />;
} else if (showB) {
return <B forceUpdate={forceUpdate} />;
} else if (showC) {
return <C forceUpdate={forceUpdate} />;
} else {
return null;
}
每個元件裡面
onClose={() => {
if (forceUpdate) {
forceUpdate();
}
}}
上述方法的關鍵就是useReducer
的變化可以引起元件的重新渲染。
四、 為字串插入jsx元素
事情是這樣的, 比如當前我們有個i18n文案
是highest price {max} - lowest price {min}
, 現有方法可以通過傳入兩個字串替換掉{max}
與{min}
, 但是隻能用字串替換, 無法插入dom結構, 比如我想用藍色的字來替換。
假設intl
方法可以把字串裡面的{max}替換掉, 我們對intl
進行擴充變成intlPlus
, 下面是平時使用的方式。
intl.format(
"highest price {max} - lowest price {min}"
[
"99", "33"
]
)
intlPlus 的編寫
import intl from './intl';
const pix = '-------------';
export default function intlPlus(i18n: string, arg: any[]) {
const len = arg.length;
const box = Array(len).fill(pix);
const text = intl.format(i18n, box);
const textArr = text.split(pix);
const res: any[] = [];
for (let i = 0; i < textArr.length; i++) {
res.push(textArr[i]);
if (arg[i] !== undefined) {
res.push(arg[i]);
}
}
return res;
}
上面的原理就是使用'-------------';
作為站位符號, 然後再按順序對佔位符號進行切割, 然後按一個文案一個插值的形式迴圈插入, 並以陣列的形式輸出出去, 使用方法如下:
intl.format(
"highest price {max} - lowest price {min}"
[
"99", <span style={{color: red}}> 33 </span>
]
)
五、 如何防止測試環境地址洩露
比如我們平時 測試環境
的地址不要讓外界看到, 比如如下的程式碼就是會洩露測試域名的程式碼:
const pathObj = {
prod: 'www.xxxxxx.com',
dev: 'www.xxxxxxx-dev.com',
test: 'www.xxxxxx-test.com'
}
// ....
if(location.host === 'localhost:3000'){
window.open(pathObj.dev)
}
外掛 webpack.DefinePlugin
閃亮登場✨, 這個外掛可以讓我們設定一些"全域性"
(帶引號的)的變數, 這些變數可以在正式 打包之前
使用:
plugins: [
new webpack.DefinePlugin({
isDev:
process.env.NODE_ENV === "production"
? JSON.stringify("false")
: JSON.stringify("true")
}),
]
上面很奇怪的使用了JSON.stringify
, 因為只有這樣webpack
才能把它當做字串來處理, 否則會被當做語句來處理, 接下來我們舉個使用時的例子:
我們觀察打包檔案:
觀察可知'oooooooo'已經不存在於打包檔案裡面了。
它的原理就是在全域性進行替換, 比如上面的程式碼:
// 打包前:
if (isDev) {
console.log("tttttttttt");
} else {
console.log("ooooooooooo");
}
// 打包時:
if (false) {
console.log("tttttttttt");
} else {
console.log("ooooooooooo");
}
很明顯了, tree-shaking
不會放過這種程式碼, 所以下面的'ooooooo'邏輯就會被刪掉。
這個外掛有點難理解的點就是, 它並不是執行時執行的, 而是tree-shaking
前進行了一次全域性的替換。
之所以它可以直接寫在全域性isDev
, 但是不可以寫window.isDev
就是因為它並沒有掛載在widnow物件上, 這類變數並不會在使用者端執行。
六、寫成{}匯出後i18n翻譯不實時生效
有很多需要實時變化的配置最好不要設計為json的形式, 比如專案裡的翻譯, 當使用者切換語言英語為中文的時候, 發現某些地方翻譯沒有變化依然還是英語, 這些地方的特徵就是全是匯出的json類似下面這種:
export UserName = {
title: <span> {i18n(USER_NAME_TITLE)} </span>
}
使用方式
import {UserName} from './userName'
// ...
<Table
columns={[UserName]}
>
上述寫法的表格表頭
並不會隨著語言的切換而改變文案。
七、 item2的分割很好用
命令列工具可以很大的緩解我們多專案啟動的問題, 如果你是在vscode
提供的shell
上啟動多個專案, 就會感覺到來回切換有點吃力了, 更糟的情況是你同時開發兩個以上微前端
專案, 所以這個item2
必須好好安利一下, 因為這個實在是太好用了, 還沒這樣玩的朋友可以快快玩起來:
每次點選都會增加一個小視窗, 讓我們看看把控制檯分成'6份':
為了防止混亂, 我們可以為每個視窗命名:
設定過程如下:
還可以設定自己喜歡的背景圖案:
設定過程如下:
八、 tip提示離奇被隱藏
我在做的一個彈出框裡面有個table表格
, 這個table
其中一列的表頭預設是彈出狀態, 但是遇到的bug是這個彈出狀態某些時候會一閃而過, 重新變回未彈出的狀態, 看過元件庫的原始碼之後確定不是元件本身的問題。
經過多次測試發現, 這個bug與點選彈出彈框的按鈕的位置有關, 這才讓我想明白bug的原因, 因為這個彈出框有一個彈出動畫, 從左邊由小變大的出現, 這個變大的過程中實際已經預渲染了這個彈框裡面的元件, 導致彈框出現的過程中會經過滑鼠, 這就導致元件認為使用者懸停過, 導致tip隱藏...
解決方式是將這個彈框出現時的300毫秒的滑鼠事件禁止掉。
九、 藉助 Whistle 設定線上域名開發原生程式碼, 防止被csrf防禦
由於server
端經常會加一些安全策略, 比如只能某個referer
的網站才能呼叫api
, 其他網址過來的都會報403
, 這個時候我們前端可憐的http://localhost:3000
就遭殃了, 每次都要與相關人員溝通本地除錯的問題。
索性我們直接使用測試環境的域名進行開發就得了, 不使用http://localhost:
(類似於windows修改本地的host檔案)總算行了吧...
第一步: 安裝Whistle
npm install -g whistle
w2 start -p 8899 // 啟動服務
直接開啟域名即可看到操作介面: http://localhost:8899
第二步: 安裝proxy
由於我們需要把瀏覽器的請求全部代理到Whistle, 所以需要用到proxy這個谷歌外掛,
搜尋 : Proxy SwitchyOmega
這一步是代理瀏覽器的請求到這個地址
第三步: 配置Whistle
前面的是被代理的地址, 空格後面是目標地址
當前有這樣一個頁面
代理之後
第六步: Whistle配置祕鑰證書
由於Whistle要幫助我們處理所有的請求, 這其中也必然會有https請求, 所以一定要匯入Whistle的證書:
大功告成, 開開心心的使用測試環境域名
進行本地開發
吧。
end
這次就是這樣, 希望與你一起進步。