1.概要
最初の2つのステップの後、テンプレートがASTモデルツリーに変換され、次にASTモデルツリーがレンダリング式に変換されます。第6章では、レンダー式を実行した後、vnodeが生成されることについて説明しました。前の例を使用して、生成されたレンダー式のフォーマットを確認します。
with(this){
return
_c('div',{
attrs:{"id":"app"}
},
[_c('ul',_l((items),function(item){
return _c('li',
[_v("\n itemid:"+_s(item.id)+"\n ")]
)}
)
)]
)}
レンダーエクスプレッションは通常3つの部分で構成され、その構造図は次のとおりです。
_c(
// 1、标签
'div',
//2、模板相关属性的数据对象
{
...
},
//3、子节点,循环其模型
[
_c(...)
]
)
レンダリング式の形式に詳しくない場合は、公式ドキュメントのレンダリング関数とJSXを参照してください。第5章では、initRenderメソッドについて説明しましたが、_cメソッドの定義があります。
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
_lはノードのリストを作成することを意味し、_vはテキストテキストを作成することを意味し、それに関連するメソッドはsrc / core / instance / render-helpers / index.jsで定義されています
export function installRenderHelpers (target: any) {
target._o = markOnce
target._n = toNumber
target._s = toString
target._l = renderList
target._t = renderSlot
target._q = looseEqual
target._i = looseIndexOf
target._m = renderStatic
target._f = resolveFilter
target._k = checkKeyCodes
target._b = bindObjectProps
target._v = createTextVNode
target._e = createEmptyVNode
target._u = resolveScopedSlots
target._g = bindObjectListeners
}
次に、レンダーエクスプレッションが生成される方法を確認します。
二、生成する
generateメソッドは/src/compiler/codegen/index.jsにあります
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
//核心部分,生成render表达式字符串主体
const code = ast ? genElement(ast, state) : '_c("div")'
return {
//最外层用with(this)包裹
render: `with(this){return ${code}}`,
//被标记为 staticRoot 节点的 VNode 就会单独生成 staticRenderFns
staticRenderFns: state.staticRenderFns
}
}
このメソッドのロジックは比較的単純ですが、核となる部分はgenElementを呼び出してレンダー式文字列を生成することです。
export function genElement (el: ASTElement, state: CodegenState): string {
//对一些标签属性的处理
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else {
// component or element
let code
//组件的处理
if (el.component) {
code = genComponent(el.component, el, state)
} else {
//核心的body部分
//1、生成节点的数据对象data的字符串
const data = el.plain ? undefined : genData(el, state)
//2、查找其子节点,生成子节点的字符串
const children = el.inlineTemplate ? null : genChildren(el, state, true)
//3、将tag,data,children拼装成字符串
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
}
// module transforms
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
return code
}
}
この方法では、最初にいくつかの特別な属性ラベルを扱います。この章の3番目の部分については詳細に説明し、コンポーネント部分については後の章で紹介します。コアボディパーツを直接確認します。
1.サンプルのdivノードのattrs属性など、ノードのデータオブジェクト文字列を生成します。
2.子ノードを見つけ、genChildrenを呼び出して、サンプルのulなどの子ノードの文字列を生成します。
3.最後に、タグ、データ、および子が_cでカプセル化され、ノード式が完成します。
genChildrenメソッドを紹介する前に、メソッドを実装する場合の方法をまず考えてみましょう。AST全体はツリー構造であるため、必ず再帰的な方法を採用し、リーフノードに到達するまで、子ノードの文字列をレイヤーごとに生成する必要があります。genChildrenコアのコードスニペットを直接見てみましょう
export function genChildren (
el: ASTElement,
state: CodegenState,
checkSkip?: boolean,
altGenElement?: Function,
altGenNode?: Function
): string | void {
const children = el.children
...
//拼装子节点的字符串
return `[${children.map(c => gen(c, state)).join(',')}]${
normalizationType ? `,${normalizationType}` : ''
}`
}
}
ループを使用して複数の子ノードが存在する場合があります。次に、gen(つまりgenNode)メソッドを呼び出して文字列を生成します。
function genNode (node: ASTNode, state: CodegenState): string {
if (node.type === 1) {//1、对于node节点,调用genElement进行递归
return genElement(node, state)
} if (node.type === 3 && node.isComment) {//2、注释
return genComment(node)
} else {//3、叶节点,生成text文本字符串
return genText(node)
}
}
この方法は私たちの考えと一致しています。
1.タイプ= 1(ノードタイプ)の要素の場合、再帰のためにgenElementを呼び出します。例では、div、ul、liなど。
2.他のタイプはリーフノードとして扱われ、通常はテキストタイプです。たとえば、サンプルのリーフノード「itemid:{{item.id}}」では、生成される文字列は次のようになります。
_v("\n itemid:"+_s(item.id)+"\n ")
3、特定のラベル属性処理
genElementメソッドでは、いくつかの特定のタグ属性が処理されます。それらのいくつかを紹介します
1、再交渉
v-for属性を含むタグの場合、このメソッドは、サンプルの「li」ノードなどの文字列を生成するために呼び出され、式は次の文字でラップされます
_l((items),function(item){
return ...
})
実際、ノードの通常の文字列は_lメソッドでラップされ、_lメソッドは実行中に呼び出され、このタイプの複数のノードがループで生成されます。メソッドを見てみましょう:
export function genFor (
el: any,
state: CodegenState,
altGen?: Function,
altHelper?: string
): string {
const exp = el.for
const alias = el.alias
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
...
//1、标记已处理
el.forProcessed = true // avoid recursion
//2、function...return...包裹字符串,调用genElement继续节点字符串生成
return `${altHelper || '_l'}((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${(altGen || genElement)(el, state)}` +
'})'
}
最初にノードのv-for属性が処理されたことをマークし(無限ループに入らないようにするため)、文字列をfunction ... return ...でラップし、ノードの文字列がgenElementを呼び出して生成を続けます。
el.forProcessedがtrueであるため、genElementメソッドに再度入るときは、genForをスキップして、コアボディパーツを実行し、ノード文字列を生成します。
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
2、genIf
v-if属性を含むノードの場合、文字列を生成するためにgenIfが呼び出されます。条件の数に応じて、ノードの文字列をラップするための多変量式文字列も生成されます。スタイルは次のとおりです。
(xxx)?....:(ttt)?...:...
export function genIf (
el: any,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
el.ifProcessed = true // avoid recursion
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
function genIfConditions (
conditions: ASTIfConditions,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
if (!conditions.length) {
return altEmpty || '_e()'
}
//对于多个condition,将调用genIfConditions进行递归生成二元表达式
const condition = conditions.shift()
if (condition.exp) {
//生成本次条件的二元表达式
return `(${condition.exp})?${
genTernaryExp(condition.block)
}:${
//递归生成后续的二元表达式
genIfConditions(conditions, state, altGen, altEmpty)
}`
} else {
return `${genTernaryExp(condition.block)}`
}
// v-if with v-once should generate code like (a)?_m(0):_m(1)
function genTernaryExp (el) {
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state)//生成节点的字符串
}
}
genIfは主にgenIfConditionsメソッドを実装し、条件の数に応じて再帰的にバイナリ式を生成し、最終的に多変量式にアセンブルします。
ノードの文字列コンテンツを生成するために、genElementメソッドが再帰的に呼び出されます。プロセスはgenForに似ています。
3、genStatic
静的ルートノードの場合、genStaticが呼び出されて文字列が生成されます
function genStatic (el: ASTElement, state: CodegenState): string {
el.staticProcessed = true
state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)
return `_m(${
state.staticRenderFns.length - 1
}${
el.staticInFor ? ',true' : ''
})`
}
文字列はstaticRenderFns配列に配置され、_m(xx)式が返されます。
genSlotについては後で紹介します。genOnceの処理ロジックは同様なので、ここでは詳しく説明しません。
4.まとめ
生成は主に、ASTモデルを多数の再帰呼び出しを使用するレンダリング式に変換することです。主なプロセスは次のとおりです。