整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Laravel8系列之路由用法详解

Laravel8系列之路由用法详解

有 Laravel 路由都定义在位于 routes 目录下的路由文件中,这些文件通过框架自动加载,相应逻辑位于 app/Providers/RouteServiceProvider 类。

有时候还需要注册一个路由响应多个 HTTP 请求动作 —— 这可以通过 match 方法来实现。或者,可以使用 any 方法注册一个路由来响应所有 HTTP 请求动作:

Route::match(['get', 'post'], 'foo', function () {
    return 'This is a request from get or post';
});
Route::any('bar', function () {
    return 'This is a request from any HTTP verb';
});

CSRF 保护

routes/web.php 路由文件中所有请求方式为 PUTPOSTDELETE 的路由对应的 HTML 表单都必须包含一个 CSRF 令牌字段,否则,请求会被拒绝。

<form method="POST" action="/profile">
    @csrf
    ...
</form>

如果我们不在 VerifyCsrfToken 中间件中排除对它的检查,那么就需要在表单提交中带上 csrf_token 字段

重定向路由

Route::redirect('/here', '/there', 301);

视图路由

Route::view('hello', 'hello', ['name'=> '学院君']);

然后在 resources/views 目录下新建一个视图模板文件 hello.blade.php,并初始化视图模板代码如下:

<h1>
    Hello, {{ $name }}!
</h1>

路由参数

Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
    return $postId . '-' . $commentId;
});

根据上面的示例,路由参数需要通过花括号 {} 进行包裹并且是拼音字母,这些参数在路由被执行时会被传递到路由的闭包。路由参数名称不能包含 - 字符,如果需要的话可以使用 _ 替代,比如如果某个路由参数定义成 {post-id} 则访问路由会报错,应该修改成 {post_id} 才行。路由参数被注入到路由回调/控制器取决于它们的顺序,与回调/控制器名称无关。

有必选参数就有可选参数,这可以通过在参数名后加一个 ? 标记来实现,这种情况下需要给相应的变量指定默认值,当对应的路由参数为空时,使用默认值:

Route::get('user/{name?}', function ($name='John') {
    return $name;
});

正则约束

Route::get('user/{id}/{name}', function ($id, $name) {
    // 同时指定 id 和 name 的数据格式
})->where(['id'=> '[0-9]+', 'name'=> '[a-z]+']);

命名路由

命名路由为生成 URL 或重定向提供了方便,实现起来也很简单,在路由定义之后使用 name 方法链的方式来定义该路由的名称:

Route::get('user/profile', function () {
    // 通过路由名称生成 URL
    return 'my url: ' . route('profile');
})->name('profile');

这样我们就可以通过以下方式定义重定向:

Route::get('redirect', function() {
    // 通过路由名称进行重定向
    return redirect()->route('profile');
});

如果命名路由定义了参数,可以将该参数作为第二个参数传递给 route 函数。给定的路由参数将会自动插入到 URL 中:

Route::get('user/{id}/profile', function ($id) {
    $url=route('profile', ['id'=> 1]);
    return $url;
})->name('profile');

如果通过数组传入额外参数,这些键/值对将会自动添加到生成的 URL 查询字符串:

Route::get('user/{id}/profile', function ($id) {
    //todo
})->name('profile');
$url=route('profile', ['id'=> 1, 'photos'=> 'yes']); //photos是额外参数
// /user/1/profile?photos=yes

如果你想要判断当前请求是否被路由到给定命名路由,可以使用 Route 实例上的 named 方法,例如,你可以从路由中间件中检查当前路由名称:

public function handle($request, Closure $next)
{
    if ($request->route()->named('profile')) {
        //
    }
    return $next($request);
}

路由分组

中间件

要给某个路由分组中定义的所有路由分配中间件,可以在定义分组之前使用 middleware 方法。中间件将会按照数组中定义的顺序依次执行:

Route::middleware(['first', 'second'])->group(function () {
    Route::get('/', function () {
        // Uses first & second Middleware
    });

    Route::get('user/profile', function () {
        // Uses first & second Middleware
    });
});

路由前缀

prefix 方法可以用来为分组中每个路由添加一个给定 URI 前缀,例如,你可以为分组中所有路由 URI 添加 admin 前缀 :

Route::prefix('admin')->group(function () {
    Route::get('users', function () {
        // Matches The "/admin/users" URL
    });
});

这样我们就可以通过 http://blog.test/admin/users 访问路由了。

兜底路由

使用 Route::fallback 方法可以定义一个当所有其他路由都未能匹配请求 URL 时所执行的路由。通常,未处理请求会通过 Laravel 的异常处理器自动渲染一个「404」页面,不过,如果你在 routes/web.php 文件中定义了 fallback 路由的话,所有 web 中间件组中的路由都会应用此路由作为兜底,当然,如果需要的话,你还可以添加额外的中间件到此路由:

Route::fallback(function () {
    //
});

注:兜底路由应该总是放到应用注册的所有路由的最后。

访问频率限制

频率限制器通过 RateLimiter 门面的 for 方法定义,该方法接收频率限制器名称和一个返回限制配置(会应用到频率限制器分配到的路由)的闭包作为参数:

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000);
});

如果进入的请求量超过了指定的频率限制上限,则会自动返回一个状态码为 429 的 HTTP 响应,如果你想要自己定义这个频率限制返回的响应,可以使用 response 方法来定义:

RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000)->response(function () {
        return response('Custom response...', 429);
    });
});

由于频率限制器回调接收的是输入的 HTTP 请求实例,因此你可以基于用户请求或者认证用户动态构建相应的频率限制:

RateLimiter::for('uploads', function (Request $request) {
    return $request->user()->vipCustomer()
                ? Limit::none()
                : Limit::perMinute(100);
});

有时候你可能希望通过特定值进一步对频率限制进行细分。例如,你可能想要限定每分钟每个 IP 地址对应的用户只能访问给定路由不超过 100 次,要实现这个功能,你可以在构建频率限制时使用 by 方法:

RateLimiter::for('uploads', function (Request $request) {
    return $request->user()->vipCustomer()
                ? Limit::none()
                : Limit::perMinute(100)->by($request->ip());
});

如果需要的话,你可以为给定频率限制器配置返回频率限制数组,每个频率限制都会基于在数组中的顺序进行执行:

RateLimiter::for('login', function (Request $request) {
    return [
        Limit::perMinute(500),
        Limit::perMinute(3)->by($request->input('email')),
    ];
});

应用频率限制器到路由

访问频率限制器可以通过 throttle 中间件应用到路由或者路由群组。throttle 中间件接收频率限制器的名称作为参数,然后再将其通过中间件的形式应用到路由即可:

Route::middleware(['throttle:uploads'])->group(function () {
    Route::post('/audio', function () {
        //
    });

    Route::post('/video', function () {
        //
    });
});

来源

https://gobea.cn/blog/detail/govQVn54.html

TML 帮助器用于修改 HTML 输出。


HTML 帮助器

通过 MVC,HTML 帮助器类似于传统的 ASP.NET Web Form 控件。

就像 ASP.NET 中的 Web Form 控件,HTML 帮助器用于修改 HTML。但是 HTML 帮助器是更轻量级的。与 Web Form 控件不同,HTML 帮助器没有事件模型和视图状态。

在大多数情况下,HTML 帮助器仅仅是一个返回字符串的方法。

通过 MVC,您可以创建您自己的帮助器,或者直接使用内建的 HTML 帮助器。


标准的 HTML 帮助器

MVC 包含了大多数常用的 HTML 元素类型的标准帮助器,比如 HTML 链接和 HTML 表单元素。


HTML 链接

呈现 HTML 链接的最简单的方法是使用 HTML.ActionLink() 帮助器。

通过 MVC,Html.ActionLink() 不连接到视图。它创建一个连接到控制器操作。

Razor 语法:

@Html.ActionLink("About this Website", "About")

ASP 语法:

<%=Html.ActionLink("About this Website", "About")%>

第一个参数是链接文本,第二个参数是控制器操作的名称。

上面的 Html.ActionLink() 帮助器,输出以下的 HTML:

<a href="/Home/About">About this Website</a>

Html.ActionLink() 帮助器的一些属性:

属性描述
.linkTextURL 文本(标签),定位点元素的内部文本。
.actionName操作(action)的名称。
.routeValues传递给操作(action)的值,是一个包含路由参数的对象。
.controllerName控制器的名称。
.htmlAttributesURL 的属性设置,是一个包含要为该元素设置的 HTML 特性的对象。
.protocolURL 协议,如 "http" 或 "https"。
.hostnameURL 的主机名。
.fragmentURL 片段名称(定位点名称)。

注释:您可以向控制器操作传递值。例如,您可以向数据库 Edit 操作传递数据库记录的 id:

Razor 语法 C#:

@Html.ActionLink("Edit Record", "Edit", new {Id=3})

Razor 语法 VB:

@Html.ActionLink("Edit Record", "Edit", New With{.Id=3})

上面的 Html.ActionLink() 帮助器,输出以下的 HTML:

<a href="/Home/Edit/3">Edit Record</a>


HTML 表单元素

以下 HTML 帮助器可用于呈现(修改和输出)HTML 表单元素:

  • BeginForm()

  • EndForm()

  • TextArea()

  • TextBox()

  • CheckBox()

  • RadioButton()

  • ListBox()

  • DropDownList()

  • Hidden()

  • Password()

ASP.NET 语法 C#:

<%=Html.ValidationSummary("Create was unsuccessful. Please correct the errors and try again.") %>

<% using (Html.BeginForm()){%>

<p>

<label for="FirstName">First Name:</label>

<%=Html.TextBox("FirstName") %>

<%=Html.ValidationMessage("FirstName", "*") %>

</p>

<p>

<label for="LastName">Last Name:</label>

<%=Html.TextBox("LastName") %>

<%=Html.ValidationMessage("LastName", "*") %>

</p>

<p>

<label for="Password">Password:</label>

<%=Html.Password("Password") %>

<%=Html.ValidationMessage("Password", "*") %>

</p>

<p>

<label for="Password">Confirm Password:</label>

<%=Html.Password("ConfirmPassword") %>

<%=Html.ValidationMessage("ConfirmPassword", "*") %>

</p>

<p>

<label for="Profile">Profile:</label>

<%=Html.TextArea("Profile", new {cols=60, rows=10})%>

</p>

<p>

<%=Html.CheckBox("ReceiveNewsletter") %>

<label for="ReceiveNewsletter" style="display:inline">Receive Newsletter?</label>

</p>

<p>

<input type="submit" value="Register" />

</p>

<%}%>


果您是 React 开发人员,您很可能正在使用 Next.js,这是一个用于快速构建和扩展 Web 应用程序的框架。

或者,您可能没有构建企业级应用程序,而是使用 React Router 编写一个小型 React 应用程序。

不管怎样,您可能熟悉路由的概念,但您知道它是如何工作的吗?

今天,我将带头尝试重建 React Router 为您提供的路由器机制的一个相当精简的版本,以及再次重新发明轮子并了解有关路由的更多信息的借口,所以让我们开始路由器和我一起训练和学习一些新东西,因为这是我第一次这样做!

如何?

从现在开始,我所知道的就是 React Router API 的大致样子,以及我们已经可以使用 History API 将路由添加到 JavaScript 应用程序。

当我必须为学校的项目编写没有框架的 JavaScript 应用程序时,我使用这个 Web API 来帮助我们在 JavaScript 应用程序中使用路由,而无需完全重新加载页面来更改 URL。

它确实是一个非常简单的 API:您可以使用 JavaScript 更改 URL,它不会重新加载页面并且......就是这样!

所有关于刷新 UI 的逻辑都将是我们的责任,但为此我们将使用 React,这样我们就不会过度重新发明轮子......

如果你不熟悉 History API,我建议你先停下来,通过阅读官方文档来了解一些关于它的知识,然后再回到这篇文章,以便我们可以一起学习如何实现我们自己的路由器组件因为我需要你!

路由

我想我们可以首先创建一个Route将路径作为 prop 的组件,以及一个将是我们想要渲染的 JSX 的子组件。听起来不错?

import { PropsWithChildren, FunctionComponent } from "react";

export interface RouteProps {
  path: string;
}

export const Route: FunctionComponent<PropsWithChildren<RouteProps>>=({ path, children })=> {
  return null;
};

到目前为止,一切都很好。但我们立刻遇到了一个问题:我怎么知道我在哪条路线?

也许,最简单的方法是向 History API 询问当前路径。

import { PropsWithChildren, FunctionComponent } from "react";

export interface RouteProps {
  path: string;
}

export const Route: FunctionComponent<PropsWithChildren<RouteProps>>=({ path, children })=> {
  if (path===window.location.pathname) {
    return children;
  }

  return null;
};

这太棒了,如果我们要测试它,它完全可以工作!

import { Route } from "./components/Route";

export const App=()=> {
  return (
    <Route path="/home">
      <h1>Welcome to the home page!</h1>
    </Route>
  );
};

重定向

让我们尝试添加一个链接,为我们的应用程序添加重定向功能。

它将依赖该window.history.pushState方法将新页面“推送”到客户端的浏览器历史记录中。就像您点击电子商务网站上的链接一样。

import { FunctionComponent, MouseEventHandler, PropsWithChildren, useCallback } from "react";

export interface LinkProps {
  to: string;
}

export const Link: FunctionComponent<PropsWithChildren<LinkProps>>=({ to, children })=> {
  const onClick: MouseEventHandler<HTMLAnchorElement>=useCallback(event=> {
    event.preventDefault(); 

    window.history.pushState(to, to, null);
  }, [to]);

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

现在我们可以使用它在页面之间导航。

import { FunctionComponent, MouseEventHandler, PropsWithChildren, useCallback } from "react";

export interface LinkProps {
  to: string;
}

export const Link: FunctionComponent<PropsWithChildren<LinkProps>>=({ to, children })=> {
  const onClick: MouseEventHandler<HTMLAnchorElement>=useCallback(event=> {
    event.preventDefault(); 

    window.history.pushState(null, to, to);
  }, [to]);

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

让我们尝试使用新的测试应用程序来测试它。

import { Fragment } from "react";
import { Route } from "./components/Route";
import { Link } from "./components/Link";

export const App=()=> {
  return (
    <Fragment>
      <header>
        <ul>
          <li>
            <Link to="/">
              Home
            </Link>
          </li>
          <li>
            <Link to="/about">
              About
            </Link>
          </li>
        </ul>
      </header>
      <main>
        <Route path="/">
          <h1>Welcome to the home page!</h1>
        </Route>
        <Route path="/about">
          <h1>Welcome to the about page!</h1>
        </Route>
      </main>
    </Fragment>
  );
};

好的,路由似乎在浏览器端工作,但在渲染的 React 应用程序上不起作用。为什么?

因为当我们渲染Route组件时,我们没有添加任何反应性数据,也没有添加反应性道具来反应,所以我们只在页面加载后渲染它,就是这样!

如果您尝试通过浏览器的搜索栏手动引导自己,您将看到渲染工作正常。这意味着我们必须使当前路由成为动态数据才能使路由正常工作。

语境

由于我们需要从Link组件传递已单击的路由到Route组件,因此我们可以使用上下文来简化这些组件之间的通信。

我们开工吧!我们将从创建一个上下文开始。

import { createContext } from "react";

export const RouterContext=createContext({
  path: "",
  setPath: (path: string)=> {}
});

然后,我们将创建我们的提供者。

import { FunctionComponent, PropsWithChildren, useMemo, useState } from "react";
import { RouterContext } from "../contexts/RouterContext";

export const RouterProvider: FunctionComponent<PropsWithChildren>=({ children })=> {
  const [path, setPath]=useState(window.location.pathname);

  const value=useMemo(()=> {
    return {
      path,
      setPath
    };
  }, [path, setPath]);

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

现在我们可以将此提供程序添加到我们的应用程序中。

import { Route } from "./components/Route";
import { Link } from "./components/Link";
import { RouterProvider } from "./providers/RouterProvider";

export const App=()=> {
  return (
    <RouterProvider>
      <header>
        <ul>
          <li>
            <Link to="/">
              Home
            </Link>
          </li>
          <li>
            <Link to="/about">
              About
            </Link>
          </li>
        </ul>
      </header>
      <main>
        <Route path="/">
          <h1>Welcome to the home page!</h1>
        </Route>
        <Route path="/about">
          <h1>Welcome to the about page!</h1>
        </Route>
      </main>
    </RouterProvider>
  );
};

我们可以开始在组件中添加新的上下文Link。

import { FunctionComponent, MouseEventHandler, PropsWithChildren, useCallback, useContext } from "react";
import { RouterContext } from "../contexts/RouterContext";

export interface LinkProps {
  to: string;
}

export const Link: FunctionComponent<PropsWithChildren<LinkProps>>=({ to, children })=> {
  const { setPath }=useContext(RouterContext);

  const onClick: MouseEventHandler<HTMLAnchorElement>=useCallback(event=> {
    event.preventDefault(); 

    window.history.pushState(null, to, to);
    setPath(to);
  }, [to, setPath]);

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

也在我们的Route组件中。

import { PropsWithChildren, FunctionComponent, useContext } from "react";
import { RouterContext } from "../contexts/RouterContext";

export interface RouteProps {
  path: string;
}

export const Route: FunctionComponent<PropsWithChildren<RouteProps>>=({ path, children })=> {
  const { path: currentPath }=useContext(RouterContext);

  if (path===currentPath) {
    return children;
  }

  return null;
};

巨大的成功!现在它运行良好,就像 React Router 一样。

诀窍是添加一个上下文,以便我们可以通知我们的组件在组件更改变量时Route对变量的任何更改做出反应。pathLink

我们将 保留window.history.pushState在组件中Link是因为我们还需要在浏览器中反映路径变化,而这个 API 就是为此目的而设计的。

Fallback

嗯,这个很棘手,因为我们无法查看源代码是什么样子,我们需要弄清楚这一点才能学习和成长。

另一个上下文呢?这样,我们可以创建一个Fallback组件,该组件能够从父上下文中知道没有可用的匹配路由,并且应该渲染该组件。

因此,让我们创建一个新的上下文,这次仅用于我们的路线。

import { FunctionComponent, PropsWithChildren, useContext, useMemo, useState } from "react";
import { RoutesContext } from "../contexts/RoutesContext";
import { RouterContext } from "../contexts/RouterContext";

export const Routes: FunctionComponent<PropsWithChildren>=({ children })=> {
  const { path }=useContext(RouterContext);
  const [routes, setRoutes]=useState<Array<string>>([]);

  const shouldFallback=useMemo(()=> {
    return !routes.some(route=> {
      return route===path;
    });
  }, [routes, path]);

  const value=useMemo(()=> {
    return {
      routes,
      setRoutes,
      shouldFallback
    };
  }, [routes, setRoutes, shouldFallback]);

  return (
    <RoutesContext.Provider value={value}>
      {children}
    </RoutesContext.Provider>
  );
};

我们不要忘记更新我们的Route组件,以便它为其父Routes组件注册一个新的路由。

import { PropsWithChildren, FunctionComponent, useContext, useEffect } from "react";
import { RouterContext } from "../contexts/RouterContext";
import { RoutesContext } from "../contexts/RoutesContext";

export interface RouteProps {
  path: string;
}

export const Route: FunctionComponent<PropsWithChildren<RouteProps>>=({ path, children })=> {
  const { path: currentPath }=useContext(RouterContext);
  const { setRoutes }=useContext(RoutesContext);

  useEffect(()=> {
    setRoutes((routes: Array<string>)=> {
      return [
        ...routes,
        path
      ]
    });
  }, [path, setRoutes]);

  if (path===currentPath) {
    return children;
  }

  return null;
};

现在,我们可以编写我们的Fallback组件了。

import { FunctionComponent, PropsWithChildren, useContext } from "react";
import { RoutesContext } from "../contexts/RoutesContext";

export const Fallback: FunctionComponent<PropsWithChildren>=({ children })=> {
  const { shouldFallback }=useContext(RoutesContext);

  if (shouldFallback) {
    return children;
  }

  return null;
};

让我们测试一下。

import { Route } from "./components/Route";
import { Link } from "./components/Link";
import { RouterProvider } from "./providers/RouterProvider";
import { Fallback } from "./components/Fallback";
import { Routes } from "./components/Routes";

export const App=()=> {
  return (
    <RouterProvider>
      <header>
        <ul>
          <li>
            <Link to="/">
              Home
            </Link>
          </li>
          <li>
            <Link to="/about">
              About
            </Link>
          </li>
          <li>
            <Link to="/undefined">
              Undefined
            </Link>
          </li>
        </ul>
      </header>
      <main>
      <Routes>
        <Route path="/">
          <h1>Welcome to the home page!</h1>
        </Route>
        <Route path="/about">
          <h1>Welcome to the about page!</h1>
        </Route>
        <Fallback>
          <h1>Page not found</h1>
        </Fallback>
      </Routes>
      </main>
    </RouterProvider>
  );
};

惊人的!只要我们位于正确的匹配页面上,它就会正常工作并显示我们的页面,并且在没有页面与我们组件中的页面匹配的情况下显示后备页面Route。

结束了,各位!

我们在那里做得非常好。我们有一个基本但功能齐全的路由应用程序,您可能已经可以使用我们共同制作的组件了,这些组件是:

  • 组件RouterProvider,用于在组件之间共享当前路径
  • 一个Routes组件,用于帮助Fallback组件知道何时渲染
  • 一个Fallback组件
  • 一个Link组件,用于更新当前路由
  • 一个Route组件,显示当前匹配的路径

这甚至没有触及 React Router 库的所有组件,本文甚至不是这个精彩而强大的库的直接替代品!

如果您想深入挖掘,您可以再次查看 React Router API,并尝试通过添加更多组件(例如嵌套路由、编程路由、搜索参数等)来增强此示例...

我希望您喜欢阅读您所阅读的内容,并且它激励您重新发明轮子,以便更多地了解您正在使用的工具。

如果您想与我们分享精彩的内容,请发表评论,我们下一篇文章见!