ホームページ  >  記事  >  ウェブフロントエンド  >  フロントエンドテンプレートとは何ですか?フロントエンドテンプレートの原理と例の紹介

フロントエンドテンプレートとは何ですか?フロントエンドテンプレートの原理と例の紹介

不言
不言オリジナル
2018-09-04 10:35:226747ブラウズ

フロントエンドテンプレートとは何ですか?フロントエンドテンプレートを実装するにはどうすればよいですか?これについてはあまり知らない友人も多いかもしれないので、次の記事でフロントエンド テンプレートの原則と簡単な実装コードを紹介します。

フロントエンドテンプレートの開発

テンプレートは、フロントエンド開発において最も一般的に使用されるツールの1つと言えます。ページの固定コンテンツをテンプレートに抽出し、サーバーから返された動的データをテンプレート内の予約されたスペースに埋め込み、最後にそれを完全なページ HTML 文字列に組み立てて、解析のためにブラウザに渡します。

テンプレートを使用すると、開発効率が大幅に向上します。テンプレートがないと、開発者は文字列を手動で入力する必要がある場合があります。

var tpl = &#39;<p>&#39; + user.name + &#39;</p>&#39;;
$(&#39;body&#39;).append(tpl);

近年のフロントエンド開発の過程で、テンプレートも変化しました:

1. PHP テンプレート JSP テンプレート

初期の頃は、フロントエンドとバックが分離されている時代はありませんでした。フロントエンドはバックエンド プロジェクト内の単なるフォルダーでした。当時の PHP と Java は両方とも独自のテンプレート エンジンを提供していました。 JSP を例に挙げます。通常、Java Web アプリケーションのページは .jsp ファイルです。このファイルの内容はほとんどが HTML であり、一部のテンプレートには独自の構文がありますが、これは HTML でも Java でもありません。

JSP 構文:index.jsp

<html>
<head><title>Hello World</title></head>
<body>
Hello World!<br/>
<%
out.println("Your IP address is " + request.getRemoteAddr());
%>
</body>
</html>

この時代のテンプレート エンジンは、多くの場合、サーバーを使用してテンプレート文字列をコンパイルし、クライアント用の HTML 文字列を生成します。

2. ハンドルバーヒゲユニバーサルテンプレート

Node は 2009 年にリリースされ、JavaScript を使用してサーバー側関数を実装することもできるため、開発者も非常に容易になります。口ひげとハンドルバーのテンプレートの誕生により、どちらのテンプレートも JavaScript を使用して実装されるようになりましたが、このフロントエンド テンプレートはサーバーまたはクライアントで実行できますが、ほとんどの使用シナリオは JS データに基づいています。サーバーによって非同期的に取得されたデータがテンプレートに挿入され、新しい DOM 挿入ページ番号が生成されます。 フロントエンド開発とバックエンド開発の両方にとって非常に有益です。 mustache 構文:index.mustache

<p>Username: {{user.name}}</p>
{{#if (user.gender === 2)}}
    <p>女</p>
{{/if}}

3. React の vue JSX のテンプレート


次世代の vue のテンプレート記述方法は、以前のテンプレートとは異なり、より強力です。クライアントとサーバーの両方で使用できますが、使用シナリオは大きく異なります。データに応じてページが変更されることが多く、テンプレートによって生成される DOM も変更されるため、テンプレートには高いパフォーマンスが必要です。

vue構文:index.vue

<p>Username: {{user.name}}</p>
<template v-if="user.gender === 2">
    <p>女</p>
</div>

テンプレートによって実装される関数

JSPからvueテンプレートに至るまで、テンプレートの構文はますますシンプルになり、機能が豊富になってきていますが、基本的な関数は不可欠です:

    変数出力 (エスケープ/アンエスケープ): セキュリティ上の理由から、テンプレートは基本的に変数の文字列をエスケープして出力します。 もちろん、エスケープなしで出力する機能も実装されているため、注意して使用してください。
  1. 条件判断(if else): 開発でよく必要となる機能です。
  2. ループ変数: 配列をループし、繰り返しコード スニペットを多数生成します。
  3. テンプレートのネスト: テンプレートのネストを使用すると、多くの重複コードを削減でき、ネストされたテンプレートはスコープを統合します。
  4. 上記の関数は基本的に、ほとんどのテンプレートの基本関数をカバーしています。これらの基本関数については、テンプレートの実装方法を調べることができます。

テンプレート実装の原則

タイトルにあるように、テンプレートは本質的にプレーンテキスト文字列です。文字列は js プログラムでどのように動作するのでしょうか。

テンプレートの使用法:

var domString = template(templateString, data);

テンプレート エンジンは、テンプレート文字列とテンプレートのスコープを取得し、コンパイル後に完全な DOM 文字列を生成します。

ほとんどのテンプレートの実装原則は基本的に同じです:

テンプレート文字列は、まずさまざまな方法で通常の文字列とテンプレート構文文字列を分離し、抽象構文ツリー AST を生成します。その後、テンプレート構文フラグメントをコンパイルし、その間にテンプレート変数が削除されます。エンジンは入力変数を検索し、テンプレート構文フラグメントは通常の HTML フラグメントを生成し、出力用に元の通常の文字列と結合されます。

実際、テンプレートのコンパイル ロジックは特に複雑ではありません。データを動的にバインドする Vue などのテンプレートについては、時間があれば記事の最後にあるリンクを参照してください。

簡単なテンプレートをすばやく実装する

次に、口ひげテンプレートを例として、基本的な機能を実装するテンプレートを手動で実装します。

テンプレート文字列テンプレート:index.txt

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Page Title</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" type="text/css" media="screen" href="main.css" />
  <script src="main.js"></script>
</head>
<body>
  <h1>Panda模板编译</h1>
  <h2>普通变量输出</h2>
  <p>username: {{common.username}}</p>
  <p>escape:{{common.escape}}</p>
  <h2>不转义输出</h2>
  <p>unescape:{{&common.escape}}</p>
  <h2>列表输出:</h2>
  <ul>
  {{#each list}}
    <li class="{{value}}">{{key}}</li>
  {{/each}}
  </ul>
  <h2>条件输出:</h2>
  {{#if shouldEscape}}
    <p>escape{{common.escape}}</p>
  {{else}}
    <p>unescape:{{&common.escape}}</p>
  {{/if}}
</body>
</html>

テンプレート対応データ:

module.exports = {
  common: {
    username: &#39;Aus&#39;,
    escape: &#39;<p>Aus</p>&#39;
  },
  shouldEscape: false,
  list: [
    {key: &#39;a&#39;, value: 1},
    {key: &#39;b&#39;, value: 2},
    {key: &#39;c&#39;, value: 3},
    {key: &#39;d&#39;, value: 4}
  ]
};

テンプレートの使用方法:

var fs = require("fs");
var tpl = fs.readFileSync(&#39;./index.txt&#39;, &#39;utf8&#39;);
var state = require(&#39;./test&#39;);
var Panda = require(&#39;./panda&#39;);

Panda.render(tpl, state)

次に、テンプレートを実装します:

1. 定期的に文字列をカットします

テンプレートエンジンはテンプレート文字列を取得しますその後、通常は正規表現を使用して文字列を切り取り、静的な文字列とコンパイルが必要なコード ブロックを区別し、抽象構文ツリー (AST) を生成します。

// 将未处理过的字符串进行分词,形成字符组tokens
Panda.prototype.parse = function (tpl) {
  var tokens = [];
  var tplStart = 0;
  var tagStart = 0;
  var tagEnd = 0;

  while (tagStart >= 0) {
    tagStart = tpl.indexOf(openTag, tplStart);
    if (tagStart < 0) break;
    // 纯文本
    tokens.push(new Token(&#39;text&#39;, tpl.slice(tplStart, tagStart)));

    tagEnd = tpl.indexOf(closeTag, tagStart) + 2;
    if (tagEnd < 0) throw new Error(&#39;{{}}标签未闭合&#39;);
    // 细分js

    var tplValue = tpl.slice(tagStart + 2, tagEnd - 2);
    var token = this.classifyJs(tplValue);
    tokens.push(token);

    tplStart = tagEnd;
  }

  // 最后一段
  tokens.push(new Token(&#39;text&#39;, tpl.slice(tagEnd, tpl.length)));

  return this.parseJs(tokens);
};

文字列を分割するこのステップは、通常、正規表現を使用して行われます。正規メソッドは、後で文字列を取得するときに広範囲に使用されます。

このステップでは、通常、テンプレート タグが異常に閉じられていることを確認し、エラーを報告できます。

2. テンプレート構文の分類

ASTを生成した後は、通常の文字列は管理する必要がなくなり、最終的にはテンプレート構文の分類に焦点を当てて直接出力されます。

// 专门处理模板中的js
Panda.prototype.parseJs = function (tokens) {
  var sections = [];
  var nestedTokens = [];
  var conditionsArray = [];
  var collector = nestedTokens;
  var section;
  var currentCondition;

  for (var i = 0; i < tokens.length; i++) {
    var token = tokens[i];
    var value = token.value;
    var symbol = token.type;

    switch (symbol) {
      case &#39;#&#39;: {
        collector.push(token);
        sections.push(token);

        if(token.action === &#39;each&#39;){
          collector = token.children = [];
        } else if (token.action === &#39;if&#39;) {
          currentCondition = value;
          var conditionArray;
          collector = conditionArray = [];
          token.conditions = token.conditions || conditionsArray;

          conditionsArray.push({
            condition: currentCondition,
            collector: collector
          });
        }
        break;
      }
      case &#39;else&#39;: {
        if(sections.length === 0 || sections[sections.length - 1].action !== &#39;if&#39;) {
          throw new Error(&#39;else 使用错误&#39;);
        }

        currentCondition = value;
        collector = [];

        conditionsArray.push({
          condition: currentCondition,
          collector: collector
        });

        break;
      }
      case &#39;/&#39;: {
        section = sections.pop();

        if (section && section.action !== token.value) {
          throw new Error(&#39;指令标签未闭合&#39;);
        }

        if(sections.length > 0){
          var lastSection = sections[sections.length - 1];
          if(lastSection.action === &#39;each&#39;){
            collector = lastSection.chidlren;
          } else if (lastSection.action = &#39;if&#39;) {
            conditionsArray = [];
            collector = nestedTokens;
          }
        } else {
          collector = nestedTokens;
        }

        break;
      }
      default: {
        collector.push(token);
        break;
      }
    }
  }

  return nestedTokens;
}

前のステップで AST を生成しました。この AST は単語分割トークンの配列です:

[
    Token {},
    Token {},
    Token {},
]

このトークンは、トークン タイプ、アクション、サブトークン、条件付きトークン、その他の情報を記録する各文字列です。 。

/**
 * token类表示每个分词的标准数据结构
 */
function Token (type, value, action, children, conditions) {
  this.type = type;
  this.value = value;

  this.action = action;
  this.children = children;
  this.conditions = conditions;
}

在这一步要将循环方法中的子token嵌套到对应的token中,以及条件渲染子token嵌套到对应token中。

这步完成之后,一个标准的带有嵌套关系的AST完成了。

3. 变量查找与赋值

现在开始根据token中的变量查找到对应的值,根据相应功能生成值得字符串。

/**
 * 解析数据结构的类
 */
function Context (data, parentContext) {
  this.data = data;
  this.cache = { '.': this.data };
  this.parent = parentContext;
}

Context.prototype.push = function (data) {
  return new Context(data, this);
}

// 根据字符串name找到真实的变量值
Context.prototype.lookup = function lookup (name) {
  name = trim(name);

  var cache = this.cache;

  var value;
  // 查询过缓存
  if (cache.hasOwnProperty(name)) {
    value = cache[name];
  } else {
    var context = this, names, index, lookupHit = false;

    while (context) {
      // user.username
      if (name.indexOf('.') > 0) {
        value = context.data;
        names = name.split('.');
        index = 0;

        while (value != null && index < names.length) {
          if (index === names.length - 1) {
            lookupHit = hasProperty(value, names[index]);
          }

          value = value[names[index++]];
        }
      } else {
        value = context.data[name];
        lookupHit = hasProperty(context.data, name);
      }

      if (lookupHit) {
        break;
      }

      context = context.parent;
    }

    cache[name] = value;
  }

  return value;
}

为了提高查找效率,采用缓存代理,每次查找到的变量存储路径方便下次快速查找。

不同于JavaScript编译器,模板引擎在查找变量的时候找不到对应变量即终止查找,返回空并不会报错。

4. 节点的条件渲染与嵌套

这里开始讲模板语法token和普通字符串token开始统一编译生成字符串,并拼接成完整的字符串。

// 根据tokens和context混合拼接字符串输出结果
Panda.prototype.renderTokens = function (tokens, context) {
  var result = '';
  var token, symbol, value;

  for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
    value = undefined;
    token = tokens[i];
    symbol = token.type;

    if (symbol === '#') value = this.renderSection(token, context);
    else if (symbol === '&') value = this.unescapedValue(token, context);
    else if (symbol === '=') value = this.escapedValue(token, context);
    else if (symbol === 'text') value = this.rawValue(token);

    if (value !== undefined) result += value;
  }

  return result;
}

5. 绘制页面

页面字符串已经解析完成,可以直接输出:

Panda.prototype.render = function (tpl, state) {
  if (typeof tpl !== 'string') {
    return new Error('请输入字符串!');
  }

  // 解析字符串
  var tokens = this.cache[tpl] ? tokens : this.parse(tpl);
  // 解析数据结构
  var context = state instanceof Context ? state : new Context(state);
  // 渲染模板
  return this.renderTokens(tokens, context);
};

输出页面字符串被浏览器解析,就出现了页面。

以上只是简单的模板实现,并没有经过系统测试,仅供学习使用,源码传送门。成熟的模板引擎是有完整的异常处理,变量查找解析,作用域替换,优化渲染,断点调试等功能的。

总结

前端模板这块能做的东西还很多,很多框架都是集成模板的功能,配合css,js等混合编译生成解析好样式和绑定成功事件的dom。

另外实现模板的方式也有很多,本文的实现方式参考了mustache源码,模板标签内的代码被解析,但是是通过代码片段分类,变量查找的方式来执行的,将纯字符串的代码变成了被解释器执行的代码。

另外向vue这种可以实现双向绑定的模板可以抽空多看一看。

相关推荐:

javascript - 大家前端js模板是用underscore还是handlebars呢?

学习前端模板引擎 jade (一)_html/css_WEB-ITnose

以上がフロントエンドテンプレートとは何ですか?フロントエンドテンプレートの原理と例の紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。