フロントエンドテンプレートとは何ですか?フロントエンドテンプレートを実装するにはどうすればよいですか?これについてはあまり知らない友人も多いかもしれないので、次の記事でフロントエンド テンプレートの原則と簡単な実装コードを紹介します。
テンプレートは、フロントエンド開発において最も一般的に使用されるツールの1つと言えます。ページの固定コンテンツをテンプレートに抽出し、サーバーから返された動的データをテンプレート内の予約されたスペースに埋め込み、最後にそれを完全なページ HTML 文字列に組み立てて、解析のためにブラウザに渡します。
テンプレートを使用すると、開発効率が大幅に向上します。テンプレートがないと、開発者は文字列を手動で入力する必要がある場合があります。
var tpl = '<p>' + user.name + '</p>'; $('body').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}}
次世代の vue のテンプレート記述方法は、以前のテンプレートとは異なり、より強力です。クライアントとサーバーの両方で使用できますが、使用シナリオは大きく異なります。データに応じてページが変更されることが多く、テンプレートによって生成される DOM も変更されるため、テンプレートには高いパフォーマンスが必要です。
vue構文:index.vue
<p>Username: {{user.name}}</p> <template v-if="user.gender === 2"> <p>女</p> </div>
テンプレートによって実装される関数
テンプレート実装の原則
テンプレートの使用法:
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: 'Aus', escape: '<p>Aus</p>' }, shouldEscape: false, list: [ {key: 'a', value: 1}, {key: 'b', value: 2}, {key: 'c', value: 3}, {key: 'd', value: 4} ] };
テンプレートの使用方法:
var fs = require("fs"); var tpl = fs.readFileSync('./index.txt', 'utf8'); var state = require('./test'); var Panda = require('./panda'); Panda.render(tpl, state)
次に、テンプレートを実装します:
1. 定期的に文字列をカットします
// 将未处理过的字符串进行分词,形成字符组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('text', tpl.slice(tplStart, tagStart))); tagEnd = tpl.indexOf(closeTag, tagStart) + 2; if (tagEnd < 0) throw new Error('{{}}标签未闭合'); // 细分js var tplValue = tpl.slice(tagStart + 2, tagEnd - 2); var token = this.classifyJs(tplValue); tokens.push(token); tplStart = tagEnd; } // 最后一段 tokens.push(new Token('text', tpl.slice(tagEnd, tpl.length))); return this.parseJs(tokens); };
文字列を分割するこのステップは、通常、正規表現を使用して行われます。正規メソッドは、後で文字列を取得するときに広範囲に使用されます。
このステップでは、通常、テンプレート タグが異常に閉じられていることを確認し、エラーを報告できます。
2. テンプレート構文の分類
// 专门处理模板中的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 '#': { collector.push(token); sections.push(token); if(token.action === 'each'){ collector = token.children = []; } else if (token.action === 'if') { currentCondition = value; var conditionArray; collector = conditionArray = []; token.conditions = token.conditions || conditionsArray; conditionsArray.push({ condition: currentCondition, collector: collector }); } break; } case 'else': { if(sections.length === 0 || sections[sections.length - 1].action !== 'if') { throw new Error('else 使用错误'); } currentCondition = value; collector = []; conditionsArray.push({ condition: currentCondition, collector: collector }); break; } case '/': { section = sections.pop(); if (section && section.action !== token.value) { throw new Error('指令标签未闭合'); } if(sections.length > 0){ var lastSection = sections[sections.length - 1]; if(lastSection.action === 'each'){ collector = lastSection.chidlren; } else if (lastSection.action = 'if') { 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完成了。
现在开始根据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编译器,模板引擎在查找变量的时候找不到对应变量即终止查找,返回空并不会报错。
这里开始讲模板语法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; }
页面字符串已经解析完成,可以直接输出:
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 サイトの他の関連記事を参照してください。