Vue 2.0 解读之 用 render 实现 replace: false

张夫尧 2017-10-20 11:02:17

背景

vue2.0 中去掉了 replace: false 这个参数,原因是 vue2.0 采用了 virtual dom,而其设计的数据结构需要唯一的根节点。

但是,根据实际需求就会发现,我们不可能永远让一个组件在内部控制其对外布局的样式,更多的情况应该是在外控制其对外布局的样式。从这个角度考虑,replace: false 参数对我们是有用的。

所幸,vue2.0 中提供了 render 这个方法,这个方法使得开发者可以通过 js 生成自己需要的 virtual dom。

同时,vue2.0 中还有 Vue.compile 这个 api,用于将模板编译为 virtual dom。

还有一点要注意的是,vue2.0 要求的是唯一的根节点,所以无所谓这个根节点是 dom 表现出来的还是 render 出来的,哪怕是 v-if v-else,只要最终展现出的是唯一的根节点就行。

以上三者,为我们提供了无限的可能。

探索

render 和 compile 这两个东西如何结合?官方文档没有说的很明白。但是通过写一些 demo 的测试和源码阅读,发现使用方式如下:

const res = Vue.compile('<div><span>{{ msg }}</span></div>');
new Vue({
  data: {
    msg: 'hello'
  },
  render: res.render,
  staticRenderFns: res.staticRenderFns
})

其中,Vue.compile 接受一个参数 template(html string),编译完成后返回三个参数:render、staticRenderFns、error

  • render: 返回 render 参数需要的函数
  • staticRenderFns: 性能优化的处理,vue 会把不需要动态改变的 virtual dom 放到这里,以提高使用的性能
  • error: 编译错误

搞懂了以上两个 api 如何使用,就可以将要用到的模板写成 render,很简单的实现 replace: false 的效果啦!

工程化

然而,事情没有这么顺利。对于单一的节点,我们可以这么使用。但是对于一个网站,很多地方都要用到 replace: false,用这种方式写太麻烦了,也不好维护,怎么办?

一番探索后,最终的解决办法是写这样一个 mixin:

// @filename mixins/replace_hacker.js
module.exports = {
    beforeCreate: function() {
        // hack for replace: false
        if(this.$options.replace === false) {
            if(!this.$options.el)return console.log("using replace hacker need to init with el option.");
            var $el = document.querySelector(this.$options.el);
            $el.innerHTML = this.$options.template;
            var av = Vue.compile($el.outerHTML);
            this.$options.render = av.render;
            this.$options.staticRenderFns = av.staticRenderFns;
        }
    }
};

思路就不介绍啦,自己看源码吧。

最终的使用方式如下(demo):

// in common
Vue.mixin(require('replace_hacker.js'));

// in page
new Vue({
  el: "#el",
  replace: false,
  ...
});