【Vue2.0ソースコード学習】テンプレート編集~テンプレート解析(コード生成段階)

1 はじめに

前回の記事に続いて、ユーザーが作成したテンプレート文字列を解析段階で解析して、対応する抽象構文ツリーを生成し、最適化段階で静的ノードと静的ルート ノードをマークASTAST、いよいよテンプレート コンパイルの 3 つの段階の最後の段階であるコード生成段階に到達しました。いわゆるコード生成フェーズ、どのようなコードを生成するのか? 回答:renderファンクション文字列を生成します。

Vueインスタンスがマウントされると、インスタンスが独自の関数を呼び出して、renderインスタンスにtemplate対応するオプションを生成することがわかりますVNode。簡単に言うと、Vue関数が呼び出されている限りrender、テンプレートは対応する virtual に変換できますDOM関数をVue呼び出したい場合はまずこの関数を用意する必要があります。では、この関数はどこから来たのでしょうか? ユーザーが書いたものですか、それとも自分で生成したものですか? 答えはすべて可能です。日常の開発では、コンポーネントのオプションにオプションを手動で記述することができ、その値が関数に対応し、その関数が関数となり、ユーザーが関数を手動で記述すると、ユーザーが記述したこの関数がコンポーネントのマウント時に呼び出されることを知っています。ユーザーが書き込まなかったらどうなるでしょうか? このとき、コンポーネントのマウント時に呼び出す関数をテンプレートの内容に基づいて生成する必要があります。そして、テンプレートの内容に従って関数を生成するプロセスが、この記事で紹介するコード生成段階です。renderrenderrenderVueVuerenderrenderrenderVuerenderVuerenderVuerender

いわゆるコード生成とは、実際にはASTテンプレートに対応する抽象構文ツリーに従って関数を生成することであり、この関数を呼び出すことで、テンプレートに対応する仮想を取得できることがわかりましたDOM

2. ASTに従ってレンダリング関数を生成する方法

上記のことから、コード生成フェーズの主な作業は、コンポーネントがマウントされたときに呼び出す既存の関数に基づいて、対応する関数を生成することでASTあることがわかりますrender。コンポーネントがこの関数を呼び出す限り、対応する仮想関数をrender取得できますでは、それに応じて関数を生成するにはどうすればよいでしょうか? これはどのようなプロセスですか? 次に、詳しく分析してみましょう。ASTDOMVNodeASTrender

次のテンプレートがあるとします。

<div id="NLRX"><p>Hello {
   
   {name}}</p></div>

テンプレートが解析および最適化されると、AST次のようになります。

ast = {
    
    
    'type': 1,
    'tag': 'div',
    'attrsList': [
        {
    
    
            'name':'id',
            'value':'NLRX',
        }
    ],
    'attrsMap': {
    
    
      'id': 'NLRX',
    },
    'static':false,
    'parent': undefined,
    'plain': false,
    'children': [{
    
    
      'type': 1,
      'tag': 'p',
      'plain': false,
      'static':false,
      'children': [
        {
    
    
            'type': 2,
            'expression': '"Hello "+_s(name)',
            'text': 'Hello {
    
    {name}}',
            'static':false,
        }
      ]
    }]
  }

次に、既存の関数に従って、AST対応する関数を生成しますrender関数を生成するrenderプロセスは実際には再帰的なプロセスであり、各ノードを上から下に順番に再帰しAST異なるノード タイプに応じてAST異なるタイプを作成します。VNode次に、既存のテンプレートを比較し、AST実際にrender関数を生成するプロセスをデモしてみましょう。

  1. まず、ルート ノードはdiv要素タイプASTノードです。次に、要素タイプを作成する必要がありますVNode。要素タイプVNodeを作成するメソッドを呼び出します_c(tagName,data,children)現時点ではそれが何であるかは気にしません。要素 type を作成する_c()呼び出しを知る必要があるだけです次に、次のコードを生成できます。_c()VNode

    _c('div',{
          
          attrs:{
          
          "id":"NLRX"}},[/*子节点列表*/])
    
  2. ルート ノードdivには子ノードがあり、次に子ノード リストを入力して子childrenノードを走査し、子ノードpも要素タイプであることを確認します。次に、要素タイプの作成を続けてVNode、上記のコードのルート ノードの子ノード リストに要素タイプを追加します。

    _c('div',{
          
          attrs:{
          
          "id":"NLRX"}},[_c('p',{
          
          attrs:{
          
          }},[/*子节点列表*/])])
    
  3. 同様に、pノードの子ノードをトラバースし続け、それがテキスト ノードであることを確認し、テキスト タイプを作成してノードの子ノード リストVNodeに挿入します。同様に、次のようにテキスト タイプを作成するメソッドを呼び出します。pVNode_v()

    _c('div',{
          
          attrs:{
          
          "id":"NLRX"}},[_c('p',{
          
          attrs:{
          
          }},[_v("Hello "+_s(name))])])
    
  4. この時点で、走査全体ASTが完了したので、取得したコードを次のように再パッケージ化します。

     `
     with(this){
       reurn _c(
         'div',
         {
           attrs:{"id":"NLRX"},
         },
         [
           _c(
             'p',
             {
               attrs:{}
             },
             [
               _v("Hello "+_s(name))
             ]
           )
         ]
       )
     }
     `
    
  5. 最後に、上記で取得した関数文字列をcreateFunction関数に渡します (この関数については後で紹介します)。createFunctionこの関数は、取得した関数文字列を実際の関数に変換し、それをコンポーネント内のオプションに割り当てるのに役立ちます。つまり、関数renderです。render次のように:

    res.render = createFunction(compiled.render, fnGenErrors)
    
    function createFunction (code, errors) {
          
          
      try {
          
          
        return new Function(code)
      } catch (err) {
          
          
        errors.push({
          
           err, code })
        return noop
      }
    }
    

以上が単純なテンプレートに対応する関数をAST生成するプロセスですrenderが、理論的なプロセスは理解できましたが、実際にソースコードにどのように実装されるのでしょうか。次に、ソース コードに戻って、その具体的な実装プロセスを分析します。

3. ソースコードに戻る

コード生成ステージのソース コードはsrc/compiler/codegen/index.jsにあります。ソース コードは非常に長いですが、ロジックは複雑ではありません。コア ロジックは次のとおりです。

export function generate (ast,option) {
    
    
  const state = new CodegenState(options)
  const code = ast ? genElement(ast, state) : '_c("div")'
  return {
    
    
    render: `with(this){return ${
      
      code}}`,
    staticRenderFns: state.staticRenderFns
  }
}
const code = generate(ast, options)

generate関数を呼び出して最適化された値を渡しastgenerateまず関数内で空かどうかを判断しast、空でない場合はgenElement(ast, state)関数を呼び出して作成しVNode、空の場合は空の要素div型を作成しますVNode結果はwith(this){return ${code}}パッケージで返されます。これが実際に機能する関数であることがわかります。関数の内部genElementを見ていきましょう。genElement

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 {
    
    
      const data = el.plain ? undefined : genData(el, state)

      const children = el.inlineTemplate ? null : genChildren(el, state, true)
      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
  }
}

genElement関数のロジックは非常に明確で、現在の要素ノードのさまざまな属性に従ってASTさまざまなコード生成関数を実行します。要素ノードの属性にはたくさんの種類がありますが、実際に最終的に作成されるのはVNode要素ノード、テキストノード、コメントノードの3種類だけです。render次に、これら 3 つのノード タイプの機能を生成する方法の分析に焦点を当てます。

3.1 要素ノード

要素型ノードを生成するrender関数コードは次のとおりです。

const data = el.plain ? undefined : genData(el, state)

const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${
      
      el.tag}'${
      
      
data ? `,${ 
        data}` : '' // data
}${
      
      
children ? `,${ 
        children}` : '' // children
})`

要素ノードを生成する機能はrender、一連の関数呼び出しを生成することであり_c()、前述したように、_c()この関数は、ノードのラベル名tagName、ノードの属性data、およびノー​​ドの子ノードのリストの3 つのパラメータを受け取りますchildrenあとは 3 つの部分をすべて入力するだけです。

  1. ノード属性データの取得

    まずplain属性が であるかどうかを判断し、属性がtrueある場合trueはノードに属性がないことを意味して値をdata代入しundefined、そうでない場合はtrue関数を呼び出してgenDataノードの属性dataデータを取得します。genData関数の定義は次のとおりです。

    export function genData (el: ASTElement, state: CodegenState): string {
          
          
      let data = '{'
      const dirs = genDirectives(el, state)
      if (dirs) data += dirs + ','
    
        // key
        if (el.key) {
          
          
            data += `key:${
            
            el.key},`
        }
        // ref
        if (el.ref) {
          
          
            data += `ref:${
            
            el.ref},`
        }
        if (el.refInFor) {
          
          
            data += `refInFor:true,`
        }
        // pre
        if (el.pre) {
          
          
            data += `pre:true,`
        }
        // 篇幅所限,省略其他情况的判断
        data = data.replace(/,$/, '') + '}'
        return data
    }
    

    genDataソース コードは非常に長いですが、そのロジックは非常に単純で、文字列を結合し、最初にdata値を代入し{ 、次にどの属性データが存在するかを判断し、これらのデータを に結合しdata、最後に 1 を追加し}、最後にノードのすべての属性を取得していることがわかりますdata

  2. 子ノードのリストを取得します。

    子ノード リストの取得は、実際には、 attribute内の要素childrenを走査し、次に次のように、さまざまな要素属性に従ってさまざまな作成関数呼び出し文字列を生成します。ASTchildrenVNode

    export function genChildren (el):  {
          
          
        if (children.length) {
          
          
            return `[${
            
            children.map(c => genNode(c, state)).join(',')}]`
        }
    }
    function genNode (node: ASTNode, state: CodegenState): string {
          
          
      if (node.type === 1) {
          
          
        return genElement(node, state)
      } if (node.type === 3 && node.isComment) {
          
          
        return genComment(node)
      } else {
          
          
        return genText(node)
      }
    }
    
  3. 上記の 2 つの手順が完了すると、_c()次のように関数呼び出し文字列が生成されます。

    code = `_c('${
            
            el.tag}'${
            
            
            data ? `,${ 
              data}` : '' // data
          }${
            
            
            children ? `,${ 
              children}` : '' // children
          })`
    

3.2 テキストノード

テキスト タイプは関数VNodeを呼び出すことで作成できるため、_v(text)テキスト ノードを生成するrender関数は_v(text)関数呼び出しの文字列を生成することになります。この関数はテキスト コンテンツをパラメータとして受け取り、テキストが動的テキストの場合は動的テキスト ノードのプロパティ_v()を使用し、純粋な静的テキストの場合はそのプロパティを使用します。生成されたコードは次のとおりです。ASTexpressiontext

export function genText (text: ASTText | ASTExpression): string {
    
    
  return `_v(${
      
      text.type === 2
    ? text.expression // no need for () because already wrapped in _s()
    : transformSpecialNewlines(JSON.stringify(text.text))
  })`
}

3.3 コメントノード

コメント タイプは関数VNodeを呼び出すことで作成できるため、_e(text)コメント ノードを生成するrender関数は_e(text)関数呼び出しの文字列を生成することになります。_e()この関数はコメントの内容をパラメータとして受け取り、生成されるコードは次のとおりです。

export function genComment (comment: ASTText): string {
    
    
  return `_e(${
      
      JSON.stringify(comment.text)})`
}

4. まとめ

この記事では、テンプレート コンパイルの 3 つのフェーズのうちの最後のフェーズであるコード生成フェーズについて説明します。

まず、コード生成フェーズが存在する理由と、コード生成フェーズが主に行うことを紹介します。コード生成とは、実際にはコンポーネントのマウント時に呼び出されるテンプレートに対応する抽象構文ツリーASTに基づいて関数を生成することであり、この関数を呼び出すことでテンプレートに対応する仮想を取得することができますDOM

次に、テンプレートを再帰的に走査し、最終的にrender単純なテンプレートを通じて関数を生成するプロセスを示します。

最後にソースコードに戻り、renderソースコードを分析することで生成関数の具体的な実装プロセスを理解します。

おすすめ

転載: blog.csdn.net/weixin_46862327/article/details/131744331