当前位置: 首页 > news >正文

工程化与框架系列(16)--前端路由实现

前端路由实现 🧭

前端路由是单页应用(SPA)的核心功能,它使得在不刷新页面的情况下实现视图切换和状态管理。本文将深入探讨前端路由的实现原理和关键技术。

前端路由概述 🌐

💡 小知识:前端路由是指在单页应用中,通过 JS 动态操作改变页面内容,而不触发浏览器刷新的技术,使得用户体验更加流畅。

为什么需要前端路由

在传统的多页面应用中,页面跳转需要向服务器发送请求并重新加载页面。而在单页应用中,前端路由带来以下优势:

  1. 提升用户体验

    • 无刷新页面切换
    • 更快的响应速度
    • 平滑的过渡效果
    • 保持页面状态
  2. 减轻服务器压力

    • 减少HTTP请求
    • 按需加载资源
    • 降低带宽消耗
    • 减轻服务器负载
  3. 前后端分离

    • 清晰的职责边界
    • 独立开发和部署
    • 更好的可维护性
    • 更灵活的架构

前端路由的实现方式 🔄

前端路由主要有两种实现方式:Hash模式和History模式。

Hash模式

Hash模式是基于URL的hash(即#后面的部分)实现的,hash值的变化不会触发页面刷新。

// hash-router.ts
class HashRouter {
    private routes: Record<string, Function>;
    private currentHash: string;

    constructor() {
        this.routes = {};
        this.currentHash = '';
        this.init();
    }

    private init() {
        // 初始化时监听hashchange事件
        window.addEventListener('hashchange', this.refresh.bind(this));
        // 初始加载
        window.addEventListener('load', this.refresh.bind(this));
    }

    private refresh() {
        // 获取当前hash值,去除#号
        this.currentHash = location.hash.slice(1) || '/';
        // 执行对应路由回调
        this.routes[this.currentHash] && this.routes[this.currentHash]();
    }

    // 注册路由和回调
    public route(path: string, callback: Function) {
        this.routes[path] = callback;
    }

    // 导航到指定路由
    public navigateTo(path: string) {
        location.hash = path;
    }
}

// 使用示例
const router = new HashRouter();

router.route('/', () => {
    document.getElementById('app')!.innerHTML = '<h1>首页</h1>';
});

router.route('/about', () => {
    document.getElementById('app')!.innerHTML = '<h1>关于我们</h1>';
});

router.route('/contact', () => {
    document.getElementById('app')!.innerHTML = '<h1>联系我们</h1>';
});

// 使用导航
document.getElementById('homeLink')!.addEventListener('click', () => {
    router.navigateTo('/');
});

History模式

History模式基于HTML5的History API,通过pushStatereplaceState方法实现URL变化但不刷新页面。

// history-router.ts
class HistoryRouter {
    private routes: Record<string, Function>;
    private currentPath: string;

    constructor() {
        this.routes = {};
        this.currentPath = '';
        this.init();
    }

    private init() {
        // 监听popstate事件
        window.addEventListener('popstate', this.handlePopState.bind(this));
        // 初始加载
        window.addEventListener('load', this.refresh.bind(this));
        // 拦截所有a标签点击事件
        document.addEventListener('click', e => {
            const target = e.target as HTMLElement;
            if (target.tagName === 'A') {
                e.preventDefault();
                const href = (target as HTMLAnchorElement).getAttribute('href');
                if (href) this.navigateTo(href);
            }
        });
    }

    private handlePopState() {
        this.refresh();
    }

    private refresh() {
        this.currentPath = location.pathname || '/';
        this.routes[this.currentPath] && this.routes[this.currentPath]();
    }

    // 注册路由和回调
    public route(path: string, callback: Function) {
        this.routes[path] = callback;
    }

    // 导航到指定路由
    public navigateTo(path: string) {
        history.pushState(null, '', path);
        this.refresh();
    }
}

// 使用示例
const router = new HistoryRouter();

router.route('/', () => {
    document.getElementById('app')!.innerHTML = '<h1>首页</h1>';
});

router.route('/about', () => {
    document.getElementById('app')!.innerHTML = '<h1>关于我们</h1>';
});

两种模式的对比

特性Hash模式History模式
URL格式带有#号(example.com/#/page)干净的URL(example.com/page)
服务器配置不需要特殊配置需要服务器配置支持所有路由返回index.html
SEO友好性较差,搜索引擎可能忽略#后内容较好,URL格式符合传统网页规范
兼容性兼容性好,支持旧版浏览器需要HTML5 History API支持
刷新页面保持路由状态需要服务器配置,否则可能404

高级路由实现 🚀

接下来,我们将实现一个更加完善的前端路由,支持参数匹配、嵌套路由和路由守卫等高级功能。

路由参数和匹配

// advanced-router.ts
interface RouteConfig {
    path: string;
    component: any;
    beforeEnter?: (to: Route, from: Route, next: () => void) => void;
    children?: RouteConfig[];
}

interface Route {
    path: string;
    params: Record<string, string>;
    query: Record<string, string>;
}

class Router {
    private routes: RouteConfig[];
    private currentRoute: Route | null;
    private previousRoute: Route | null;
    private mode: 'hash' | 'history';

    constructor(options: {
        routes: RouteConfig[];
        mode?: 'hash' | 'history';
    }) {
        this.routes = options.routes;
        this.currentRoute = null;
        this.previousRoute = null;
        this.mode = options.mode || 'hash';
        
        this.init();
    }

    private init() {
        if (this.mode === 'hash') {
            window.addEventListener('hashchange', this.handleRouteChange.bind(this));
            window.addEventListener('load', this.handleRouteChange.bind(this));
        } else {
            window.addEventListener('popstate', this.handleRouteChange.bind(this));
            window.addEventListener('load', this.handleRouteChange.bind(this));
            
            // 拦截链接点击
            document.addEventListener('click', e => {
                const target = e.target as HTMLElement;
                if (target.tagName === 'A') {
                    e.preventDefault();
                    const href = (target as HTMLAnchorElement).getAttribute('href');
                    if (href) this.navigateTo(href);
                }
            });
        }
    }

    private handleRouteChange() {
        const path = this.getPath();
        const route = this.matchRoute(path);
        
        if (route) {
            this.previousRoute = this.currentRoute;
            this.currentRoute = route;
            
            // 执行路由钩子
            this.executeRouteGuards();
        }
    }

    private getPath(): string {
        if (this.mode === 'hash') {
            return location.hash.slice(1) || '/';
        } else {
            return location.pathname || '/';
        }
    }

    // 解析URL参数
    private parseQuery(queryString: string): Record<string, string> {
        const query: Record<string, string> = {};
        const pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&');
        
        for (let pair of pairs) {
            if (pair === '') continue;
            const parts = pair.split('=');
            query[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1] || '');
        }
        
        return query;
    }

    private matchRoute(path: string): Route | null {
        // 分离查询参数
        const [pathWithoutQuery, queryString] = path.split('?');
        const query = this.parseQuery(queryString || '');
        
        for (let route of this.routes) {
            const params = this.matchPath(pathWithoutQuery, route.path);
            if (params !== null) {
                return {
                    path: route.path,
                    params,
                    query
                };
            }
        }
        
        return null;
    }

    // 路径匹配逻辑,支持动态参数
    private matchPath(path: string, routePath: string): Record<string, string> | null {
        // 将路由路径转换为正则表达式
        const paramNames: string[] = [];
        const regexPath = routePath.replace(/:([^/]+)/g, (_, paramName) => {
            paramNames.push(paramName);
            return '([^/]+)';
        }).replace(/\*/g, '.*');
        
        const match = path.match(new RegExp(`^${regexPath}$`));
        
        if (!match) return null;
        
        const params: Record<string, string> = {};
        
        // 从匹配结果中提取参数值
        for (let i = 0; i < paramNames.length; i++) {
            params[paramNames[i]] = match[i + 1];
        }
        
        return params;
    }

    // 执行路由守卫
    private executeRouteGuards() {
        const routeConfig = this.findRouteConfig(this.currentRoute!.path);
        
        if (routeConfig && routeConfig.beforeEnter) {
            routeConfig.beforeEnter(
                this.currentRoute!,
                this.previousRoute!,
                () => this.renderComponent(routeConfig.component)
            );
        } else {
            if (routeConfig) this.renderComponent(routeConfig.component);
        }
    }

    private findRouteConfig(path: string): RouteConfig | null {
        return this.routes.find(route => route.path === path) || null;
    }

    private renderComponent(component: any) {
        // 实际项目中这里会根据框架不同有不同实现
        // 这里简化为直接将组件内容插入到指定容器
        document.getElementById('app')!.innerHTML = component.template || '';
    }

    public navigateTo(path: string) {
        if (this.mode === 'hash') {
            location.hash = path;
        } else {
            history.pushState(null, '', path);
            this.handleRouteChange();
        }
    }
}

// 使用示例
const router = new Router({
    mode: 'history',
    routes: [
        {
            path: '/',
            component: { template: '<h1>首页</h1>' }
        },
        {
            path: '/user/:id',
            component: { template: '<h1>用户详情页</h1>' },
            beforeEnter: (to, from, next) => {
                // 路由守卫逻辑
                console.log(`${from.path}导航到${to.path}`);
                console.log(`用户ID: ${to.params.id}`);
                next();
            }
        },
        {
            path: '/about',
            component: { template: '<h1>关于我们</h1>' }
        }
    ]
});

嵌套路由实现

// nested-routes.ts
interface RouteConfig {
    path: string;
    component: any;
    children?: RouteConfig[];
}

class NestedRouter {
    private routes: RouteConfig[];
    private currentPath: string;

    constructor(routes: RouteConfig[]) {
        this.routes = routes;
        this.currentPath = '';
        this.init();
    }

    private init() {
        window.addEventListener('hashchange', this.refresh.bind(this));
        window.addEventListener('load', this.refresh.bind(this));
    }

    private refresh() {
        this.currentPath = location.hash.slice(1) || '/';
        this.render(this.routes, this.currentPath);
    }

    // 递归渲染嵌套路由
    private render(routes: RouteConfig[], path: string, parentPath: string = '') {
        for (const route of routes) {
            // 构建完整的路由路径
            const fullPath = parentPath + route.path;
            
            // 检查当前路径是否匹配该路由
            if (path === fullPath || path.startsWith(fullPath + '/')) {
                // 渲染当前路由组件
                this.renderComponent(route.component, 'router-view');
                
                // 如果有子路由,并且当前路径比当前路由更长,则尝试渲染子路由
                if (route.children && path.length > fullPath.length) {
                    this.render(route.children, path, fullPath);
                }
                
                return;
            }
        }
    }

    private renderComponent(component: any, selector: string) {
        // 这里是一个简化的实现,实际情况需要根据具体框架调整
        const el = document.querySelector(selector);
        if (el) {
            el.innerHTML = component.template || '';
        }
    }

    public navigateTo(path: string) {
        location.hash = path;
    }
}

// 使用示例
const router = new NestedRouter([
    {
        path: '/',
        component: {
            template: `
                <div>
                    <h1>首页</h1>
                    <div class="router-view"></div>
                </div>
            `
        },
        children: [
            {
                path: '/dashboard',
                component: { template: '<h2>仪表盘</h2>' }
            },
            {
                path: '/profile',
                component: {
                    template: `
                        <div>
                            <h2>个人资料</h2>
                            <div class="router-view"></div>
                        </div>
                    `
                },
                children: [
                    {
                        path: '/profile/info',
                        component: { template: '<h3>基本信息</h3>' }
                    },
                    {
                        path: '/profile/settings',
                        component: { template: '<h3>账户设置</h3>' }
                    }
                ]
            }
        ]
    },
    {
        path: '/about',
        component: { template: '<h1>关于我们</h1>' }
    }
]);

路由组件实现 🧩

自定义Link组件

// router-components.ts
// 自定义Link组件
class RouterLink extends HTMLElement {
    constructor() {
        super();
        
        this.addEventListener('click', this.handleClick.bind(this));
    }
    
    static get observedAttributes() {
        return ['to'];
    }
    
    connectedCallback() {
        this.render();
    }
    
    attributeChangedCallback() {
        this.render();
    }
    
    private render() {
        const to = this.getAttribute('to') || '/';
        this.innerHTML = `<a href="${to}">${this.textContent || to}</a>`;
    }
    
    private handleClick(e: Event) {
        e.preventDefault();
        const to = this.getAttribute('to') || '/';
        
        // 触发自定义事件,让Router处理导航
        this.dispatchEvent(new CustomEvent('router-navigate', {
            bubbles: true,
            detail: { to }
        }));
    }
}

// 注册自定义元素
customElements.define('router-link', RouterLink);

// 路由视图组件
class RouterView extends HTMLElement {
    constructor() {
        super();
    }
    
    // 设置组件的内容
    setContent(content: string) {
        this.innerHTML = content;
    }
}

// 注册自定义元素
customElements.define('router-view', RouterView);

// 路由器实现
class WebComponentRouter {
    private routes: Record<string, Function>;
    private viewElement: RouterView | null;
    
    constructor() {
        this.routes = {};
        this.viewElement = null;
        
        // 监听链接点击事件
        document.addEventListener('router-navigate', ((e: CustomEvent) => {
            this.navigateTo(e.detail.to);
        }) as EventListener);
        
        // 初始化
        window.addEventListener('load', this.handleLocationChange.bind(this));
        window.addEventListener('popstate', this.handleLocationChange.bind(this));
    }
    
    public setView(view: RouterView) {
        this.viewElement = view;
        this.handleLocationChange();
    }
    
    private handleLocationChange() {
        const path = window.location.pathname;
        this.renderRoute(path);
    }
    
    public route(path: string, callback: Function) {
        this.routes[path] = callback;
    }
    
    private renderRoute(path: string) {
        if (this.viewElement && this.routes[path]) {
            const content = this.routes[path]();
            this.viewElement.setContent(content);
        }
    }
    
    public navigateTo(path: string) {
        history.pushState(null, '', path);
        this.renderRoute(path);
    }
}

// 使用示例
const router = new WebComponentRouter();

// 获取路由视图元素
const routerView = document.querySelector('router-view') as RouterView;
router.setView(routerView);

// 注册路由
router.route('/', () => '<h1>首页</h1>');
router.route('/about', () => '<h1>关于我们</h1>');
router.route('/contact', () => '<h1>联系我们</h1>');

// HTML中的使用
/*
<body>
  <nav>
    <router-link to="/">首页</router-link>
    <router-link to="/about">关于</router-link>
    <router-link to="/contact">联系</router-link>
  </nav>
  
  <router-view></router-view>
</body>
*/

实际框架中的路由实现 🔍

React Router简化版实现

// react-router-simple.tsx
import React, { useState, useEffect, createContext, useContext, ReactNode } from 'react';

// 路由上下文
interface RouterContextType {
    currentPath: string;
    navigateTo: (path: string) => void;
}

const RouterContext = createContext<RouterContextType>({
    currentPath: '/',
    navigateTo: () => {}
});

// 路由器组件
interface RouterProps {
    children: ReactNode;
}

export const Router: React.FC<RouterProps> = ({ children }) => {
    const [currentPath, setCurrentPath] = useState(window.location.pathname);

    useEffect(() => {
        // 处理浏览器前进后退
        const handlePopState = () => {
            setCurrentPath(window.location.pathname);
        };

        window.addEventListener('popstate', handlePopState);
        return () => window.removeEventListener('popstate', handlePopState);
    }, []);

    const navigateTo = (path: string) => {
        window.history.pushState(null, '', path);
        setCurrentPath(path);
    };

    return (
        <RouterContext.Provider value={{ currentPath, navigateTo }}>
            {children}
        </RouterContext.Provider>
    );
};

// 路由组件
interface RouteProps {
    path: string;
    component: React.ComponentType<any>;
}

export const Route: React.FC<RouteProps> = ({ path, component: Component }) => {
    const { currentPath } = useContext(RouterContext);

    // 简单的路径匹配
    return currentPath === path ? <Component /> : null;
};

// 链接组件
interface LinkProps {
    to: string;
    children: ReactNode;
    className?: string;
}

export const Link: React.FC<LinkProps> = ({ to, children, className }) => {
    const { navigateTo } = useContext(RouterContext);

    const handleClick = (e: React.MouseEvent) => {
        e.preventDefault();
        navigateTo(to);
    };

    return (
        <a href={to} onClick={handleClick} className={className}>
            {children}
        </a>
    );
};

// 使用示例
const App = () => {
    return (
        <Router>
            <nav>
                <Link to="/">首页</Link>
                <Link to="/about">关于</Link>
                <Link to="/contact">联系</Link>
            </nav>

            <div className="content">
                <Route path="/" component={() => <h1>首页内容</h1>} />
                <Route path="/about" component={() => <h1>关于我们</h1>} />
                <Route path="/contact" component={() => <h1>联系我们</h1>} />
            </div>
        </Router>
    );
};

Vue Router简化版实现

// vue-router-simple.ts
import { ref, h, defineComponent, Component, VNode } from 'vue';

// 路由配置接口
interface RouteConfig {
    path: string;
    component: Component;
}

// 创建路由器
export function createRouter(options: { routes: RouteConfig[] }) {
    // 当前路径
    const currentPath = ref(window.location.pathname);
    
    // 路由映射
    const routeMap = new Map<string, Component>();
    
    // 初始化路由表
    for (const route of options.routes) {
        routeMap.set(route.path, route.component);
    }
    
    // 处理路由变化
    const handleRouteChange = () => {
        currentPath.value = window.location.pathname;
    };
    
    // 监听popstate事件
    window.addEventListener('popstate', handleRouteChange);
    
    // 路由导航方法
    const push = (path: string) => {
        window.history.pushState(null, '', path);
        currentPath.value = path;
    };
    
    // 路由替换方法
    const replace = (path: string) => {
        window.history.replaceState(null, '', path);
        currentPath.value = path;
    };
    
    // 创建并返回路由器实例
    return {
        currentPath,
        routes: options.routes,
        routeMap,
        push,
        replace,
        install(app: any) {
            // 注册全局组件
            app.component('RouterLink', RouterLink);
            app.component('RouterView', RouterView);
            
            // 提供路由器实例
            app.provide('router', this);
        }
    };
}

// RouterLink组件
const RouterLink = defineComponent({
    name: 'RouterLink',
    props: {
        to: {
            type: String,
            required: true
        }
    },
    setup(props, { slots }) {
        const router = inject('router') as ReturnType<typeof createRouter>;
        
        const handleClick = (e: Event) => {
            e.preventDefault();
            router.push(props.to);
        };
        
        return () => h(
            'a',
            {
                href: props.to,
                onClick: handleClick
            },
            slots.default && slots.default()
        );
    }
});

// RouterView组件
const RouterView = defineComponent({
    name: 'RouterView',
    setup() {
        const router = inject('router') as ReturnType<typeof createRouter>;
        
        return () => {
            const currentComponent = router.routeMap.get(router.currentPath.value);
            return currentComponent 
                ? h(currentComponent) 
                : h('div', 'Not Found');
        };
    }
});

// 使用示例
const Home = { template: '<div>Home Page</div>' };
const About = { template: '<div>About Page</div>' };

const router = createRouter({
    routes: [
        { path: '/', component: Home },
        { path: '/about', component: About }
    ]
});

const app = createApp({
    template: `
        <div>
            <nav>
                <router-link to="/">Home</router-link>
                <router-link to="/about">About</router-link>
            </nav>
            <router-view></router-view>
        </div>
    `
});

app.use(router);
app.mount('#app');

最佳实践建议 ⭐

路由设计原则

  1. 保持URL语义化

    • 使用清晰、描述性的URL
    • 遵循RESTful风格
    • 避免过长或复杂的路径
    • 使用连字符分隔单词(不用下划线)
  2. 处理未匹配路由

    • 提供默认的404页面
    • 重定向到主页或上一页
    • 清晰的错误提示
    • 提供导航建议
  3. 合理使用路由参数

    • 区分必要和可选参数
    • 使用查询参数处理筛选和排序
    • 参数命名清晰
    • 处理参数缺失情况
  4. 路由权限管理

    • 实现路由守卫
    • 基于角色的路由访问控制
    • 登录状态检查
    • 权限不足时提供反馈

实践示例

  1. 良好的路由组织结构
// 结构化路由配置
const routes = [
    {
        path: '/',
        component: Layout,
        children: [
            {
                path: '',
                component: Home,
                meta: { title: '首页', requiresAuth: false }
            },
            {
                path: 'dashboard',
                component: Dashboard,
                meta: { title: '仪表盘', requiresAuth: true }
            }
        ]
    },
    {
        path: '/user',
        component: UserLayout,
        children: [
            {
                path: 'profile',
                component: UserProfile,
                meta: { title: '个人资料', requiresAuth: true }
            },
            {
                path: 'settings',
                component: UserSettings,
                meta: { title: '账户设置', requiresAuth: true }
            }
        ]
    },
    {
        path: '/auth',
        component: AuthLayout,
        children: [
            {
                path: 'login',
                component: Login,
                meta: { title: '登录', guest: true }
            },
            {
                path: 'register',
                component: Register,
                meta: { title: '注册', guest: true }
            }
        ]
    },
    // 404页面应放在最后
    {
        path: '*',
        component: NotFound,
        meta: { title: '页面未找到' }
    }
];
  1. 权限控制
// 路由守卫实现
router.beforeEach((to, from, next) => {
    // 设置页面标题
    document.title = to.meta.title ? `${to.meta.title} - 应用名称` : '应用名称';
    
    // 权限控制
    const isLoggedIn = !!localStorage.getItem('token');
    
    // 需要登录但用户未登录
    if (to.meta.requiresAuth && !isLoggedIn) {
        next({
            path: '/auth/login',
            query: { redirect: to.fullPath } // 登录后重定向
        });
        return;
    }
    
    // 已登录用户不应访问游客页面(如登录页)
    if (to.meta.guest && isLoggedIn) {
        next('/dashboard');
        return;
    }
    
    // 正常导航
    next();
});
  1. 路由过渡动画
// Vue中的路由过渡
const App = {
    template: `
        <div class="app">
            <transition name="fade" mode="out-in">
                <router-view />
            </transition>
        </div>
    `
};

// CSS
/*
.fade-enter-active,
.fade-leave-active {
    transition: opacity 0.3s;
}

.fade-enter,
.fade-leave-to {
    opacity: 0;
}
*/
  1. 动态路由加载
// 根据用户角色动态加载路由
function generateRoutesFromUserRole(role) {
    const asyncRoutes = [
        {
            path: '/admin',
            component: Admin,
            meta: { roles: ['admin'] },
            children: [
                {
                    path: 'users',
                    component: UserManagement,
                    meta: { roles: ['admin'] }
                },
                {
                    path: 'settings',
                    component: SystemSettings,
                    meta: { roles: ['admin'] }
                }
            ]
        },
        {
            path: '/editor',
            component: Editor,
            meta: { roles: ['editor', 'admin'] }
        },
        {
            path: '/user',
            component: User,
            meta: { roles: ['user', 'editor', 'admin'] }
        }
    ];

    // 过滤适合当前用户角色的路由
    const accessibleRoutes = filterRoutes(asyncRoutes, role);
    
    // 添加到路由器
    router.addRoutes(accessibleRoutes);
    
    return accessibleRoutes;
}

function filterRoutes(routes, role) {
    return routes.filter(route => {
        if (route.meta && route.meta.roles) {
            // 检查当前用户角色是否匹配
            return route.meta.roles.includes(role);
        }
        
        // 默认允许访问
        return true;
    }).map(route => {
        // 递归处理子路由
        if (route.children) {
            route.children = filterRoutes(route.children, role);
        }
        
        return route;
    });
}

结语 📝

前端路由是现代单页应用的基础设施,掌握其实现原理和最佳实践可以帮助我们构建更高效、用户体验更佳的前端应用。通过本文,我们学习了:

  1. 前端路由的核心概念和工作原理
  2. 两种主流的路由实现方式及其区别
  3. 路由参数匹配、嵌套路由和路由守卫的实现
  4. 如何构建路由组件
  5. 主流框架中路由的实现细节

💡 学习建议:

  1. 尝试手动实现一个简单的路由系统
  2. 深入研究主流路由库的源码
  3. 学习路由相关的性能优化技巧
  4. 设计合理的路由结构来提升应用体验
  5. 了解路由与状态管理的结合方式

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关文章:

  • Kotlin协程(二)协程的生命周期及管理
  • 在Vscode开发QT,完成QT环境的配置
  • 体育数据分析:竞技表现优化与商业价值挖掘的技术范式
  • Golang的数据库分库分表
  • AI 外呼产品架构解读:让智能外呼更精准高效
  • PDF万能水印删除工具
  • 利用Adobe Acrobat 实现PPT中图片分辨率的提升
  • 自己编译RustDesk,并将自建ID服务器和key信息写入客户端
  • java 项目中设计模式 之单例模式
  • 简述一下Spark中的hashShuffle和Sortshuffle两中shauffle的流程
  • 命名管道——进程间通信
  • 【JAVA面试题】设计模式之原型模式
  • 安装Linux操作系统
  • uni-app开发安卓和iOS 打包流程(云打包)
  • 代码随想录刷题学习日记
  • Oracle 11g的部署配置
  • go语言中字符串嵌套
  • 表格管理---React
  • Ubuntu 20.04下配置VSCode以支持ROS开发
  • Mysql面试篇笔记:
  • 北京2025年住房发展计划:供应商品住房用地240-300公顷,建设筹集保租房5万套
  • 欧洲承诺投资6亿欧元吸引外国科学家
  • 欧盟公布关税反制清单,瞄准美国飞机、汽车等产品
  • 98岁动物学家、北京大学教授杨安峰逝世
  • 中国中古史集刊高质量发展论坛暨《唐史论丛》创刊四十周年纪念会召开
  • 重庆动物园大熊猫被游客扔玻璃瓶,相同地方曾被扔可乐瓶