問題
這幾年忙着寫 Taro 相關業務,所以很久沒有再接觸 ReactRouter 了。從當年使用的 ReactRouter v3 & VueRouter v2,功能和寫法都沒什麼差別,而到現在的 ReactRouter v6,就感覺變化十分大。這裏從使用者的角度聊聊,初次上手 v6 的感受和如何應對這些變化。
變化
範式
函數化和標準化,讓其源碼減少了一半
- v6 全面擁抱 Hooks,API 不再集中在一個對象上。同時這意味着如果你的項目還不支持 Hooks,那就應該使用更早前的版本。
- 使用 URLSearchParams。它是標準化的 WebAPI,
react-router-dom庫目標是儘可能往標準化靠攏,少一些自定義對象和方法。
URL 參數獲取
- useParams() 用於獲取路徑參數,比如 URL 格式是 '/user-info/:userId',就可以用
let { userId } = useParams()獲得用户 id。 -
useSearchParams() 用於獲取 search 參數,比如常見的 '?name=foo&id=boo' ,就可以用
[searchParams, setSearchParams] = useSearchParams()得到數據和數據修改方法- 從教程和 searchParams 的類型可知,searchParams 的類型是 URLSearchParams,不是普通對象。
- 參數值要通過
searchParams.get('name')的方式獲取。 - 通過 setSearchParams() 可以即時改變當前 URL 的 search 部分
URL 參數設置
官方文檔的例子,只提供了手動拼接路徑參數或者 search 參數成 URL 的方式,比如
const navigate = useNavigate();
const url = 'foo/boo' + '?name=1&id=2';
navigate(url);
但是,我們希望在編寫業務邏輯的時候可以方便地傳入對象,比如 navigate('foo/boo', {name:1, id:2})。再根據上面提到的,範式上傾向於使用 URLSearchParams,所以我們封裝了以下方法
/**
* @description: 路由參數設置到 url 上,得到新的 url
* @param url 跳轉地址
* @param params 路由參數(非必填,所有單據都是一個路由,只有參數不一樣,這個時候需要增加這個參數)
* @return 組合出來的帶參數地址
*/
function setRouterParams(url: string, params?: Record<PropertyKey, any>): string {
const searchParams = new URLSearchParams(params);
return `${url}?${searchParams.toString()}`;
}
const url = setRouterParams('foo/boo', {name:1, id:2}); // 'foo/boo?name=1&id=2'
navigate(url);
History 對象
我們想要監聽 history 的變化,以動態改變頁面的標題。但是搜索發現,createBrowserHistory() 已經不在文檔內了。
從 issue 討論可知,v6.4 後 history 的主要功能已經合併到數據路由中(通過 createHashRouter(), createBrowserRouter() 之類生成的路由),比如想要實現監聽功能
const router = createHashRouter(routerList);
function App() {
const [pageTitle, setPageTitle] = useState('');
const { hash } = window.location;
useEffect(() => {
router.subscribe(({ location }) => {
const { pathname } = location;
const targetRoute = routerList.find((item) => item.path === pathname);
setPageTitle(targetRoute?.title || ''); // 標題跟着變化
});
}, []);
return (
<div className='App'>
{/* react-helmet 可以方便地修改 document.title */}
<Helmet>
<title>{pageTitle}</title>
</Helmet>
<RouterProvider router={router} />
</div>
);
}
更多特性變化未完待續...
總結
React 的相關插件都有函數化和標準化的趨勢。為了更好地適應技術更新,我們需要更新 WebAPI 知識,更熟練地掌握 Hooks 範式。
不過還是需要吐槽,Hooks 的初衷是,渲染是一種功能而已,沒必要把屬性都綁定在 state 裏,所以只需要把渲染作為函數引入即可。它的目的是不是“反 OOP”,只是 Hooks 範式更好。ReactRouter v6 把功能都拆散,業務邏輯的模塊化程度就降低了,無疑增加了學習和維護成本。