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); ```