Polymer 使声明性地创建 Web 组件变得简单。

新的 Web 开发者能够简单地使用标记来添加自定义 HTML 元素到一个网页上去。这就像您已经熟悉的使用 HTML 标签的用法一样:

<h1>A heading!</h1>
<fancy-thing>A fancy thing!</fancy-thing>

Experienced web developers can use Polymer's special features to reduce boilerplate and make it even easier to build complex, interactive elements. In this tour, you'll learn how to:

  • 注册元素
  • 使用生命周期回调
  • 观察属物
  • 使用模板创建阴影 DOM
  • 使用数据绑定

In this section you can tour the Polymer library, without installing anything. Click the Edit on StackBlitz button to open any of the samples in an interactive sandbox.

点击每个功能后面的按钮了解更多信息。

To register a new element, create an ES6 class that extends PolymerElement, then call the customElements.define method, which registers a new element with the browser. Registering an element associates an element name with a class, so you can add properties and methods to your custom element. The custom element's name must start with an ASCII letter and contain a dash (-).

custom-element.js
import {PolymerElement} from '@polymer/polymer/polymer-element.js';

// Define the class for a new element called custom-element
class CustomElement extends PolymerElement {
  constructor() {
    super();
    this.textContent = 'I\'m a custom-element.';
  }
}
// Register the new element with the browser
customElements.define('custom-element', CustomElement);

/*
  If you’re familiar with your browser’s developer tools, try printing the
  custom element’s `tagName` property to the console.
  Hint: add `console.log(this.tagName);` to the constructor method!
*/

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <custom-element></custom-element>
  </body>
</html>

Try it out in StackBlitz:

  • Try modifying the contents of this.textContent.
  • If you’re familiar with your browser’s developer tools, try printing the custom element’s tagName property to the console. Hint: add console.log(this.tagName); to the constructor method!

此示例使用一个生命周期回调把内容添加到 <custom-element> 上,在它初始化完成时。 当自定义元素完成其初始化时,ready 生命周期回调被调用。 您可以使用 ready 回调进行一次性的初始化工作,在元素被创建以后。

了解更多:元素注册

了解更多:生命周期回调

许多元素包括一些内部 DOM 节点来实现元素的 UI 和行为。您可以使用 Polymer 的 DOM 模板化为元素创建一个阴影 DOM 树。

dom-element.js
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js'

// Define the class for a new element called custom-element
class DomElement extends PolymerElement {

  static get template () {
    return html`
      <p>I'm a DOM element. This is my shadow DOM!</p>

      <!-- TODO: Try adding some other html elements inside the
           template. For example, add <h1>A heading!</h1> or
           <a href="stuff.html">A link!</a>
      -->
    `;
  }
}
// Register the new element with the browser
customElements.define('dom-element', DomElement);

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <dom-element></dom-element>
  </body>
</html>

Try it out in StackBlitz:

  • Try adding some other html elements inside the block. For example, add <h1>A heading!</h1> or <a href="stuff.html">A link!</a>

阴影 DOM 被封装在元素内。

了解更多:DOM 模板化

阴影 DOM 可让您控制 构造。元素的子元素可以是 分布式的 以让它们可以被渲染成像被插入到阴影 DOM 树中一样。

此示例创建一个简单的标签,通过使用一个样式化的 <div> 标签来包装一个图片来装饰该图片。

picture-frame.js
import {PolymerElement, html} from "@polymer/polymer/polymer-element.js"

class PictureFrame extends PolymerElement {
  static get template() {
    return html`
    <!-- scoped CSS for this element -->
    <style>
      div {
        display: inline-block;
        background-color: #ccc;
        border-radius: 8px;
        padding: 4px;
      }
    </style>
    <!--
    TODO: Try adding other HTML elements to the DOM template
    to see how they are positioned relative to the distributed
    child nodes.
    -->
    <div>
      <!-- any children are rendered here -->
      <slot></slot>
    </div>
    `;
  }
}
customElements.define('picture-frame', PictureFrame);

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <!--
    TODO: Try adding <div> elements in index.html to observe
    how they are unaffected by the encapsulated styles applied
    to the divs in picture-frame.html.
    -->
    <picture-frame>
      <img src="https://www.polymer-project.org/images/logos/p-logo-32.png">
    </picture-frame>
  </body>
</html>

Try it out in StackBlitz:

  • Try adding a <div> to index.html; is it affected by the styles in <picture-frame>'s shadow DOM?
  • Try adding other HTML elements to the DOM template to see how they are positioned relative to the distributed child nodes.

注意: <dom-module> 里面定义的 CSS 样式的 作用域 仅限于元素的阴影 DOM。 所以这里的 div 规则仅仅影响 <picture-frame> 里的 <div> 标签。

Learn more: Composition & distribution

当然,静态阴影 DOM 还不够。您通常想让您的元素动态地更新它的阴影 DOM。

数据绑定是快速传播元素内改变并减少样板代码的好方法。 您可以使用“双胡子”语法 ({{}}) 来绑定组件中的属物。 这 {{}} 由大括号中引用的属物的值所替换。

name-tag.js
import {PolymerElement, html} from "@polymer/polymer/polymer-element.js"

class NameTag extends PolymerElement {
  constructor() {
    super();

    /* set this element's owner property */
    this.owner = 'Daniel';
  }
  static get template() {
    return html`
      <!-- bind to the "owner" property -->
      This is <b>{{owner}}</b>'s name-tag element.
    `;
  }
}
customElements.define('name-tag', NameTag);
/* TODO:
  * - Try editing the value of the `owner` property.
  * - Try adding another property and binding it in
  *   your component. Hint: Add the following property
  *   definition to the constructor:
  *   `this.propertyName = "Property contents";`
  *   and add `{{propertyName}}` to the element’s shadow DOM.
  */

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <name-tag></name-tag>
  </body>
</html>

Try it out in StackBlitz:

  • Try editing the value of the owner property.
  • Try adding another property and binding it in your component. Hint: Add this.propertyName = 'Property contents'; to the constructor and add something like <p>{{propertyName}}</p> to the element’s template.

Learn more: data binding

属物是元素的公共 API 的重要组成部分。Polymer 声明的属物 支持许多常见的用于属物的模式——设置默认值,从标记配置属物,观察属物更改,以及更多。

以下示例声明来自于上一个示例的 owner 属物。 它还展示了在 index.html 中用标记配置 owner 属物。

configurable-name-tag.js
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js'

class ConfigurableNameTag extends PolymerElement {
  static get properties () {
    return {
      // Configure owner property
      owner: {
        type: String,
          value: 'Daniel',
      }
    };
  }
  static get template () {
    return html`
      <!-- bind to the "owner" property -->
      This is <b>[[owner]]</b>'s name-tag element.
    `;
  }
}
customElements.define('configurable-name-tag', ConfigurableNameTag);

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <!-- configure a property from markup by setting
         the corresponding attribute:
    -->
    <configurable-name-tag owner="Scott"></configurable-name-tag>
    <!--
      TODO: Try editing the initial value of `owner` in
      index.html. Observe how this sets the property directly
      from your HTML.
    -->
  </body>
</html>

Try it out in StackBlitz:

  • Try editing the initial value of owner in index.html. Observe how this sets the property directly from your HTML.

Learn more: declared properties

除文本内容之外,您还可以绑定到元素的 属物 (使用 property-name="[[binding]]")。Polymer 属物可以选择性地支持双向绑定, 使用花括号 (property-name="{{binding}}")。

The following example uses two-way binding: binding the value of a custom input element (iron-input) to the element's owner property, so it's updated as the user types.

editable-name-tag.js
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
import '@polymer/iron-input/iron-input.js';

class EditableNameTag extends PolymerElement {
  static get properties () {
    return {
      owner: {
        type: String,
        value: 'Daniel'
      }
    };
  }
  static get template () {
    return html`
      <!-- bind to the 'owner' property -->
      <p>This is <b>[[owner]]</b>'s name-tag element.</p>

      <!-- iron-input exposes a two-way bindable input value -->
      <iron-input bind-value="{{owner}}">
        <!--
          TODO: Edit the placeholder text to see two-way data
          binding at work.
        -->
      <input is="iron-input" placeholder="Your name here...">
      </iron-input>
    `;
  }
}

customElements.define('editable-name-tag', EditableNameTag);

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <editable-name-tag></editable-name-tag>
  </body>
</html>

Try it out in StackBlitz:

  • Edit the placeholder text to see two-way data binding at work.

注意: <iron-input> 元素包装了原生的 <input> 元素,并提供双向数据绑定和输入校验。

Learn more: Two-way binding

The template repeater (<dom-repeat>) is a specialized template that binds to an array. It creates one instance of the template's contents for each item in the array.

employee-list.js
//import the Polymer library
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js'

//import the template repeater
import '@polymer/polymer/lib/elements/dom-repeat.js'

class EmployeeList extends PolymerElement {
  constructor() {
    super();
    /* TODO:
     * - Change the first and last names inside this.employees
     * - Add another employee by inserting another object
     *   into the array definition after Tony Mori:
     *   {first: 'Shawna', last: 'Williams'}
     *   Remember to make sure your commas are correct!
     */

    this.employees = [
      {first: 'Bob', last: 'Li'},
      {first: 'Ayesha', last: 'Johnson'},
      {first: 'Fatma', last: 'Kumari'},
      {first: 'Tony', last: 'Mori'}
    ];
  }
  static get template () {
    return html`
    <div> Employee list: </div>
    <p></p>
    <template is="dom-repeat" items="{{employees}}">
        <div>First name: <span>{{item.first}}</span></div>
        <div>Last name: <span>{{item.last}}</span></div>
        <p></p>
    </template>
    `;
  }
}
customElements.define('employee-list', EmployeeList);

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
    <script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
  </head>
  <body>
    <employee-list></employee-list>
  </body>
</html>

Try it out in StackBlitz:

  • Change the first and last names inside this.employees
  • Add another employee by inserting the following item into the array definition:
       {first: 'Shawna', last: 'Williams'}
    

Don't forget to make sure your commas are correct!

Learn more: Template repeater

Now that you understand these fundamental Polymer concepts, you can build an app with App Toolbox or see a feature overview of the Polymer library.