Maison >interface Web >js tutoriel >Comment résoudre le problème de compilation de modèles dans Vue.js
Cet article présente principalement le problème de la compilation de modèles dans Vue.js, comme référence pour tout le monde. J'espère qu'après avoir lu cet article, vous aurez une solution claire au problème de compilation de modèles dans Vue.js.
Écrit devant
Parce que je suis très intéressé par Vue.js et que la pile technologique sur laquelle je travaille habituellement est également Vue.js, j'ai passé du temps à étudier et à apprendre Vue.js. au cours des derniers mois, le code source, puis je l'ai résumé et affiché.
Adresse originale de l'article : https://github.com/answershuto/learnVue.
Pendant le processus d'apprentissage, des commentaires chinois ont été ajoutés à Vue https://github.com/answershuto/learnVue/tree/master/vue-src. J'espère que cela pourra aider d'autres amis qui souhaitent apprendre les sources de Vue. code Aide.
Il peut y avoir des divergences de compréhension. N'hésitez pas à soulever des problèmes et à les signaler pour apprendre et progresser ensemble.
$mount
Regardez d'abord le code de montage
/*把原本不带编译的$mount方法保存下来,在最后会调用。*/ const mount = Vue.prototype.$mount /*挂载组件,带模板编译*/ Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function /*处理模板templete,编译成render函数,render不存在的时候才会编译template,否则优先使用render*/ if (!options.render) { let template = options.template /*template存在的时候取template,不存在的时候取el的outerHTML*/ if (template) { /*当template是字符串的时候*/ if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } } else if (template.nodeType) { /*当template为DOM节点的时候*/ template = template.innerHTML } else { /*报错*/ if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { /*获取element的outerHTML*/ template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } /*将template编译成render函数,这里会有render以及staticRenderFns两个返回,这是vue的编译时优化,static静态不需要在VNode更新时进行patch,优化性能*/ const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, delimiters: options.delimiters }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`${this._name} compile`, 'compile', 'compile end') } } } /*Github:https://github.com/answershuto*/ /*调用const mount = Vue.prototype.$mount保存下来的不带编译的mount*/ return mount.call(this, el, hydrating) }
Grâce au code de montage, nous pouvons voir que pendant le processus de montage, si la fonction de rendu n'existe pas ( render Si la fonction existe, render sera utilisé en premier) et le modèle sera compiléToFunctions pour obtenir render et staticRenderFns. Par exemple, si un modèle est ajouté à un composant manuscrit, il sera compilé au moment de l'exécution. La fonction de rendu renverra le nœud VNode après son exécution pour le rendu de la page et l'application des correctifs pendant la mise à jour. Voyons ensuite comment le modèle est compilé.
Quelques bases
Tout d'abord, le modèle sera compilé dans un arbre de syntaxe AST, alors qu'est-ce que l'AST ?
En informatique, un arbre syntaxique abstrait (ou abrégé en AST), ou arbre syntaxique (arbre syntaxique), est une représentation arborescente de la structure syntaxique abstraite du code source, en particulier un langage de programmation. code.
AST obtiendra la fonction de rendu via generate. La valeur de retour de render est VNode est le nœud DOM virtuel de Vue. La définition spécifique est la suivante :
export default class VNode { tag: string | void; data: VNodeData | void; children: ?Array<VNode>; text: string | void; elm: Node | void; ns: string | void; context: Component | void; // rendered in this component's scope functionalContext: Component | void; // only for functional component root nodes key: string | number | void; componentOptions: VNodeComponentOptions | void; componentInstance: Component | void; // component instance parent: VNode | void; // component placeholder node raw: boolean; // contains raw HTML? (server only) isStatic: boolean; // hoisted static node isRootInsert: boolean; // necessary for enter transition check isComment: boolean; // empty comment placeholder? isCloned: boolean; // is a cloned node? isOnce: boolean; // is a v-once node? /*Github:https://github.com/answershuto*/ constructor ( tag?: string, data?: VNodeData, children?: ?Array<VNode>, text?: string, elm?: Node, context?: Component, componentOptions?: VNodeComponentOptions ) { /*当前节点的标签名*/ this.tag = tag /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/ this.data = data /*当前节点的子节点,是一个数组*/ this.children = children /*当前节点的文本*/ this.text = text /*当前虚拟节点对应的真实dom节点*/ this.elm = elm /*当前节点的名字空间*/ this.ns = undefined /*编译作用域*/ this.context = context /*函数化组件作用域*/ this.functionalContext = undefined /*节点的key属性,被当作节点的标志,用以优化*/ this.key = data && data.key /*组件的option选项*/ this.componentOptions = componentOptions /*当前节点对应的组件的实例*/ this.componentInstance = undefined /*当前节点的父节点*/ this.parent = undefined /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/ this.raw = false /*静态节点标志*/ this.isStatic = false /*是否作为跟节点插入*/ this.isRootInsert = true /*是否为注释节点*/ this.isComment = false /*是否为克隆节点*/ this.isCloned = false /*是否有v-once指令*/ this.isOnce = false } // DEPRECATED: alias for componentInstance for backwards compat. /* istanbul ignore next */ get child (): Component | void { return this.componentInstance } }
Pour certains. Pour plus de détails sur VNode, veuillez vous référer au nœud VNode.
createCompiler
createCompiler est utilisé pour créer un compilateur, et la valeur de retour est compile et compileToFunctions. compile est un compilateur qui convertit le modèle entrant en arborescence AST, fonction de rendu et fonction staticRenderFns correspondantes. compileToFunctions est un compilateur mis en cache, et les fonctions staticRenderFns et render seront converties en objets Function.
Étant donné que différentes plates-formes ont des options différentes, createCompiler transmettra une baseOptions en fonction de la plate-forme et sera fusionné avec les options transmises par la compilation elle-même pour obtenir les finalOptions finales.
compileToFunctions
Tout d'abord, publions le code de compileToFunctions.
/*带缓存的编译器,同时staticRenderFns以及render函数会被转换成Funtion对象*/ function compileToFunctions ( template: string, options?: CompilerOptions, vm?: Component ): CompiledFunctionResult { options = options || {} /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production') { // detect possible CSP restriction try { new Function('return 1') } catch (e) { if (e.toString().match(/unsafe-eval|CSP/)) { warn( 'It seems you are using the standalone build of Vue.js in an ' + 'environment with Content Security Policy that prohibits unsafe-eval. ' + 'The template compiler cannot work in this environment. Consider ' + 'relaxing the policy to allow unsafe-eval or pre-compiling your ' + 'templates into render functions.' ) } } } /*Github:https://github.com/answershuto*/ // check cache /*有缓存的时候直接取出缓存中的结果即可*/ const key = options.delimiters ? String(options.delimiters) + template : template if (functionCompileCache[key]) { return functionCompileCache[key] } // compile /*编译*/ const compiled = compile(template, options) // check compilation errors/tips if (process.env.NODE_ENV !== 'production') { if (compiled.errors && compiled.errors.length) { warn( `Error compiling template:\n\n${template}\n\n` + compiled.errors.map(e => `- ${e}`).join('\n') + '\n', vm ) } if (compiled.tips && compiled.tips.length) { compiled.tips.forEach(msg => tip(msg, vm)) } } // turn code into functions const res = {} const fnGenErrors = [] /*将render转换成Funtion对象*/ res.render = makeFunction(compiled.render, fnGenErrors) /*将staticRenderFns全部转化成Funtion对象 */ const l = compiled.staticRenderFns.length res.staticRenderFns = new Array(l) for (let i = 0; i < l; i++) { res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors) } // check function generation errors. // this should only happen if there is a bug in the compiler itself. // mostly for codegen development use /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production') { if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) { warn( `Failed to generate render function:\n\n` + fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n$[code]\n`).join('\n'), vm ) } } /*存放在缓存中,以免每次都重新编译*/ return (functionCompileCache[key] = res) }
Nous pouvons constater que dans la fermeture, il y aura un objet functionCompileCache comme cache.
/*作为缓存,防止每次都重新编译*/ const functionCompileCache: { [key: string]: CompiledFunctionResult; } = Object.create(null)
Après avoir entré compileToFunctions, il vérifiera d'abord s'il y a des résultats compilés dans le cache, et s'il y a des résultats, ils seront lus directement à partir du cache. Cela évite de devoir compiler le même modèle à plusieurs reprises à chaque fois.
// check cache /*有缓存的时候直接取出缓存中的结果即可*/ const key = options.delimiters ? String(options.delimiters) + template : template if (functionCompileCache[key]) { return functionCompileCache[key] }
Les résultats de la compilation seront mis en cache à la fin de compileToFunctions
/*存放在缓存中,以免每次都重新编译*/ return (functionCompileCache[key] = res)
compile
/*编译,将模板template编译成AST树、render函数以及staticRenderFns函数*/ function compile ( template: string, options?: CompilerOptions ): CompiledResult { const finalOptions = Object.create(baseOptions) const errors = [] const tips = [] finalOptions.warn = (msg, tip) => { (tip ? tips : errors).push(msg) } /*做下面这些merge的目的因为不同平台可以提供自己本身平台的一个baseOptions,内部封装了平台自己的实现,然后把共同的部分抽离开来放在这层compiler中,所以在这里需要merge一下*/ if (options) { // merge custom modules /*合并modules*/ if (options.modules) { finalOptions.modules = (baseOptions.modules || []).concat(options.modules) } // merge custom directives if (options.directives) { /*合并directives*/ finalOptions.directives = extend( Object.create(baseOptions.directives), options.directives ) } // copy other options for (const key in options) { /*合并其余的options,modules与directives已经在上面做了特殊处理了*/ if (key !== 'modules' && key !== 'directives') { finalOptions[key] = options[key] } } } /*基础模板编译,得到编译结果*/ const compiled = baseCompile(template, finalOptions) if (process.env.NODE_ENV !== 'production') { errors.push.apply(errors, detectErrors(compiled.ast)) } compiled.errors = errors compiled.tips = tips return compiled }
compile fait principalement deux choses, l'une est l'option de fusion ( comme mentionné précédemment, la propre option de la plateforme est fusionnée avec l'option entrante), et l'autre est baseCompile, qui compile le modèle.
Jetons un coup d'œil à baseCompile
baseCompile
function baseCompile ( template: string, options: CompilerOptions ): CompiledResult { /*parse解析得到ast树*/ const ast = parse(template.trim(), options) /* 将AST树进行优化 优化的目标:生成模板AST树,检测不需要进行DOM改变的静态子树。 一旦检测到这些静态树,我们就能做以下这些事情: 1.把它们变成常数,这样我们就再也不需要每次重新渲染时创建新的节点了。 2.在patch的过程中直接跳过。 */ optimize(ast, options) /*根据ast树生成所需的code(内部包含render与staticRenderFns)*/ const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } }
baseCompile analysera d'abord le modèle pour obtenir un arbre de syntaxe AST, puis effectuera une optimisation via optimiser, et enfin générer Get render et staticRenderFns.
analyser
Le code source de l'analyse peut être trouvé sur https://github.com/answershuto/learnVue/blob/master/vue-src/compiler/parser/index.js# L53.
parse utilisera des méthodes régulières pour analyser les instructions, classes, styles et autres données du modèle afin de former un arbre de syntaxe AST.
optimiser
La fonction principale d'optimiser est de marquer les nœuds statiques. Il s'agit d'une optimisation de Vue pendant le processus de compilation. Plus tard, lorsque l'interface de mise à jour sera mise à jour, il y aura un processus de correctif. , diff L'algorithme ignorera directement les nœuds statiques, réduisant ainsi le processus de comparaison et optimisant les performances des correctifs.
generate
generate est le processus de conversion de l'arborescence syntaxique AST en une chaîne de fonction de rendu, et le résultat est une chaîne de rendu et une chaîne staticRenderFns.
À ce stade, notre modèle de modèle a été converti en l'arbre de syntaxe AST, la chaîne de fonction de rendu et la chaîne staticRenderFns dont nous avons besoin.
Par exemple
Regardez le résultat de la compilation de ce code
<p class="main" :class="bindClass"> <p>{{text}}</p> <p>hello world</p> <p v-for="(item, index) in arr"> <p>{{item.name}}</p> <p>{{item.value}}</p> <p>{{index}}</p> <p>---</p> </p> <p v-if="text"> {{text}} </p> <p v-else></p> </p>
Après conversion, l'arbre AST est obtenu, comme indiqué ci-dessous :
Nous pouvons voir que le p le plus externe est le nœud racine de cet arbre AST. Il y a beaucoup de données sur le nœud qui représentent la forme du nœud. Par exemple, statique indique. s'il s'agit d'un nœud statique et staticClass indique un attribut de classe statique (et non bind:class). children représente les nœuds enfants du nœud. Vous pouvez voir que children est un tableau d'une longueur de 4, qui contient les quatre p nœuds enfants sous le nœud. Les nœuds des enfants ont une structure similaire à celle du nœud parent et forment une arborescence de syntaxe AST couche par couche.
Regardons la fonction de rendu obtenue à partir de AST
with(this){ return _c( 'p', { /*static class*/ staticClass:"main", /*bind class*/ class:bindClass }, [ _c( 'p', [_v(_s(text))]), _c('p',[_v("hello world")]), /*这是一个v-for循环*/ _l( (arr), function(item,index){ return _c( 'p', [_c('p',[_v(_s(item.name))]), _c('p',[_v(_s(item.value))]), _c('p',[_v(_s(index))]), _c('p',[_v("---")])] ) } ), /*这是v-if*/ (text)?_c('p',[_v(_s(text))]):_c('p',[_v("no text")])], 2 ) }
_c, _v, _s, _q
En regardant la chaîne de la fonction de rendu, nous avons constaté qu'il existe un beaucoup de _c , _v, _s, _q, quelles sont exactement ces fonctions ?
带着问题,我们来看一下core/instance/render。
/*处理v-once的渲染函数*/ Vue.prototype._o = markOnce /*将字符串转化为数字,如果转换失败会返回原字符串*/ Vue.prototype._n = toNumber /*将val转化成字符串*/ Vue.prototype._s = toString /*处理v-for列表渲染*/ Vue.prototype._l = renderList /*处理slot的渲染*/ Vue.prototype._t = renderSlot /*检测两个变量是否相等*/ Vue.prototype._q = looseEqual /*检测arr数组中是否包含与val变量相等的项*/ Vue.prototype._i = looseIndexOf /*处理static树的渲染*/ Vue.prototype._m = renderStatic /*处理filters*/ Vue.prototype._f = resolveFilter /*从config配置中检查eventKeyCode是否存在*/ Vue.prototype._k = checkKeyCodes /*合并v-bind指令到VNode中*/ Vue.prototype._b = bindObjectProps /*创建一个文本节点*/ Vue.prototype._v = createTextVNode /*创建一个空VNode节点*/ Vue.prototype._e = createEmptyVNode /*处理ScopedSlots*/ Vue.prototype._u = resolveScopedSlots /*创建VNode节点*/ vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
通过这些函数,render函数最后会返回一个VNode节点,在_update的时候,经过patch与之前的VNode节点进行比较,得出差异后将这些差异渲染到真实的DOM上。
相关推荐:
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!