在 TypeScript 中使用

有时候会需要使用编程式导航,比如上方导航栏里面选项,响应按钮事件,进行路由跳转。react 的做法是通过高阶函数,函数体内部向组件的 props 注册一些路由的方法,最后返回一个新的组件。

下面是一个结合 TypeScript 使用 withRouter 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
interface NavigationState {
routes: Array<{
path: string;
name: string;
key: string;
}>;
selectedKey: string;
}

interface NavigationProps {
name: string;
}

class Navigation extends Component<
RouteComponentProps & NavigationProps, // 使用「交叉类型」来处理Props的关系
NavigationState
> {
state = {
routes: [],
selectedKey: "1"
};

toggleRoute = (event: ClickParam) => {
this.props.history.push(path); // route的方法已经被注册到了Props上
};

render() {
// ...
);
}
}

export default withRouter(Navigation);

严格匹配: exact 和 strict

exact已经关闭了模糊匹配。那么strict设置为true(默认是false),会有什么区别呢。例如 path 为 /a的时候:

  • 两个 prop 均为true:不匹配/a/,只匹配/a
  • strict 为 false:/a//a均匹配

路由配置化

可以直接使用 react-router-config 组件,实现原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Route, Switch, SwitchProps, RouteProps } from "react-router-dom";

function renderRoutes(params: {
routes: RouteProps[];
switchProps?: SwitchProps;
}) {
const { switchProps, routes } = params;
return (
<Switch {...switchProps}>
{routes.map((route, index) => (
<Route
key={index}
path={route.path}
component={route.component}
exact={route.exact || true}
strict={route.strict || false}
></Route>
))}
</Switch>
);
}

假设我们的路由如下:

1
2
3
4
5
6
7
8
9
10
11
12
import { RouteProps } from "react-router-dom";

const config: RouteProps[] = [
{
path: "/",
component: HomePage
},
{
path: "/user",
component: UserPage
}
];

路由变化响应

在 VueJS 技术栈中,vue-router 是提供路由响应的钩子函数,例如:beforeEachafterEach等等。

但是在 React 中,react-router 并不提供相关的钩子函数。那么如果有顶部导航栏,不同页面切换时,高亮不同的标签,那么应该怎么实现响应路由变化呢

首先即使是路由,在 React 中,它也是一个组件对象。因此,如果要更新试图,必须触发组件的 render。而触发组件的关键在于,props 发生改变。

第一步:需要使用withRouter来包装对应的组件,将路由的信息作为 props 注入组件,比如顶部导航栏。

第二步:下面是 React17 前后的简单例子。

React17 之前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { withRouter, RouteComponentProps } from "react-router-dom";

class Navigation extends Component<RouteComponentProps, any> {
state = {
selectedPath: "/"
};

// 在componentWillReceiveProps中接受新的props
// 决定是否更新state
componentWillReceiveProps(nextProps: RouteComponentProps) {
if (nextProps.location.pathname === this.props.location.pathname) {
this.setState({ selectedPath: nextProps.location.pathname });
}
}

render() {
// 这里的render渲染,取决于state是否更新
const { selectedPath } = this.state;
return <div>导航栏选中信息:{selectedPath}</div>;
}
}

export default withRouter(Navigation);

在 React17 之后,不推荐使用componentWillReceiveProps等不确定的生命周期。处理的思路是:render 函数返回的视图中,变量的变化依赖 props 属性的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { withRouter, RouteComponentProps } from "react-router-dom";

class Navigation extends Component<RouteComponentProps, any> {
state = {
paths: ["/", "/a"]
};

render() {
const { pathname } = this.props.location;
const { paths } = this.state;

let selectedPath = "";
paths.some(path => {
if (path === pathname) {
selectedPath = path;
return true;
}
return false;
});

return <div>导航栏选中信息:{selectedPath}</div>;
}
}

export default withRouter(Navigation);