头图

问题

这几年忙着写 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 把功能都拆散,业务逻辑的模块化程度就降低了,无疑增加了学习和维护成本。


机器马
307 声望8 粉丝

技术冲关,Work in English