React-router V7(配置路由)

React-router-v7

1.安装

1.从react-router-v7开始,将使用如下方式安装。

bash 复制代码
npm i react-router

v6即之前为react-router-dom@6

2.你的应用程序外层渲染一个 <BrowserRouter> 组件:

jsx 复制代码
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter} from "react-router";

import App from "./App.tsx";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <BrowserRouter>
      <App/>
    </BrowserRouter>
  </StrictMode>
);

2.路由器

react-router在组件顶层放置了一个路由器xxRouter组件,里面维护了各种路由Route组件。

路由器一共分为两种,BrowserRouterHashRouter

BrowserRouter

  • 基于history模式:页面跳转原理是使用了HTML5为浏览器全局的history对象新增了两个API,包括 history.pushStatehistory.replaceState

    vue router的history模式实现一致

  • 最终实现,直接拼接路径,例如localhost:8080/about

  • 后端需要做处理,因为我们在浏览器中输入链接时会触发一个get请求,localhost:8080/about的形式,实际上也会去匹配后端的/about路由。

HashRouter

  • 基于hash模式 :页面跳转原理是使用了location.hashlocation.replace;和vue router的hash模式实现一致

  • 在域名后,先拼接/#,再拼接路径;也就是利用锚点,实现路由的跳转;例如:localhost:8080/#/about

    我们切换路由时实际上切换的是#/xx的部分,不改变域名,所以不会触发新的请求,也就无需后端做处理。

示例

二者的使用方式是一样的,不会影响到其他组件。

jsx 复制代码
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { HashRouter} from "react-router";

import App from "./App.tsx";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <HashRouter>
      <App/>
    </HashRouter>
  </StrictMode>
);

3.路由

配置路由

路由是通过渲染 <Routes><Route> 组件来进行配置的,它将 path 和 对应组件关联了起来。

jsx 复制代码
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter, Routes, Route } from "react-router";
import App from "./app";

const root = document.getElementById("root");

ReactDOM.createRoot(root).render(
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<App />} />
    </Routes>
  </BrowserRouter>
);

下面是稍微复杂一点的例子:

jsx 复制代码
<Routes>
  <Route index element={<Home />} />
  <Route path="about" element={<About />} />

  <Route element={<AuthLayout />}>
    <Route path="login" element={<Login />} />
    <Route path="register" element={<Register />} />
  </Route>

  <Route path="concerts">
    <Route index element={<ConcertsHome />} />
    <Route path=":city" element={<City />} />
    <Route path="trending" element={<Trending />} />
  </Route>
</Routes>
<Routes>

开辟路由空间,用于包裹<Route>,提示内部是具体的路由规则组件。

<Route>

具体的路由规则。从源码我们可以知道其上的属性。

ts 复制代码
interface PathRouteProps {
    caseSensitive?: NonIndexRouteObject["caseSensitive"];
    path?: NonIndexRouteObject["path"];
    id?: NonIndexRouteObject["id"];
    lazy?: LazyRouteFunction<NonIndexRouteObject>;
    loader?: NonIndexRouteObject["loader"];
    action?: NonIndexRouteObject["action"];
    hasErrorBoundary?: NonIndexRouteObject["hasErrorBoundary"];
    shouldRevalidate?: NonIndexRouteObject["shouldRevalidate"];
    handle?: NonIndexRouteObject["handle"];
    index?: false;
    children?: React.ReactNode;
    element?: React.ReactNode | null;
    hydrateFallbackElement?: React.ReactNode | null;
    errorElement?: React.ReactNode | null;
    Component?: React.ComponentType | null;
    HydrateFallback?: React.ComponentType | null;
    ErrorBoundary?: React.ComponentType | null;
}
/**
 * @category Types
 */
interface LayoutRouteProps extends PathRouteProps {
}
/**
 * @category Types
 */
interface IndexRouteProps {
    caseSensitive?: IndexRouteObject["caseSensitive"];
    path?: IndexRouteObject["path"];
    id?: IndexRouteObject["id"];
    lazy?: LazyRouteFunction<IndexRouteObject>;
    loader?: IndexRouteObject["loader"];
    action?: IndexRouteObject["action"];
    hasErrorBoundary?: IndexRouteObject["hasErrorBoundary"];
    shouldRevalidate?: IndexRouteObject["shouldRevalidate"];
    handle?: IndexRouteObject["handle"];
    index: true;
    children?: undefined;
    element?: React.ReactNode | null;
    hydrateFallbackElement?: React.ReactNode | null;
    errorElement?: React.ReactNode | null;
    Component?: React.ComponentType | null;
    HydrateFallback?: React.ComponentType | null;
    ErrorBoundary?: React.ComponentType | null;
}
type RouteProps = PathRouteProps | LayoutRouteProps | IndexRouteProps;
/**
 * Configures an element to render when a pattern matches the current location.
 * It must be rendered within a {@link Routes} element. Note that these routes
 * do not participate in data loading, actions, code splitting, or any other
 * route module features.
 *
 * @category Components
 */
declare function Route$1(_props: RouteProps): React.ReactElement | null;

嵌套路由

路由可以被嵌套在父路由中。

jsx 复制代码
<Routes>
  <Route path="dashboard" element={<Dashboard />}>
    <Route index element={<Home />} />
    <Route path="settings" element={<Settings />} />
  </Route>
</Routes>

父路由的 path 会自动添加到子路由的 path 中,所以上面的例子中,会创建两个路由 "/dashboard""/dashboard/settings"

子路由通过父路由组件中的 <Outlet/> 渲染。

jsx 复制代码
import { Outlet } from "react-router";

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      {/* 将被渲染成 <Home/> 或 <Settings/> */}
      <Outlet />
    </div>
  );
}
<Outlet/>

子路由占位符组件,可以理解为他就是一个插槽。

常规组件渲染
jsx 复制代码
import Hello from "./Hello.jsx"

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Hello />
    </div>
  );
}

手动引入组件并渲染。

路由组件的逻辑
jsx 复制代码
import { Outlet } from "react-router";

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      {/* 将被渲染成 <Home/> 或 <Settings/> */}
      <Outlet />
    </div>
  );
}

当匹配到/home,对应的路由组件为<Home/>,则<Outlet/>被替换为<Home/>.

jsx 复制代码
//引入的实际是个插槽,会根据路由规则被真实组件替换
import { Outlet } from "react-router";

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      {/* 根据路由规则匹配到的组件进行实际渲染*/}
      <Home />
    </div>
  );
}

布局路由

没有 path 的路由看起来会有一定的嵌套关系,但不会在路由上添加额外的路径。

jsx 复制代码
<Routes>
  <Route element={<MarketingLayout />}>
    <Route index element={<MarketingHome />} />
    <Route path="contact" element={<Contact />} />
  </Route>

  <Route path="projects">
    <Route index element={<ProjectsHome />} />
    <Route element={<ProjectsLayout />}>
      <Route path=":pid" element={<Project />} />
      <Route path=":pid/edit" element={<EditProject />} />
    </Route>
  </Route>
</Routes>

所谓布局路由,一般是提供布局样式,但是我们不改变路由路径,在合适的位置放入<Outlet/>渲染路由,例如<MarketingLayout />

jsx 复制代码
export function MarketingLayout(){
    return (
    	<>
        	<siderBar/>
        	{/* 用于渲染路由 */}
            <Outlet/>
        	<Footer/>
        <>
    )
}

索引路由

索引路由会在其父路由组件对应的 <Outlet/> 中进行渲染(就像是默认的子路由一样)。它们通过 index 属性来进行配置。

jsx 复制代码
<Routes>
  <Route path="/" element={<Root />}>
    {/* 访问 "/" 会被渲染到 Outlet 里 */}
    <Route index element={<Home />} />

    <Route path="dashboard" element={<Dashboard />}>
      {/* 访问 "/dashboard" 会被渲染到 Outlet 里  */}
      <Route index element={<DashboardHome />} />
      <Route path="settings" element={<Settings />} />
    </Route>
  </Route>
</Routes>

请注意,索引路由不能有子路由。如果你期望实现有子路由这种行为,那你可能需要一个布局路由

错误示范

jsx 复制代码
<Route index element={<Home />}>
    <Route path="settings" element={<Settings />} />
</Route>

错误原因:索引路由不能有子路由。

路由前缀

一个不带 element 属性的 <Route path> 会为其子路由添加路径前缀,无须引入父布局。

jsx 复制代码
<Route path="projects">
  <Route index element={<ProjectsHome />} />
  <Route element={<ProjectsLayout />}>
    <Route path="home" element={<Project />} />
  </Route>
</Route>

可以理解为给路由进行分组,单纯添加前缀,而不影响组件布局。我们将用/projects/home渲染出<Project />

动态传参

如果路由的 path 包含 :,那么它会被视为动态参数,当路由被匹配时,动态参数将被解析并提供给其他路由 API 接口,比如 useParams

路由规则

jsx 复制代码
<Route path="teams/:teamId" element={<Team />} />

路由组件内接收

tsx 复制代码
import { useParams } from "react-router";

export default function Team() {
  let params = useParams();
  // params.teamId
}

在一个路由中可以有多个动态参数:

jsx 复制代码
<Route path="/c/:categoryId/p/:productId" element={<Product />} />

可选参数

将结合路由传参讲解

在动态参数后面添加 ? 可以使其变为可选参数。

jsx 复制代码
<Route path=":lang?/categories" element={<Categories />} />

也可以在静态字符串后面加上 ?,使其变为可选参数:

jsx 复制代码
<Route path="users/:userId/edit?" component={<User />} />

通配符

也被称为 "捕获所有"* 路由,如果一个路由模式以 /* 结尾,后面的任意字符串都会匹配到,也包括其他的 /

路由规则

jsx 复制代码
<Route path="files/*" element={<File />} />

路由组件内接收

ts 复制代码
let params = useParams();
// `params["*"]` 将包含 `files/` 之后剩余的URL内容。
let filePath = params["*"];

你可以对 * 进行解构,只是必须给它赋一个新的名称。一般命名为 splat

jsx 复制代码
let { "*": splat } = useParams();

4.导航

声明式路由导航

所谓声明式路由导航,就是在组件中添加跳转链接,点击该链接进行跳转,本质是对<a>标签的封装。

不需要激活样式时,可以使用 <Link> 组件。

jsx 复制代码
import { Link } from "react-router";

export function LoggedOutMessage() {
  return (
    <p>
      您已退出. <Link to="/login">再次登录</Link>
    </p>
  );
}

该组件适用于那些需要渲染激活状态的导航链接。

jsx 复制代码
import { NavLink } from "react-router";

export function MyAppNav() {
  return (
    <nav>
      <NavLink to="/" end>
        Home
      </NavLink>
      <NavLink to="/trending" end>
        Trending Concerts
      </NavLink>
      <NavLink to="/concerts">All Concerts</NavLink>
      <NavLink to="/account">Account</NavLink>
    </nav>
  );
}

<NavLink> 处于激活状态时,它会自动拥有一个 .active 类名,以便于使用 CSS 轻松设置样式:

css 复制代码
a.active {
  color: red;
}

它还在 className(类名)、style(样式)以及 children(子元素)上有回调属性,这些回调属性带有活动状态,可用于内联样式设置或条件渲染。

案例1

jsx 复制代码
// className
<NavLink
  to="/messages"
  className={({ isActive }) => (isActive ? "text-red-500" : "text-black")}
>
  Messages
</NavLink>

案例2

jsx 复制代码
// style
<NavLink
  to="/messages"
  style={({ isActive }) => ({
    color: isActive ? "red" : "black",
  })}
>
  Messages
</NavLink>

案例3

jsx 复制代码
// children
<NavLink to="/message">
  {({ isActive }) => (
    <span className={isActive ? "active" : ""}>
      {isActive ? "👉" : ""} Tasks
    </span>
  )}
</NavLink>

编程式路由导航

按照编程要求进行路由跳转,例如:

  • 我们点击某个按钮,然后导航到其他页面。
  • 延时多少秒后,自动导航到其他页面。

核心是满足某个条件触发方法进行跳转,而不是让用户手动去点击导航链接。

该方式使得路由跳转更灵活,且不会增加新的DOM元素。

useNavigate
jsx 复制代码
import { useNavigate } from "react-router";

export function LoginPage() {
  let navigate = useNavigate();

  return (
    <>
      <MyHeader />
      <MyLoginForm
        onSuccess={() => {
          navigate("/dashboard");
        }}
      />
      <MyFooter />
    </>
  );
}

使用步骤:

1.引入hooks

jsx 复制代码
import { useNavigate } from "react-router";

2.获得导航方法

jsx 复制代码
let navigate = useNavigate();

3.触发导航事件,和声明式路由导航的to一样。

jsx 复制代码
navigate("/dashboard");

小结

声明式路由导航的优点

对于常规导航而言,最好使用 <Link><NavLink>

它们能提供更好的默认用户体验,比如键盘事件、无障碍标签、"在新窗口中打开"、右键上下文菜单等等。

编程式路由导航的优点

核心是满足某个条件触发方法进行跳转,而不是让用户手动去点击导航链接。

该方式使得路由跳转更灵活,且不会增加新的DOM元素。

5.路由传参

url查询参数

1.路由跳转时在url后用?拼接的参数。

2.在路由组件中使用useSearchParams()接收。

参数传递
jsx 复制代码
import { useNavigate } from "react-router";

export function Father() {
  	let navigate = useNavigate();
    const handleUrl = ()=>{
        navigate("/dashboard?name=zhansan&age=19&isOk=true");
    }

    return (
        <>
        	<button onClick={handleUrl}>url传参</button>
        </>
    );
}

实际上传递时是不带类型的,和前后端query传参一样,都是双方事先约定好了类型,接收方再根据要求自行转换。

封装方法

React及其社区都遵循极简规范,我们可以根据实际开发需求来封装方法。

例如使用navigate()传递url查询参数时,其参数实际上就是个query字符串,我们可以用对象进行转换。

ts 复制代码
//传递一个对象,然后返回query字符串
export function getNavigateURLString(url:string,params:object){
    for(let key of params){
        url += `${key}=${params[key]}` 
    }
    return url
}

然后使用即可

jsx 复制代码
import { useNavigate } from "react-router";
import {getNavigateURLString} from '@/utils'

export function Father() {
  let navigate = useNavigate();
  const handleUrl = ()=>{
      let params = {
          name:"zhansan",
          age:"19",
          isOk:true
      }
      navigate(getNavigateURLString("/dashboard",params));
  }
    
  return (
    <>
      <button onClick={handleUrl}>url传参</button>
    </>
  );
}

推荐结合实际需求封装一个hook

参数接收

在被导航到的路由组件,我们可通过 useSearchParams 来获取它们,该函数会返回一个URLSearchParams的实例。

通过URLSearchParams实例.get(键)来获取url查询参数上对应的值。

jsx 复制代码
function Son() {
  let [searchParams] = useSearchParams();
  return (
    <div>
      <p>
        {/* searchParams.get("age") 得到19 */}
        You searched for <i>{searchParams.get("age")}</i>
      </p>
    </div>
  );
}
适用场景

适用于参数较多,且会在url中显示的情况。

path参数

也被称为路由参数。

1.要在路由规则处设置动态参数占位符

2.跳转时会解析路由,按照/,进行划分,将对应位置替换后得到最终的跳转链接

3.在路由组件中使用useParams 钩子中获取占位符参数。

本质上是动态路由

路由规则设置
jsx 复制代码
<Route path="/" element={<App />}>
    <Route path="dashboard/:val" element={<Home />}></Route>
</Route>
参数传递
jsx 复制代码
import { useNavigate } from "react-router";

export function Father() {
  	let navigate = useNavigate();
    const handlePath = ()=>{
        navigate(getNavigateURLString("/dashboard/true",params));
    }
  return (
    <>
      <button onClick={handlePath}>path传参</button>
    </>
  );
}
参数接收
jsx 复制代码
import { useParams } from "react-router";

export default function Home() {
  const params = useParams();
  return <div>home-{params.val}</div>;
}
原理讲解
跳转机制

1.我们调用navigate("/dashboard/true"),导航跳转到/dashboard/true路由,该路由存在,且匹配到/dashboard/:xx

2.我们在/dashboard/:xx对应的路由组件中使用useParams就可以获得对应的值。

注意事项

如果我们使用了错误的导航地址,例如:

1.调用navigate("/dashboard/true/abc"),导航被跳转到/dashboard/true/abc,不会去匹配/dashboard/:xx

小结

本质上是先改变路由,跳转到对应的路由组件,然后该路由组件再使用方法获取。

匹配机制
jsx 复制代码
<Route path="/" element={<App />}>
    <Route path="dashboard/:id" element={<About />}></Route>
    <Route path="dashboard/:val" element={<Home />}></Route>
</Route>

对于路由跳转而言,dashboard/:iddashboard/:val实际上是一样的,都是dashboard/:xx.

所以按照从上到下的匹配规则,我们只能匹配到<About/>.

小结

path参数传参的类型不够灵活,适用于参数较少的情况。

适合于文档导航或者商品导航,这种通过改变一个值就实现页面切换的需求。

state参数

1.使用state对象传递参数

2.在路由组件中使用useLocation()获取。

参数传递
jsx 复制代码
import { useNavigate } from "react-router";

export function Father() {
    let navigate = useNavigate();
    const handleState = () => {
        navgiate("/news", {
            state: {
                name: "zhansan",
                age: 19,
                isOk: true,
            },
        });
    };

    return (
        <>
            <button onClick={handleState}>state传参</button>
        </>
    );
}
参数接收
js 复制代码
import { useLocation } from "react-router";

export default function News() {
  const { state } = useLocation();
  console.log(state);

  return <div>news-{state.name}</div>;
}
小结

1.state传参是使用navgiate()的第二个参数,是一个配置对象,给该对象上的state属性赋值。

js 复制代码
navgiate(
    "/news", 
    //一个配置对象,state传参是该对象上的属性
    {
        state: {
            name: "zhansan",
            age: 19,
            isOk: true,
        },
    }
);

2.参数接收时,使用useLocation(),他获取的值如下:

js 复制代码
{
    "pathname": "/news",
    "search": "",
    "hash": "",
    "state": {
        "name": "zhansan",
        "age": 19,
        "isOk": true
    },
    "key": "clc8brc7"
}

state传递的参数可以从上面解构。

js 复制代码
const { state } = useLocation();
console.log(state);

小结

注意: 以上三种方式在页面刷新时都不会丢失。

6.路由重定向

1.Navigate声明式重定向

使用Navigate组件来执行声明性重定向

案例1

在组件中使用,每当用户导航到About页面时,Navigate组件将声明性地执行重定向到/news

jsx 复制代码
import {
  Routes,
  Route,
  Link,
  Navigate,
} from 'react-router';


export const About = () => {
  const shouldRedirect = true;

  return (
    <>
      <h2>About</h2>
      {shouldRedirect && <Navigate replace to="/news" />}
    </>
  );
};

案例2

直接在路由规则中使用,根据shouldRedirect,决定是否采用重定向组件

jsx 复制代码
<Route
    path="about"
    element={
        shouldRedirect ? (
            <Navigate replace to="/news" />
        ) : (
            <About />
        )
    }
    />

2.useNavigate编程式重定向

本质上是使用useNavigate的跳转功能。

在About组件中使用useEffect,在初始渲染时跳转到其他页面,这样就实现重定向的效果。

jsx 复制代码
import {useEffect} from 'react'
import {
  Routes,
  Route,
  Link,
  useNavigate,
} from 'react-router';


const About = () => {
  const shouldRedirect = true;

  const navigate = useNavigate();

  useEffect(() => {
    if (shouldRedirect) {
      navigate('/home');
    }
  });

  return (
    <>
      <h2>About</h2>
    </>
  );
};

About组件初始渲染时,调用navigate()方法跳转到其他页面。

7.路由匹配

顺序匹配

在路由匹配时根据从上到下的原则按照顺序匹配,例如/home/first.

现有路由规则

jsx 复制代码
<Route path="/" element={<App />}>
    <Route path="home" element={<Home />}>
    	<Route path="first" element={<HomeFirst />}></Route>
    </Route>
    <Route path="about" element={<About />}>
    	<Route path="first" element={<AboutFirst />}></Route>
    </Route>
</Route>

在这个案例中,/home/first在匹配时,先匹配/,然后找home子规则,找到后继续匹配first

注意事项:除了第一个入口规则以/开头,后续所有子规则前都没有/

先入为主

匹配到第一个满足条件的就返回,不会继续往下匹配,后续的会被忽略。

即相同的路由规则,只有先出现的会被匹配到。

现有路由规则

jsx 复制代码
<Route path="/" element={<App />}>
    <Route path="home" element={<Home />}>
    	<Route path="first" element={<HomeFirst />}></Route>
    	<Route path="first" element={<HomeSecond />}></Route>
    </Route>
</Route>

在这个案例中,用/home/first进行匹配。

jsx 复制代码
//只会匹配到这条规则
<Route path="first" element={<HomeFirst />}></Route>
//这条规则将永远不会被匹配到。
<Route path="first" element={<HomeSecond />}></Route>
回溯匹配

路由匹配实际上是个回溯的过程,例如

jsx 复制代码
<Route path="/" element={<App />}>
    <Route path="home">
        <Route path="first" element={<About />}></Route>
    </Route>
    <Route path="home">
        <Route path="second" element={<News />}></Route>
    </Route>
</Route>

1./home/second开始匹配,他先匹配到/,进入匹配。

2.继续匹配到home,匹配这一组

jsx 复制代码
<Route path="home">
    <Route path="first" element={<About />}></Route>
</Route>

3.匹配完发现没有满足条件的,回溯到上一节点继续向下匹配.

jsx 复制代码
<Route path="home">
    <Route path="second" element={<News />}></Route>
</Route>

4.最终匹配到<News>这条规则。

通配符

也称为兜底,*可以匹配任何规则。

jsx 复制代码
<Route path="/" element={<App />}>
    <Route path="home">
        <Route path="first" element={<About />}></Route>
        {/* 此处会匹配所有的 /home/xx */}
        <Route path="*" element={<Hello />}></Route>
    </Route>
    <Route path="home">
        <Route path="second" element={<News />}></Route>
    </Route>
    {/* 此处会匹配所有的 /xx */}
    <Route path="*" element={<About />}></Route>
</Route>
1.*现象

现象1:我们将*写在了最前面,但是我们仍然能匹配到/home/first

jsx 复制代码
<Route path="/" element={<App />}>
    <Route path="home">
        <Route path="*" element={<Home />}></Route>
        <Route path="first" element={<About />}></Route>
        <Route path="second">
            <Route path="news" element={<News />}></Route>
        </Route>
    </Route>
</Route>

现象2:我们在后面也写了一组home,但是仍然能匹配到/home/third

jsx 复制代码
<Route path="/" element={<App />}>
    <Route path="home">
        <Route path="*" element={<Home />}></Route>
        <Route path="first" element={<About />}></Route>
        <Route path="second">
            <Route path="news" element={<News />}></Route>
        </Route>
    </Route>
    <Route path="home">
        <Route path="third" element={<Third />}></Route>
    </Route>
</Route>

现象3:我们用/home/ppt,匹配到的是/home/*,而不是/*

jsx 复制代码
<Route path="/" element={<App />}>
    <Route path="*" element={<Hello />}></Route>
    <Route path="home">
        <Route path="*" element={<Home />}></Route>
        <Route path="first" element={<About />}></Route>
        <Route path="second">
            <Route path="news" element={<News />}></Route>
        </Route>
    </Route>
    <Route path="home">
        <Route path="third" element={<Third />}></Route>
    </Route>
</Route>
2.小结:

1.*代表通用符号,即任意值,但他不具有拦截常规路由的功能,只能拦截同样的路由。

jsx 复制代码
<Route path="*" element={<Hello />}></Route>
<Route path="*" element={<Home />}></Route>

只会匹配到第一条。

2.*,本质上是个任意值匹配,用于兜底来替代没有的路由值,例如/home/ppt/abc

相关推荐
喜樂的CC1 小时前
[react]Next.js之自适应布局和高清屏幕适配解决方案
javascript·react.js·postcss
天天扭码1 小时前
零基础 | 入门前端必备技巧——使用 DOM 操作插入 HTML 元素
前端·javascript·dom
咖啡虫2 小时前
css中的3d使用:深入理解 CSS Perspective 与 Transform-Style
前端·css·3d
拉不动的猪2 小时前
设计模式之------策略模式
前端·javascript·面试
旭久2 小时前
react+Tesseract.js实现前端拍照获取/选择文件等文字识别OCR
前端·javascript·react.js
独行soc2 小时前
2025年常见渗透测试面试题-红队面试宝典下(题目+回答)
linux·运维·服务器·前端·面试·职场和发展·csrf
uhakadotcom3 小时前
Google Earth Engine 机器学习入门:基础知识与实用示例详解
前端·javascript·面试
麓殇⊙3 小时前
Vue--组件练习案例
前端·javascript·vue.js
outstanding木槿3 小时前
React中 点击事件写法 的注意(this、箭头函数)
前端·javascript·react.js
会点php的前端小渣渣3 小时前
vue的计算属性computed的原理和监听属性watch的原理(新)
前端·javascript·vue.js