next训练营 | 10. 路由守卫 | 实现全局loading

语言: CN / TW / HK

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 { store: any; routeChangeStart: any; routeChangeComplete: any; state = { loading: false, }; constructor(props) { super(props); console.log("constructor LayoutApp");

//此构造函数只会在客户端执行一次 这里的 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 (

首页 {" "} 用户管理 个人中心 登录 {currentUser && (
{currentUser.username}
)}
{this.state.loading ? ( ) : ( )}
@copyright wzyan
); } } export default withRouter(LayoutApp); ```