学习记录-week2(React Router)

298 阅读5分钟

学习目标

  1. 单页应用是什么
  2. 前端路由是什么
  3. React-router 的简单使用

单页应用

在古老的 web 开发领域,从一个页面跳转到另一个页面,通常会从服务器重新请求对应的页面。比如,我们从一个购物网站的商品列表页 www.shop.com , 跳转到商品详情页 www.item.com 。 在访问 www.shop.com 时,会请求 shop.html 以及相应的资源,而在访问 www.item.com 时,会请求 item.html 以及相应的资源。这种页面之前的跳转以及资源请求,往往会给用户在页面之间的切换造成割裂感,带来不好的用户体验。而单页应用则是另外一种模型,它会通过访问不同的页面 url ,动态的重写当前页面,来实现页面之间的切换。也就是说,在单页应用的模式中,我们往往只是请求了一次 index.html 以及相应的资源,而页面之间的切换,往往是通过 js 的逻辑进行控制的

现在常见的开发模式,一般是多页与单页共同使用。而更多的关于单页应用与多页应用的信息,优劣势比较,大家可以自行查阅资料。

路由

路由在不同的领域可能有不同的定义。但是在前端开发领域,我们可以简单的理解为,它是一种保证 url 和页面同步的技术。更进一步说,前端路由,是指保证我们在开发单页应用时,不同的 url 对应不同页面(组件)的技术。

单页应用的 url 与页面同步

打开我们之前的工程,在 src 目录下先建立一个 App.js(因为还没有学 ts ,所以先用 js 来写,下同)。

假设我们拥有两个页面,商品列表页,和商品详情页,所以我们来声明下这两个组件:

const Shop = () => {
  return <div>商品列表页</div>;
};

const ShopItem = () => {
  return <div>商品详情页</div>;
};

接着我们来写下页面的整体结构,有一个导航列表,点击对应的页面,跳转到对应的页面(此处我们采用哈希 hash 路由)。导航列表下面是对应的不同页面,默认为空。

const App = () => {
  return (
    <div>
      <ul>
        <li><a href="#/shop">Shop</a></li>
        <li><a href="#/shop-item">ShopItem</a></li>
      </ul>
      {/* 此处放页面内容 */}
    </div>
  );
};

之后,我们需要:

  1. state 来记录当前页面的路由,当路由变化时,触发 App 的 re-render
  2. 页面内容根据当前的路由 state 来渲染不同的页面内容
  3. 在跳转到不同的路由时更改路由 state ,来保证路由 state 与 url 是同步的。这里有两种做法:
    1. 在跳转时将路由 state 更改(a 标签点击事件)
    2. 利用浏览器的 hashchange 事件,在对应的回调函数里更改 state

这里我们选用第二种(监听 hashchange 事件)方法:

const Page = ({ route }) => {
  if (route === '/shop') {
    return <Shop />;
  } else if (route==='/shop-item') {
    return <ShopItem />;
  }
  return null;
};

const App = () => {
  const [route, setRoute] = useState('');

  useEffect(() => {
    window.addEventListener('hashchange', () => {
      const r = window.location.hash.substr(1);
      setRoute(r);
    });
  }, []);

  return (
    <div>
      <ul>
        <li><a href="#/shop">Shop</a></li>
        <li><a href="#/shop-item">ShopItem</a></li>
      </ul>
      <Page route={route} />
    </div>
  );
};

之后,我们更改下入口文件 src/index.tsx 中的引用:

+ import App from './App';

ReactDOM.render(
  <React.StrictMode>
-   <div>123</div>
+   <App />
  </React.StrictMode>,
  document.getElementById('root')
);

最后,我们npm start运行项目。

如此一来,我们就实现了 url 与页面的同步。

但是呢,上面的代码是有一些改进空间的。如果当路由多了之后,我们需要改动的地方有:

  1. APP 组件中的列表加入新的项目
  2. Page 组件中增加比较逻辑

为了避免这种情况,造成的维护困难,我们是可以进一步分离出一个路由配置层的(虽然我们之后不大可能还是自己来实现路由,但是此处还是提供一种让代码更健壮的思路):

// 我们将这个路由配置层,称为路由表 routes ,它是一个数组:
const routes = [
  {
    path: '/shop',
    component: Shop,
    text: 'Shop',
  },
  {
    path: '/shop-item',
    component: ShopItem,
    text: 'ShopItem',
  }
];

// 抽象一个由 path 获得 hash 路径的方法
const getHashPath = (path) => {
  return `#${path}`;
};

const Page = ({ route }) => {
  // 将路由表,转换成一个路由表 map ,key 为 path ,value 为 path 对应的路由配置
  const routesMap = routes.reduce((res, route) => {
    const { path } = route;
    res[path] = route;
    return res;
  }, {});
  if (routesMap[route]) {
    const Route = routesMap[route].component;
    return <Route />;
  }
  return null;
};

...
<ul>
  {
    routes.map((route) => {
      const { path, text } = route;
      return (
        <li key={path}>
          <a href={getHashPath(path)}>{text}</a>
        </li>
      );
    })
  }
</ul>
...

之后,我们再添加路由时,只需要修改路由表即可。

React Router

因为我们选用的技术栈是 React ,而 React 周边生态中有一个非常好用并且广受欢迎的路由库,那就是 React Router。所以我们的路由库,自然就选用的是 React Router。

因为 React Router 配置相关的内容比较多,虽然会简单的说一部分,但是更好的学习方式还是看文档:reactrouter.com/web/guides/…

接着我们来简单过一下常用的几个组件:

  • <Router> 一个通用的路由器组件,来保持你的UI与URL同步
  • <BrowserRouter> 使用 HTML5 history API 的路由器组件
  • <HashRouter> 使用 url 中哈希部分 的路由器组件(不推荐使用了)
  • <Link> 导航组件
  • <Route> 当 url 路径与配置路径匹配时,呈现对应 UI 的组件
  • <Switch> 渲染第一个被 location 匹配到的子元素的 <Route> 或者 <Redirect>

接下来是实战环节。

首先,我们需要先安装 React Router :

npm install react-router-dom

接下来我们使用 React Router 配合 HTML5 history API 的形式,来改造下我们之前的例子:

const App = () => {
  return (
    <div>
      <BrowserRouter>
        <ul>
          {
            routes.map((route) => {
              const { path, text } = route;
              return (
                <li key={path}>
                  <Link to={path}>{text}</Link>
                </li>
              );
            })
          }
        </ul>
        <Switch>
          {
            routes.map((route) => {
              const { path, component } = route;
              return <Route key={path} path={path} component={component} exact />;
            })
          }
        </Switch>
      </BrowserRouter>
    </div>
  );
};

其中,我们删除了很多用来保持 url 与 组件匹配的代码,而是利用 React Router 配置来实现。

结语

好了,本期的内容到这里就差不多了,不算难,配置相关的内容会多一些。我个人除了常用的配置外,不是很推荐去死记硬背这些配置内容的,用的时候去翻下文档就好了。

工程地址:github.com/VarHug/stud…