問題描述
項(xiàng)目技術(shù)棧是react+ant-design趁桃;
產(chǎn)品需求是在某個頁面表單有信息輸入或變更雁歌,用戶未保存要離開該頁面時呻引,自定義提示用戶提示颜及,如果用戶確認(rèn)離開則跳轉(zhuǎn)到新頁面掖肋,否則留在當(dāng)前頁仆葡。
思考
在react單頁應(yīng)用中,需要根據(jù)用戶的選擇來是否跳轉(zhuǎn)頁面志笼,也就是需要阻塞瀏覽器的頁面跳轉(zhuǎn)沿盅,類似<a>標(biāo)簽點(diǎn)擊后的return false效果,待到用戶操作后再執(zhí)行后續(xù)操作纫溃。
首先腰涧,能想到的是瀏覽器的window.confirm方法,在交互上完全符合需求紊浩,那么自定義需求呢窖铡?經(jīng)過查閱資料發(fā)現(xiàn),目前主流瀏覽器都不支持該方法樣式的自定義坊谁,因此放棄费彼。
其次,就是react的方案口芍,react中箍铲,控制頁面路由的是react-router,若官方提供了這種能力便是更好的鬓椭。于是颠猴,我查閱了其官方文檔:http://react-guide.github.io/react-router-cn/docs/API.html关划、或者https://reacttraining.com/react-router/core/api/Prompt、中文版https://segmentfault.com/a/1190000014294604?utm_source=tag-newest
關(guān)鍵文檔
由于項(xiàng)目中使用的是react-router-v4翘瓮,所以直接查看最新的API(如果是較老版本有比較簡單的實(shí)現(xiàn)贮折,可參考https://blog.csdn.net/ISaiSai/article/details/53908903
)
一、Prompt組件
import { Prompt } from 'react-router-dom';
....
render(){
return(
<Prompt message="確定要離開春畔?" when={true}/>
)
}
如上代碼脱货,可以使用react-router-dom的Prompt,在JSX的任意render方法中(不僅僅是在router組件中)都可以使用律姨,使用后當(dāng)前頁面組件離開就會給用戶提示信息振峻,默認(rèn)彈框?yàn)闉g覽器的window.confirm彈框。
- message: string
當(dāng)用戶離開當(dāng)前頁面時择份,設(shè)置的提示信息扣孟。
<Prompt message="確定要離開?" /> - message: func
當(dāng)用戶離開當(dāng)前頁面時荣赶,設(shè)置的回掉函數(shù)凤价,返回 true 時允許直接跳轉(zhuǎn)
<Prompt message={location => (
Are you sue you want to go to ${location.pathname}?
)} /> - when: bool
通過設(shè)置一定條件要決定是否啟用 Prompt,屬性值為true時啟用防止轉(zhuǎn)換拔创;
二利诺、Router組件的getUserConfirmation方法
用于確認(rèn)導(dǎo)航的函數(shù),默認(rèn)使用 window.confirm剩燥。例如慢逾,當(dāng)從 /a
導(dǎo)航至 /b
時,會使用默認(rèn)的 confirm
函數(shù)彈出一個提示灭红,用戶點(diǎn)擊確定后才進(jìn)行導(dǎo)航侣滩,否則不做任何處理。需要配合 <Prompt>
一起使用变擒。
const getConfirmation = (message, callback) => {
const allowTransition = window.confirm(message);
callback(allowTransition);
}
<BrowserRouter getUserConfirmation={getConfirmation} />
實(shí)現(xiàn)方案
中心思想:阻塞頁面君珠,等待用戶操作后異步處理頁面跳轉(zhuǎn)(router變更)。
探索一:Prompt既然是組件娇斑,那么在組件內(nèi)使用state變更的方式去render是否可行策添?
嘗試如下代碼:
...
<Prompt
message = {(location)=>{
return this.state.customPromt,
}
}
/>
...
實(shí)踐證明這是不行的,prompt組件的message方法被同步調(diào)用了毫缆,不能達(dá)到阻塞頁面跳轉(zhuǎn)的效果唯竹。
探索二:嘗試async/await
既然是要異步執(zhí)行,那么就可以嘗試async/await的異步處理方式悔醋,同步寫摩窃。恰好message也可以是一個function。于是,新的嘗試代碼如下:
...
handlePrompt = async () => {
const leaveConfirm = new Promise((res, rej) => {
confirm({
content: 'Are you sure?',
onOk() {
res(true);
},
onCancel() {
res(false);
},
});
});
const leave = await leaveConfirm;
return leave ;
}
...
<Prompt message={(location) => {this.handlePrompt} />
...
滿懷期待猾愿,然后...還是不行鹦聪。仔細(xì)查閱文檔發(fā)現(xiàn),message方法返回的是一個字符串或者true蒂秘,返回字符串則調(diào)用瀏覽器默認(rèn)的prompt方法阻塞頁面跳轉(zhuǎn)泽本,和直接使用window.prompt同效果,不符合需求姻僧;返回true則直接跳轉(zhuǎn)规丽。
message: func
Will be called with the next
location
andaction
the user is attempting to navigate to. Return a string to show a prompt to the user ortrue
to allow the transition.
以上嘗試代碼,雖然添加了異步方法撇贺,也會執(zhí)行異步函數(shù)赌莺,但是始終會跳轉(zhuǎn)頁面,經(jīng)查原因?yàn)椋?a target="_blank">https://github.com/ReactTraining/react-router/issues/6669松嘶。
探索三:getUserConfirmation方法艘狭,https://github.com/ReactTraining/history#customizing-the-confirm-dialog
前兩種方案都失敗了,那么再次閱讀文檔發(fā)現(xiàn)翠订,getUserConfirmation方法就是提供給我們自定義用的巢音,默認(rèn)情況下,當(dāng)頁面使用了prompt組件后尽超,調(diào)用的getUserConfirmation方法是瀏覽器默認(rèn)的window.prompt官撼。如果需要自定義,直接覆蓋即可似谁。
簡單示例代碼如下:
const getConfirmation = (message, callback) => {
const allowTransition = window.confirm(message);
callback(allowTransition);
}
<BrowserRouter getUserConfirmation={getConfirmation} />
ps:
注意該方法需要寫在BrowserRouter 或 MemoryRouter 上傲绣。
那么接下來的問題,就是將自定義的或者其他UI組件融合到該方法內(nèi)棘脐。
已實(shí)現(xiàn)的代碼如下:
import { Modal } from 'antd';
...
function getConfirmation(message, callback) { // 至關(guān)重要的callback方法斜筐,可以異步執(zhí)行
if (!G.pageChangeConfirm) { // G.pageChangeConfirm為頁面內(nèi)的全局變量龙致,用于數(shù)據(jù)交互與條件判斷
callback(true);
return;
}
Modal.confirm({
title: '離開該頁面蛀缝,表單信息將不被保留?是否確定離開該頁面目代?',
content: '',
okText: '離開',
cancelText: '取消',
onOk() {
callback(true);
},
onCancel() {
callback(false);
},
});
}
ReactDOM.render((
<BrowserRouter
getUserConfirmation={getConfirmation}
>
<App />
</BrowserRouter>
, document.getElementById('react-wraper')
);
...
探索四:查看GitHub的issue屈梁,查找其他解決方案
花了半天時間,在issue中找到了這個帖子https://github.com/ReactTraining/react-router/issues/4635榛了,感興趣的可以看下討論過程在讶。其中提到了兩種解決方案:
getUserConfirmation,類似我上方的解決方案霜大,可運(yùn)行的完整參考代碼:https://codepen.io/pshrmn/pen/MpOpEY
history.block构哺,利用history的API,需要withRouter包裝,傳送門:https://github.com/ReactTraining/react-router/issues/4635#issuecomment-297828995