templatebinding在前端开发中的应用

发布时间:2023-05-20

一、templatebinding twoway

templatebinding twoway 是一种实现数据绑定双向同步的方法。当绑定一个 input 元素时,它不仅读取属性值显示在界面上,而且当输入框内容发生变化时,数据绑定也会将值存储到属性中。为了实现此功能,我们需要引入一个构造器 InputData,它将被绑定到表格中的每个单元格。我们的目标是使 InputData 对象尽可能表现为一个简单对象,仅在同步时才涉及模板绑定的细节。

class InputData {
  constructor(value = "", valid = true) {
    this.value = value;
    this.valid = valid;
  }
}
function bindInput(inputElement, data, property) {
  inputElement.value = data[property];
  inputElement.oninput = function () {
    data[property] = inputElement.value;
  };
}
let input = new InputData("Initial value");
bindInput(document.getElementById("inputElement"), input, "value");

在上面的代码示例中,我们定义了一个 InputData 类,并在实例化之后将 InputData 传递到 bindInput() 函数来执行绑定。bindInput() 函数被告知数据绑定的实际值(在这里是 InputData.value 属性),并将输入元素传递到该函数中。

二、templatebinding content

templatebinding content 的作用是使模板绑定可以将数据绑定到元素的 innerHTML 属性。在实际应用中,很少直接使用 innerHTML 属性,因为这种做法会将字符串解析为节点,消耗大量内存。然而,如果正确使用 innerHTML,它可以更改节点的内部结构,而不会冒着解析字符串的风险。

class BoundElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
        }
      </style>
      <slot></slot>
    `;
  }
  static get observedAttributes() {
    return ["text"];
  }
  attributeChangedCallback(name, oldValue, newValue) {
    this.shadowRoot.querySelector("slot").textContent = newValue;
  }
}
customElements.define("bound-element", BoundElement);
let boundElement = document.querySelector("bound-element");
boundElement.setAttribute("text", "Some text");

在上面的代码示例中,我们先定义了一个 BoundElement 类,并在该类中定义了元素的 DOM 模板。然后我们使用 customElements.define() 方法注册该元素类。最后,我们通过调用 setAttribute() 方法为元素设置 text 属性,并在属性更改时触发了 attributeChangedCallback() 方法。

三、templatebinding 无法绑定路径

templatebinding 可能无法绑定路径,因为一些数据绑定系统实际上是静态的部分。当这种情况发生时,我们可以使用各种简便方法,例如借助属性的语法糖,在表达式所在的对象之前添加一个引用,以使该属性不再成为绑定路径的一部分。

class App {
  constructor() {
    this.name = "TemplateBinding";
    this.person = {
      firstName: "John",
      lastName: "Doe"
    };
  }
  getFullName() {
    return this.person.firstName + " " + this.person.lastName;
  }
}
let app = new App();
let template = `
  <div>Hello, {{person.firstName}} {{person.lastName}}!</div>
  <div>Hello, {{app.person.firstName}} {{app.person.lastName}}!</div>
  <div>Hello, {{getFullName()}}!</div>
`;
class BoundElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
        }
      </style>
      ${template}
    `;
  }
}
customElements.define("bound-element", BoundElement);

在上面的代码示例中,我们定义了一个 App 类来存储一些数据,然后在 innerHTML 模板中使用了三个不同的表达式,通过 templatebinding 来显示文本。注意到我们为 firstNamelastName 添加了前缀,以避免属性路径的问题。

四、templatebinding 在实际应用中的应用

templatebinding 在实际应用中有着广泛的应用,比如可以绑定到表单元素、表格等常见页面元素上,实现自动更新,提高页面交互效率。此外,在构建前端组件时,templatebinding 也可以用于动态渲染以及绑定组件的数据状态。

class TodoList extends HTMLElement {
  constructor() {
    super();
    this.todos = [];
    this.attachShadow({ mode: "open" });
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
        }
        ul {
          list-style: none;
          padding: 0;
          margin: 0;
        }
        li {
          display: flex;
          align-items: center;
          margin-bottom: 0.5rem;
        }
        input[type="checkbox"] {
          margin-right: 0.5rem;
        }
        span {
          flex: 1;
        }
      </style>
      <ul></ul>
    `;
    this.render();
  }
  addTodo() {
    let input = this.shadowRoot.querySelector("input[type='text']");
    this.todos.push({
      text: input.value,
      completed: false
    });
    input.value = "";
    this.render();
  }
  toggleCompleted(index) {
    this.todos[index] = {
      ...this.todos[index],
      completed: !this.todos[index].completed
    };
    this.render();
  }
  render() {
    this.shadowRoot.querySelector("ul").innerHTML = `
      ${this.todos
        .map(
          (todo, index) => `
            <li>
              <input
                type="checkbox"
                ${todo.completed ? "checked" : ""}
                onchange="this.parentElement.parentElement.toggleCompleted(${index})"
              />
              <span>${todo.text}</span>
            </li>
          `
        )
        .join("")}
      <li>
        <input type="text" />
        <button onclick="this.parentElement.addTodo()">Add todo</button>
      </li>
    `;
  }
}
customElements.define("todo-list", TodoList);

在上面的代码示例中,我们定义了一个 TodoList 组件,用来显示一个待办事项列表。我们将整个组件放在了 HTML 中,并在模板中使用了 templatebinding。我们在组件类中实现了一些方法来添加和更新 todos 列表,并在每个列表项上绑定了一个 change 事件来切换 completed 属性。最后,我们在 render() 函数中根据 todos 列表生成了列表元素。