laravel

几个月前,作为前端开发者,尝试用 koa2(Node.js) 开发项目,不用过多在意语法语,数据库操作基于 LeanCloud,功能简单,开发难度较低。但 koa2 自身功能集成不高,常用功能需安装各种中间件和社区包得到支持。

PHP 作为 Web开发的热门语言,可以全球说过半数网站都使用了它。 Laravel 作为优雅的 PHP 框架,一直有所耳闻。近些日子阅读了社区翻译的文档,逐渐上手了如何使用这款优质的开源后端框架。


起步

开发环境

在这之前需要一套开发的基础环境,至少包括:

  • PHP 及它的包管理工具 Composer
  • HTTP 服务器 Nginx
  • 关系型数据库 MySQL

这里我用的是 PhpStudy Windows 2016 版本 ,当然你也可以自行搭建。


创建新项目

传统的方式是从 Git 仓库中克隆到本地。像前端 Vue CLI 一样,优雅的 Laravel 也有自家的手脚架,可快速地创建新项目.

使用 composer 命令安装一个全局安装器:

composer global require laravel/installer

当前路径下创建新项目:

laravel new laravel-project

项目配置

HTTP 服务器

重装后恢复 phpstudy 提供的开发环境

环境变量文件 .env

独立的环境配置文件,在这里可以修改数据库驱动,与线上环境区分开。
连接本地的 MySQL 服务:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
Windows 下更新 MySQL 至 5.7.22

Artisan 命令行

学习编程的前期,我们还在手动创建源文件。 Laravel 命令行工具带来了一套全新的操作,创建模型、控制器、数据库种子文件,并将有关的功能关联起来,省去了手动新建重命名文件的麻烦。
而在后面会用到更多类似的命令:

创建 TestController 的控制器:

php artisan make:controller TestController 
// will be created TestController.pnp in /app/Http/Controllers

创建 Test 的模型,和对应的数据库迁移文件:

php artisan make:model Test -m

入门

一些必不可少的功能被抽象成路由、模板、表单验证等 API 提供给开发者实用,其互相独立又不可分割,组成了现今 Web 后端框架的基础功能。

路由

Nginx 将浏览器请求的 URL 转发到 Laravel 里,这样就能针对不同的路径和查询参数,处理结果给客户端。

默认的路由声明位于 /routes 文件夹中,routes/web.php 中约定了由模板渲染的路由结果:

Route::view('contact', 'contact.create');

view() 函数渲染 resources/views/contact/create.blade.php 模板文件得到 HTML 格式的文档,最后返回给浏览器。

当系统功能过多,路由文件就变得冗余,不妨将资源约定成 RESTFul 风格,Laravel 将自动将其解析成若干个路由:

Route::resource('customers', 'CustomersController');
HTTP MethodURIAcitonRoute name
GET/customersindexcustomers.index
GET/customers/createcreatecustomers.create
POST/customersstorecustomers.store
GET/customers/{id}showcustomers.show
GET/customers/{id}/editeditcustomers.edit
PUT/PATCH/customers/{id}updatecustomers.update
DELETE/customers/{id}destroycustomers.destroy

控制器

我们可以在路由文件中处理所有的请求逻辑,随着时间的推移,路由会变得十分拥挤。

Route::get('test', function (){
    return 'hello world!';
});

一般控制器文件约定被存放在 app/Http/Controllers目录中。

同样,使用 Artisan 助手创建控制器文件:

php artisan make:controller CustomersController 

将逻辑代码迁移到控制器中:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class CustomersController extends Controller
{
    public function index() {
        $customers = [
            'John Doe',
            'Jane Doe',
            'Bob The Builder',
        ];

        return view('internals.customers',[
            'customers' => $customers
        ]);
    }
}
Route::get('customers', 'CustomersController@index')
    ->name('customers.index');

当 GET 请求与路由 URI 匹配,CustomersController 控制器中的 index 方法将会被执行,将数据渲染到对应的视图。

Customers 控制器继承框架提供的 Controller 基类,这样可以使用 Laravel 提供的控制器功能。

表单验证

有段时间,我在编写前端代码,使用了若干个流程控制语句 if 来判断用户输入的内容是否为空,是否符合正则或其他格式,最终达到表单校验的需求。

在控制器中我们可以渲染对应的视图,也可获取从客户端发来的 HTTP 请求,一般称为表单数据。

如上一小节所说,继承了 Controller 控制器基类的 BooksController,它提供了一系列方法去验证请求:

php artisan make:controller BooksController -m -r

同样,在路由文件声明到达控制器的路径和方法:

Route::post('books', 'BooksController@store');

除了控制器,还需要使用 模型 与数据库建立关系,并创建数据库迁移文件:

php artisan make:model Book -m

Book 模型配置 $guarded 属性类似于黑名单机制,经过 模型 过滤后再写入数据库

//app/Book.php
protected $guarded = [];
较难理解的是 MVC 模型在经典模式下的存在价值。
网路请求经由 Nginx 转发到 PHP-FPM ,被框架路由过滤,执行控制器内对应的函数,查询数据表,将结果同步回调到页面渲染函数,返回页面给客户端。
laravel

数据库迁移文件替代了我们手动在数据库中添加字段,改变结构。创建关于 Books 表的字段描述,书名和作者:

//database/migrations/2019_09_26_152922_create_books_table.php
class CreateBooksTable extends Migration
{
    public function up()
    {
        Schema::create('customers', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');
            $table->string('author');
            $table->timestamps();
        });
    }
}

别忘了创建完后刷新数据表:

php artisan migrate

同时,为了不让复杂的验证逻辑堆积在 Controller 中,创建一个 StoreCustomer 表单请求类来处理:

php artisan make:request StoreBook

验证规则

对于表单校检验证规则,Laravel 提供了非常详尽的验证规则,配置 rules 函数返回它们:

  • title author 为必填项,
  • type 项为 ebookfile 项为必填
class StoreBook extends FormRequest
{

    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'title'  => 'required|min:3',
            'author' => 'required',
            'type'   => '',
            'file'   => 'required_if:type,ebook'
        ];
    }
    
    public function messages()
    {
        return [
            'title.required'   => 'A title is required',
            'author.required'  => 'A author is required',
        ];
    }
}

校验失败时,可知自定义 messages 函数返回字段提示信息,默认情况下也有提示。

将表单校检分离至 Requests/StoreBook.php,无需在控制器中写任何验证逻辑。

当数据验证成功后,即可操作 Book 模型 内的 Eloquent ORM API ,完成数据记录。

使用 Book 模型的 create 方法,将数据新增到数据库中:

class BooksController extends Controller
{
    public function store(StoreBook $request)
    {
        $book = Book::create($request ->validated());

        return response()->json(['code' => '200', 'msg'=> 'ok']);
    }
    
}

使用 Postman 发出 POST 请求,请求内容为 { "title": "CSAPP": "author": "Randal E.Bryant"} 。返回结果:

{
    "code": "200",
    "msg": "ok"
}

显然请求通过了 rules() 校验,将内容经过直接 protected $guarded 过滤,再使用 ORM API 写入 books 数据表,返回 ok

错误处理

那提交的内容不符合规则呢?把校验器错误收集起来,经 JSON 格式化后返回给客户端。

Requests/StoreBook.php 创建 failedValidation 函数,优化验证失败的返回结果:

//app/Http/Requests/StoreBook.php

protected function failedValidation(Validator $validator)
{

    $data = [
        'code' => 422,
        'msg'  => $validator->errors()->first(),
    ];
    
    $response = new Response(json_encode($data), 422);
    throw (new ValidationException($validator, $response))
        ->errorBag($this->errorBag)
        ->redirectTo($this->getRedirectUrl());
}

当 title 字段少于 3个英文字符:

{
    "code": 422,
    "msg": "The title must be at least 3 characters."
}

当 type 字段为 ebook 时,没有提交 file 字段:

{
    "code": 422,
    "msg": "The file field is required when type is ebook."
}

Laravel Validator 实例支持非常多验证规则,从常规格式到业务逻辑,复杂规则可以使用 Illuminate\Validation\Rule 类来构造规则,好处在于不用手动地编写验证函数或规则字符串。如图片的尺寸比:

use Illuminate\Validation\Rule;

Validator::make($data, [
    //'avatar' => 'dimensions:ratio=3/2' 
    'avatar' => [
        'required',
        Rule::dimensions()->maxWidth(1000)->maxHeight(500)->ratio(3 / 2),
    ],
]);

在不熟悉框架的前提下,虽有强大的校检规则,部分开发者仍会通过自行编写的校检函数来解决问题。随着时间推移,加大了后期代码维护难度。所以在大型项目中,代码审核显得尤为重要,保证了项目的良性发展。


HTTP 单元测试

刚入门的开发者,使用各种软件来制造 HTTP 请求,并观察编写的函数是否正常输出。

就像上面的表单校验往往需要调试数次,面对堆积如山的需求时,调试难以保证产出率。如果可以使用编程方式,按照需求编写若干完整请求和结果验证器,便事半功倍。

Laravel 结合了 PHPUnit 并提供一些便利的辅助函数,能很好地编写测试用例,比如上一节 Books 接口。

别忘了修改测试功能的配置文件 phpunit.xml。使用 laravel_test 数据库,这样能避免与开发数据库内容冲突:

<php>
    <server name="TELESCOPE_ENABLED" value="false"/>
    <server name="DB_CONNECTION" value="mysql"/>
    <server name="DB_DATABASE" value="laravel_test"/>
</php>

同样,创建测试文件 php artisan make:test BooksTest,并填充一些基本的表单数据:

class BooksTest extends TestCase
{
    private function data()
    {
        return [
            'title' => 'Test book title',
            'author' => 'Test author',
        ];
    }
}

TestCase 类提供了测试 JSON 的函数,用来发出 HTTP 请求,配合 assert 断言方法,判断请求结果是否符合预期,
比如提交 title author 字段,正常情况下 Laravel 会返回 200 响应头和对应的 JSON:

public function test_a_book_can_be_added_through_post()
{
    $response = $this->json('POST', '/books', $this->data());
    $response
        ->assertStatus(200)
        ->assertJson([
            'code' => '200'
        ]);
}

进入项目根目录,敲入:

.\vendor\bin\phpunit --filter test_a_book_can_be_added_through_post

.                                                                   1 / 1 (100%)

Time: 316 ms, Memory: 16.00 MB

OK (1 test, 2 assertions)

进入 laravel_test 数据库,查询 books 表所有内容:

mysql> SELECT * FROM books;
+----+-----------------+-------------+---------------------+---------------------+
| id | title           | author      | created_at          | updated_at          |
+----+-----------------+-------------+---------------------+---------------------+
|  1 | Test book title | Test author | 2019-09-29 08:52:26 | 2019-09-29 08:52:26 |
+----+-----------------+-------------+---------------------+---------------------+
1 rows in set (0.00 sec)

假如提交 title 字段缺失,我们可以建立这样的测试案例:

public function test_a_title_is_required()
{
    $response = $this->json('POST', '/books', array_merge($this->data(), ['title' => '']) );

    $response
        ->assertStatus(422)
        ->assertJson([
            'code' => '422'
        ]);
}

断言返回的 JSON 含有 code: 422 内容,切响应头的状态为 422

还有当 type 项为 ebookfile 项为必填,可以这么编写:

public function test_the_file_field_is_required_when_type_is_ebook()
{
    $response = $this->json('POST', '/books', array_merge($this->data(),[
        'title'  => '123',
        'author' => '123',
        'type'   => 'ebook'
    ]));
    $response
        ->assertStatus(422)
        ->assertJson([
            'code' => '422'
        ]);
}

HTTP 单元测试的颗粒度根据项目内测试压力进行调整,如果前端、测试人员充足,保证表单校验和业务流程。


模板引擎(视图)

它支撑了页面渲染,在修改 blade 文件后编译并缓存起来。如多数前端框架的视图层一样,具有模板继承、扩展、逻辑判断等基础功能。

在前端框架突飞猛进地时代,Laravel 的模板引擎 Blade ,它提供更多后端逻辑功能,使得后端无需转向前后分离的协作模式,也能轻易地实现提交表单、权限控制、布局组件化。

下面展示了典型 Blade 内容:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <div id="app">
        @include('nav')

        <main class="py-4">
            <div class="container">
                @yield('content')
            </div>
        </main>
    </div>
</body>
</html>

就像 Vue 一样,在双重 {{ }} 花括号内可以使用表达式,通过编译后最终得到 PHP 文件,缓存在 storage/framework/views 目录。

需要注意常用指令的使用方法:

@include 引入其他模板文件,常用来插入其他一些 HTML 片段

@section 常以 @show @stop @overwrite @append 指令来结束, 不建议使用已废弃的 @endsection

@section@yield 都是在母版中定义可替代的区块,在子模板中使用 @section 来扩展区块时,表现并不相同;

@yield

@yield 并不能扩展原来有母版中的内容,使用 @parent 关键字也不能让原有的内容与扩展内容并存

@yield('title', '默认标题')

出现在母版中,第二个参数为默认值。

子模板使用:

@extends('master')

@section('title')
    @parent
    新的标题
@stop

结果:

新的标题 

@section

@section 用来继承并扩展原有区块的内容。子模板中使用 @parent 使得母版中原有的内容被保留,然后融合新的内容。

母版:

@section('content')
    默认的内容
@show

子模板:

@extends('master')

@section('content')
    @parent
    扩展的内容
@stop

结果:

默认的内容 扩展的内容 

@show & @stop

  • 建议在定义 @section 时用 @show 结尾,替换或扩展时用 @stop 结尾
  • 解析子模板时遇到 @show 结尾的区块会立即显示内容,然后套用模板继承机制,继续渲染内容

母版:

<div id="zoneA">
    @section('zoneA')
        This is zone A master
    @show
</div>

<div id="zoneB">
    @section('zoneB')
        This is zone B master
    @stop
</div>

<div id="zoneC">
    @section('zoneC')
        This is zone C master
    @show
</div>

子模板:

@extends('master')


@section('zoneA')
    This is zone A slave
@stop

@section('zoneB')
    This is zone B slave
@stop

@section('zoneC')
    This is zone C slave
@show

结果:

This is zone C slave

<div id="zoneA">
        This is zone A slave
</div>

<div id="zoneB">
</div>

<div id="zoneC">
        This is zone C slave
</div>

这种错误的写法导致 zone B 区块丢失,并且使扩展的 This is zone C slave内容过早出现在页面的首位。

@append

@append 用于多次将内容添加到对应的区块中

@section('content')
    content A
@append

@section('content')
    content B
@append

@section 的结束指令 @override 在 Laravel 5.8 中没有效果

大部分功能 与 ThinkPHP5 模板引擎 使用备忘 里描述的相似

模板引擎使得后端开发不用过多地学习现代前端的专业技能,在页面渲染上也能得心应手。

总结

Laravel 拥有 Web 后端大部分的基础功能,包括:路由、视图(模板)、数据库操作,还有家喻互晓 MVC 的设计模式。更高级的身份授权、代码调试、等功能,都已提供完善的支持。

根据发展需求,将技术重点放在数据库优化、提供微型服务、自动化部署、促进 DevOps 协作等方面,使后端技术得到提升。

当评估完项目需要时候哪款框架,就必要遵循它的使用方法,甚至了解的设计原理。否则选用框架进行开发,有可能是表面高大上,内部结构不堪入目。

互联网诞生不够50年,其中 Web 发展到了 3.0,为更好服务其他行业,经过无数公司的努力,制定了非常多业界通用的设计和模式,从 RFC 标准到通用协议,除了基本的技术手段,比如 身份验证 JWT、支付系统、订单系统等经典设计,都需要去了解。

掌握基本的提问智慧,大部分目前想要实现的东西,别人已经研究过,通过搜索引擎,能较快能找到思路,不必埋头苦造轮子。站在巨人的肩膀上才能看的更远。


参考

起因是入手了最近很火的锐龙平台,其他配件是:

  • 微星 B450M MORTAR 迫击炮,BIOS 7B89v18
  • AMD Ryzen 7 3700X
  • 威刚 XPG DDR4 16G 3200Mhz

猜测导致蓝屏错误的配置:

  • 安装 AMD StoreMI,没有配置加速
  • 标准 SATA AHCI 控制器变为 Virtualized AHCI Controller
  • 更新主板 BIOS,从 7B89v18 至 7B89v19

INACCESSIBLE_BOOT_DEVICE

思路

不大可能是 BIOS 更新引起的错误

BOOT_DEVICE 表明与硬件配置有关,其症状与 Windows Xp 无限卡进度条的情况相似,是 SATA 协议没有切换至 AHCI。

但 B450 芯片组只有 AHCI 和 RAID 模式,表明 AHCI 模式与当前系统驱动不相容。

AMD 的 Virtualized AHCI Controller 在安装后可能修改了硬件底层的配置,使得系统能正常启动。

因为是磁盘驱动问题,安全模式也进不去,在引导模式下用命令行 Dism 来替换 AHCI 驱动。

结论

搜索了一大圈都是让卸载各种硬件驱动解决问题,现在的情况进不了系统,Dism 可以卸载对应的驱动,但没有效果

命令行 Dism 操作略麻烦,花费时间调试,没有第二台电脑让我边查资料边试验,最后选择重装系统。

参考

关于 AMD SATA 驱动安装的补充

How to Fix the Inaccessible Boot Device Error in Windows 10

起因

在搭建 Laravel 项目时执行 php artisan migrate,报出如下错误:

    In Connection.php line 664:
    
      SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was t
      oo long; max key length is 1000 bytes (SQL: alter table `users` add unique
      `users_email_unique`(`email`))
    
    
    In PDOStatement.php line 107:
    
      SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was t
      oo long; max key length is 1000 bytes
    
    
    In PDOStatement.php line 105:
    
      SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was t
      oo long; max key length is 1000 bytes

因为有数据库最大 key 长度限制,造成访问冲突。

Google 了一下原因,在 Laravel 5.4 尝试合并数据表时也出现类似报错:

Laravel 默认使用 utf8mb4 字符,它支持在数据库中存储 "emojis" 。

如果你是在版本低于 5.7.7 的 MySQL release 或者版本低于 10.2.2 的 MariaDB release 上创建索引,那就需要你手动配置迁移生成的默认字符串长度。

即在 AppServiceProvider 中调用 Schema::defaultStringLength 方法来配置它 :

use Illuminate\Support\Facades\Schema;

/**
 * 引导任何应用程序服务。
 *
 * @return void
 */
public function boot()
{
    Schema::defaultStringLength(191);
}

或者,你可以开启数据库的 innodb_large_prefix 选项。 至于如何正确开启,请自行查阅数据库文档。

数据库不支持 utf8mb4 字符集的原因,是它自身版本太低,并开始着手升级由 phpStudy 面板集成的 MySQL 5.5.53 。这是继承开发环境的版本:

  • Laravel Version: ^5.5.0
  • PHP Version: 7.1.15
    mysql> select
        -> version();
    +-----------+
    | version() |
    +-----------+
    | 5.5.53    |
    +-----------+
    1 row in set (0.00 sec)

备份

防止万一,备份是必要的

  1. 关闭 Nginx 及数据库程序
  2. 备份原 MySQL 中所有数据库
  3. 备份 MySQL 主程序
  4. 清空 MySQL 主程序文件夹

安装

  1. 点击 mysql-5.7.22-winx64.zip 下载新版数据库。
  2. 解压至原MySQL 主程序文件夹
E:\phpStudy\MySQL
  1. 发现压缩包中并没有配置文件 my.ini 。没关系,这里有一份:
保存为 ANSI 编码,否则会出现 [ERROR] Found option without preceding group in config file
    [client]
    port=3306
    default-character-set=utf8
    [mysqld] 
    # 设置为自己MYSQL的安装目录 
    basedir="E:/phpStudy/MySQL/"
    # 设置为MYSQL的数据目录 
    datadir="E:/phpStudy/MySQL/data/"
    port=3306
    character_set_server=utf8
    sql_mode=NO_ENGINE_SUBSTITUTION,NO_AUTO_CREATE_USER
    #开启查询缓存
    explicit_defaults_for_timestamp=true
    skip-grant-tables

环境变量

将 MySQL 主程序目录 ;E:\phpStudy\MySQL\bin 添加到 Windows 环境变量中

别忘了重启系统

安装

执行

    mysqld --initialize

执行

    mysqld --install

执行

    net start MySQL 

启动 MySQL 主程序

    C:\Windows\system32>net start MySQL
    MySQL 服务正在启动 .
    MySQL 服务已经启动成功。

使用 root 帐户登陆数据库

    C:\Windows\system32>mysql -uroot -p
    Enter password:
    Welcome to the MySQL monitor.  Commands end with ; or \g.
    Your MySQL connection id is 2
    Server version: 5.7.22 MySQL Community Server (GPL)
    
    Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
    
    Oracle is a registered trademark of Oracle Corporation and/or its
    affiliates. Other names may be trademarks of their respective
    owners.
    
    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
    
    mysql>

查看 MySQL 版本号

    mysql> select version();
    +-----------+
    | version() |
    +-----------+
    | 5.7.22    |
    +-----------+
    1 row in set (0.00 sec)
    
    mysql>

恢复

从备份的 *.sql 文件中恢复所有数据库

最后

执行 php artisan migrate 便生成正常的数据表

    E:\phpStudy\WWW\passport>php artisan migrate
    Migration table created successfully.
    Migrating: 2014_10_12_000000_create_users_table
    Migrated:  2014_10_12_000000_create_users_table
    Migrating: 2014_10_12_100000_create_password_resets_table
    Migrated:  2014_10_12_100000_create_password_resets_table
    ...

参考

https://serverfault.com/questions/8855/how-do-you-add-a-windows-environment-variable-without-rebooting

Node.js

reify
Enable ECMAScript 2015 modules in Node today. No caveats. Full stop.

ES6 的模块语法 import 在 node 环境中仍然不兼容,或者说需要 ejs 文件格式。

入口文件使用 reify 解决:

require("reify")

不嫌弃配置麻烦的话,也可以考虑 babel

中间件

Koa2 需要配合非常多中间件来使用,才能成为一个后端框架。

koa-conditional-get

Conditional Get 又名条件式请求,常见实现有 Last-Modified 和 ETag 两种。

const conditional = require('koa-conditional-get');
const etag = require('koa-etag');
const Koa = require('koa');
const app = new Koa();

// use it upstream from etag so
// that they are present

app.use(conditional());

// add etags

app.use(etag());
koa-bodyparser

解析 HTTP 主体

var Koa = require('koa');
var bodyParser = require('koa-bodyparser');

var app = new Koa();
app.use(bodyParser());

app.use(async ctx => {
  // the parsed body will store in ctx.request.body
  // if nothing was parsed, body will be an empty object {}
  ctx.body = ctx.request.body;
});
koa-static

静态服务器

import staticServe from 'koa-static'

app.use(staticServe(path.join(__dirname, '../assets'), {
    maxAge: 24 * 60 * 60
}))
koa-views

模板渲染中间件

const views = require('koa-views');

app.use(views(path.join(__dirname, 'views')));
koa-mount

通过 URL 挂载,将其他 Koa 实例挂在到一个主实例中

const a = new Koa();

const b = new Koa();

app.use(mount('/hello', a));
app.use(mount('/world', b));
koa-connect

兼容 Express 中间件可以在 Koa 中使用

import connect from 'koa-connect'

app.use(c2k(connectMiddlware))

起因是使用 iView2.x Table 组件时,需要用到 render 函数显示自定义的内容。

从渲染简单的文字,到自定义组件,甚至捕获组件事件。

简单的用法

render: (h, params)=>{
    const title = params.row.title

    if(!params.row.title) return "—"
    return <span>{ title }</span>;
}

复杂的用法

大部分的模板指令都不适用于这种 JSX ,除了 v-show,相关功能需要自己实现

render(h, params){
    const item = params.row;
    
    const enabledHtml  = <a href="javascript:;" onClick={ ()=>vm.publish(item) }>发布</a>;
    const disabledHtml = <a href="javascript:;" onClick={ ()=>vm.recall(item) }>撤回</a>;
    return (
        <adm-link-group>
            { [enabledHtml, disabledHtml][item.status] }
            <router-link to={ {name: `${ROUTER}`, params:{ id }, query: { }} }>
                <a href="javascript:;">修改</a>
            </router-link>
            <a href="javascript:;" class="" onClick={ ()=>vm.destroy(item) }>删除</a>
        </adm-link-group>
    );
},
  • 动态绑定 v-bind 指令,去掉了 : 写法,用一对花括号 { } 取代
  • 在花括号内 { } 使用表达式,判断需要渲染的内容
  • 返回的模板中可以使用 自义定组件如 <adm-link-group></adm-link-group> (小写字符开头)
组件捕获事件
render(h, params){
    const item = params.row
    return (
    <iv-select
        style={{ width: '120px' }}
        v-model={item.comment_status} vOn:on-change={ (val)=> { vm.saveCommentRule({val, item}) } } >
        <iv-option key={1} value={1}> 先审核后显示 </iv-option>
        <iv-option key={2} value={2}> 先显示后审核 </iv-option>
    </iv-select>)
},

根据 Babel Preset JSX - Directives 说明,元素和组件的事件监听器 v-on 缩写 @ 写法有差别,原先在模板中是这样使用的:

<iv-select @on-change="selectChanged" ></iv-select>

在 JSX 中:

<iv-select vOn:on-change={ vm.selectChanged } ></iv-select>

模板渲染 & JSX

多数情况下都在使用 template 方式创建视图,因为有 v-if v-for 等指令减少了工作量。

渲染函数可以让我们使用 JavaScript 编程的方式创建视图层的东西。

createElement

createElement 可以创建虚拟节点 VNode,把这个 VNode 返回给 render 函数即可:

render: (h, params) => {
  return h('div', [
    h('Icon', {
      props: {
        type: 'person'
      }
    }),
    h('strong', params.row.name)
  ]);
}
将 h 作为 createElement 的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的,如果在作用域中 h 失去作用,在应用中会触发报错。

但使用 render 函数创建的可读性就很差了,因此一般使用 JSX 语法进行编程。

而且通过 Vue-cli3 创建的项目就已经支持若干 JSX 语法啦!

参考文档

Table 表格 - iView

渲染函数 & JSX

Babel Preset JSX

babel-plugin-transform-vue-jsx