【Vue源码】第五节数据驱动之_render方法

vue的_render定义在src/core/instance/render.js文件中:

// src/core/instance/render.js
Vue.prototype._render = function(): VNode {
  const vm: Component = this;
  // 在 Vue 2.0 版本中,所有 Vue 的组件的渲染最终都需要 render 方法
  const { render, _parentVnode } = vm.$options;

  if (_parentVnode) {
    vm.$scopedSlots = normalizeScopedSlots(
      _parentVnode.data.scopedSlots,
      vm.$slots,
      vm.$scopedSlots
    );
  }

  vm.$vnode = _parentVnode;
  let vnode;
  try {
    currentRenderingInstance = vm;
    // 重点在_renderProxy和$createElement函数
    // ======================================
    vnode = render.call(vm._renderProxy, vm.$createElement);
    // ======================================
  } catch (e) {
    handleError(e, vm, `render`);
    if (process.env.NODE_ENV !== "production" && vm.$options.renderError) {
      try {
        vnode = vm.$options.renderError.call(
          vm._renderProxy,
          vm.$createElement,
          e
        );
      } catch (e) {
        handleError(e, vm, `renderError`);
        vnode = vm._vnode;
      }
    } else {
      vnode = vm._vnode;
    }
  } finally {
    currentRenderingInstance = null;
  }

  if (Array.isArray(vnode) && vnode.length === 1) {
    vnode = vnode[0];
  }

  if (!(vnode instanceof VNode)) {
    if (process.env.NODE_ENV !== "production" && Array.isArray(vnode)) {
      warn(
        "Multiple root nodes returned from render function. Render function " +
          "should return a single root node.",
        vm
      );
    }
    vnode = createEmptyVNode();
  }
  // set parent
  vnode.parent = _parentVnode;
  return vnode;
};
_renderProxy
// src/core/instance/init.js

Vue.prototype._init = function(options?: Object) {
  //...
  // 在生产环境,`_renderProxy`就是其本身,开发环境就会调用`initProxy`
  if (process.env.NODE_ENV !== "production") {
    initProxy(vm);
  } else {
    vm._renderProxy = vm;
  }

  // ...
};

关于initProxy函数:

// src/core/instance/proxy.js
let initProxy;
// 判断一下浏览器是否支持Proxy
const hasProxy = typeof Proxy !== "undefined" && isNative(Proxy);

initProxy = function initProxy(vm) {
  // 如果浏览器支持proxy
  if (hasProxy) {
    // determine which proxy handler to use
    const options = vm.$options;
    // handlers,是负责定义代理行为的对象。
    // options.render._withStripped 的取值一般情况下都是 false ,所以 handlers 的取值为 hasHandler
    const handlers =
      options.render && options.render._withStripped ? getHandler : hasHandler;
      vm._renderProxy = new Proxy(vm, handlers);
  } else {
     vm._renderProxy = vm;
  }
};

// getHandler方法主要是针对读取代理对象的某个属性时进行的操作,
// 该方法可以在开发者错误的调用vm属性时,提供提示作用
const getHandler = {
    get (target, key) {
      if (typeof key === 'string' && !(key in target)) {
        warnNonPresent(target, key)
      }
      return target[key]
    }
}

// hasHandler方法的应用场景在于查看vm实例是否拥有某个属性
const hasHandler = {
  // has函数就是求出属性查询的结果然后存入 has变量中
  has(target, key) {
    // 首先使用in操作符判断该属性是否在vm实例上存在
    const has = key in target;
    // allowedGlobals看属性名称是否可用
	// 就是检查key是不是这些全局的属性、函数其中的任意一个。
    // 所以isAllowed为true的条件就是key是js全局关键字或者非vm.$data下的以_开头的字符串。
    const isAllowed =
      // allowedGlobals最终存储的是一个代表特殊属性名称的映射表
      allowedGlobals(key) ||
      (typeof key === "string" &&
        key.charAt(0) === "_" &&
        !(key in target.$data));
    // 如果!has(访问的key在vm不存在)和!isAllowed(不在特殊属性名称映射表中,或没有以_符号开头)同时成立的话
    if (!has && !isAllowed) {
      // 在data中声明了, 但是在Vue中,以$或_开头的属性不会被代理,因为有可能与内置属性产生冲突
      if (key in target.$data) warnReservedPrefix(target, key);
      // 在data中没有声明,但是却被渲染了
      else warnNonPresent(target, key);
    }
    return has || !isAllowed;
  }
};

const allowedGlobals = makeMap(
  "Infinity,undefined,NaN,isFinite,isNaN," +
    "parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent," +
    "Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl," +
    "require" // for Webpack/Browserify
);

const warnReservedPrefix = (target, key) => {
  warn(
    `Property "${key}" must be accessed with "$data.${key}" because ` +
      'properties starting with "$" or "_" are not proxied in the Vue instance to ' +
      "prevent conflicts with Vue internals. " +
      "See: https://vuejs.org/v2/api/#data",
    target
  );
};

const warnNonPresent = (target, key) => {
  warn(
    `Property or method "${key}" is not defined on the instance but ` +
    'referenced during render. Make sure that this property is reactive, ' +
    'either in the data option, or for class-based components, by ' +
    'initializing the property. ' +
    'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',
    target
  )
}
$createElement

vm.$createElement的定义是在initRender函数中:

function initRender(vm: Component) {
  // ...
  // 这两行是分别给实例vm加上_c和$createElement方法。
  // 这两个方法都调用了createElement方法,只是最后一个参数值不同
  // vm._c是内部函数,它是被模板编译成的 render 函数使用
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false);
  // vm.$createElement是提供给用户编写的 render 函数使用
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true);

  // ...
}

之前我们使用了template编写代码:

<div id="app">{{message}}</div>
<script>
  var app = new Vue({
    el: "#app",
    data() {
      return {
        message: "森林小哥哥"
      };
    }
  });
</script>

如果我们不使用template,而是编写render

data(){
    return {
        message: 'Hello Wolrd'
    }
},
render: function(createElement){
    return createElement('div', {
        attrs: {
            id: app
        }
    }, this.message)
}

根据源代码解读我们可以改写成

render: function(){
    return this.$createElement('div', {
        attrs: {
            id: app
        }
    }, this.message)
}

使用字符串模板的话,在相关代码执行完前,会先在页面显示 {{ message }} ,然后再展示 Hello World;而我们手动编写 render 函数的话,内部就不用执行把字符串模板转换成 render 函数这个操作,并且是空白页面之后立即就显示 Hello World ,用户体验会更好。

总结
_render函数中 主要调用了vnode = render.call(vm._renderProxy, vm.$createElement);来获得vnode并返回。其中_renderProxy就是确定vm._renderProxy的值,如果浏览器Proxy属性存在,则把包装后的vm属性赋值给_renderProxy属性值,否则把vm是实例本身赋值给_renderProxy属性。$createElement函数的作用就是生成一个 VNode节点(虚拟dom)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值