React單頁應用中使用react-router-v4自定義頁面離開確認彈框的方法

問題描述

項目技術棧是react+ant-design;html

產品需求是在某個頁面表單有信息輸入或變動,用戶未保存要離開該頁面時,自定義提示用戶提示,若是用戶確認離開則跳轉到新頁面,不然留在當前頁。react

需求UI-已實現的.jpg

思考

在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

1、Prompt組件

import { Prompt } from 'react-router-dom';
....
render(){
  return(
      <Prompt message="肯定要離開?"  when={true}/>
  )
}
複製代碼

如上代碼,可使用react-router-dom的Prompt,在JSX的任意render方法中(不只僅是在router組件中)均可以使用,使用後當前頁面組件離開就會給用戶提示信息,默認彈框爲瀏覽器的window.confirm彈框。瀏覽器

  1. message: string 當用戶離開當前頁面時,設置的提示信息。
  2. message: func 當用戶離開當前頁面時,設置的回掉函數,返回 true 時容許直接跳轉 <Prompt message={location => ( Are you sue you want to go to ${location.pathname}? )} />
  3. when: bool 經過設置必定條件要決定是否啓用 Prompt,屬性值爲true時啓用防止轉換;

2、Router組件的getUserConfirmation方法

用於確認導航的函數,默認使用 window.confirm。例如,當從 /a 導航至 /b 時,會使用默認的 confirm 函數彈出一個提示,用戶點擊肯定後才進行導航,不然不作任何處理。須要配合 <Prompt> 一塊兒使用。bash

const getConfirmation = (message, callback) => {
  const allowTransition = window.confirm(message);
  callback(allowTransition);
}

<BrowserRouter getUserConfirmation={getConfirmation} />
複製代碼

實現方案

中心思想:阻塞頁面,等待用戶操做後異步處理頁面跳轉(router變動)。antd

探索一:Prompt既然是組件,那麼在組件內使用state變動的方式去render是否可行?

嘗試以下代碼:react-router

...
<Prompt
   message = {(location)=>{
     return this.state.customPromt,
     }
   }
  />
...
複製代碼

實踐證實這是不行的,prompt組件的message方法被同步調用了,不能達到阻塞頁面跳轉的效果。

探索二:嘗試async/await

既然是要異步執行,那麼就能夠嘗試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 and action the user is attempting to navigate to. Return a string to show a prompt to the user or true to allow the transition.

以上嘗試代碼,雖然添加了異步方法,也會執行異步函數,可是始終會跳轉頁面,經查緣由爲:github.com/ReactTraini…

探索三:getUserConfirmation方法,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')
);
...
複製代碼

探索四:查看GitHub的issue,查找其餘解決方案

花了半天時間,在issue中找到了這個帖子https://github.com/ReactTraining/react-router/issues/4635,感興趣的能夠看下討論過程。其中提到了兩種解決方案:

  1. getUserConfirmation,相似我上方的解決方案,可運行的完整參考代碼:codepen.io/pshrmn/pen/…

  2. history.block,利用history的API,須要withRouter包裝,傳送門:github.com/ReactTraini…

探索五:閱讀源碼,unpkg.com/react-route…

結論:有至少2種方法能夠實現prompt自定義,getUserConfirmation和history.block,若是您有更好的解決方案,期待您的反饋。

最後:以上方法頁面刷新時無效,那麼刷新頁面怎麼實現相似功能呢?

相關文章
相關標籤/搜索