一、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
来显示文本。注意到我们为 firstName
和 lastName
添加了前缀,以避免属性路径的问题。
四、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
列表生成了列表元素。