項目技術棧是react+ant-design;html
產品需求是在某個頁面表單有信息輸入或變動,用戶未保存要離開該頁面時,自定義提示用戶提示,若是用戶確認離開則跳轉到新頁面,不然留在當前頁。react
在react單頁應用中,須要根據用戶的選擇來是否跳轉頁面,也就是須要阻塞瀏覽器的頁面跳轉,相似標籤點擊後的return false效果,待到用戶操做後再執行後續操做。git
首先,能想到的是瀏覽器的window.confirm方法,在交互上徹底符合需求,那麼自定義需求呢?通過查閱資料發現,目前主流瀏覽器都不支持該方法樣式的自定義,所以放棄。github
其次,就是react的方案,react中,控制頁面路由的是react-router,若官方提供了這種能力即是更好的。因而,我查閱了其官方文檔:react-guide.github.io/react-route…、或者reacttraining.com/react-route…、中文版segmentfault.com/a/119000001…segmentfault
因爲項目中使用的是react-router-v4,因此直接查看最新的API(若是是較老版本有比較簡單的實現,可參考blog.csdn.net/ISaiSai/art… )api
import { Prompt } from 'react-router-dom';
....
render(){
return(
<Prompt message="肯定要離開?" when={true}/>
)
}
複製代碼
如上代碼,可使用react-router-dom的Prompt,在JSX的任意render方法中(不只僅是在router組件中)均可以使用,使用後當前頁面組件離開就會給用戶提示信息,默認彈框爲瀏覽器的window.confirm彈框。瀏覽器
用於確認導航的函數,默認使用 window.confirm。例如,當從 /a
導航至 /b
時,會使用默認的 confirm
函數彈出一個提示,用戶點擊肯定後才進行導航,不然不作任何處理。須要配合 <Prompt>
一塊兒使用。bash
const getConfirmation = (message, callback) => {
const allowTransition = window.confirm(message);
callback(allowTransition);
}
<BrowserRouter getUserConfirmation={getConfirmation} />
複製代碼
中心思想:阻塞頁面,等待用戶操做後異步處理頁面跳轉(router變動)。antd
嘗試以下代碼:react-router
...
<Prompt
message = {(location)=>{
return this.state.customPromt,
}
}
/>
...
複製代碼
實踐證實這是不行的,prompt組件的message方法被同步調用了,不能達到阻塞頁面跳轉的效果。
既然是要異步執行,那麼就能夠嘗試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} />
...
複製代碼
滿懷期待,而後...仍是不行。仔細查閱文檔發現,message方法返回的是一個字符串或者true,返回字符串則調用瀏覽器默認的prompt方法阻塞頁面跳轉,和直接使用window.prompt同效果,不符合需求;返回true則直接跳轉。
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.
以上嘗試代碼,雖然添加了異步方法,也會執行異步函數,可是始終會跳轉頁面,經查緣由爲:github.com/ReactTraini…
前兩種方案都失敗了,那麼再次閱讀文檔發現,getUserConfirmation方法就是提供給咱們自定義用的,默認狀況下,當頁面使用了prompt組件後,調用的getUserConfirmation方法是瀏覽器默認的window.prompt。若是須要自定義,直接覆蓋便可。
簡單示例代碼以下:
const getConfirmation = (message, callback) => {
const allowTransition = window.confirm(message);
callback(allowTransition);
}
<BrowserRouter getUserConfirmation={getConfirmation} />
複製代碼
ps:
注意該方法須要寫在BrowserRouter 或 MemoryRouter 上。
那麼接下來的問題,就是將自定義的或者其餘UI組件融合到該方法內。 已實現的代碼以下:
import { Modal } from 'antd';
...
function getConfirmation(message, callback) { // 相當重要的callback方法,能夠異步執行
if (!G.pageChangeConfirm) { // G.pageChangeConfirm爲頁面內的全局變量,用於數據交互與條件判斷
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')
);
...
複製代碼
花了半天時間,在issue中找到了這個帖子https://github.com/ReactTraining/react-router/issues/4635,感興趣的能夠看下討論過程。其中提到了兩種解決方案:
getUserConfirmation,相似我上方的解決方案,可運行的完整參考代碼:codepen.io/pshrmn/pen/…
history.block,利用history的API,須要withRouter包裝,傳送門:github.com/ReactTraini…