In the previous chapter we saw that updateComponent actually calls vm._render to generate a VNode and vm._update to update the DOM.
Before we look at the _render function, let’s take a look at how to use the render function in Vue. The render function takes a createElement method as the first argument to create a VNode.
render: function (createElement) {
return createElement('div', {
attrs: {
id: 'app'
},
}, this.message)
}
Copy the code
. The body begins to vm and _render defined in SRC/core/instance/render. The js file
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render. _parentVnode } = vm.$options
if (_parentVnode) {
// Slot-related logic
}
// Allow the render function to access placeholder vnode data
vm.$vnode = _parentVnode
let vnode
try {
// There is no need to maintain a stack, since all render functions are called separately from each other
// The render function of nested components is called when the parent component patched
currentRenderingInstance = vm
// Core: call render function
vnode = render.call(vm._renderProxy. vm.$createElement)
} catch (e) {
// Error handling...
} finally {
currentRenderingInstance = null
}
// Allow the returned array to contain only one node
if (Array.isArray(vnode) && vnode.length = = = 1) {
vnode = vnode[0]
}
// set parent
vnode.parent = _parentVnode
return vnode
}Copy the code
The core logic is to call the Render function, pass in the vm.$createElement parameter, bind this to vm._renderProxy, and return a VNode.
Let’s look at vm._renderProxy and vm.$createElement
1. vm._renderProxy
Vm. _renderProxy is a VM in production and a Proxy object in development. It is defined in the _init method.
// src/core/instance/init.js
Vue.prototype._init = function (options?: Object) {
// ...
if (process.env.NODE_ENV ! = = 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// ...
}Copy the code
Production environment with the initProxy function. InitProxy specifically defined in SRC/core/instance/proxy. Js
// src/core/instance/proxy.js
initProxy = function initProxy (vm) {
// Check whether the browser supports proxy
if (hasProxy) {
_withStripped to decide which proxy handler to use
const options = vm.$options
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler
vm._renderProxy = new Proxy(vm. handlers)
} else {
vm._renderProxy = vm
}
}Copy the code
- First check whether the browser supports it
Proxy
, if supported, judge_withStripped
attribute_withStripped
Is true:Proxy handler
usinggetHandler
methods_withStripped
False:Proxy handler
usinghasHandler
Methods.
- If not, directly
vm
Assigned to_renderProxy
1.1. getHandler
// src/core/instance/proxy.js
const getHandler = {
get (target. key) {
if (typeof key = = = 'string' && !(key in target)) {
if (key in target.$data) warnReservedPrefix(target. key)
else warnNonPresent(target. key)
}
return target[key]
}
}Copy the code
Main Function When reading properties of a proxy object, a warning is thrown for different situations if the properties are not in the real VM instance
- Case 1:
key
Not invm
In, but invm.$data
To show our custom data$
或_
At first, this case is not to be data brokered, that is, directly throughthis.$xx
Access will getundefined
(About data brokershereA) - Situation two: No
vm
, throws a warning
1.2. hasHandler
// src/core/instance/proxy.js
const hasHandler = {
has (target. key) {
const has = key in target
const isAllowed = allowedGlobals(key) ||
(typeof key = = = 'string' && key.charAt(0) = = = '_' && !(key in target.$data))
if (!has && !isAllowed) {
if (key in target.$data) warnReservedPrefix(target. key)
else warnNonPresent(target. key)
}
return has || !isAllowed
}
}Copy the code
- The first to use
in
Operator to determine whether the property is invm
Exists on the instance. - Attribute is available if it is a special attribute (Number,Array, etc.) or a string that starts with an underscore and is not in $data.
- If the property is in
vm
Does not exist, and the property is not available, throws a warning. - return
has || ! isAllowed
.! isAllowed
Indicates that the property exists as a property if it is not available.
1.3. Why_withStripped
To use hasHandler or getHandler?
This is actually to handle render functions in different cases
Before we do that, let’s make two things clear: when proxy.foo is accessed, proxy get is triggered, with(proxy) {(foo); } triggers the Proxy’s HAS. Look at MDN in detail
For non-single-file components, using EL or Templete to create the component, vue will parse the template to generate Render, as shown in figure 1
vm.$options.render = function () {
with (this) {
// where _c is vm._c, as described below
return _c(/ *... * /)}}Copy the code
Our access to _c triggers the Proxy’s HAS, which is the hasHandler above
For SFC, the vue-loader compiles template to code that does not contain with in strict mode, but sets render._withStripped = true for time to view. Render looks like this after compiling:
var render = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c(...)
}
Copy the code
When we access the property as _vm.xx, the Proxy get, or getHandler, is triggered
2. vm.$createElement
Render function in the createElement method is vm $createElement method method, defined in SRC/core/instance/render. Js
// src/core/instance/render.js
export function initRender (vm: Component) {
// ...
vm._c = (a. b. c. d) = > createElement(vm. a. b. c. d. false)
vm.$createElement = (a. b. c. d) = > createElement(vm. a. b. c. d. true)
// ...
}Copy the code
vm._c
Is an internal function that is compiled by a templaterender
Function USESvm.$createElement
Is provided for user authoringrender
Function used
Both of them actually execute the createElement function, which is covered in a separate section
conclusion
Vm. _render finally returns vNode by executing the createElement method