Laravel项目中搭载Turbolinks

TurbolinksRuby on Rails 的前端组件,它让页面的切换更加流畅。在 Turbolinks 的作用下,每次请求对服务端来说是无区别的,但客户端在 Turbolinks 启动之后就变成一个单页应用,JavaScript 和 CSS 都不会重复编译,同时只对 body 和 title 做对应的替换。

Laravel 在很多方面借鉴了 Rails,尽管语言不同,但在对 Turbolinks 做适配时需要处理的逻辑是类似的。

按照我的想法,在 Laravel 中需要有一个 Turbo 类,可以在前置过滤器 App::before(); 和后置过滤器 App::after(); 中做处理。

期望在 app/filters.php 中可以这样子

  1. <?php 
  2.  
  3. App::before(function($request
  4.     Turbo::setUp(); 
  5. }); 
  6.  
  7. App::after(function($request$response
  8.     Turbo::handle($request$response); 
  9. }); 

那么,创建一个 Turbo 类,例如 Acme\Utils\Turbo.php

  1. <?php namespace Acme\Utils; 
  2. class Turbo {} 

同时, 需要利用 Laravel 提供的 Facade 并且绑定到 $app

例如, app/Acme/Facades/Turbo.php

  1. <?php namespace Acme\Facades; 
  2. use Illuminate\Support\Facades\Facade; 
  3. class Turbo extends Facade { 
  4.     protected static function getFacadeAccessor() 
  5.     { 
  6.         return 'turbo'; 
  7.     } 

接着, 在 app/Acme/Providers/TurboServiceProvider.php

  1. <?php namespace Acme\Providers; 
  2. use Illuminate\Support\ServiceProvider; 
  3. class TurboServiceProvider extends ServiceProvider { 
  4.     protected $defer = true; 
  5.     public function register() 
  6.     { 
  7.         $this->app->bindShared('turbo'function() 
  8.         { 
  9.             return new \Acme\Utils\Turbo; 
  10.         }); 
  11.     } 
  12.     public function provides() 
  13.     { 
  14.         return ['turbo']; 
  15.     } 

还需要在 app/config/app.php 中添加对应的 providersaliases

到目前为止, 使用 Turbo::fire(); 时, 就会调用对应的 fire 这个方法

可以使用 php artisan tinker 进行调试

Turbolinks 在前置过滤器主要做一件事, 设置 request_method

  1. <?php 
  2. class Turbo { 
  3.     public function setUp($request = null) 
  4.     { 
  5.         $request = $request ?: Request::instance(); 
  6.         $this->setRequestMethodCookie($request->getMethod()); 
  7.     } 
  8.     private function setRequestMethodCookie($method
  9.     { 
  10.         Cookie::queue('request_method'$method); 
  11.     } 

这样是为了告诉客户端, 也就是浏览器, 当前的请求的方法属于什么类型, Turbolinks 只对 GET 请求作处理而无视其它。也就是说,表单提交并不会被 Turbolinks 处理,除非声明了 GET 类型的提交

后置过滤器部分, 主要是适配 HTTP Redirection

我参考了 https://github.com/rails/turbolinks/tree/master/lib 中的 ruby 代码,为了方便阅读,以下是完整的 Turbo

  1. <?php namespace Acme\Utils; 
  2.  
  3. use Cookie; 
  4. use Request; 
  5. use Session; 
  6. use Illuminate\Http\RedirectResponse; 
  7.  
  8. class Turbo { 
  9.  
  10.     private $request
  11.  
  12.     public function __construct() {} 
  13.  
  14.     public function setUp($request = null) 
  15.     { 
  16.         $request = $request ?: Request::instance(); 
  17.         $this->setRequestMethodCookie($request->getMethod()); 
  18.     } 
  19.  
  20.     public function handle($request$response
  21.     { 
  22.         $this->request = $request
  23.  
  24.         $this->setXhrRedirectedTo($response); 
  25.  
  26.         if ($response instanceof RedirectResponse) 
  27.         { 
  28.             $this->storeForTurbolinks($response->getTargetUrl()); 
  29.             $this->abortXdomainRedirect($response); 
  30.         } 
  31.     } 
  32.  
  33.     public function redirectViaTurbolinksTo($url$status = 200) 
  34.     { 
  35.         return Response::make(join($url, ["Turbolinks.visit('""');"]), $status)->header('Content-Type''application/x-javascript'); 
  36.     } 
  37.  
  38.     private function setRequestMethodCookie($method
  39.     { 
  40.         Cookie::queue('request_method'$method); 
  41.     } 
  42.  
  43.     private function setXhrRedirectedTo($response
  44.     { 
  45.         if (Session::has('_turbolinks_redirect_to')) 
  46.         { 
  47.             $response->header('X-XHR-Redirected-To', Session::pull('_turbolinks_redirect_to')); 
  48.         } 
  49.     } 
  50.  
  51.     private function storeForTurbolinks($url
  52.     { 
  53.         if ($this->referer()) Session::put('_turbolinks_redirect_to'$url); 
  54.     } 
  55.  
  56.     private function abortXdomainRedirect($response
  57.     { 
  58.         $current = $this->request->headers->get('X-XHR-Referer') ?: null; 
  59.         $next = $response->headers->get('Location') ?: null; 
  60.  
  61.         if (! (is_null($currentor is_null($nextor $this->sameOrigin($current$next))) 
  62.         { 
  63.             $response->setStatusCode(403); 
  64.         } 
  65.     } 
  66.  
  67.     private function sameOrigin($current$next) { 
  68.         return $this->getArray($current) == $this->getArray($next); 
  69.     } 
  70.  
  71.     private function getArray($url
  72.     { 
  73.         return array_only(parse_url($url), ['scheme''host''port']); 
  74.     } 
  75.  
  76.     private function referer() 
  77.     { 
  78.         return $this->request->headers->get('X-XHR-Referer') ?: $this->request->headers->get('Referer'); 
  79.     } 

除了 Turbolinks, Rails 中还搭载着 jQuery ujs, ujs 全称是 Unobtrusive JavaScript, 在 Laravel 中使用 ujs 只需要把 rails.js 加进去, 当然不要忘了 jQuery

同时, 需要在页面加入

  1. <meta name="csrf-param" content="authenticity_token"> 
  2. <meta name="csrf-token" content="{{ csrf_token() }}"> 

ujs 的作用是可以更好地对服务端发送 RESTful 请求

例如, 在 Laravel 中, 当用户要注销的时候, 在 view 层可以这样子

  1. HTML::link('logout''注销', ['data-method'=>'delete']) 

那么, 在点击链接的时候, 就不是发出 GET 请求而是 DELETE

app/routes.php 里可以这样子

  1. <?php 
  2. Route::delete('logout''SessionController@destroy'); 

例如, 当用户使用点赞功能时, 需要 POST 请求

  1. HTML::link('post/1/like''赞', ['data-remote'=>'true''data-method'=>'post']) 

那么, 在点击链接的时候, 请求的类型就是 POST, 同时是通过 ajax 发送

这样子的话, 如果响应回来的是 javascript 脚本, 并且 Content-Typeapplication/x-javascript, 客户端就会执行此脚本

通过 Turbo::redirectViaTurbolinksTo('/home') 返回的内容 Turbolinks.visit('/home'), 可以让客户端跳转到 /home

我并没有使用 Backbone.js 或者 AngularJS 的经验, 所以, Turbolinks 对我来说是不错的选择

相关链接

转载请注明:代码家园 » Laravel项目中搭载Turbolinks

评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)