Guides

路由和导航

编辑此页面

Solid Router 简化了 Solid 应用程序中的路由,帮助开发人员通过使用 JSX 或 props 传递的对象定义路由来管理导航和渲染。


Getting started

1. 安装路由

默认情况下不包含此包。

2. 设置 <Router> 组件

通过渲染 Router 组件来启动您的应用程序。该组件将匹配 URL 以显示所需的页面。

import { render } from "solid-js/web";
import { Router } from "@solidjs/router";
render(() => <Router />, document.getElementById("root"));

3. 提供根级布局

根布局不会在页面更改时更新,是放顶级导航和 Context Providers 的理想位置。

import { render } from "solid-js/web";
import { Router } from "@solidjs/router";
const App = (props) => (
<>
<h1>Site Title</h1>
{props.children}
</>
);
render(() => <Router root={App} />, document.getElementById("root"));

4. 添加路由

每个路由都使用 Route 组件添加到 Router 中。在这里,您指定一个路径和一个在用户导航到该路径后要渲染的组件。

import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
import Home from "./pages/Home";
import Users from "./pages/Users";
const App = (props) => (
<>
<h1>Site Title</h1>
{props.children}
</>
);
render(
() => (
<Router root={App}>
<Route path="/" component={Home} />
<Route path="/users" component={Users} />
</Router>
),
document.getElementById("root")
);

5. 创建 CatchAll 路由(404 页面)

catchall 路由可用于在路由器的任何嵌套级别都找不到的页面。使用 * 将检索路径的其余部分。您还可以选择添加参数名称。

import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
import Home from "./pages/Home";
import Users from "./pages/Users";
import NotFound from "./pages/NotFound";
const App = (props) => (
<>
<h1>Site Title</h1>
{props.children}
</>
);
render(
() => (
<Router root={App}>
<Route path="/" component={Home} />
<Route path="/users" component={Users} />
<Route path="*paramName" component={NotFound} />
</Router>
),
document.getElementById("root")
);

6. 创建路由链接

<A> 组件提供应用程序路由的导航。或者,您可以使用原生锚标记。但是, <A> 组件提供了附加功能,包括 CSS 属性、inactiveClassactiveClass

import { render } from "solid-js/web";
import { Router, Route, A } from "@solidjs/router";
import Home from "./pages/Home";
import Users from "./pages/Users";
import NotFound from "./pages/NotFound";
const App = (props) => (
<>
<nav>
<A href="/">Home</A>
<A href="/users">Users</A>
</nav>
<h1>Site Title</h1>
{props.children}
</>
);
render(
() => (
<Router root={App}>
<Route path="/" component={Home} />
<Route path="/users" component={Users} />
<Route path="*paramName" component={NotFound} />
</Router>
),
document.getElementById("root")
);

延迟加载路由组件

lazy 函数延迟组件的加载,直到导航到该组件。

import { lazy } from "solid-js";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
const Users = lazy(() => import("./pages/Users"));
const Home = lazy(() => import("./pages/Home"));
const App = (props) => (
<>
<h1>Site Title</h1>
{props.children}
</>
);
render(
() => (
<Router root={App}>
<Route path="/users" component={Users} />
<Route path="/" component={Home} />
</Router>
),
document.getElementById("root")
);

动态路由

如果事先不知道路径,则可以将路径的一部分视为灵活参数。

import { lazy } from "solid-js";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
const Users = lazy(() => import("./pages/Users"));
const User = lazy(() => import("./pages/User"));
const Home = lazy(() => import("./pages/Home"));
render(
() => (
<Router>
<Route path="/users" component={Users} />
<Route path="/users/:id" component={User} />
<Route path="/" component={Home} />
</Router>
),
document.getElementById("root")
);

冒号表示 id 可以是任何字符串,只要 URL 符合该模式,<User> 组件就会显示。

然后,您可以使用 useParams 从路由组件中访问该 id。

关于动画/过渡的注意事项:

共享相同路径的路由将被视为相同路由。如果您想强制重新渲染,可以将组件包装在带 keyed<Show> 组件中:

<Show when={params.something} keyed>
<MyComponent>
</Show>

访问参数

如果您需要访问组件内的动态路由参数,则可以使用 useParams。使用 useParams 访问参数后,就可以在您的组件中使用它们:

import { useParams } from "@solidjs/router";
const User = () => {
const params = useParams(); // Retrieve the dynamic route parameters
// Now you can access the id parameter as params.id
return (
<p>
This is the user with the id of <code>{params.id}</code>
</p>
);
};

useParams 对于其他 Solid 原语(例如 createResourcecreateSignal )特别有用,它们可以根据路由参数创建动态行为。

import { createResource } from "solid-js";
import { useParams } from "@solidjs/router";
async function fetchUser(id) {
const response = await fetch(
`https://jsonplaceholder.typicode.com/users/${id}`
);
return response.json();
}
const User = () => {
const params = useParams();
const [data] = createResource(params.id, fetchUser); // Pass the id parameter to createResource
return (
<div>
<Show when={!data.loading} fallback={<p>Loading...</p>}>
<div>
<p>Name: {data().name}</p>
<p>Email: {data().email}</p>
<p>Phone: {data().phone}</p>
</div>
</Show>
</div>
);
};

在这个示例中,每次 id 参数更改时,都会调用 fetchUser 函数来获取新的用户数据。

验证路由

每个路径参数都可以使用 MatchFilter 进行验证。MatchFilter 不是简单的检查参数是否存在,而是允许更复杂的路由描述:

import { lazy } from "solid-js";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
import type { SegmentValidators } from "./types";
const User = lazy(() => import("./pages/User"));
const filters: MatchFilters = {
parent: ["mom", "dad"], // allow enum values
id: /^\d+$/, // only allow numbers
withHtmlExtension: (v: string) => v.length > 5 && v.endsWith(".html"), // only `*.html` extensions wanted
};
render(() => (
<Router>
<Route
path="/users/:parent/:id/:withHtmlExtension"
component={User}
matchFilters={filters}
/>
</Router>
), document.getElementById("root"));

在这个例子中,matchFilters属性提供了一种根据 filters中定义的 parentidwithHtmlExtension校验参数的方式。如果验证失败,路由将不匹配。

在这个例子中:

  • /users/mom/123/contact.html 会匹配,
  • /users/dad/123/about.html 会匹配,
  • /users/aunt/123/contact.html 不会匹配,因为 :parent 不是 'mom' 或 'dad',
  • /users/mom/me/contact.html 不会匹配,因为 :id 不是一个数字,
  • /users/dad/123/contact 不会匹配,因为:withHtmlExtension 缺失了 .html.

可选参数

可以通过在参数名称末尾添加问号将参数指定为可选:

// Matches stories and stories/123 but not stories/123/comments
<Route path="/stories/:id?" component={Stories} />

通配符路由

要匹配给定路径中的任何后代路由,您可以使用通配符标记 (*)。这可用于表示该路径段中的任何值。

// Will match any path beginning with foo (eg. foo/, foo/a/, foo/a/b/c)
<Route path="foo/*" component={Foo} />

要将通配符部分作为参数公开给组件,您可以将其命名为:

<Route path="foo/*any" component={Foo} />

通配符标记必须是路径的最后部分; foo/*any/bar 不会创建任何路由。

多路径

Routes 组件还支持使用数组定义多个路径。这可以避免在两个或多个匹配路径之间切换时重新渲染路线:

// Navigating from "/login" to "/register" will not cause the component to re-render
<Route path={["login", "register"]} component={Login} />

嵌套路由

仅叶 <Route> 节点(最里面的 <Route> 组件)被赋予路由:

<Route path="/users" component={Users}>
<Route path="/:id" component={User} />
</Route>

以下两个路由定义都匹配相同的 URL /users/:id 并渲染相同的组件:

<Route path="/users/:id" component={User} />
<Route path="/users">
<Route path="/:id" component={User} />
</Route>

如果你想让父路由有自己的路由,你必须单独指定它:

<Route path="/users" component={Users} />
<Route path="/users/:id" component={User} />
// or
<Route path="/users">
<Route path="/" component={Users} />
<Route path="/:id" component={User} />
</Route>

您还可以通过使用传递给路由组件的 props.children 结合嵌套一起使用。

function PageWrapper(props) {
return (
<div>
<h1> We love our users! </h1>
{props.children}
<A href="/">Back Home</A>
</div>
);
}
<Route path="/users" component={PageWrapper}>
<Route path="/" component={Users} />
<Route path="/:id" component={User} />
</Route>;

路由的配置仍然相同,但是现在它们的组件将出现在声明 props.children 的父组件内。

路由也可以无限嵌套。以下示例将仅渲染路由 /layer1/layer2 ,该路由将嵌套在 3 个 div 中。

<Route
path="/"
component={(props) => <div>Outermost layer starts here {props.children}</div>}
>
<Route
path="layer1"
component={(props) => <div>Second layer {props.children}</div>}
>
<Route path="layer2" component={() => <div>Innermost layer</div>} />
</Route>
</Route>

预加载函数

通过预加载函数,数据获取与路由加载并行开始,因此可以尽快使用。预加载函数通过在加载路线后调用或在链接悬停时立即调用来实现。

作为唯一的参数,预加载函数传递一个用于访问路由信息的对象:

import { lazy } from "solid-js";
import { Route } from "@solidjs/router";
const User = lazy(() => import("./pages/users/[id].js"));
// preload function
function preloadUser({ params, location }) {
// do preload
}

然后将预加载函数传递到 <Route> 定义中:

<Route path="/users/:id" component={User} preload={loadUser} />

您可以导出与专用[route].data.js[route].data.ts 文件中的路由相对应的预加载函数和数据包装器。此模式提供了一种无需加载任何其他内容即可导入数据函数的方法。

src/pages/users/[id].data.js
import { query } from "@solidjs/router";
export const getUser = query(async (id) => {
return (await fetch(`https://swapi.tech/api/people/${id}/`)).json();
}, "getUser");
export function preloadUser({ params, location, intent }) {
return getUser(params.id);
}

preloadUser 接收一个包含 paramslocationintent 的对象。

请注意,虽然最佳实践是将这些文件编写为 [id].data.js ,但您仍然可以编写 route.data.js

当在除“预加载”之外的任何时间调用时,预加载函数的值都会传递给页面组件。这意味着您可以初始化页面,或使用数据 API

index.jsx
import { lazy } from "solid-js";
import { render } from "solid-js/web";
import { Router, Route } from "@solidjs/router";
import { preloadUser } from "./pages/users/[id].data.js";
const Home = lazy(() => import("./pages/Home"));
const User = lazy(() => import("./pages/users/[id]"));
render(
() => (
<Router>
<Route path="/" component={Home} />
<Route path="/users/:id" component={User} preload={preloadUser} />
</Router>
),
document.getElementById("root")
);

[id].jsx 包含渲染的组件。当您将函数包装在 createAsync 中时,一旦预期的 Promise resolve,它将产生一个 signal

[id].jsx
import { createAsync } from "@solidjs/router";
import { getUser } from "./[id].data";
export default function Users(props) {
console.log("Users.props", props);
const user = createAsync(() => getUser(props.params.id));
return (
<>
<h1>User</h1>
<div>
<pre>{JSON.stringify(user(), null, 2)}</pre>
</div>
</>
);
}

要了解有关路由 Solid 应用程序的更多信息,请访问 Solid Router 文档

报告此页面问题