You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// html
<divid="app"><p>姓名:{{user.name}}</p><p>年龄:{{user.age}}</p><divb-if="show" id="#sub_app"><h1>如果show为真,我们就显示</h1><h1>如果show为假,我们就不显示</h1></div></div>
前言
相信大家都用过vue非常好用的v-if功能,那么它是如何实现的呢?回顾一下之前我们已经实现的动态数据绑定 #87 ,我们动态绑定的是一个普通文本节点和一个数据之间的关系。当数据发生改变时,修改文本节点值。
但是,我们现在要做的是,当数据发生改变时,渲染插入某个节点或者把某个节点从DOM中移除,而且这个节点不是普通的文本节点。
所以,我们不能照搬之前的那一套,需要做一些改动。
问题具象化
考虑下面的例子
这个例子是较为简单的例子,因为b-if里面不包含user.name这样的变量,我称它为不带变量的条件渲染
问题是:如何做到,当show为true时,渲染整个div。当show为false,不渲染整个div。
不带变量的条件渲染
首先,b-if指令对应的div结构内部可能是一个很复杂的DOM结构(比如上面的例子,b-if指令内部就包含两个h1标签),所以,我们更应该把"b-if"对应的DOM结构看成是一个新的vue实例,而非一个普通的Directive。我们将要实现的是:一个vue实例嵌套另一个vue实例,父实例是#app,子实例是#sub_app。
如何做到呢?我们从修改渲染节点函数入手:
代码写到这儿,我们就可以看到_directive数组中就多了一个show的Directive了,如下图所示。
![demo1](https://raw.githubusercontent.com/youngwind/blog/master/image/90/show.png)
然后,接下来是重点。
对于一个受指令控制的DOM节点,如例子中的b-if,它其实至少有两个生命周期:一个是初始化,第一次解析DOM的时候,我们称之为bind;另一个是当数据变化时,DOM节点会更新,我们称之为update。
回想一下,我们之前构造Directive的时候其实就已经隐含这样的思想,如下面代码所示(这是之前就有的代码)
所以,得出的结论是:我们需要为b-if指令也定义这样的bind和update方法,分别完成初始化和更新的动作。
所以就有了下面的代码:
实现效果如下,这个版本的代码在这儿。
Bug
然而,我们可以发现上面的做法存在一个很重大的bug。
考虑如下情况:
实际效果却是如下图所示(直接报错):
问题:对于b-if条件渲染,为什么加入了user.name和user.age之后,程序就报错呢?
这显然是不合理的,因为我们知道:必须做到条件渲染里面也可以渲染变量,我们看看如何解决这个问题。
带变量的条件渲染
为什么上面的程序会出现这样的bug呢?
通过debug代码我们发现了核心原因:因为实例化子实例#sub_app的时候我们压根没给它传data数据,所以子实例本身并没有自己的数据,所以根本拿不到user,更别说是user.name了。
但是按照我们正常的想法,即便这是一个条件渲染,也应该能够访问父实例所有的变量才对啊!
so,这就引出了一个重要的概念:作用域
在我们探索v-if指令之前,一直都只有一个vue实例,它有自己的数据,所以不存在作用域的问题。但是,当一个实例嵌套另外一个实例的时候,子实例的的作用域又是什么呢? 其实这是一个非常宽泛的问题,包括后期我们想实现组件化的时候,这个问题肯定是绕不过去的。
但是,目前组件化作用域这个问题对于我来说太难了。假如我们现在把问题简单化一些,只考虑实现v-if呢?
我们发现一个非常便利的地方:v-if的子实例的作用域完全等价于父实例的作用域。所以,我们通过下面的代码,将父实例的作用域传递到子实例。
解决了作用域的问题,那么在子实例中就可以访问父实例的数据了。(喜大普奔~~)
但是,我们还有一个大问题:**设想以下情景:当修改父实例的数据user.name时,父实例的observer能监听到,然后就会触发父实例的_updateBindingAt,然后就会将一系列watcher放到bathcer队列中去,最后父实例中的DOM元素就得到了更新。但是子实例中的user.name没有跟着更新啊!!**为毛?因为子实例自己的observer为空啊!!
所以我们需要将父实例中的observer对象也一并传过来!!
至此,我们就实现了带变量的条件渲染了。具体的效果如下图所示,这个版本的代码在这里
![demo2](https://raw.githubusercontent.com/youngwind/blog/master/image/90/demo2.gif)
后话
在写这个v-if条件渲染的时候,我参考的vue版本是这个。然而,在这个版本中,其实作者只实现了不带变量的情况,并没有实现带变量的情况。对于带变量的实现方法,是我自己想的,所以显得非常简单粗暴。
做到这儿,我能感觉到,之后作用域问题将会是一个非常核心的问题,需要好好思考思考。
更新
时间:2016/9/22
内容:当我去实现v-repeat列表渲染的时候,发现本篇采用的直接传递$data和observer的方法无法解决列表渲染的作用域问题,所以用原型链的方式重写了这一部分,可以参考下一篇中的具体解释。
The text was updated successfully, but these errors were encountered: