本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!

Antd5最最最吸引我的点

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

Antd5 官网

又好用,又能美观。

没有Antd5之前,我是选Mui的

比方我要做一个对款式有许多需求的办理后台,或许一些官网主页,我都会选MUI

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

MUI官网

这个才是我认为最能变美观的组件库,MUI走在了Antd5之前,就搞CSS in JS了,定制主题那叫一个简略,但有一个点便是他不是很好用。。。。

Antd5,几乎让我没理由不选,又好用,又能美观

MUI不怎样好用,组件功用不多,远没有Antd4好用,想用只能自己着手封装,可是能让我纠结MUI和Antd选谁的首要原因,便是MUI真美观,现在Antd5有了这么好用的自定义主题的本领,一下就完全治愈精力内讧,直接Antd5,无脑入就对了。又好用,又能美观。

ok,那么简略的介绍完我选Antd5的理由,接下来,咱们就直奔主题,快速的过一下,运用React18+Antd5+Vite+Ts搭建办理后台的全过程,并在文章结尾处贴出这个项目代码地址,供咱们快速上手实操,体会一下Antd5的痛快。

Antd5调配React18+Vite+Ts开发办理后台全流程

装备vite和tsconfig准备工作

import { fileURLToPath, URL } from "node:url";
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3120/',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^/api/, '')
      }
    }
  },
  plugins: [react()],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
})

装备 “@”,这样我在代码里能够痛快的引进文件了。

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

装备proxy,我就能够跟后台进行通信,处理开发环境的跨域问题。

tsconfig装备

{
  "include": ["env.d.ts", "src/**/*"],
  "exclude": ["src/**/__tests__/*"],
  "compilerOptions": {
    "experimentalDecorators":true,
    "composite": true,
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
    "jsx": "preserve"
  }
}

加入d.ts处理大部分引进文件报错的问题

/// <reference types="node" />
/// <reference types="react" />
/// <reference types="react-dom" />
declare namespace NodeJS {
  interface ProcessEnv {
    readonly NODE_ENV: 'development' | 'production' | 'test';
    readonly PUBLIC_URL: string;
  }
}
declare module '*.avif' {
  const src: string;
  export default src;
}
declare module '*.bmp' {
  const src: string;
  export default src;
}
declare module '*.gif' {
  const src: string;
  export default src;
}
declare module '*.jpg' {
  const src: string;
  export default src;
}
declare module '*.jpeg' {
  const src: string;
  export default src;
}
declare module '*.png' {
  const src: string;
  export default src;
}
declare module '*.webp' {
    const src: string;
    export default src;
}
declare module '*.svg' {
  import * as React from 'react';
  export const ReactComponent: React.FunctionComponent<React.SVGProps<
    SVGSVGElement
  > & { title?: string }>;
  const src: string;
  export default src;
}
declare module '*.module.css' {
  const classes: { readonly [key: string]: string };
  export default classes;
}
declare module '*.module.scss' {
  const classes: { readonly [key: string]: string };
  export default classes;
}
declare module '*.module.sass' {
  const classes: { readonly [key: string]: string };
  export default classes;
}

束缚一下node和npm的最低版别

由于用了vite,咱们需求束缚一下node和npm的版别,由于版别太低,跑不起来的。

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

开发登陆页

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

封装字体特效组件,让页面看着浓艳不俗气

import "react";
import { useRef } from "react";
import { useEffect } from "react";
import styles from "./index.module.scss";
let defaultRun: boolean = true;
let infinite: boolean = true;
let frameTime: number = 75;
let endWaitStep = 3;
let prefixString = "";
let runTexts = [""];
let colorTextLength = 5;
let step = 1;
let colors = [
  "rgb(110,64,170)",
  "rgb(150,61,179)",
  "rgb(191,60,175)",
  "rgb(228,65,157)",
  "rgb(254,75,131)",
  "rgb(255,94,99)",
  "rgb(255,120,71)",
  "rgb(251,150,51)",
  "rgb(226,183,47)",
  "rgb(198,214,60)",
  "rgb(175,240,91)",
  "rgb(127,246,88)",
  "rgb(82,246,103)",
  "rgb(48,239,130)",
  "rgb(29,223,163)",
  "rgb(26,199,194)",
  "rgb(35,171,216)",
  "rgb(54,140,225)",
  "rgb(76,110,219)",
  "rgb(96,84,200)",
];
let inst = {
  text: "",
  prefix: -(prefixString.length + colorTextLength),
  skillI: 0,
  skillP: 0,
  step: step,
  direction: "forward",
  delay: endWaitStep,
};
function randomNum(minNum: number, maxNum: number): number {
  switch (arguments.length) {
    case 1:
      return parseInt((Math.random() * minNum + 1).toString(), 10);
    case 2:
      return parseInt(
        (Math.random() * (maxNum - minNum + 1) + minNum).toString(),
        10
      );
    default:
      return 0;
  }
}
let randomTime: number = randomNum(15, 150);
let destroyed: boolean = false;
let continue2: boolean = false;
let infinite0: boolean = true;
function render(dom: HTMLDivElement, t: string, ut?: string): void {
  if (inst.step) {
    inst.step--;
  } else {
    inst.step = step;
    if (inst.prefix < prefixString.length) {
      inst.prefix >= 0 && (inst.text += prefixString[inst.prefix]);
      inst.prefix++;
    } else {
      switch (inst.direction) {
        case "forward":
          if (inst.skillP < t.length) {
            inst.text += t[inst.skillP];
            inst.skillP++;
          } else {
            if (inst.delay) {
              inst.delay--;
            } else {
              inst.direction = "backward";
              inst.delay = endWaitStep;
            }
          }
          break;
        case "backward":
          if (inst.skillP > 0) {
            inst.text = inst.text.slice(0, -1);
            inst.skillP--;
          } else {
            inst.skillI = (inst.skillI + 1) % runTexts.length;
            inst.direction = "forward";
          }
          break;
        default:
          break;
      }
    }
  }
  if (ut != null) {
    inst.text = ut.substring(0, inst.skillP);
    if (inst.skillP > ut.length) {
      inst.skillP = ut.length;
    }
  }
  dom.textContent = inst.text;
  let value;
  if (inst.prefix < prefixString.length) {
    value = Math.min(colorTextLength, colorTextLength + inst.prefix);
  } else {
    value = Math.min(colorTextLength, t.length - inst.skillP);
  }
  dom.appendChild(fragment(value));
}
function getNextColor(): string {
  return colors[Math.floor(Math.random() * colors.length)];
}
function getNextChar(): string {
  return String.fromCharCode(94 * Math.random() + 33);
}
function fragment(value: number): DocumentFragment {
  let f = document.createDocumentFragment();
  for (let i = 0; value > i; i++) {
    let span = document.createElement("span");
    span.textContent = getNextChar();
    span.style.color = getNextColor();
    f.appendChild(span);
  }
  return f;
}
let flag = false;
export default (props) => {
  const { texts } = props;
  let container = useRef();
  let container2 = useRef();
  function init(): void {
    setTimeout(() => {
      if (destroyed) {
        return;
      }
      container.current && loop();
    }, randomTime);
  }
  function loop(): void {
    if (destroyed) {
      return;
    }
    setTimeout(() => {
      if (continue2 && container.current != null) {
        if (destroyed) {
          return;
        }
        let dom = container.current;
        let index = inst.skillI;
        let originText = texts[index];
        let currentText = runTexts[index];
        if (originText != currentText) {
          render(dom, currentText, originText);
          runTexts[index] = originText;
        } else {
          render(dom, currentText);
        }
      }
      if (infinite0) {
        loop();
      } else {
        if (inst.skillP < runTexts[0].length) {
          loop();
        }
      }
    }, frameTime);
  }
  useEffect(() => {
   {
      runTexts = texts;
      continue2 = defaultRun;
      infinite0 = infinite;
      inst.delay = endWaitStep;
      if (!infinite0) {
        if (runTexts.length > 1) {
          console.warn(
            "在设置infinite=false的情况下,仅第一个字符串收效,后续字符串不再显现。"
          );
        }
      }
      init();
    }
  }, []);
  return (
    <div className={styles.content}>
      <pre ref={container} className={styles.container} id="container"></pre>
      <pre ref={container2}></pre>
    </div>
  );
};

scss

运用的css Module技能


.content{
    color: black;
    height: 100%;
    width: 100%;
    .container {
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100%;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
        white-space: pre-wrap;
        word-wrap: break-word;
    }
}

登陆卡片,用的Antd5中的Card组件

这没啥可说的,可是作用的确看起来舒服多了

import { Card } from "antd";
import type { ReactNode } from "react";
const { Meta } = Card;
const App: React.FC = (props: { children: ReactNode }) => (
  <Card
    hoverable
    style={{ width: 400 }}
    cover={
      <img
        alt="example"
        src="942w_531h_progressive.webp"
      />
    }
  >
    {props.children}
  </Card>
);
export default App;

开发主页

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台
Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

首要挑干的说说几点:

  • 左侧菜单
  • tab标签
  • 面包屑

别看就这几个,组合一起,完成各种功用就费力了,接下来详细说说

规划路由结构装备计划

路由数据的结构是树状的,咱们把这个树状结构分离出来,结构数据和内容数据

  • 结构数据:描述结构关系,经过id
  • 内容数据:依据id去匹配对应的数据内容

结构数据

export const RouteIds = {
  hello: "hello",
  sys: "sys",
  role: "role",
  user: "user",
};
export const routesStructData = [
  {
    id: RouteIds.hello,
  },
  {
    id: RouteIds.sys,
    children: [{ id: RouteIds.role }, { id: RouteIds.user }],
  },
];

内容数据

import { Login, Center, Page1, Hello, UserPage, RolePage } from "../pages";
export default {
  center: {
    meta: {
      title: "中心",
    },
  },
  hello: {
    meta: {
      title: "主页",
    },
    component: Hello,
  },
  sys: {
    meta: {
      title: "系统办理",
    },
  },
  user: {
    meta: {
      title: "用户办理",
    },
    component: UserPage,
  },
  role: {
    meta: {
      title: "人物办理",
    },
    component: RolePage,
    state: { a: 1111 },
  },
};

然后二者组成完好路由数据的根本数据,这么做的优点便是能够把重视的事情分开,有些逻辑需求结构,就用结构数据,有些地方需求内容,就经过结构的id进行获取,这样,代码组织的难度更小。

别忘了运用的组件,要用lazy进行懒加载

import { lazy } from "react";
const Center = lazy(() => import("./center"));
const Login = lazy(() => import("./login"));
const Page1 = lazy(() => import("./page1"));
const Hello = lazy(() => import("./hello"));
const UserPage = lazy(() => import("./sys/user"));
const RolePage = lazy(() => import("./sys/role"));
export { Center, Login, Page1, Hello, UserPage, RolePage };

然后规划一个递归算法,生成整个完好的路由结构数据

const processRoute = (children: any[], routesData: any[], prefix: string) => {
  routesData.forEach((routeItem, index) => {
    const { id } = routeItem;
    if (permissions.includes(id)) {
      let routeData = routerConfig[id];
      // 沿途记载,然后拼接成path
      routeData.path = prefix + "/" + id;
      routeData.routeId = id;
      const { component: Component } = routeData;
      if (Component) {
        routeData.element = (
          <Suspense>
            <Component></Component>
          </Suspense>
        );
      }
      children!.push(routeData);
      if (routeItem.children!?.length > 0) {
        routeData.children = [];
        processRoute(routeData.children, routeItem.children!, routeData.path);
      }
    }
  });
};

经过算法,咱们尽可能少的装备数据,有些要害数据,完全能够经过这个算法计算出来。

比方路由组件的path,咱们就能够经过分析结构办理,拼接起来

优点是,咱们不必但系调整结构数据,连带的path命名和修正的心智担负。

依据路由数据驱动显现菜单

在组件内部,经过useEffect,呼应路由数据的创立完结

useEffect(() => {
    if (routerData.length) {
      let result = [];
      processRoute(routerData[1].children, result);
      setMenuData(result);
    }
  }, [routerData]);

然后下一步进行,依据路由结构数据,烘托菜单结构

const processRoute = (data, result: any) => {
  data.forEach((item) => {
    let temp: any = {
      key: item.routeId,
      icon: createElement(UserOutlined),
      label: item.meta.title,
    };
    result.push(temp);
    if (item?.children?.length) {
      temp.children = [];
      processRoute(item.children, temp.children);
    }
  });
};

然后将数据经过setMenuData之后,驱动显现菜单

{menuData.length > 0 && (
    <Menu
      theme="dark"
      mode="inline"
      selectedKeys={defaultSelectedKeys}
      defaultOpenKeys={defaultOpenKeys}
      style={{ height: "100%", borderRight: 0 }}
      items={menuData}
      onClick={({ key }) => {
        const path = routerConfig[key]?.path;
        if (path) {
          navigate(path);
        }
      }}
    />
  )}

Antd5的用法没啥改动,这儿说说用selectedKeysopenKeys的原因:

selectedKeysdefaultOpenKeys需求让Menu变为可控组件 原因便是,我需求动态呼应路由的改动,就算我直接改写,也能够选中正确的菜单项目,打开正确的折叠项

为啥是defaultOpenKeys,由于不这样,你都点不开折叠,当然你也能够完全设置可控。

那么说到这,咱们需求完成一个监听,react路由改动的功用,你知道怎样规划么?

封装useLocationListenhook,完成路由改动监听

import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export default (listener) => {
  let location = useLocation();
  useEffect(() => {
    listener(location);
  }, [location]);
};

然后在组件内运用

useLocationListen((location: Location) => {
    const { pathname } = location;
    let temp = pathname.split("/").filter((item) => {
      return item;
    });
    setDefaultSelectedKeys([temp.at(-1)]);
    let temp2 = temp.slice(1, temp.length - 1);
    if (temp2.length) {
      setDefaultOpenKeys(temp2);
    }
    // 这个地便利是存储tab标签记载的逻辑
    globalStore.addTabHistory(location);
  })

然后传入回调函数,就能够完成呼应,然后分析路由,获取Menu组件的打开和选中状况数据,使其完全可控。

也这是由于完成了路由的监听,菜单和下面要介绍的tab标签栏,完美联动,经过监听同一个location对象

记载路由改动,烘托标签栏

每当路由改动,都会经过一个数据存下来,而且还能够夸组件共享,那么就需求mobx这样的状况办理库了,咱们装置一下mobx

yarn add mobx mobx-react

完成一个大局仓库

import { action, makeAutoObservable, toJS } from "mobx";
import type { Location } from "react-router-dom";
// Model the application state.
class Global {
     ...
  permissions: any[] = [];
        ...
  constructor() {
    makeAutoObservable(this);
  }
  init() {
  ...
    this.tabsHistory = {};
   ...
  }
  ...
  addTabHistory = (newItem: Location) => {
    let temp = toJS(this.tabsHistory);
    temp[newItem.pathname] = newItem;
    this.tabsHistory = temp;
  };
  deleteTabHistory = (pathName: string) => {
    let temp = toJS(this.tabsHistory);
    if (Object.values(temp).length > 1) {
      Reflect.deleteProperty(temp, pathName);
      this.tabsHistory = temp;
    }
  };
    ...
}
export default new Global();

用法相当的简略,只不过,我完成的对象的特点添加削减的监听,就算我用deep都不好使,所以我用了一个小技巧,便是我先把数据经过tojs转化成一般的数据,让后再修正,最终直接赋值给状况,这样,引用地址改动,就会触发组件的刷线,那么怎样react组件呼应mobx的改写?

react组件需求用到mobx-react供给的hocobserver

export default observer(() => {
    ...
    useEffect(() => {
    let tabsHistory = Object.values(toJS(globalStore.tabsHistory));
    setItems(
      tabsHistory.map((item) => {
        const { pathname } = item;
        let routeId = pathname.split("/").at(-1);
        const { meta } = routeConfig[routeId];
        return { label: meta.title, key: pathname };
      })
    );
  }, [globalStore.tabsHistory]);
  ...
})

这样,在主页的组件中进行路由大局监听,当路由发生改动就会记载,然后tabs标签组件内部,就会呼应更新,然后烘托数据。

    <Tabs
      className={styles.content}
      type="editable-card"
      onChange={onChange}
      activeKey={activeKey}
      items={items}
      hideAdd={true}
      onEdit={(e, action) => {
        if (action == "remove") {
          ;
          globalStore.deleteTabHistory(e);
        }
      }}
    />

这便是Antd5中Tabs的运用,可是我需求修正他的默认款式,由于,我仅仅需求tab3供给切换,内部没有什么内容,可是会有多余的margin,我要去覆盖掉,还不能污染大局。

.content {
    :global(.ant-tabs-nav) {
        margin: initial !important;
    }
}

运用css module:global来搞就完了。

KeepAlive组件

咱们做的是一个办理后台,常常会有表单填写,不能咱们切换标签了,再回来啥都重置了,那体会可不好,我完成了keepAlive,放置切换重置。

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

keepAlive的代码,我封装在了R6helper里。

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

或许你能够直接在项目完成:

import { useRef, useEffect, useReducer, useMemo, memo } from 'react'
import { useLocation, useOutlet } from 'react-router-dom'
const KeepAlive = (props: any) => {
  const outlet = useOutlet()
  const { include, keys } = props
  const { pathname } = useLocation()
  const componentList = useRef(new Map())
  const forceUpdate = useReducer((bool: any) => !bool, true)[1] // 强制烘托
  const cacheKey = useMemo(
    () => pathname + '__' + keys[pathname],
    [pathname, keys]
  ) // eslint-disable-line
  const activeKey = useRef<string>('')
  useEffect(() => {
    componentList.current.forEach(function (value, key) {
      const _key = key.split('__')[0]
      if (!include.includes(_key) || _key === pathname) {
        this.delete(key)
      }
    }, componentList.current)
    activeKey.current = cacheKey
    if (!componentList.current.has(activeKey.current)) {
      componentList.current.set(activeKey.current, outlet)
    }
    forceUpdate()
  }, [cacheKey, include]) // eslint-disable-line
  return (
    <div>
      {Array.from(componentList.current).map(([key, component]) => (
        <div key={key}>
          {key === activeKey.current ? (
            <div>{component}</div>
          ) : (
            <div style={{ display: 'none' }}>{component}</div>
          )}
        </div>
      ))}
    </div>
  )
}
export default memo(KeepAlive)

然后替换Outlet标签,去掉<Outlet/>

 <KeepAlive
    include={["/center/sys/user", "/center/sys/role"]}
    keys={[]}
></KeepAlive>

封装主题定制Hoc,答应我别再重复定制主题了,封装一下好么。

Antd定制主题真的不要太便利,方法便是经过Antd供给的ConfigProvider装备theme就行了

import React from 'react';
import { ConfigProvider, Button } from 'antd';
const App: React.FC = () => (
  <ConfigProvider
    theme={{
      token: {
        colorPrimary: '#00b96b',
      },
    }}
  >
    <Button />
  </ConfigProvider>
);
export default App;

类似这样,可是你不能定制一个主题,就写这么一长串吧,那也太不优雅了,这你不赶紧封装一个Hoc

import "react";
import { ConfigProvider, Button } from "antd";
export default (Comp, theme) => {
  return (props) => {
    return (
      <ConfigProvider theme={theme}>
        <Comp {...props} />
      </ConfigProvider>
    );
  };
};

然后包装一下组件,然后传入theme数据,大大便利了定制过程。


export default themeProviderHoc(center, {
  components: {
    Menu: {
      colorPrimary: "blue",
    },
  },
});

Antd5主题,考究了一个token,我不管它为啥这么叫,总之便是能设置款式,而且antd5还供给了,主题定制网页,

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

导出装备

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

复制过来就行了,

可是需求提示一下,便是这个装备文件是经过localStorage存储的,有记载,想整理的话,手动整理一下。

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

权限规划,的确有点难

我目前对权限的认知,便是希望希望经过装备一个权限数据,然后传给后台,后台依据当前用户进行记载,当这个用户登陆的时分,重新获取权限然后依据权限去操控的显现和操作。

我觉得目前了解够用了,究竟一个简略的上手模板项目,并不需求那么复杂的权限规划。

可是我遇到了一个问题,权限操控一个大工作,便是前端的路由的鉴权,这块在React上我花了不少的功夫。

React router你就变吧,咋变你也不好用

我用的React router 6.4.3,咋感觉变了呢~所以我查了一下,加了不少东西啊

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

我个人观点,我就觉得react的路由不是很好用,比较Vue router体会上我觉得差许多,技能上我没才能说谁规划好,可是我就从运用下来的体会,文档的清晰度,还是Vue router更好的,可能有许多原因是vue文档是中文的,哈哈哈哈,恶作剧的,我觉得我在完成权限操控时分,让我很别扭,比较vue,直接就有api addRoute,react我找半响发现,压根就没有这玩意,233333。

React router 6.4.3

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

我之前创立路由是经过在最顶层创立一个Router,然后在内部我再经过useRoutes创立routes和一系列route的,成果这次这个api,把这三个都整合了,变成了一个。

import React from "react";
import { createRoot } from "react-dom/client";
import {
  createBrowserRouter,
  RouterProvider,
  Route,
  Link,
} from "react-router-dom";
const router = createBrowserRouter([
  {
    path: "/",
    element: (
      <div>
        <h1>Hello World</h1>
        <Link to="about">About Us</Link>
      </div>
    ),
  },
  {
    path: "about",
    element: <div>About</div>,
  },
]);
createRoot(document.getElementById("root")).render(
  <RouterProvider router={router} />
);

经过RouterProvider供给一个provider然后再经过createBrowserRouter创立一个router

可是这个RouterProvider有点意思啊,不能够传children。。。。。,好吧,你赢了。

是简练了许多,我用起来还挺舒服,可是当我想动态设置路由的时分,我就发现,逻辑写起来怎样那么乱呢。

我要动态设置路由

为什么这么规划,首要觉得权限应该操控路由的创立,而不是,路由创立出来,你经过鉴权的方式去防止跳转啥的。

我觉得最好的方式,便是路由依据权限来判别是否存在。

压根就没这个路由权限,就直连续这个路由都不烘托,这不更完全,所以我需求动态创立路由,可是createBrowserRouter集成在一起了,我需求分开,这样我的逻辑能更清晰。

所以我用了原滋原味的路由装备。

 <>
      {routerData && (
        <Routes>
          <Route path="/" element={<Login></Login>}></Route>
          <Route
            path="/center"
            element={<Center></Center>}
            children={routerData?.[1]?.children?.map((item) => {
              return toRenderRoute(item);
            })}
          ></Route>
        </Routes>
      )}
    </>

有没有中梦回React router v4的感觉,哈哈哈,相同能完成功用,这如同React router说到做到了,他一直发起不给你全部,只给你元功用,然后让你拼装,现在看来,的确有点悟了。

然后在cneter这个首要的路由上,动态装备其内部的子组件,这样就完成的React的动态路由

 const toRenderRoute = (item) => {
    const { children } = item;
    let arr = [];
    if (children) {
      arr = children.map((item) => {
        return toRenderRoute(item);
      });
    }
    return (
      <Route
        children={arr}
        key={item.path}
        path={item.path}
        element={item.element}
      ></Route>
    );
  };

上面这段便是递归创立路由的算法逻辑,这样就完成了路由的动态装备了。

当然我这种合作权限,动态设置路由的方式仅仅我的一种计划,仅供参考,23333。

完成monorepo,简化发动流程

项目目前是前端+后端的项目,后端运用的koa完成的简易版nodejs服务,这两个放在了一个repo里发动的话,需求顺次装置依靠而且发动,这样操作稍微不便利,那么咱们简略的经过yarn workspace完成一下monorepo,然后仅仅一条指令就能完成发动。

先在package.json中装备workspace

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

然后在项目根目录下创立packages文件,然后把前后端两个项目移动这儿。

Antd5一出,治好了我组件库挑选内讧,我直接调配React18+Vite+Ts做了一个办理后台

然后履行履行:

// 第一步:装置依靠
yarn
// 第二步:发动
yarn start

经过这样的小技巧,就能快速的发动项目了,简化了开发流程。

结尾

这篇先讲这么多,下一篇,咱们详细聊聊如何规划权限办理,以及nodejs开发服务的逻辑。

项目地址 github.com/DLand-Team/…

至此,一个对新手友爱的办理后台项目就构建好了,而且还在不断完善中,未来会补全Java后端服务项目,敬请期待,有问题能够随时咨询我,或许留言,我整了个群叫闲D岛,群号551406017,结识一帮情投意合的小伙伴,交流技能,欢迎水群(我就会玩qq,整其他,我也不会,比方公众号啥的。。。哈哈哈哈)