一些不太常見的前端開發(fā)問題,你遇到過嗎? ( TS 相關(guān)知識較多)
點(diǎn)擊下方星標(biāo)本公眾號,實(shí)用前端技術(shù)文章及時(shí)了解
前言
終于湊齊一些有趣的bug與問題了, 比如在ts方面做了深入的研究, 國際化開發(fā)方面有了一些思考等等
1: url的編碼操作
當(dāng)我們通過url來傳遞一些信息的時(shí)候, 可能會出現(xiàn)一些讀取的問題,我們常用encodeURI與encodeURIComponent進(jìn)行編碼后再進(jìn)行傳遞, 但是我發(fā)現(xiàn)項(xiàng)目中所有地方都用encodeURIComponent, 為什么會這樣這兩種有什么區(qū)別?
因?yàn)?code style="font-size: 14px;border-radius: 4px;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">encodeURI并不會對;/?:@&=+$,#之類的字符進(jìn)行轉(zhuǎn)移, 這就會導(dǎo)致某些特殊情況下解析uri出現(xiàn)問題(后端使用的語言不同導(dǎo)致解析方式不同), encodeURIComponent會轉(zhuǎn)義URI各個(gè)部分的標(biāo)點(diǎn)符號比如常用的連接符&與?, 也就是說它轉(zhuǎn)義的更徹底安全性更高, 所以建議盡可能使用encodeURIComponent來處理。
2: 國際化項(xiàng)目左右翻轉(zhuǎn)(前端 RTL 適配)
來到國際化前端團(tuán)隊(duì)才學(xué)習(xí)到, 從左往右寫的為"LTR", 從右往左寫的為"RTL", 比如'希伯來語'、'阿拉伯語'等,如果你的公司要開發(fā)一款app提供給多個(gè)國家使用, 那就要考慮到有的國家書寫文字是從右往左的, 并且很多圖片也要從右往左展示, 比如把返回按鈕放在右上方!
我們要做的就是文字書寫的翻轉(zhuǎn)、輸入框的翻轉(zhuǎn)、圖標(biāo)的自身翻轉(zhuǎn)以及位置的鏡像、但是某些圖標(biāo)不用反轉(zhuǎn)比如 "時(shí)鐘" 亦或者 30%不用反轉(zhuǎn)為%03, 當(dāng)然這些頭疼的問題也是有成熟的解決方案的。


第一種: dir="rtl"屬性設(shè)置
為body元素加上屬性dir="rtl", 瀏覽器就可以自動翻轉(zhuǎn)了, 沒試過的快試試很好玩的。
缺點(diǎn)也很明顯, 就比如我們的css屬性margin: left; 仍然是作用于左邊。
第二種: rtlcss
rtlcss的官網(wǎng), 他的實(shí)現(xiàn)思路就是配合rtl屬性使用, 將頁面上的left相關(guān)屬性都轉(zhuǎn)為right屬性, 核心思想就是某些屬性的全局替換。
3: 后端int64類型出錯(cuò)
公司內(nèi)部有一個(gè)庫可以把后端的rpc接口規(guī)范直接轉(zhuǎn)成ts規(guī)范供前端使用, 但是突然有一天出現(xiàn)了類型錯(cuò)誤, 比如后端規(guī)定返回參數(shù)為code數(shù)字類型, msg為字符串類型, 那么就會生成如下文件:
export?type?XxxxApi?=?{
???code:?number;
???msg:?string;
}
但是一天夜里后端返回的code對應(yīng)的類型竟然變成了string, 我和同事查看了后端同學(xué)的代碼, 定義的也的確是int類型, 但不過不是int32而是int64, 原來是因?yàn)?code style="font-size: 14px;border-radius: 4px;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">js的數(shù)字的極限是2的53次方:

所以才采用string的方式來表達(dá)int64這個(gè)數(shù)據(jù)類型, 后端同學(xué)將類型改為int32就沒問題了, 當(dāng)然啦后端同學(xué)擅自改類型不通知我們要提出批評??。
4: 開發(fā)時(shí)候樣式好好的, 打包后就出問題了
* bug 場景
???? 明明開發(fā)時(shí)候好好的, 但為什么打包之后就會出現(xiàn)各種錯(cuò)誤, 比如樣式丟失, 這里說下原因之一:
很久之前的某天我把開發(fā)好的前端項(xiàng)目代碼發(fā)布到了服務(wù)器上, 在我本地訪問時(shí)樣式很完美, 但是當(dāng)我通過測試環(huán)境url打開這個(gè)項(xiàng)目的時(shí)候, 竟然表格樣式有些崩壞寬高與我本地的不一樣, 但是我沒有想明白bug的原因, 就去與 '同學(xué)a' 交流為什么出現(xiàn)這種現(xiàn)象。
'同學(xué)a' 說是因?yàn)橛脩舻臑g覽器和我不一樣導(dǎo)致的, 可是我就是用戶, 開發(fā)就在我的瀏覽器上也是我用瀏覽器訪問的測試環(huán)境, 但是是同一個(gè)瀏覽器, 但'a同學(xué)'堅(jiān)持說不可能發(fā)生這種狀況, 我就給他演示了一遍從開發(fā)到發(fā)布到測試環(huán)境的全流程, 看到bug確實(shí)再次出現(xiàn)的他說是我'人品'的問題.... (后來是通過改了一些css的寫法解決的)
我對這個(gè)事情印象還是比較深刻的, 但在今年的某一天, 我在配置webpack的時(shí)候突然發(fā)現(xiàn)了一個(gè)問題點(diǎn), 比如postcss在配置的時(shí)候會有一個(gè)設(shè)置, 在development與production兩種模式下分別兼容到主流瀏覽器什么版本, 那這里其實(shí)就很可能是問題所在, 因?yàn)獒槍﹂_發(fā)與打包進(jìn)行了不同的翻譯, 這就會導(dǎo)致無法預(yù)期的錯(cuò)誤產(chǎn)生, 雖然已經(jīng)不在那家公司了當(dāng)年的代碼已經(jīng)找不到了, 但想到這點(diǎn)還是會很強(qiáng)烈的感覺到之前毫無頭緒的問題有了一個(gè)解決方向!
5: pc端喚起WhatsApp & Email 為何失效
URL Schema
要想學(xué)習(xí)喚起app就要先知道Schema是什么, 我通俗點(diǎn)講一下, 就是你下載到系統(tǒng)里的每個(gè)app其實(shí)都可以注冊一個(gè)屬于它的url地址, 這個(gè)地址你可以理解為就是Schema。
而我們喚起某個(gè)app就可以利用這個(gè)"Schema 地址"。
調(diào)起WhatsApp
假設(shè)聯(lián)系人的電話號碼為 18200000000, 并且為中國的號碼區(qū)號為(86)
-?window.open("https://wa.me/8618200000000")??會閃屏
-?或
-?window.open("https://api.whatsapp.com/send/?phone=8618200000000")

不建議用window.location.href的方式跳轉(zhuǎn), 他會導(dǎo)致閃屏。
pc端為何無法通過給定的WhatsApp號碼喚起WhatsApp?

因?yàn)閃hatsApp屬于國際軟件它要兼容區(qū)分各個(gè)國家, 所以要加上國家的區(qū)號。 當(dāng)然這個(gè)網(wǎng)址有時(shí)候不穩(wěn)定也會導(dǎo)致加載不出來。
什么是 mailto
mailto是一種類似http的url協(xié)議, 但它屬于本地協(xié)議(本地協(xié)議比較典型的還有file), 也就是不需要連接網(wǎng)絡(luò)就可以解析的協(xié)議, mailto的功能是喚起默認(rèn)郵箱。
喚起Email
假設(shè)聯(lián)系人的郵箱號碼為 [email protected]
指定收件人
-?window.location.href="mailto:[email protected]"
如果為多個(gè)人發(fā)郵件則?','?分割
-?window.location.href="mailto:[email protected],[email protected]"
如果要添加主題,?增加subject參數(shù)
-?window.location.href=`mailto:[email protected]?subject=${encodeURIComponent("我是主題xxx")}`
如果要添加主題,?增加body參數(shù)
-?window.location.href=`mailto:[email protected]?subject=${encodeURIComponent("我是主題xxx")}&body=${encodeURIComponent('我是內(nèi)容xxx')}`

6: React.FC
經(jīng)常出現(xiàn)React.Fc這個(gè)函數(shù), 比如我不使用React.Fc來處理組件的函數(shù), 則在組件里面使用props.children會報(bào)錯(cuò), 我們一起進(jìn)入源碼分析一下。
?type?FC?=?FunctionComponent
;
????interface?FunctionComponent
?{
????????//?第一句
????????(props:?PropsWithChildren
,?context?:?any):?ReactElement?|?null;
????????propTypes?:?WeakValidationMap?|?undefined;
????????contextTypes?:?ValidationMap?|?undefined;
????????defaultProps?:?Partial?|?undefined;
????????displayName?:?string?|?undefined;
????}
FC這個(gè)type接收一個(gè)參數(shù)P, 默認(rèn)值為空對象, 而這個(gè)P。FunctionComponent就是個(gè)過度的名稱而已, 可以認(rèn)為FC就是FunctionComponent。第一句意義為第一個(gè)參數(shù)為 PropsWithChildren類型, 第二個(gè)參數(shù)可有可無, 有則為任意類型, 返回React的dom或者返回null。后面四個(gè)參數(shù)不是必填, 我們主要研究第一句。
我們追查一下PropsWithChildren
?type?PropsWithChildren?=?P?&?{?children?:?ReactNode?|?undefined?};
只是將傳入的P的類型與{ children?: ReactNode | undefined }合并而已, 看到這里我們就明白了, 其實(shí)用React.FC包裹一下是可以幫助ts推導(dǎo)出props身上可能有children屬性。
7: RematchRootState
rematch官網(wǎng);
rematch是對Redux的二次封裝, 而RematchRootState是rematch導(dǎo)出的一個(gè)ts推導(dǎo)函數(shù), RematchRootState到底做了什么令程序員脫發(fā)的操作...
項(xiàng)目里使用了這個(gè)RematchRootState之后, 發(fā)現(xiàn)某些類型推導(dǎo)出來never類型如圖所示:

一起探索源碼里的奧義
export?type?RematchRootState?=?ExtractRematchStateFromModels
export?type?ExtractRematchStateFromModels?=?{
????[modelKey?in?keyof?M]:?M[modelKey]?extends?ModelConfig???M[modelKey]['state']?:?never
}?
在使用
RematchRootState時(shí)我們使用typeof的形式導(dǎo)出了一個(gè)ts類型, typeof在ts里面使用就不是js里面的意義了:Xxx<typeof?{name:'金毛',?age:9}>
//?此處就相當(dāng)于:
Xxx<typeof?{name:string,?age:number}>[modelKey in keyof M]循環(huán)M對象里面所有的key值, 每次循環(huán)時(shí)命名為modelKey。M[modelKey]就是取出對應(yīng)的值, 這里特指ts里面的類型值。M[modelKey] extends ModelConfig ? M[modelKey]['state'] : never, 也就是每次取出'值', 并且此'值'符合ModelConfig類型的話則返回M[modelKey]['state']的類型, 否則返回never, 這里的extends你可以理解為is用來判斷某個(gè)值是不是符合規(guī)范的, 以后文章還會涉及extends的其他用法。
至此我們已經(jīng)明白了, 問題一定出在M[modelKey]的類型未符合 ModelConfig的類型規(guī)范導(dǎo)致的返回的never, 那ModelConfig又是什么規(guī)范那?
export?interface?ModelConfig?{
????name?:?string
????state:?S
????baseReducer?:?(state:?SS,?action:?Action)?=>?SS
????reducers?:?ModelReducers
????effects?:
????????|?ModelEffects
????????|?(?|?void?=?void>(dispatch:?RematchDispatch )?=>?ModelEffects)
}?
由于調(diào)用 ModelConfig時(shí)什么參數(shù)都沒傳, 則使用默認(rèn)值。當(dāng) name屬性我們賦予了number類型時(shí)會導(dǎo)致錯(cuò)誤。state對應(yīng)的S類型, 也就是默認(rèn)的any任何類型都可以。baseReducer的參數(shù)不符合規(guī)范, 或是返回值不符合規(guī)范時(shí)。
effects 要單獨(dú)拿出來講
第一個(gè): effects = ModelEffects
type?ModelEffects?=?{
????[key:?string]:?(?this:?{?[key:?string]:?(payload?:?any,?meta?:?any)?=>?Action?},payload:?any,rootState:?S)?=>?void
}
export?type?Action?=?{
????type:?string,
????payload?:?P,
????meta?:?M,
}?
[key: string]這種寫法意思就是取出里面所有的項(xiàng)進(jìn)行循環(huán)。ModelEffects每一項(xiàng)都為函數(shù), 并且沒有返回值。ModelEffects對象的每個(gè)函數(shù)的第一個(gè)參數(shù)為一個(gè)對象, 這個(gè)對象里面值都為函數(shù), 并且返回值為Action。ModelEffects對象的每個(gè)函數(shù)的第二個(gè)參數(shù)為任意類型。ModelEffects對象的每個(gè)函數(shù)的第三個(gè)參數(shù)為rootState: SS類型, S則是我們上一步傳入進(jìn)來的, 也就是any。
第二個(gè):
effects?=?(?|?void?=?void>(dispatch:?RematchDispatch )?
=>?ModelEffects)
M是新定義的一個(gè)泛型, 它符合Models的規(guī)范, 如果不符合的話就為void類型。這個(gè) K就是上面默認(rèn)的string(寫到這里我都感覺好麻煩??)。
這里是Models的類型:
export?type?Models?=?{
????[key?in?K]:?ModelConfig
}?
每項(xiàng)都是ModelConfig, 而ModelConfig我們上面已經(jīng)講過啦。
這個(gè)函數(shù)接收的第一個(gè)參數(shù) dispatch要符合類型RematchDispatch, 這里就不再擴(kuò)展了, 往下還有特別深:
export?type?RematchDispatchvoid>?=
??ExtractRematchDispatchersFromModels?&
????(RematchDispatcher?|?RematchDispatcherAsync)?&
????(Redux.Dispatch)?
可以看出來, 這個(gè)參數(shù)的類型我們用Redux.Dispatch來定義就沒問題的。
返回值必須為: ModelEffects這個(gè)我們剛才也講過了。
正確的用法可以是如下的樣子:
?effects:?(dispatch:?Redux.Dispatch)?=>?({
????async?FnXxx(_:?any,?state:?RootState)?{
??????console.log(state.xxx.xxxList)
????},
??}),?
總結(jié)一句話就是"讀這代碼好心累"!
8: ts 修改函數(shù)參數(shù)
實(shí)現(xiàn): 增加函數(shù)一個(gè)參數(shù)
假設(shè)當(dāng)前我有這樣一個(gè)type:
?type?Obj?=?{
????getX:?(a:?string,?c:?boolean)?=>?void;
????getN:?(a:?number)?=>?void;
??};
而我希望將這個(gè)type處理成下面這個(gè)樣子:
?type?Obj?=?{
????getX:?(s:?string[],?a:?string,?c:?boolean)?=>?void;
????getN:?(s:?string[],?a:?number)?=>?void;
??};
這里的關(guān)鍵點(diǎn)就是取到函數(shù)返回值的類型, 以及函數(shù)參數(shù)的類型集合, 實(shí)現(xiàn)代碼如下:
?type?Obj2?=?{
????[Key?in?keyof?T]:?T[Key]?extends?(...arg:?any)?=>?any
???????(s:?string[],?...arg:?Parameters )?=>?ReturnType
??????:?T[Key];
??};
循環(huán)泛型 T里面所有的值。如果 T[Key]不滿足(...arg: any) => any則不處理, 因?yàn)?code style="font-size: 14px;border-radius: 4px;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">T[Key]可能不是函數(shù)類型。反之 T[Key]為函數(shù)類型, 則第一個(gè)參數(shù)為s: string[]。...arg為后續(xù)參數(shù)類型,Parameters<>為自帶方法, 可以推導(dǎo)出函數(shù)的所有參數(shù)組成的數(shù)組的類型。ReturnType<>為自帶方法, 可以推導(dǎo)出函數(shù)的返回值的類型。
使用方法就是:
type?NewObj?=?Obj2?
實(shí)現(xiàn): 去掉函數(shù)第一個(gè)參數(shù)
假設(shè)當(dāng)前我有這樣一個(gè)type:
?type?Obj?=?{
????getX:?(a:?string,?c:?boolean)?=>?void;
????getN:?(a:?number)?=>?void;
??};
而我希望將這個(gè)type處理成下面這個(gè)樣子:
?type?Obj?=?{
????getX:?(c:?boolean)?=>?void;
????getN:?()?=>?void;
??};
這里的關(guān)鍵點(diǎn)就是, 在ts里如何剔除數(shù)組的第一個(gè)元素, 并使用剩下的元素組成數(shù)組返回出來:
?type?Obj2?=?{
????[Key?in?keyof?T]:?T[Key]?extends?(s:?any,?...arg:?infer?Arg)?=>?any
????????(...arg:?Arg)?=>?ReturnType
??????:?T[Key];
??};?
這里整體的邏輯是不變的, 與上面一個(gè)原理。 (s: any, ...arg: infer Arg) => any, 這里是核心, 將函數(shù)處理第一個(gè)參數(shù)以外的參數(shù)單獨(dú)拿出來命名為Arg, 然后使用Arg來定義函數(shù)的參數(shù)。infer是ts內(nèi)置的關(guān)鍵字, 有點(diǎn)類似js中的var, 他可以定義一個(gè)變量。
使用方法就是:
type?NewObj?=?Obj2?
9: gzip壓縮可以用什么替代
之前我一直認(rèn)為gzip壓縮是當(dāng)前最好的前端壓縮方案, 但是其壓縮方案并不唯一并且有著很多分類, 壓縮方式被"無狀態(tài)壓縮", "有狀態(tài)壓縮"。
無狀態(tài)意味著它看到的任何大塊數(shù)據(jù),它都會壓縮,而不依賴于以前的輸入。速度更快但通常壓縮程度更低;有狀態(tài)壓縮查看以前的數(shù)據(jù)來決定如何壓縮當(dāng)前數(shù)據(jù),但速度較慢但壓縮好得多。
比如zstd壓縮屬于有狀態(tài)壓縮, 會根據(jù)壓縮過程中遇到的重復(fù)代碼塊生成字典, 再遇到相同的代碼用字典里對應(yīng)的key來標(biāo)識即可。
end
這次就是這樣, 希望與你一起進(jìn)步。
作者:lulu_up
https://segmentfault.com/a/1190000040632852
祝 您:2022 年暴富!萬事如意!
點(diǎn)贊和在看就是最大的支持,
比心??
