小白篇 -- 用 useContext 和 useReducer 思考全局状态以及 全局loading

260 阅读2分钟

普通版

layout.jsx

import { createContext, useReducer, useLayoutEffect } from "react";

import AxiosDemo from "./axiosDemo";

import { axiosConfig } from "./servers/index";

import "./styles.css";

const initState = { loading: false };

export const GlobalContext = createContext();

const globalReducer = (state, action) => {
	switch (action.type) {
		case "updateState":
			return {
				...state,
				...action.data
			};
		default:
			throw new Error();
	}
};

export default function App() {
	const [globalState, dispatch] = useReducer(globalReducer, initState);

	const { loading } = globalState;

	useLayoutEffect(() => {
		axiosConfig({ loading, dispatch });
	}, []);

	return (
		<GlobalContext.Provider value={{ loading, dispatch }}>
			<div className="App">
				<h1>Hello CodeSandbox</h1>
				<h2>Start editing to see some magic happen!</h2>
				<AxiosDemo />

				{/* global loading */}
				<div
					style={{
						display: loading ? "block" : "none",
						position: "fixed",
						top: 0,
						left: 0,
						width: "100vw",
						height: "100vh",
						background: " rgba(255, 255, 255, 0.8)"
					}}
				>
					<div
						style={{
							width: "100%",
							height: "100%",
							display: "flex",
							alignItems: "center",
							justifyContent: "center"
						}}
					>
						loading...
					</div>
				</div>
			</div>
		</GlobalContext.Provider>
	);
}

axiosDemo.jsx

import { useEffect, useContext } from "react";
import axios from "axios";

import { GlobalContext } from "./App";

const getInfo = () => {
	return axios.get("https://api.github.com/users/ruanyf");
};

const AxiosDemo = () => {
	const { loading } = useContext(GlobalContext);

	useEffect(() => {
		getInfo().then((res) => {
			// console.log(res, "res--0");
			getInfo().then((res1) => {
				// console.log(res1, "res--1");
			});
		});
		getInfo();
	}, []);

	return (
		<div>
			<div>axios demo</div>
			<button
				onClick={() => {
					getInfo().then((res) => {
						// console.log(res, "res--click");
					});
				}}
			>
				click getInfo(loadingStatus:{`${loading}`})
			</button>
		</div>
	);
};

export default AxiosDemo;

axiosConfig.js

import axios from "axios";

// 是否展示 loading (做一个类似任务队列,避免 loading 闪动)
const requestUrls = [];
const isShowLoading = ({ requestUrl, responseUrl, loading, dispatch }) => {
	if (requestUrl) {
		requestUrls.push(requestUrl);
	} else {
		const index = requestUrls.indexOf(responseUrl as string);
    if (index !== -1) {
      requestUrls.splice(index, 1);
    }
	}

	if (requestUrls.length > 0) {
		// ⚠️⚠️⚠️ 由于传入的 loading 值(优化点,多个接口一起请求就会一直dispatch)在 useLayoutEffect 没有监听 loading 值,故在此只能一直拿到 false;可以参考在 context 中改变 loading 值时加入到 window 里面,dispatch 同理,即不需要传入 {loading, dispatch },从全局对象 window 拿这两个值
		if (loading === false) {
			dispatch({ type: "updateState", data: { loading: true } });
		}
	} else {
		dispatch({
			type: "updateState",
			data: { loading: false }
		});
	}
};

export const axiosConfig = ({ loading, dispatch }) => {
	axios.interceptors.request.use(
		function (config) {
			// console.log(1111111);
			const { url } = config;
			isShowLoading({ requestUrl: url, loading, dispatch });
			return config;
		},
		function (error) {
			return Promise.reject(error);
		}
	);

	axios.interceptors.response.use(
		function (response) {
			// console.log(222222);
			const {
				config: { url }
			} = response;
			isShowLoading({ responseUrl: url, loading, dispatch });
			return response;
		},
		function (error) {
			// isShowLoading({ responseUrl: url, loading, dispatch }); 失败也需要调 isShowLoading,该处没拿到 url,故注释了,真实场景需要开启
			return Promise.reject(error);
		}
	);
};

window 版

layout.jsx

import { createContext, useReducer } from "react";

import AxiosDemo from "./axiosDemo";

import { axiosConfig } from "./servers/index";

import "./styles.css";

axiosConfig();

const initState = { loading: false };

export const GlobalContext = createContext();

const globalReducer = (state, action) => {
	switch (action.type) {
		case "updateState":
			return {
				...state,
				...action.data
			};
		default:
			throw new Error();
	}
};

export default function App() {
	const [globalState, dispatch] = useReducer(globalReducer, initState);

	const { loading } = globalState;

	window.customWindowState = {};
	window.customWindowState.loading = loading;
	window.customWindowState.dispatch = dispatch;

	return (
		<GlobalContext.Provider value={{ loading, dispatch }}>
			<div className="App">
				<h1>Hello CodeSandbox</h1>
				<h2>Start editing to see some magic happen!</h2>
				<AxiosDemo />

				{/* loading */}
				<div
					style={{
						display: loading ? "block" : "none",
						position: "fixed",
						top: 0,
						left: 0,
						width: "100vw",
						height: "100vh",
						background: " rgba(255, 255, 255, 0.8)"
					}}
				>
					<div
						style={{
							width: "100%",
							height: "100%",
							display: "flex",
							alignItems: "center",
							justifyContent: "center"
						}}
					>
						loading...
					</div>
				</div>
			</div>
		</GlobalContext.Provider>
	);
}

axiosConfig.js

import axios from "axios";

// 是否展示 loading (做一个类似任务队列,避免 loading 闪动)
const requestUrls = [];
const isShowLoading = ({ requestUrl, responseUrl }) => {
	const {
		customWindowState: { loading, dispatch }
	} = window;

	console.log(loading, "loading");
	if (requestUrl) {
		requestUrls.push(requestUrl);
	} else {
	const index = requestUrls.indexOf(responseUrl as string);
    if (index !== -1) {
      requestUrls.splice(index, 1);
    }
	}

	if (requestUrls.length > 0) {
		if (loading === false) {
			dispatch({ type: "updateState", data: { loading: true } });
		}
	} else {
		dispatch({
			type: "updateState",
			data: { loading: false }
		});
	}
};

export const axiosConfig = () => {
	axios.interceptors.request.use(
		function (config) {
			// console.log(1111111);
			const { url } = config;
			isShowLoading({ requestUrl: url });
			return config;
		},
		function (error) {
			return Promise.reject(error);
		}
	);

	// Add a response interceptor
	axios.interceptors.response.use(
		function (response) {
			// console.log(222222);
			const {
				config: { url }
			} = response;
			isShowLoading({ responseUrl: url });
			return response;
		},
		function (error) {
			// isShowLoading({ responseUrl: url }); // isShowLoading({ responseUrl: url, loading, dispatch }); 失败也需要调 isShowLoading,该处没拿到 url,故注释了,真实场景需要开启
			return Promise.reject(error);
		}
	);
};