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

React 路由传参的多种方式

2022-01-133.6k 阅读

React 路由传参的多种方式

1. 通过 URL 参数(params)传参

在 React 应用中,通过 URL 参数(params)进行路由传参是一种常见且直观的方式。这种方式允许我们将特定的数据嵌入到 URL 中,使得页面之间的导航和数据传递紧密结合。

1.1 配置路由时定义参数

首先,在配置 React Router 的路由时,需要定义参数。以 React Router v5 为例,假设我们有一个 ProductDetails 组件,用于展示特定产品的详细信息,每个产品通过 id 进行唯一标识。在 App.js 文件中配置路由如下:

import React from 'react';
import { BrowserRouter as Router, Routes, Route } from'react-router-dom';
import ProductList from './components/ProductList';
import ProductDetails from './components/ProductDetails';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/products" element={<ProductList />} />
        <Route path="/products/:id" element={<ProductDetails />} />
      </Routes>
    </Router>
  );
}

export default App;

在上述代码中,/products/:id 表示这是一个动态路由,其中 :id 就是我们定义的参数。任何匹配这个路由模式的 URL,都会将 id 参数的值传递给 ProductDetails 组件。

1.2 在组件中获取参数

ProductDetails 组件中,可以使用 useParams 钩子函数(在 React Router v5 及以上版本)来获取传递过来的参数。示例代码如下:

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

function ProductDetails() {
  const { id } = useParams();
  return (
    <div>
      <h1>Product Details</h1>
      <p>The product ID is: {id}</p>
    </div>
  );
}

export default ProductDetails;

ProductDetails 组件内部,通过 const { id } = useParams(); 解构出 id 参数的值。这样就可以在组件中根据这个 id 去获取相应产品的详细数据,比如从后端 API 中请求数据。

1.3 传递参数进行导航

ProductList 组件中,当用户点击某个产品时,需要传递产品的 id 来导航到 ProductDetails 页面。可以使用 Link 组件来实现这一功能,示例代码如下:

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

const products = [
  { id: 1, name: 'Product 1' },
  { id: 2, name: 'Product 2' },
  { id: 3, name: 'Product 3' }
];

function ProductList() {
  return (
    <div>
      <h1>Product List</h1>
      <ul>
        {products.map(product => (
          <li key={product.id}>
            <Link to={`/products/${product.id}`}>{product.name}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ProductList;

ProductList 组件中,通过 Link 组件的 to 属性,将产品的 id 嵌入到 URL 中,当用户点击链接时,就会导航到对应的 ProductDetails 页面,并将 id 参数传递过去。

这种通过 URL 参数传参的方式优点在于简单直观,参数直接体现在 URL 中,方便用户分享链接,并且刷新页面时参数依然存在。但缺点是参数暴露在 URL 中,如果传递敏感信息,可能存在安全风险,同时 URL 的长度也可能受到限制。

2. 通过查询字符串(query string)传参

查询字符串是另一种常见的路由传参方式,它将参数附加在 URL 的问号(?)后面,以键值对的形式呈现。

2.1 导航时传递查询字符串参数

假设我们有一个搜索功能,需要将搜索关键词传递到结果页面。在 Search.js 组件中,使用 history.push 方法(在 React Router v5 中,history 对象可以通过 useHistory 钩子获取)来传递查询字符串参数,示例代码如下:

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

function Search() {
  const [keyword, setKeyword] = useState('');
  const history = useHistory();

  const handleSearch = () => {
    history.push(`/search-results?keyword=${encodeURIComponent(keyword)}`);
  };

  return (
    <div>
      <input
        type="text"
        value={keyword}
        onChange={(e) => setKeyword(e.target.value)}
        placeholder="Search..."
      />
      <button onClick={handleSearch}>Search</button>
    </div>
  );
}

export default Search;

在上述代码中,当用户点击搜索按钮时,handleSearch 函数会将 keyword 作为查询字符串参数附加到 /search - results URL 中,并使用 encodeURIComponent 对关键词进行编码,以防止特殊字符导致 URL 解析错误。

2.2 在目标组件中获取查询字符串参数

SearchResults.js 组件中,可以通过 useLocation 钩子函数获取当前的 URL 位置信息,进而解析出查询字符串参数。示例代码如下:

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

function SearchResults() {
  const location = useLocation();
  const queryParams = new URLSearchParams(location.search);
  const keyword = queryParams.get('keyword');

  return (
    <div>
      <h1>Search Results</h1>
      <p>Search keyword: {keyword}</p>
    </div>
  );
}

export default SearchResults;

SearchResults 组件中,useLocation 获取当前位置信息,new URLSearchParams(location.search) 创建一个 URLSearchParams 对象,用于解析查询字符串。通过 queryParams.get('keyword') 可以获取 keyword 参数的值。

通过查询字符串传参的优点是可以传递多个参数,并且参数相对灵活,不会影响 URL 的主要结构。缺点是同样参数暴露在 URL 中,不太适合传递敏感信息,并且在 URL 长度方面也存在一定限制,同时解析过程相对 URL 参数稍微复杂一些。

3. 通过状态(state)传参

通过状态传参是一种不依赖于 URL 来传递数据的方式,这种方式更加安全,适合传递敏感信息或较大的数据对象。

3.1 使用 history.push 传递状态

在 React Router v5 中,可以在 history.push 方法中传递一个包含 state 的对象。例如,在一个用户登录组件 Login.js 中,当用户登录成功后,需要将用户信息传递到首页,示例代码如下:

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

function Login() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const history = useHistory();

  const handleLogin = () => {
    // 假设这里进行了登录验证
    const user = { username, password };
    history.push({
      pathname: '/home',
      state: { user }
    });
  };

  return (
    <div>
      <input
        type="text"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
        placeholder="Username"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
      />
      <button onClick={handleLogin}>Login</button>
    </div>
  );
}

export default Login;

handleLogin 函数中,创建了一个包含用户信息的 user 对象,并通过 history.pushstate 属性传递到 /home 页面。

3.2 在目标组件中获取状态

Home.js 组件中,可以通过 useLocation 钩子函数获取传递过来的状态。示例代码如下:

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

function Home() {
  const location = useLocation();
  const { user } = location.state || {};

  return (
    <div>
      <h1>Home</h1>
      {user && (
        <p>Welcome, {user.username}!</p>
      )}
    </div>
  );
}

export default Home;

Home 组件中,通过 location.state 获取传递过来的状态对象,并解构出 user 对象。需要注意的是,由于刷新页面会导致状态丢失,所以在获取状态时要进行空值检查。

通过状态传参的优点是数据不暴露在 URL 中,安全性较高,并且可以传递复杂的数据结构。缺点是刷新页面时状态会丢失,如果需要在页面刷新后依然保留数据,可能需要结合其他方式,如本地存储等。

4. 通过 Context 传参

Context 是 React 提供的一种跨组件传递数据的方式,也可以应用于路由传参场景。它适用于在多个组件之间共享数据,而不需要通过层层传递 props 的方式。

4.1 创建 Context

首先,创建一个 Context 对象。在项目根目录下创建一个 UserContext.js 文件,示例代码如下:

import React from'react';

const UserContext = React.createContext();

export default UserContext;

上述代码创建了一个名为 UserContext 的 Context 对象。

4.2 使用 Context.Provider 提供数据

App.js 中,假设我们有一个 UserProvider 组件,用于提供用户数据。示例代码如下:

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

const user = { username: 'JohnDoe' };

function UserProvider({ children }) {
  return (
    <UserContext.Provider value={user}>
      {children}
    </UserContext.Provider>
  );
}

function App() {
  return (
    <Router>
      <UserProvider>
        <Routes>
          <Route path="/" element={<Login />} />
          <Route path="/home" element={<Home />} />
        </Routes>
      </UserProvider>
    </Router>
  );
}

export default App;

App.js 中,UserProvider 组件通过 UserContext.Provideruser 对象作为 value 传递下去,所有嵌套在 UserProvider 中的组件都可以访问这个 user 数据。

4.3 在目标组件中消费 Context

Home.js 组件中,可以使用 useContext 钩子函数来消费 UserContext。示例代码如下:

import React from'react';
import { useContext } from'react';
import UserContext from '../UserContext';

function Home() {
  const user = useContext(UserContext);

  return (
    <div>
      <h1>Home</h1>
      <p>Welcome, {user.username}!</p>
    </div>
  );
}

export default Home;

通过 useContext(UserContext) 获取到 UserContext 中的 user 数据,并在组件中使用。

使用 Context 传参的优点是可以在多个组件之间共享数据,避免了 props 层层传递的繁琐。缺点是如果滥用 Context,可能会导致组件之间的依赖关系不清晰,增加代码的维护难度。

5. 通过 Redux 传参

Redux 是一个用于管理应用状态的库,在 React 应用中可以借助 Redux 来实现路由传参。

5.1 安装和配置 Redux

首先,需要安装 reduxreact - redux 库。在项目根目录下运行以下命令:

npm install redux react-redux

然后,在项目中创建 storereducer 等相关文件。以一个简单的计数器为例,在 reducers/counterReducer.js 文件中定义 reducer:

const initialState = {
  count: 0
};

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
       ...state,
        count: state.count + 1
      };
    case 'DECREMENT':
      return {
       ...state,
        count: state.count - 1
      };
    default:
      return state;
  }
};

export default counterReducer;

store.js 文件中创建 Redux store:

import { createStore } from'redux';
import counterReducer from './reducers/counterReducer';

const store = createStore(counterReducer);

export default store;

App.js 中,通过 Provider 组件将 store 提供给整个应用:

import React from'react';
import { BrowserRouter as Router, Routes, Route } from'react-router-dom';
import { Provider } from'react-redux';
import store from './store';
import Home from './components/Home';
import Counter from './components/Counter';

function App() {
  return (
    <Provider store={store}>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/counter" element={<Counter />} />
        </Routes>
      </Router>
    </Provider>
  );
}

export default App;

5.2 通过 Redux 传递数据

假设在 Home 组件中有一个按钮,点击按钮时将数据传递到 Counter 组件。在 Home.js 组件中,通过 dispatch 触发 action 来更新 Redux store 中的数据:

import React from'react';
import { useDispatch } from'react-redux';

function Home() {
  const dispatch = useDispatch();

  const handleClick = () => {
    dispatch({ type: 'INCREMENT' });
  };

  return (
    <div>
      <h1>Home</h1>
      <button onClick={handleClick}>Increment in Home</button>
    </div>
  );
}

export default Home;

Counter 组件中,通过 useSelector 钩子函数从 Redux store 中获取数据:

import React from'react';
import { useSelector } from'react-redux';

function Counter() {
  const count = useSelector(state => state.count);

  return (
    <div>
      <h1>Counter</h1>
      <p>Count: {count}</p>
    </div>
  );
}

export default Counter;

通过 Redux 传参的优点是可以在整个应用中统一管理状态,方便数据的共享和更新。缺点是引入 Redux 会增加项目的复杂度,需要编写较多的 boilerplate 代码,对于小型项目可能有些过度设计。

6. 通过自定义事件传参

在 React 应用中,还可以通过自定义事件来实现组件之间的传参,这在一些特定场景下非常有用。

6.1 创建自定义事件总线

首先,创建一个自定义事件总线。在项目根目录下创建一个 EventBus.js 文件,示例代码如下:

class EventBus {
  constructor() {
    this.events = {};
  }

  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(callback);
  }

  emit(eventName, data) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(callback => callback(data));
    }
  }
}

const eventBus = new EventBus();

export default eventBus;

上述代码定义了一个简单的事件总线类 EventBus,包含 on 方法用于注册事件监听器,emit 方法用于触发事件并传递数据。

6.2 使用自定义事件传参

假设在 ComponentA 组件中,当用户点击按钮时,通过自定义事件将数据传递给 ComponentB 组件。在 ComponentA.js 中:

import React from'react';
import eventBus from './EventBus';

function ComponentA() {
  const handleClick = () => {
    const data = { message: 'Hello from ComponentA' };
    eventBus.emit('custom - event', data);
  };

  return (
    <div>
      <h1>ComponentA</h1>
      <button onClick={handleClick}>Send Data</button>
    </div>
  );
}

export default ComponentA;

ComponentB.js 中,注册事件监听器来接收数据:

import React, { useEffect } from'react';
import eventBus from './EventBus';

function ComponentB() {
  useEffect(() => {
    const handleEvent = (data) => {
      console.log('Received data:', data);
    };
    eventBus.on('custom - event', handleEvent);
    return () => {
      eventBus.off('custom - event', handleEvent);
    };
  }, []);

  return (
    <div>
      <h1>ComponentB</h1>
    </div>
  );
}

export default ComponentB;

ComponentB 组件的 useEffect 钩子中,通过 eventBus.on 注册了 custom - event 事件的监听器,并在组件卸载时通过返回的清理函数使用 eventBus.off 移除监听器。

通过自定义事件传参的优点是可以实现松耦合的组件间通信,适合一些临时性的、不需要持久化数据传递的场景。缺点是这种方式相对比较底层,需要手动管理事件的注册和注销,容易出现内存泄漏等问题,如果使用不当,可能会导致代码难以维护。

7. 综合使用多种传参方式

在实际项目中,往往不会局限于使用一种传参方式,而是根据不同的需求和场景综合使用多种方式。

例如,在一个电商应用中,产品列表页面跳转到产品详情页面时,可以通过 URL 参数(params)传递产品的 id,这样既方便用户分享链接,又能直观地在 URL 中体现产品标识。而在用户登录后,将用户的敏感信息(如用户的登录令牌等)通过状态(state)传递到首页,保证信息的安全性。

同时,对于一些全局共享的数据,如用户的设置偏好等,可以通过 Context 或 Redux 来管理和传递,使得多个组件都能方便地获取和更新这些数据。在一些特定的组件间通信场景下,如购物车组件和商品详情组件之间的交互,可以使用自定义事件来实现传参,实现松耦合的通信。

通过合理地综合使用多种路由传参方式,可以使 React 应用在数据传递和组件交互方面更加灵活、高效,同时也能兼顾安全性、性能和代码的可维护性。

总之,React 路由传参的多种方式各有优缺点,开发者需要根据具体的业务需求、数据特性以及项目的规模和复杂度来选择合适的传参方式,以打造出高质量的 React 应用程序。在实际开发过程中,不断地实践和总结经验,能够更好地掌握和运用这些传参技巧,提升应用的开发效率和用户体验。