好的看完了前面的配置文件,今天review下一层
先给大家看一下src目录的整体结构(对应下图):
从外向内,先讲看得到的文件
main.tsx(项目启动文件)
import { createRoot } from "react-dom/client";// 1. 导入 React 渲染核心工具 import App from "./app/App.tsx";// 2. 导入项目的根组件(所有页面的爹) import "./styles/index.css";// 3. 导入全局样式 // 4. 把根组件挂载到 HTML 里,启动项目 createRoot(document.getElementById("root")!).render(<App />);这个文件是整个React项目的启动入口,和我们Java中SpringBoot的启动类功能完全一样,都是用来启动整个项目的。
导入相关依赖的代码比较简单,这里就不详细说了,重点看最核心的启动代码:
createRoot(document.getElementById("root")!).render(<App />);这段代码有点抽象,我们拆开来一步步看,就很好理解了:
// 找到 HTML 里的容器 const container = document.getElementById("root"); // 创建根节点 → 把根组件 App 渲染进去 const root = createRoot(container); root.render(<App />);上一篇文章中说过,在index.html文件中定义了id为root的盒子
const container = document.getElementById("root");
讲盒子取出
再通过root.render();将组件渲染进去
整个过程,完全可以类比我们Java中SpringBoot的启动类:
public class MyBlogRefractApplication { public static void main(String[] args) { SpringApplication.run(MyBlogRefractApplication.class, args); } }App.tsx(根组件/全局配置组件)
import { RouterProvider } from 'react-router';// 导入路由(页面跳转管理器) import { useEffect } from 'react';// React 钩子(执行初始化逻辑) import { Toaster } from 'sonner';// 消息提示组件 import { router } from './routes';// 路由配置文件(所有页面地址) import { ErrorBoundary } from './components/ErrorBoundary';// 错误边界(全局异常捕获) function App() { useEffect(() => { document.documentElement.classList.add('dark'); }, []); return ( <ErrorBoundary> <RouterProvider router={router} /> <Toaster theme="dark" richColors position="top-center" closeButton /> </ErrorBoundary> ); } export default App;这个组件是整个React项目的根组件,相当于我们Java中的全局配置类,负责初始化全局逻辑、配置全局组件。我们重点讲两个核心部分:
1. useEffect 钩子函数
useEffect(() => { }, [])这是React中处理“副作用”的核心钩子,简单说就是用来执行和组件渲染无关,但必须执行的初始化逻辑。
() => { }:里面写要执行的核心逻辑(这里是给整个项目开启深色模式);
[]:控制钩子的执行时机——空数组表示只执行一次(项目启动时执行);
如果传变量(比如 [count, a, b]):只有这些变量变化时,钩子才会重新执行;
注意:不建议不填[](即 useEffect(() => { })),会导致每次组件渲染都执行,造成性能浪费。
这个钩子的作用,我们后端同学很好理解,相当于:
Vue中的 onMounted(()=>{}),或者Java常用框架中的 @PostConstruct(严格来说,是@PostConstruct(初始化)+ @PreDestroy(资源清理)的组合,若钩子包含return清理函数则完全对应,仅初始化则对应@PostConstruct)。
2. 返回的全局组件结构
return ( <ErrorBoundary> <RouterProvider router={router} /> <Toaster theme="dark" richColors position="top-center" closeButton /> </ErrorBoundary> );这段代码返回了整个项目的基础UI结构,配置了三个核心全局组件,类比Java中的全局配置类(类名随便写的doge):
@Configuration public class GlobalConfig { // 等同于 <ErrorBoundary> 错误捕获 @Bean public GlobalExceptionHandler globalExceptionHandler() { return new GlobalExceptionHandler(); } // 等同于 <RouterProvider> 路由 @Bean public RouterProvider routerProvider() { return new RouterProvider(); } // 等同于 <Toaster> 全局消息提示 @Bean public Toaster toaster() { return new Toaster(); } }总的来说
<RouterProvider> 负责页面跳转,
<Toaster> 负责全局消息提示,
<ErrorBoundary> 负责捕获全局错误
三、routes.ts(路由配置文件)
import {createBrowserRouter} from 'react-router'; ......... import {RequireAdmin} from './components/RequireAdmin'; const routerBasename = getRouterBasename(); export const router = createBrowserRouter( [ { path: '/', Component: Layout, children: [ {index: true, Component: Home}, {path: 'about', Component: About}, {path: 'blog', Component: BlogFeedPage}, {path: 'lab', Component: Lab}, {path: 'category/:id', Component: CategoryPage}, {path: 'post/:id', Component: PostPage}, {path: 'admin123/login', Component: AdminLogin}, {path: '*', Component: NotFound}, ], }, ], routerBasename ? {basename: routerBasename} : {} );这个文件是整个项目的路由配置中心,相当于我们Java中的Controller层+视图解析器的组合,负责映射URL和页面组件,我们分两部分讲解:
1. 全局路径前缀(basename)
const routerBasename = getRouterBasename(); routerBasename ? {basename: routerBasename} : {}这两段配置了前端的baseurl
就像在Springboot项目中的xml文件里配置了
server.servlet.context-path=/{routerBasename}所有对前端的访问都会在域名后加上routerBasename,之后才是路由访问路径
对应 {index: true, Component: Home},
对应 {path: 'about', Component: About},
2. 核心路由配置
export const router = createBrowserRouter( [ { path: '/', Component: Layout, children: [ {index: true, Component: Home}, {path: 'about', Component: About}, {path: 'blog', Component: BlogFeedPage}, {path: 'lab', Component: Lab}, {path: 'category/:id', Component: CategoryPage}, {path: 'post/:id', Component: PostPage}, {path: 'admin123/login', Component: AdminLogin}, {path: '*', Component: NotFound}, ], }, ]这一部分是项目的路由,可理解为后端的Controller层+视图解析器的组合——和Controller一样负责URL映射,
只不过后端Controller返回数据(或通过视图解析器返回页面),
而前端路由直接返回对应的页面组件。
如下:
@Controller @RequestMapping("/") // 对应 path: '/' public class PageController { // 首页 对应 index: true @GetMapping("") public String home() { return "Home";} // /about @GetMapping("/about") public String about() {return "About";} // /blog @GetMapping("/blog") public String blog() {return "BlogFeedPage";} // /lab @GetMapping("/lab") public String lab() {return "Lab";} // /category/123 @GetMapping("/category/{id}") public String category(@PathVariable Long id) {return "CategoryPage";} // /post/456 @GetMapping("/post/{id}") public String post(@PathVariable Long id) {return "PostPage";} // 后台登录 @GetMapping("/admin123/login") public String adminLogin() {return "AdminLogin";} // 404 对应 path: "*" @GetMapping("/*") public String notFound() {return "NotFound";} }今天就review到这里
这是我的博客网站https://refract.top/refract-blog/