next训练营 | 10. 路由守卫 | 实现全局loading
theme: cyanosis highlight: atom-one-dark
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
王志远,微医前端技术部
前言
路由切换的控制时机很关键,我们用一个【切换路由页面未加载完成时显示 loading】的效果来学习下如何在 next 中使用路由守卫。效果如下
开始实现
实现思路
- 全局组件实例上存放 loading 开关属性,并实现在 loading 为 true 时显示加载 gif,为 false 时才展示内容
- 组件挂载时注册路由守卫事件:路由开始变化时开启 loading,路由结束变化时关闭 loading
- 组件卸载时关闭路由守卫事件
具体实现
以下修改均在_app.tsx
中实现
引入路由
import router from "next/router";
全局组件实例上存放 loading 开关属性
tsx
routeChangeStart: any; // 路由开始变化时事件
routeChangeComplete: any; // 路由结束变化时事件
state = {
loading: false,
};
组件挂载时注册
tsx
componentDidMount() {
this.routeChangeStart = (url) => {
this.setState({ loading: true });
};
this.routeChangeComplete = (url) => {
this.setState({ loading: false });
};
router.events.on("routeChangeStart", this.routeChangeStart);
router.events.on("routeChangeComplete", this.routeChangeComplete);
}
组件卸载时
tsx
componentWillUnmount() {
router.events.off("routeChangeStart", this.routeChangeStart);
router.events.off("routeChangeComplete", this.routeChangeComplete);
}
最终的_app.tsx
```tsx
import App, { Container } from "next/app";
import Link from "next/link";
import { Layout, Menu, Icon, Avatar, Spin } from "antd";
import router from "next/router";
import "antd/dist/antd.css";
import { withRouter } from "next/router";
const { Header, Footer } = Layout;
import * as TYEPS from "../store/action-types";
import axios from "../utils/axios";
import createStore from "../store";
import { Provider } from "react-redux";
const REDUX_STORE = "REDUX_STORE";
function getStore(initialState) {
if (typeof window == "undefined") {
//如果 在服务器端运行的。那么直接创建新仓库返回
return createStore(initialState);
} else {
//如果此代码是在客户端执行的,第一次会创建,以后每次都复用上一次创建的
if (!window[REDUX_STORE]) {
window[REDUX_STORE] = createStore(initialState);
}
return window[REDUX_STORE];
}
}
class LayoutApp extends App
//此构造函数只会在客户端执行一次 这里的 initialState 是 getInitialProps 返回的
this.store = getStore(props.initialState);
}
// 在页面级别渲染时只会被执行一次,即服务端渲染或每次切换客户端渲染都会被执行,但服务端渲染时客户端不会执行
static async getInitialProps({ Component, ctx }) {
let store = getStore({});
let pageProps = {};
console.log("2. getInitialProps");
let options: any = {
url: "/api/currentUser",
};
//如果此方法是在服务器执行的,那么会有 ctx.req 属性,它代表本次 node 请求对象
if (ctx.req && ctx.req.headers.cookie) {
options.headers = options.headers || {};
options.headers.cookie = ctx.req.headers.cookie;
}
let response = await axios(options);
if (response.data.code === 0) {
// 当前登录的用户
let currentUser = response.data.data;
store.dispatch({ type: TYEPS.SET_USER_INFO, payload: currentUser });
}
if (Component.getInitialProps) {
// 执行当前页面的 getInitialProps
let data = await Component.getInitialProps(ctx);
pageProps = { ...data };
}
return { pageProps, initialState: store.getState() };
}
componentDidMount() {
this.routeChangeStart = (url) => {
this.setState({ loading: true });
};
this.routeChangeComplete = (url) => {
this.setState({ loading: false });
};
router.events.on("routeChangeStart", this.routeChangeStart);
router.events.on("routeChangeComplete", this.routeChangeComplete);
}
componentWillUnmount() {
router.events.off("routeChangeStart", this.routeChangeStart);
router.events.off("routeChangeComplete", this.routeChangeComplete);
}
render() {
console.log("3.LayoutApp.render");
let { Component, pageProps } = this.props as any;
let { currentUser } = this.store.getState();
let pathname = this.props.router.pathname;
pathname = "/" + pathname.split("/")[1];
return (