MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

React 动态路由参数的使用技巧

2021-01-264.7k 阅读

一、React 路由与动态路由参数基础

在 React 应用开发中,路由起着至关重要的作用,它允许我们构建单页应用(SPA),实现不同页面之间的导航和切换,同时保持应用的流畅性和用户体验。React Router 是最常用的路由库之一,它提供了强大且灵活的路由功能。

动态路由参数则是 React Router 中的一个关键特性,它允许我们在路由路径中传递动态数据。例如,在一个博客应用中,每篇文章都有一个唯一的标识符,我们可以通过动态路由参数将这个标识符传递到文章详情页面,以便获取并展示具体文章的内容。

二、配置动态路由参数

在 React Router 中配置动态路由参数非常直观。首先,我们需要安装 react-router-dom 库。假设我们使用的是 React Router v6,安装命令如下:

npm install react-router-dom

安装完成后,在应用的入口文件(通常是 index.js)中,我们可以设置路由。例如:

import React from 'react';
import ReactDOM from'react-dom/client';
import { BrowserRouter as Router, Routes, Route } from'react-router-dom';
import Home from './components/Home';
import Article from './components/Article';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Router>
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/article/:id" element={<Article />} />
    </Routes>
  </Router>
);

在上述代码中,/article/:id 就是一个带有动态路由参数的路径。:id 表示这是一个参数占位符,在实际使用中,id 可以被具体的值替换,比如 /article/123

三、获取动态路由参数

1. 使用 useParams Hook(React Router v6)

在 React Router v6 中,获取动态路由参数变得更加简洁,通过 useParams Hook 即可轻松实现。以之前的文章详情组件 Article 为例:

import React from'react';
import { useParams } from'react-router-dom';

const Article = () => {
  const { id } = useParams();
  return (
    <div>
      <h1>Article Details</h1>
      <p>The article ID is: {id}</p>
    </div>
  );
};

export default Article;

在上述代码中,useParams Hook 返回一个对象,对象的属性名与路由路径中定义的参数名相对应。在这里,我们通过 { id } 解构出了 id 参数,并在页面上进行展示。

2. 旧版本获取方式(React Router v5 及之前)

在 React Router v5 及之前的版本中,获取动态路由参数的方式略有不同。通常,我们需要通过 withRouter 高阶组件或者在 props.match.params 中获取参数。

import React from'react';
import { withRouter } from'react-router-dom';

const Article = (props) => {
  const id = props.match.params.id;
  return (
    <div>
      <h1>Article Details</h1>
      <p>The article ID is: {id}</p>
    </div>
  );
};

export default withRouter(Article);

这里通过 withRouter 高阶组件将路由相关的 props 传递给 Article 组件,然后在 props.match.params 中获取 id 参数。

四、动态路由参数的应用场景

1. 详情页展示

如前文提到的博客文章详情页,通过动态路由参数传递文章的唯一标识符,组件可以根据这个标识符从数据库或 API 中获取具体文章的内容并展示。

import React, { useEffect, useState } from'react';
import { useParams } from'react-router-dom';

const Article = () => {
  const { id } = useParams();
  const [article, setArticle] = useState(null);

  useEffect(() => {
    const fetchArticle = async () => {
      const response = await fetch(`https://api.example.com/articles/${id}`);
      const data = await response.json();
      setArticle(data);
    };
    fetchArticle();
  }, [id]);

  if (!article) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <h1>{article.title}</h1>
      <p>{article.content}</p>
    </div>
  );
};

export default Article;

在这个例子中,我们根据 id 参数从 API 中获取文章详情数据,并展示文章的标题和内容。

2. 用户个性化页面

在社交应用中,用户个人资料页面可以通过动态路由参数来展示不同用户的信息。例如,/user/:username,其中 :username 是动态路由参数,通过这个参数获取特定用户的资料并展示。

import React, { useEffect, useState } from'react';
import { useParams } from'react-router-dom';

const UserProfile = () => {
  const { username } = useParams();
  const [user, setUser] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      const response = await fetch(`https://api.example.com/users/${username}`);
      const data = await response.json();
      setUser(data);
    };
    fetchUser();
  }, [username]);

  if (!user) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
    </div>
  );
};

export default UserProfile;

3. 多语言支持

在国际化应用中,可以通过动态路由参数来切换语言。例如,/en/home 表示英文的首页,/zh/home 表示中文的首页。通过获取动态路由参数中的语言标识,应用可以加载相应语言的资源和界面。

import React, { useEffect } from'react';
import { useParams } from'react-router-dom';
import i18n from './i18n';

const Home = () => {
  const { lang } = useParams();

  useEffect(() => {
    i18n.changeLanguage(lang);
  }, [lang]);

  return (
    <div>
      <h1>{i18n.t('home.title')}</h1>
      <p>{i18n.t('home.description')}</p>
    </div>
  );
};

export default Home;

这里使用了国际化库 i18next,根据动态路由参数 lang 切换语言,并展示相应语言的标题和描述。

五、动态路由参数与导航

1. 使用 Link 组件传递参数

在 React Router 中,Link 组件用于创建导航链接。当我们需要在链接中传递动态路由参数时,可以通过设置 to 属性来实现。例如,在博客应用的文章列表页面,我们要链接到每篇文章的详情页:

import React from'react';
import { Link } from'react-router-dom';

const ArticleList = () => {
  const articles = [
    { id: 1, title: 'Article 1' },
    { id: 2, title: 'Article 2' }
  ];

  return (
    <div>
      <h1>Article List</h1>
      <ul>
        {articles.map(article => (
          <li key={article.id}>
            <Link to={`/article/${article.id}`}>{article.title}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default ArticleList;

在上述代码中,Link 组件的 to 属性设置为 "/article/" + article.id,这样就将文章的 id 作为动态路由参数传递到了文章详情页。

2. 使用 navigate 函数传递参数(React Router v6)

在 React Router v6 中,navigate 函数取代了旧版本的 history.push 等方法。我们可以使用 navigate 函数来实现编程式导航,并传递动态路由参数。例如,在一个表单提交后,导航到文章详情页:

import React, { useState } from'react';
import { useNavigate } from'react-router-dom';

const CreateArticleForm = () => {
  const [articleId, setArticleId] = useState('');
  const navigate = useNavigate();

  const handleSubmit = (e) => {
    e.preventDefault();
    // 假设这里保存文章并获取文章ID
    const newArticleId = 123;
    setArticleId(newArticleId);
    navigate(`/article/${newArticleId}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" placeholder="Article Title" />
      <textarea placeholder="Article Content" />
      <button type="submit">Create Article</button>
    </form>
  );
};

export default CreateArticleForm;

在这个例子中,表单提交后,通过 navigate 函数导航到文章详情页,并传递了文章的 id 参数。

六、动态路由参数的嵌套路由

在复杂的应用中,我们经常会遇到嵌套路由的情况,动态路由参数在嵌套路由中同样起着重要作用。例如,在一个电商应用中,商品分类下有多个商品,商品详情页又有不同的子页面(如规格、评论等)。

import React from'react';
import { Routes, Route, Link } from'react-router-dom';

const Category = () => {
  return (
    <div>
      <h1>Category Page</h1>
      <Link to="product/123">Product 123</Link>
      <Routes>
        <Route path="product/:productId" element={<Product />}>
          <Route path="specs" element={<Specs />} />
          <Route path="reviews" element={<Reviews />} />
        </Route>
      </Routes>
    </div>
  );
};

const Product = () => {
  return <div>Product Page</div>;
};

const Specs = () => {
  return <div>Product Specs Page</div>;
};

const Reviews = () => {
  return <div>Product Reviews Page</div>;
};

export default Category;

在上述代码中,Category 组件包含了一个嵌套路由,product/:productId 是带有动态路由参数的子路由,并且这个子路由又有自己的子路由 specsreviews。这样,通过动态路由参数 productId,我们可以展示不同商品的具体信息以及其子页面。

七、动态路由参数的匹配模式

1. 精确匹配

在 React Router 中,默认情况下,路由是精确匹配的。例如,/article/:id 只会匹配以 /article/ 开头且后跟一个参数的路径,不会匹配 /article/123/related 这样的路径。如果我们希望实现更灵活的匹配,可以使用通配符。

2. 通配符匹配

通配符匹配允许我们匹配更广泛的路径。在 React Router v6 中,可以使用 * 来实现通配符匹配。例如:

import React from'react';
import { Routes, Route } from'react-router-dom';
import NotFound from './components/NotFound';

const routes = (
  <Routes>
    <Route path="/article/:id" element={<Article />} />
    <Route path="*" element={<NotFound />} />
  </Routes>
);

export default routes;

在上述代码中,path="*" 表示匹配任何未被其他路由匹配的路径,这里我们将其指向 NotFound 组件,用于展示 404 页面。

3. 多参数匹配

有时候,我们可能需要在一个路由路径中传递多个动态路由参数。例如,在一个地理位置应用中,路径 /city/:cityName/area/:areaName 可以传递城市名和区域名两个参数。

import React from'react';
import { useParams } from'react-router-dom';

const Location = () => {
  const { cityName, areaName } = useParams();
  return (
    <div>
      <h1>Location Details</h1>
      <p>City: {cityName}</p>
      <p>Area: {areaName}</p>
    </div>
  );
};

export default Location;

在这个例子中,通过解构 useParams 返回的对象,我们获取了 cityNameareaName 两个参数。

八、动态路由参数的注意事项

1. 参数类型

动态路由参数在 URL 中是以字符串形式传递的。如果我们需要使用数字、布尔值等其他类型,需要在组件中进行类型转换。例如:

import React from'react';
import { useParams } from'react-router-dom';

const Article = () => {
  const { id } = useParams();
  const articleId = parseInt(id);
  return (
    <div>
      <h1>Article Details</h1>
      <p>The article ID (as number): {articleId}</p>
    </div>
  );
};

export default Article;

在这里,我们使用 parseInt 将从 URL 中获取的 id 参数转换为数字类型。

2. 路由变化与组件更新

当动态路由参数发生变化时,组件默认不会重新渲染。例如,从 /article/1 导航到 /article/2,如果组件没有正确处理参数变化,页面可能不会更新。在 React Router v6 中,可以使用 useEffect Hook 结合 useParams 来监听参数变化并执行相应的操作。

import React, { useEffect } from'react';
import { useParams } from'react-router-dom';

const Article = () => {
  const { id } = useParams();
  useEffect(() => {
    // 当id参数变化时,重新获取文章数据
    const fetchArticle = async () => {
      const response = await fetch(`https://api.example.com/articles/${id}`);
      const data = await response.json();
      // 更新文章状态
    };
    fetchArticle();
  }, [id]);

  return (
    <div>
      <h1>Article Details</h1>
      <p>The article ID is: {id}</p>
    </div>
  );
};

export default Article;

在这个例子中,useEffect 的依赖数组中包含 id,当 id 参数变化时,useEffect 中的回调函数会被执行,从而重新获取文章数据。

3. 安全性

在使用动态路由参数时,要注意安全性。特别是在从 URL 中获取参数并用于数据库查询或其他敏感操作时,要防止 SQL 注入、XSS 攻击等安全漏洞。例如,在使用参数进行数据库查询时,要使用参数化查询。

import mysql from'mysql2';

const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'test'
});

const articleId = 123; // 假设从动态路由参数获取并转换为数字
connection.query('SELECT * FROM articles WHERE id =?', [articleId], (error, results, fields) => {
  if (error) throw error;
  console.log(results);
});

在上述代码中,使用 ? 占位符和数组形式传递参数,避免了直接将参数拼接到 SQL 语句中,从而提高了安全性。

九、动态路由参数与 SEO

在单页应用中,动态路由参数对搜索引擎优化(SEO)有一定影响。搜索引擎爬虫在抓取页面时,可能无法正确解析动态路由参数。为了提高 SEO 效果,可以采取以下措施:

1. 预渲染

使用静态站点生成(SSG)或服务器端渲染(SSR)技术,在构建或请求时将动态路由参数对应的页面内容渲染为静态 HTML。例如,Next.js 是一个基于 React 的框架,支持 SSG 和 SSR。通过 getStaticPropsgetServerSideProps 方法,可以在页面构建或请求时获取动态路由参数,并渲染出完整的页面内容,便于搜索引擎抓取。

import React from'react';

const Article = ({ article }) => {
  return (
    <div>
      <h1>{article.title}</h1>
      <p>{article.content}</p>
    </div>
  );
};

export async function getStaticProps(context) {
  const { id } = context.params;
  const response = await fetch(`https://api.example.com/articles/${id}`);
  const article = await response.json();
  return {
    props: {
      article
    },
    revalidate: 60 // 每60秒重新验证一次(仅适用于增量静态再生)
  };
}

export async function getStaticPaths() {
  const response = await fetch('https://api.example.com/articles');
  const articles = await response.json();
  const paths = articles.map(article => ({
    params: { id: article.id.toString() }
  }));
  return { paths, fallback: false };
}

export default Article;

在这个 Next.js 示例中,getStaticPaths 方法用于生成所有可能的动态路由参数路径,getStaticProps 方法用于根据动态路由参数获取文章数据并传递给组件进行渲染。

2. 规范 URL 结构

保持 URL 结构简洁明了,使用有意义的参数名称。例如,/article/123/article?id=123 更有利于 SEO。搜索引擎更容易理解前者的含义,并且在搜索结果中显示更友好。

3. 元数据设置

在页面中设置合适的元数据(如标题、描述、关键词等),这些元数据可以根据动态路由参数进行动态生成。例如,在文章详情页,根据文章标题和内容生成页面的标题和描述。

import React, { useEffect } from'react';
import { useParams } from'react-router-dom';
import { Helmet } from'react-helmet';

const Article = () => {
  const { id } = useParams();
  const [article, setArticle] = useState(null);

  useEffect(() => {
    const fetchArticle = async () => {
      const response = await fetch(`https://api.example.com/articles/${id}`);
      const data = await response.json();
      setArticle(data);
    };
    fetchArticle();
  }, [id]);

  if (!article) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <Helmet>
        <title>{article.title}</title>
        <meta name="description" content={article.excerpt} />
      </Helmet>
      <h1>{article.title}</h1>
      <p>{article.content}</p>
    </div>
  );
};

export default Article;

在这个例子中,使用 react - helmet 库来动态设置页面的标题和描述元数据,有助于提高搜索引擎对页面内容的理解和展示。

十、动态路由参数的性能优化

1. 懒加载组件

当应用中包含大量带有动态路由参数的页面时,为了提高性能,可以使用懒加载技术。在 React Router 中,结合 React.lazy 和 Suspense 可以实现组件的懒加载。例如:

import React, { lazy, Suspense } from'react';
import { Routes, Route } from'react-router-dom';

const Article = lazy(() => import('./components/Article'));

const routes = (
  <Routes>
    <Route path="/article/:id" element={
      <Suspense fallback={<div>Loading...</div>}>
        <Article />
      </Suspense>
    } />
  </Routes>
);

export default routes;

在上述代码中,Article 组件使用 React.lazy 进行懒加载,Suspense 组件用于在组件加载时显示加载提示,这样可以避免在应用启动时加载所有组件,提高初始加载性能。

2. 缓存数据

对于根据动态路由参数获取的数据,可以进行缓存。例如,使用 useMemo Hook 或外部缓存库(如 lru - cache)来缓存数据。这样,当相同的动态路由参数再次出现时,可以直接从缓存中获取数据,而不需要再次发起请求。

import React, { useEffect, useMemo } from'react';
import { useParams } from'react-router-dom';
import { getArticle } from './api';

const Article = () => {
  const { id } = useParams();
  const cachedArticle = useMemo(() => {
    // 假设这里有一个缓存机制,从缓存中获取文章数据
    return getCachedArticle(id);
  }, [id]);

  const [article, setArticle] = useState(cachedArticle || null);

  useEffect(() => {
    if (!cachedArticle) {
      const fetchArticle = async () => {
        const data = await getArticle(id);
        setArticle(data);
        // 假设这里将获取到的数据存入缓存
        setCachedArticle(id, data);
      };
      fetchArticle();
    }
  }, [id, cachedArticle]);

  if (!article) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <h1>{article.title}</h1>
      <p>{article.content}</p>
    </div>
  );
};

export default Article;

在这个例子中,useMemo 用于从缓存中获取文章数据,如果缓存中没有数据,则通过 useEffect 发起请求获取数据,并将数据存入缓存。

3. 代码分割

除了组件懒加载,还可以进行代码分割。Webpack 等构建工具可以根据路由或功能模块将代码分割成多个文件,只有在需要时才加载相应的代码。例如,在一个大型应用中,不同模块的动态路由参数相关的代码可以分割到不同的文件中,提高加载速度和应用性能。

通过以上对 React 动态路由参数的使用技巧的详细介绍,包括配置、获取、应用场景、导航、嵌套路由、匹配模式、注意事项、SEO 及性能优化等方面,希望能帮助开发者在实际项目中更好地运用动态路由参数,构建出高效、安全且用户体验良好的 React 应用。