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

React 路由历史记录操作方法

2022-04-291.3k 阅读

React 路由与历史记录概述

在 React 应用开发中,路由是实现单页应用(SPA)页面导航和页面切换的关键技术。而路由历史记录则允许开发者控制用户在应用内的导航轨迹,实现诸如前进、后退、跳转到特定历史记录点等操作。

React Router 是 React 生态系统中最常用的路由库,它提供了强大的路由功能和对历史记录的操作支持。在 React Router v5 及之前版本中,主要使用 react - router - dom 包来处理浏览器环境下的路由,而从 React Router v6 开始,对路由的设计和 API 进行了较大的改动。

React Router v5 中的历史记录操作

1. 获取历史对象

在 React Router v5 中,通过 withRouter 高阶组件或者 useHistory 钩子函数(仅在函数式组件中可用)来获取历史对象。

使用 withRouter 高阶组件

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

class MyComponent extends React.Component {
    handleClick = () => {
        // 这里的 this.props.history 就是历史对象
        this.props.history.push('/new - path');
    };
    render() {
        return (
            <button onClick={this.handleClick}>
                跳转到新路径
            </button>
        );
    }
}

export default withRouter(MyComponent);

上述代码中,withRouter 高阶组件将 history 对象注入到 MyComponentprops 中,从而可以在组件内使用 history 对象的方法来操作路由历史。

使用 useHistory 钩子函数

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

const MyFunctionalComponent = () => {
    const history = useHistory();
    const handleClick = () => {
        history.push('/new - path');
    };
    return (
        <button onClick={handleClick}>
            跳转到新路径
        </button>
    );
};

export default MyFunctionalComponent;

在函数式组件中,useHistory 钩子函数直接返回 history 对象,使用起来更加简洁。

2. 常用的历史操作方法

  • history.push(path, state):将新的路径添加到历史记录栈顶,并跳转到该路径。state 是一个可选参数,可以携带一些额外的数据,这些数据在页面跳转后可以通过 location.state 获取。
import React from'react';
import { useHistory } from'react - router - dom';

const PushExample = () => {
    const history = useHistory();
    const handleClick = () => {
        const state = { message: '这是传递的状态数据' };
        history.push('/destination', state);
    };
    return (
        <button onClick={handleClick}>
            推送新路径并传递状态
        </button>
    );
};

export default PushExample;

在目标组件中,可以这样获取状态数据:

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

const DestinationComponent = () => {
    const location = useLocation();
    return (
        <div>
            {location.state && <p>{location.state.message}</p>}
        </div>
    );
};

export default DestinationComponent;
  • history.replace(path, state):与 push 类似,但它会替换当前历史记录栈顶的记录,而不是添加新的记录。这在某些场景下很有用,比如用户完成了一个流程后,不希望用户通过后退按钮回到流程中的某个步骤。
import React from'react';
import { useHistory } from'react - router - dom';

const ReplaceExample = () => {
    const history = useHistory();
    const handleClick = () => {
        const state = { message: '这是替换记录传递的状态数据' };
        history.replace('/replacement - path', state);
    };
    return (
        <button onClick={handleClick}>
            替换当前路径并传递状态
        </button>
    );
};

export default ReplaceExample;
  • history.go(n):在历史记录栈中向前或向后移动 n 步。n 为正数表示向前移动,n 为负数表示向后移动。例如,history.go(1) 等同于点击浏览器的前进按钮,history.go(-1) 等同于点击浏览器的后退按钮。
import React from'react';
import { useHistory } from'react - router - dom';

const GoExample = () => {
    const history = useHistory();
    const handleForward = () => {
        history.go(1);
    };
    const handleBack = () => {
        history.go(-1);
    };
    return (
        <div>
            <button onClick={handleForward}>前进</button>
            <button onClick={handleBack}>后退</button>
        </div>
    );
};

export default GoExample;
  • history.goBack():等同于 history.go(-1),用于返回上一个历史记录。
import React from'react';
import { useHistory } from'react - router - dom';

const GoBackExample = () => {
    const history = useHistory();
    const handleClick = () => {
        history.goBack();
    };
    return (
        <button onClick={handleClick}>
            返回上一页
        </button>
    );
};

export default GoBackExample;
  • history.goForward():等同于 history.go(1),用于前进到下一个历史记录。
import React from'react';
import { useHistory } from'react - router - dom';

const GoForwardExample = () => {
    const history = useHistory();
    const handleClick = () => {
        history.goForward();
    };
    return (
        <button onClick={handleClick}>
            前进到下一页
        </button>
    );
};

export default GoForwardExample;

React Router v6 中的历史记录操作

1. 获取历史对象的变化

在 React Router v6 中,获取历史对象的方式发生了变化。不再使用 withRouteruseHistory 钩子函数,而是通过 useNavigate 钩子函数来获取导航函数。

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

const MyV6Component = () => {
    const navigate = useNavigate();
    const handleClick = () => {
        navigate('/new - path');
    };
    return (
        <button onClick={handleClick}>
            跳转到新路径
        </button>
    );
};

export default MyV6Component;

useNavigate 钩子函数返回的 navigate 函数就是用于操作路由导航和历史记录的核心函数。

2. 常用的导航操作

  • navigate(to, options):这是 React Router v6 中用于导航的主要函数。to 可以是一个字符串路径,也可以是一个 { pathname, search, hash, state } 对象。options 是一个可选对象,包含 replacestate 等属性。
import React from'react';
import { useNavigate } from'react - router - dom';

const NavigateExample = () => {
    const navigate = useNavigate();
    const handleClick = () => {
        const state = { message: '这是传递的状态数据' };
        navigate('/destination', { state, replace: false });
    };
    return (
        <button onClick={handleClick}>
            导航到目标路径并传递状态
        </button>
    );
};

export default NavigateExample;

如果 replace 设置为 true,则会替换当前历史记录栈顶的记录,类似于 React Router v5 中的 history.replace

  • 后退和前进操作:在 React Router v6 中,仍然可以实现后退和前进操作。通过传递负数或正数给 navigate 函数来实现。
import React from'react';
import { useNavigate } from'react - router - dom';

const NavigateBackForwardExample = () => {
    const navigate = useNavigate();
    const handleBack = () => {
        navigate(-1);
    };
    const handleForward = () => {
        navigate(1);
    };
    return (
        <div>
            <button onClick={handleBack}>后退</button>
            <button onClick={handleForward}>前进</button>
        </div>
    );
};

export default NavigateBackForwardExample;

基于历史记录的页面状态管理

在 React 应用中,利用路由历史记录可以实现一些与页面状态相关的功能。例如,在用户导航离开某个页面时,保存页面的当前状态,当用户返回时恢复该状态。

1. 保存和恢复表单状态

假设我们有一个表单页面,用户在填写表单过程中可能会导航离开。我们可以在 componentWillUnmount(在类组件中)或者 useEffect 返回的清理函数(在函数式组件中)中保存表单状态到 localStorage 或者通过路由历史记录的 state 来保存。

类组件示例

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

class FormComponent extends React.Component {
    state = {
        inputValue: ''
    };
    handleChange = (e) => {
        this.setState({ inputValue: e.target.value });
    };
    componentWillUnmount() {
        const { history } = this.props;
        const { inputValue } = this.state;
        history.replace({
           ...history.location,
            state: { inputValue }
        });
    }
    componentDidMount() {
        const { location } = this.props;
        if (location.state && location.state.inputValue) {
            this.setState({ inputValue: location.state.inputValue });
        }
    }
    render() {
        return (
            <input
                type="text"
                value={this.state.inputValue}
                onChange={this.handleChange}
            />
        );
    }
}

export default withRouter(FormComponent);

函数式组件示例

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

const FormFunctionalComponent = () => {
    const [inputValue, setInputValue] = useState('');
    const location = useLocation();
    const navigate = useNavigate();
    useEffect(() => {
        if (location.state && location.state.inputValue) {
            setInputValue(location.state.inputValue);
        }
    }, [location]);
    useEffect(() => {
        return () => {
            navigate({
               ...location,
                state: { inputValue }
            }, { replace: true });
        };
    }, [inputValue, location, navigate]);
    return (
        <input
            type="text"
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
        />
    );
};

export default FormFunctionalComponent;

在上述示例中,当组件即将卸载(用户导航离开)时,将表单的当前状态保存到路由历史记录的 state 中。当组件重新挂载(用户返回)时,从 location.state 中读取状态并恢复表单。

2. 实现面包屑导航

面包屑导航是一种显示用户当前位置在应用导航层次结构中的方式。通过监听路由历史记录的变化,可以动态生成面包屑导航。

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

const Breadcrumb = () => {
    const location = useLocation();
    const [crumbs, setCrumbs] = useState([]);
    useEffect(() => {
        const pathParts = location.pathname.split('/').filter(part => part);
        const newCrumbs = pathParts.map((part, index) => {
            const path = '/' + pathParts.slice(0, index + 1).join('/');
            return { label: part, path };
        });
        setCrumbs(newCrumbs);
    }, [location]);
    return (
        <div>
            {crumbs.map((crumb, index) => (
                <span key={index}>
                    <a href={crumb.path}>{crumb.label}</a>
                    {index < crumbs.length - 1 &&'/' }
                </span>
            ))}
        </div>
    );
};

export default Breadcrumb;

上述代码通过 useEffect 监听 location 的变化,每次路由变化时,根据当前路径生成面包屑导航数据,并渲染面包屑导航。

历史记录操作的注意事项

  1. 状态管理与性能:在使用路由历史记录传递状态数据时,要注意状态数据的大小和复杂性。过多的数据可能会影响性能,并且在某些情况下,状态数据可能会丢失(例如用户通过浏览器刷新页面)。对于复杂的状态管理,建议结合 Redux、MobX 等状态管理库。
  2. 浏览器兼容性:虽然 React Router 在主流浏览器上都有很好的兼容性,但在一些旧版本浏览器上可能会出现问题。特别是在使用 HTML5 History API 相关功能时,要注意部分不支持该 API 的浏览器可能需要使用哈希路由(例如 http://example.com/#/path)。
  3. SSR 与同构应用:在服务器端渲染(SSR)和同构应用开发中,路由历史记录的操作需要特别注意。因为服务器端没有浏览器的 history 对象,需要通过特定的方式来模拟和处理路由历史。例如,在 Next.js 等 SSR 框架中,会对路由和历史记录进行特殊的处理和封装,以确保在服务器端和客户端都能正确工作。

通过深入理解和掌握 React 路由历史记录的操作方法,开发者可以构建更加流畅、用户体验更好的单页应用。无论是页面导航、状态管理还是与用户交互相关的功能,路由历史记录都起着至关重要的作用。在实际开发中,根据项目的需求和特点,合理选择和运用这些方法,将有助于提升应用的质量和性能。