view组件

大家都知道,与视图相关的东西有很多,比方说表单的创建,表单的验证,验证码,Gridview,分页,AssetBundle等等。我们就不挨个介绍了,毕竟一些 Widget 都大同小异。

今天我们准备讲一下视图被渲染的主要流程,在这其中,简要概括,可能会包括以下一些小问题

render渲染的过程
主题 theme 的视图文件是在什么时候替换掉普通视图文件的
render指定的视图名都支持哪些方式
视图文件内的 $this 为什么指的是 yii\web\View,其他变量又是如何从控制器传递的
yii2支持模板引擎吗,怎么实现的
layout又是如何实现的
等等,可能有很多你想要问的问题。

以 site/index 操作渲染视图页面为例,假设 site/index 代码如下:

1
return $this->render('index', ['p' => 123]);

首先我们简单的理一下类与类之间的结构关系。

1
2
frontend\controllers\SiteController => yii\web\Controller => yii\base\Controller => yii\base\Component
yii\web\View => yii\base\View => yii\base\Component

yii\base\Component 我们都熟悉,就不再提了。

从 site/index 操作内发起对 render 方法的调用开始,各位追踪一下会发现这个 SiteController 的父类 yii\web\Controller 只有一个 renderAjax 方法,render 方法则存在于 yii\base\Controller。二者的区别,待我们分析完这篇文章,自己再去领悟吧。

有同学在平时开发过程中,可能更钟爱于 IDE 快速找到 render 方法所在的类,但是这里我更建议各位手动找找看,方便吗?不方便,个人喜好,小小建议而已。

我们知道,view组件,指的是 yii\web\View ,我们可以通过 Yii::$app->getView() 或者 Yii::$app->view 获取。

下面各位跟我一起找到 yii\base\Controller::render 方法,看一下在这个过程中,程序上是如何找到 frontend/views/site/index.php 并做处理的。

1
2
3
4
5
public function render($view, $params = [])
{
$content = $this->getView()->render($view, $params, $this);
return $this->renderContent($content);
}

上面是 yii\base\Controller::render 方法的代码,第一个参数就是我们在 site/index 方法内指定的视图名 index ,第二个参数是要传递给视图文件的参数 [‘p’ => 123]。

注意这里的一个写法,是调用 yii\base\Controller::getView 方法获取 view 组件实例,而不是直接用 Yii::$app->getView 方法获取。

这是因为在 yii\base\Controller 类的一个周期内,可能要频繁的获取 view 组件实例,我们写代码的原则肯定是能简洁则简洁了,yii\base\Controller 通过 _view 属性保存 view 组件实例那自然是更好一些。

通过 $this->getView()->render($view, $params, $this) 的调用,我们直接跳到 yii\web\View::render 方法了,实际上是 yii\base\View::render 方法。注意这里的第三个参数,是把 frontend\controller\SiteController 的实例传递给了 yii\web\View,更通俗的说,是把控制器实例传递给了视图。

闭上眼思考一下,view 组件后面要实现哪些东西?

没错,第一步肯定是根据视图名找到具体的视图文件,不然怎么渲染呢?

找到 yii\base\View::render 方法,代码如下

1
2
3
4
5
public function render($view, $params = [], $context = null)
{
$viewFile = $this->findViewFile($view, $context);
return $this->renderFile($viewFile, $params, $context);
}

首先第一步跟我们预期的一样,调用 yii\base\View::findViewFile 方法寻找指定的视图文件。

并不是说我们把路径一写,直接就在 frontend/views/site/ 目录下找到 index.php 文件了,这其中有很多种写法,来看具体实现。

yii\base\View::findViewFile 代码不贴了,我说,各位自己看。

从该方法的实现来看,视图名支持多种写法,比如 @app/views/main,//layouts/main,/site/index 等等。预留这么多方案,其实都是一个目的:找到具体的视图文件。

现在我们都知道实现方式了,你说说是不是想怎么用就怎么用了?

有同学总是疑问,哎这个那个方法都哪来的呀,手册上怎么没找到?其实很多东西,看源码才是最能知其所以然的。

来看一个问题,找到视图文件之后,就可以直接对视图文件渲染了吗?

我们继续看代码,看看能不能找到答案。

找到 yii\base\View::renderFile 方法的前三行代码我们有所发现

1
2
3
if ($this->theme !== null) {
$viewFile = $this->theme->applyTo($viewFile);
}

我们知道,yii2中有主题theme的概念。theme的应用我们在yii2实战系列有过介绍 ,不了解的可以去看看。

这段代码的含义是说,如果我们配置了theme,那么实际渲染的页面未必就是 yii\base\View::findViewFile 方法找到的视图文件了,除非在theme中没有找到。

找到最终的视图文件,这下该真正的渲染了吧?

没错,是时候渲染视图文件了,折腾的够呛。但是渲染之前和渲染之后,这里会分别调用 yii\base\View::beforeRender 和 yii\base\View::afterRender 方法触发 yii\base\View::EVENT_BEFORE_RENDER 和 yii\base\View::EVENT_AFTER_RENDER 两个事件,以便不时之需。

此时,我们碰到了另外一个问题。

早些年博主用那个啥模板引擎,哎容我查查,一时还真没想起来。哦,smarty,早些年用smarty的时候,甚至麻烦,还要单独掌握一套模板方法,各种头疼。但是不乏有喜爱之人。

其实也有很多人在问yii2如何使用smarty,twig等之类的模板引擎,这里我内心是拒绝透漏的,不建议使用,了解一下这里怎么实现的就好。

yii\base\View::renderers 属性,大家可以配置下相关的模板引擎。从 yii\base\View::renderFile 方法中能看到,如果视图文件的后缀是 yii\base\View::renderers 属性所配置的键,这里就会根据这个键找到具体的模板解析类,并调用相关的render方法进行一系列的渲染。

用yii2那么久,可惜还没尝试过,万一说错了,还请见谅,评论区留言,我改。

我们仅仅看一下视图文件是php脚本的情况,这取决于下面这行代码

1
$output = $this->renderPhpFile($viewFile, $params);

还是刚刚 site/index 操作的那个例子,我们知道,我们可以直接在视图文件内使用 $p 变量,它的值就是我们设置的123,这是为什么?

来看看 yii\base\View::renderPhpFile 方法。

这里用到了ob缓存的一些方法,不熟悉ob缓存的可以查些资料学习一下,这方面后面还会接触很多。

在 yii\base\View::renderPhpFile 方法中我们看出,从 site/index 传递的参数,原来是通过 extract 函数处理的,所以在下面通过 require 加载视图页面时,我们才可以直接使用。

另外一点,这也就很自然的说明了,视图文件内的 $this 是 view的实例,而不是controller的实例,还在混淆的你现在明白了吗?

另外,有一点需要单独注意:

yii\base\View::renderFile 方法中,$this->context = $context; 注意这里,是把 controller 的实例,赋值给了 yii\base\View::context 属性,所以我们才可以在视图文件内通过 $this->context 引用controller实例。

你以为到此就结束了吗?事实上,目前为止,yii\base\View::render 函数的返回值,仅仅是 @view/site/index.php 脚本的内容,一串字符串而已。

通常,我们还会利用布局剥离头尾,也就是说为了展示一个完美的页面,后面肯定还会有布局的实现。上面的流程听明白了,下面自然就简单了很多,我们也简单的说一下。

回到调用 yii\base\View::render 函数的地方 ———— yii\base\Controller::render 方法。

该方法在拿到视图主题的“字符串内容”之后,继续调用 yii\base\Controller::renderContent 方法处理。

这个方法就简单了,寻找布局文件,找不到就直接返回内容,找到就把内容渲染一下再做返回。

最终交由 yii\web\Application::handleRequest 方法接管后续流程,巴拉巴拉一堆,先前介绍过这里就此打住了。

文章开头我们说过,让各位自己去悟一下 yii\web\Controller::renderAjax 和 yii\base\Controller::render 方法的区别,不要忘记了哦。