7. 合理划分子视图

介绍

这是一个比较容易让人忽略的问题,而有时候这个问题却比数据库 N+1 问题还要严重。

接下来我们探讨下视图划分时,需要注意的性能问题。

做个实验

首先在 Debugbar 上打开模板加载视图,并记录响应时间,加载 9 个视图的情况下:

视图划分

假如我们对 resources/views/topics/_topic_list.blade.php 做优化,将话题显示的内容存放到独自的子模板中,以便后面可以复用此子模板,修改后如下:

resources/views/topics/_topic_list.blade.php

@if (count($topics))
  <ul class="list-unstyled">
    @foreach ($topics as $topic)

      @include('topics._topic')

      @if ( ! $loop->last)
        <hr>
      @endif

    @endforeach
  </ul>

@else
  <div class="empty-block">暂无数据 ~_~ </div>
@endif

新建视图文件 topics._topic

resources/views/topics/_topic.blade.php

<li class="media">
  <div class="media-left">
    <a href="{{ route('users.show', [$topic->user_id]) }}">
      <img class="media-object img-thumbnail mr-3" style="width: 52px; height: 52px;" src="{{ $topic->user->avatar }}" title="{{ $topic->user->name }}">
    </a>
  </div>

  <div class="media-body">

    <div class="media-heading mt-0 mb-1">
      <a href="{{ $topic->link() }}" title="{{ $topic->title }}">
        {{ $topic->title }}
      </a>
      <a class="float-right" href="{{ route('topics.show', [$topic->id]) }}">
        <span class="badge badge-secondary badge-pill"> {{ $topic->reply_count }} </span>
      </a>
    </div>

    <small class="media-body meta text-secondary">

      <a class="text-secondary" href="{{ route('categories.show', $topic->category_id) }}" title="{{ $topic->category->name }}">
        <i class="far fa-folder"></i>
        {{ $topic->category->name }}
      </a>

      <span> • </span>
      <a class="text-secondary" href="{{ route('users.show', [$topic->user_id]) }}" title="{{ $topic->user->name }}">
        <i class="far fa-user"></i>
        {{ $topic->user->name }}
      </a>
      <span> • </span>
      <i class="far fa-clock"></i>
      <span class="timeago" title="最后活跃于:{{ $topic->updated_at }}">{{ $topic->updated_at->diffForHumans() }}</span>
    </small>

  </div>
</li>

这个时候刷新页面,可以看见一些轻微的变化:

视图划分

以上是输出 20 条数据的情况,为了更好的示范,接下来我们改为 100 条,打开控制器文件 app/Http/Controllers/TopicsController.php ,如下修改:

视图划分

刷新页面,可以看到加载 100 多个模板文件,响应时间也接近 400 毫秒:

视图划分

为了方便维护,我们决定,头像部分的 HTML 会在项目的其他地方用到,也要创建子模板,新建子模板:

resources/views/topics/_user_avatar.blade.php

<img class="media-object img-thumbnail mr-3" style="width: 52px; height: 52px;" src="{{ $topic->user->avatar }}" title="{{ $topic->user->name }}">

修改 topics._topic 文件加载模板:

resources/views/topics/_topic.blade.php

<li class="media">
  <div class="media-left">
    <a href="{{ route('users.show', [$topic->user_id]) }}">
      @include('topics._user_avatar')
    </a>
  </div>

  <div class="media-body">

    <div class="media-heading mt-0 mb-1">
      <a href="{{ $topic->link() }}" title="{{ $topic->title }}">
        {{ $topic->title }}
      </a>
      <a class="float-right" href="{{ route('topics.show', [$topic->id]) }}">
        <span class="badge badge-secondary badge-pill"> {{ $topic->reply_count }} </span>
      </a>
    </div>

    <small class="media-body meta text-secondary">

      <a class="text-secondary" href="{{ route('categories.show', $topic->category_id) }}" title="{{ $topic->category->name }}">
        <i class="far fa-folder"></i>
        {{ $topic->category->name }}
      </a>

      <span> • </span>
      <a class="text-secondary" href="{{ route('users.show', [$topic->user_id]) }}" title="{{ $topic->user->name }}">
        <i class="far fa-user"></i>
        {{ $topic->user->name }}
      </a>
      <span> • </span>
      <i class="far fa-clock"></i>
      <span class="timeago" title="最后活跃于:{{ $topic->updated_at }}">{{ $topic->updated_at->diffForHumans() }}</span>
    </small>

  </div>
</li>

子模板里加载子模板的情况,注意加载视图数量和响应时间,已经是超过 500 毫秒了:

视图划分

重点:子模板嵌套子模板是 比较危险 的操作,有时会写出来类似于数据库 N+1 的性能问题。例如说一个页面罗列 100 篇文章,每篇文章又有各自的 5 个标签,每个标签的加载都是使用子模板形式加载,那么渲染出来就是 100 * 5 + 100 = 600 文件包含,响应时间一下子可能会慢几倍。

总结

可以看到,这种模板嵌套的方式,如果规划时只考虑 复用性 而不考虑 性能的局限性 的话,有时候会付出高昂的性能代价。

至于可复用性与性能的抉择,建议你从项目的商业逻辑上入手,商业项目建议优先选择性能。因为随着功能不断累积,积年累月项目里每个地方都消耗一点点性能,累积在一起整个项目会响应如蜗牛。

不局限于 Laravel,所有 PHP 项目中文件包含,都是非常昂贵的操作,平时编写代码时,要小心谨慎,养成不滥用的好习惯。

最后为了方便后续文章,我们清理下项目代码:

$ git checkout . && git clean -f

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 0
发起讨论 只看当前版本


暂无话题~