官术网_书友最值得收藏!

2.6.2 decodeURI解碼問題

在push、replace方法或者瀏覽器“前進”和“后退”時的事件回調函數中,history源碼都會調用createLocation創建location對象。

每次進行相關操作都會對pathname解碼一次,但是并沒有對search與hash進行相關的操作。

1.search沒有解碼帶來的問題

以browserHistory為例,由于history.push/replace方法最終會調用瀏覽器的pushState/replaceState方法,因此如果pushState/replaceState方法入參中有Unicode字符,則瀏覽器會將非特殊的Unicode字符的UTF-8碼中的每個字節加上“%”作為實際地址,window.location.search或者window.location.hash得到的路徑中都將帶有“%”。但是在history庫中,如果調用history庫的方法,如history.push/replace,則參數列表中傳入的字符串也將同步到history.location.search或者history.location.hash中,將不會做編碼處理:

從瀏覽器記錄的角度來看,其記錄URL字符串為編碼后的字符串。此時,如果進行瀏覽器的“前進”和“后退”操作,則browserHistory將更新地址,browserHistory的search和hash此時將從window.location中獲取,獲取到的值將為編碼后的帶“%”的字符串,因此造成search與hash前后不一致的問題:

這在編碼時需要注意。

2.pathname解碼

對于pathname,在history源碼中,設置history.location前對pathname做了一次decodeURI解碼處理。因此解決了上述問題,無論是執行push、replace操作,還是瀏覽器在“前進”和“后退”時,如browserHistory.location.pathname,都將得到解碼后的字符:

這解決了在導航過程中pathname前后不一致的問題,給路由匹配帶來了兩個好處。

1)編碼便利

由于browserHistory.location.pathname前后提供的pathname一致,因此對于Route的path屬性中的特殊字符,可不用進行編碼(Route將在第6章進行介紹):

提供給該Route的進行命中匹配的路徑將永遠為解碼后的字符串,即不會提供/%E4%B8%AD%E6%96%87給該Route進行匹配。

2)參數解析

對于命名路由(命名路由將在第6章進行介紹),如:

當調用如browserHistory.push('/foo/中文')時,由于得到的browserHistory.location.pathname都為解碼后的字符串,因此獲取name這個命名變量的值也會得到解碼后的字符串,即:

永遠不會出現{name:"/%E4%B8%AD%E6%96%87"}這樣的情況。

由于在源碼中進行了decodeURI解碼,因此開發者在編碼過程中如聲明路由、進行導航等操作時不需要關心字符的解碼問題。

3.pathname解碼帶來的問題

雖然前后一致的pathname帶來了編碼的方便,但是引入pathname解碼同時也帶來了一些問題。

前面對push等方法的調用,傳入參數字符串中沒有特殊字符“%”。對于沒有“%”的字符串的導航路徑,則不會有相關問題。但是如果導航路徑中有“%”,則需要注意,由于瀏覽器使用百分號編碼,如"/%E4%B8%AD%E6%96%87",在每個字節前都加上了“%”,因此此時的“%”被認為有特殊作用,后面跟一個字節的十六進制形式。如果希望導航路徑中的“%”沒有特殊作用,則需要對“%”進行一次編碼,調用encodeURI('%')得到“%25”。下面以一個例子來說明。

如果不編碼而直接調用,如:

對于history庫來說,由于其在導航操作時會對pathname進行decodeURI解碼操作,這就默認“%”為特殊符號。上述錯誤即產生于:

無法解碼非法的“%”,如果不希望此錯誤產生,能導航到/abc%d路徑,則可對路徑部分進行一次encode操作:

由于history庫內部會執行一次decodeURI操作,因此得到的browserHistory.location.pathname,即原路徑/abc%d,不會出現報錯。細心的讀者會發現瀏覽器路徑此時為/abc%d:

瀏覽器認為“%”有特殊意義,不會對其進行處理,上述操作等同于:

由于當前路徑為/abc%d,因此雖然編碼過一次沒有報錯,但是如果在瀏覽器中執行一次后退操作,再執行一次前進操作,又回到此路徑時,則會出現解碼錯誤:

由于window.location.pathname為/abc%d,在導航過程中,當路徑/abc%d傳入history庫中后,同樣會執行一次decodeURI操作,這時無法解析/abc%d中非法的“%”字符,因而報錯。

這就是在history庫中引入decodeURI所帶來的問題。

要解決此問題,需要使瀏覽器地址中無特殊意義的“%”也得到編碼,即要得到:

則調用pushState時需要傳入對“%”進行編碼后的字符串:

由于history庫會執行一次decodeURI操作,因此要想獲得上述pushState的效果,則需要在調用push/replace方法時進行兩次編碼:

第一次編碼是為了對特殊字符進行編碼,第二次編碼是為了抵消history庫中的decodeURI操作。當執行完兩次編碼操作后,調用push方法得到:

注意,由于此時的browserHistory.location.pathname為編碼后的值/abc%25d,因此獲取原始值/abc%d需要進行一次解碼(decodeURI(browserHistory.location.pathname))才可實現。

在這樣處理之后,在瀏覽器中執行一次前進、后退操作,由于window.location.pathname為/abc%25d,因此在傳入history庫中后,要進行一次解碼:

這樣最終解決了兩處報錯的問題,但是執行push/replace操作得到的location路徑將與瀏覽器前進、后退時得到的location路徑不一致:

由于有此種問題存在,在此場景中,使用browserHistory.location.pathname變量獲取原始路徑可能有編碼和未編碼兩種情況,對此可引入一些幫助函數(如safeDecode)來處理:

同時,由于獲取路徑存在兩種情況,因此如在Route聲明時,也需聲明兩條路徑:

這樣的聲明使得兩條路徑中的任意一條都能匹配成功。

正是由于存在上述問題,在history計劃的5.x版本中,可能會將內部的decodeURI解碼邏輯移除。但是如果移除了decodeURI解碼,則編碼的便利性也將消失,如對于“/中文”路徑,Route可能會接收到“/%E4%B8%AD%E6%96%87”“/中文”兩類字符串,這可能需要React Router庫內部調用safaDecode等邏輯進行聯動修改,才能兼容history的升級改動。

主站蜘蛛池模板: 家居| 太保市| 舒兰市| 靖远县| 且末县| 永城市| 济阳县| 太和县| 无为县| 江津市| 定安县| 光泽县| 苏州市| 嘉义市| 昭觉县| 江西省| 开远市| 济源市| 汉沽区| 凤庆县| 偃师市| 城市| 巨野县| 肃北| 孟州市| 普兰店市| 堆龙德庆县| 涪陵区| 彩票| 三河市| 湖北省| 双流县| 宁明县| 东方市| 岑溪市| 资兴市| 益阳市| 宁海县| 铁岭县| 错那县| 麻阳|