使用 Polymer 在同一 Web 组件的实例之间进行通信的最佳方式?
Best way to communicate between instances of the same web component with Polymer?
我正在尝试在同一元素的实例之间同步我的一些 Web 组件属性,因此如果其中一个属性发生更改,则相同的 属性 会在所有实例中更新并具有相应的绑定和事件.
注意:我想使用Polymer Data System Concepts进行实例间的通信。
例子
我的-element.html
<dom-module id="my-element">
<script>
Polymer({
is: 'my-element',
properties: {
myProp: {
type: String,
notify: true
}
});
</script>
</dom-module>
我的-其他-element.html
<dom-module id="my-other-element">
<template>
<my-element my-prop="{{otherProp}}"></my-element>
</template>
<script>
Polymer({
is: 'my-other-element',
properties: {
otherProp: {
type: String,
notify: true,
readOnly: true
}
}
})
</script>
</dom-module>
我的-app.html
<dom-module id="my-app">
<template>
<my-element id="element"></my-element>
<my-other-element id="otherElement"
on-other-prop-changed="onPropChanged"
></my-other-element>
</template>
<script>
Polymer({
is: 'my-app',
attached: function () {
// should set 'myProp' to 'test' and trigger
// the event 'my-prop-changed' in all my-element instances
this.$.element.myProp = 'test'
},
onPropChanged: function (ev, detail) {
console.log(detail.value); // should print 'test'
console.log(this.$.element.myProp); // should print 'test'
console.log(this.$.otherElement.otherProp); // should print 'test'
}
});
</script>
</dom-module>
PD: 使用类似标准的模式和良好实践会很好。
tl;博士
我创建了一个自定义行为,可以同步具有 notify: true
的所有元素的属性。工作原型:JSBin.
目前,此原型不区分不同种类的元素,这意味着它只能同步同一自定义元素的实例 - 但这可以轻松更改。
您还可以定制行为,以便只同步所需的属性,而不是全部与 notify: true
同步。但是,如果您采用此路径,请注意您要同步的所有属性 必须 具有 notify: true
,因为该行为会侦听 <property-name>-changed
事件,该事件仅当 属性 具有 notify: true
.
时才会触发
详情
让我们从自定义 SyncBehavior
行为开始:
(function() {
var SyncBehaviorInstances = [];
var SyncBehaviorLock = false;
SyncBehavior = {
attached: function() {
// Add instance
SyncBehaviorInstances.push(this);
// Add listeners
for(var property in this.properties) {
if('notify' in this.properties[property] && this.properties[property].notify) {
// Watch all properties with notify = true
var eventHanler = this._eventHandlerForPropertyType(this.properties[property].type.name);
this.listen(this, Polymer.CaseMap.camelToDashCase(property) + '-changed', eventHanler);
}
}
},
detached: function() {
// Remove instance
var index = SyncBehaviorInstances.indexOf(this);
if(index >= 0) {
SyncBehaviorInstances.splice(index, 1);
}
// Remove listeners
for(var property in this.properties) {
if('notify' in this.properties[property] && this.properties[property].notify) {
// Watch all properties with notify = true
var eventHanler = this._eventHandlerForPropertyType(this.properties[property].type.name);
this.unlisten(this, Polymer.CaseMap.camelToDashCase(property) + '-changed', eventHanler);
}
}
},
_eventHandlerForPropertyType: function(propertyType) {
switch(propertyType) {
case 'Array':
return '__syncArray';
case 'Object':
return '__syncObject';
default:
return '__syncPrimitive';
}
},
__syncArray: function(event, details) {
if(SyncBehaviorLock) {
return; // Prevent cycles
}
SyncBehaviorLock = true; // Lock
var target = event.target;
var prop = Polymer.CaseMap.dashToCamelCase(event.type.substr(0, event.type.length - 8));
if(details.path === undefined) {
// New array -> assign by reference
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
instance.set(prop, details.value);
}
});
} else if(details.path.endsWith('.splices')) {
// Array mutation -> apply notifySplices
var splices = details.value.indexSplices;
// for all other instances: assign reference if not the same, otherwise call 'notifySplices'
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
var instanceReference = instance.get(prop);
var targetReference = target.get(prop);
if(instanceReference !== targetReference) {
instance.set(prop, targetReference);
} else {
instance.notifySplices(prop, splices);
}
}
});
}
SyncBehaviorLock = false; // Unlock
},
__syncObject: function(event, details) {
var target = event.target;
var prop = Polymer.CaseMap.dashToCamelCase(event.type.substr(0, event.type.length - 8));
if(details.path === undefined) {
// New object -> assign by reference
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
instance.set(prop, details.value);
}
});
} else {
// Property change -> assign by reference if not the same, otherwise call 'notifyPath'
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
var instanceReference = instance.get(prop);
var targetReference = target.get(prop);
if(instanceReference !== targetReference) {
instance.set(prop, targetReference);
} else {
instance.notifyPath(details.path, details.value);
}
}
});
}
},
__syncPrimitive: function(event, details) {
var target = event.target;
var value = details.value;
var prop = Polymer.CaseMap.dashToCamelCase(event.type.substr(0, event.type.length - 8));
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
instance.set(prop, value);
}
});
},
};
})();
请注意,我使用 IIFE 模式隐藏了包含自定义元素所有实例的变量 my-element
。这是必不可少的,所以不要更改它。
如您所见,该行为由六个函数组成,即:
attached
,它将当前实例添加到实例列表中,并为具有 notify: true
. 的所有属性注册侦听器
detached
,它从实例列表中删除当前实例并删除具有 notify: true
. 的所有属性的侦听器
_eventHandlerForPropertyType
,其中 returns 函数 4-6 之一的名称,取决于 属性 类型。
__syncArray
,同步实例之间的数组类型属性。请注意,我忽略了当前目标并实现了一个简单的锁定机制以避免循环。该方法处理两种情况:分配一个新数组和改变一个现有数组。
__syncObject
,同步实例之间的对象类型属性。请注意,我忽略了当前目标并实现了一个简单的锁定机制以避免循环。该方法处理两种情况:分配一个新对象,以及更改现有对象的 属性。
__syncPrimitive
,在实例之间同步属性的原始值。请注意,我忽略了当前目标以避免循环。
为了 test-drive 我的新行为,我创建了一个示例自定义元素:
<dom-module id="my-element">
<template>
<style>
:host {
display: block;
}
</style>
<h2>Hello [[id]]</h2>
<ul>
<li>propString: [[propString]]</li>
<li>
propArray:
<ol>
<template is="dom-repeat" items="[[propArray]]">
<li>[[item]]</li>
</template>
</ol>
</li>
<li>
propObject:
<ul>
<li>name: [[propObject.name]]</li>
<li>surname: [[propObject.surname]]</li>
</ul>
</li>
</ul>
</template>
<script>
Polymer({
is: 'my-element',
behaviors: [
SyncBehavior,
],
properties: {
id: {
type: String,
},
propString: {
type: String,
notify: true,
value: 'default value',
},
propArray: {
type: Array,
notify: true,
value: function() {
return ['a', 'b', 'c'];
},
},
propObject: {
type: Object,
notify: true,
value: function() {
return {'name': 'John', 'surname': 'Doe'};
},
},
},
pushToArray: function(item) {
this.push('propArray', item);
},
pushToNewArray: function(item) {
this.set('propArray', [item]);
},
popFromArray: function() {
this.pop('propArray');
},
setObjectName: function(name) {
this.set('propObject.name', name);
},
setNewObjectName: function(name) {
this.set('propObject', {'name': name, 'surname': 'unknown'});
},
});
</script>
</dom-module>
有1个String属性、1个Array属性、1个Object属性;全部 notify: true
。自定义元素还实现了 SyncBehavior
行为。
要将以上所有内容组合到一个工作原型中,您只需执行以下操作:
<template is="dom-bind">
<h4>Primitive type</h4>
propString: <input type="text" value="{{propString::input}}" />
<h4>Array type</h4>
Push to propArray: <input type="text" id="propArrayItem" /> <button onclick="_propArrayItem()">Push</button> <button onclick="_propNewArrayItem()">Push to NEW array</button> <button onclick="_propPopArrayItem()">Delete last element</button>
<h4>Object type</h4>
Set 'name' of propObject: <input type="text" id="propObjectName" /> <button onclick="_propObjectName()">Set</button> <button onclick="_propNewObjectName()">Set to NEW object</button> <br />
<script>
function _propArrayItem() {
one.pushToArray(propArrayItem.value);
}
function _propNewArrayItem() {
one.pushToNewArray(propArrayItem.value);
}
function _propPopArrayItem() {
one.popFromArray();
}
function _propObjectName() {
one.setObjectName(propObjectName.value);
}
function _propNewObjectName() {
one.setNewObjectName(propObjectName.value);
}
</script>
<my-element id="one" prop-string="{{propString}}"></my-element>
<my-element id="two"></my-element>
<my-element id="three"></my-element>
<my-element id="four"></my-element>
</template>
在这个原型中,我创建了 my-element
的四个实例。一个将 propString
绑定到输入,而其他则根本没有任何绑定。我创建了一个简单的表格,涵盖了我能想到的所有场景:
- 更改原始值。
- 将项目推入数组。
- 正在创建一个新数组(包含一项)。
- 正在从数组中删除一个项目。
- 设置对象属性.
- 正在创建新对象。
编辑
我更新了我的 post 和原型以解决以下问题:
- non-primitive 值的同步,即数组和对象。
- 正确地将 属性 名称从 Dash 大小写转换为 Camel 大小写(和 vice-versa)。
像这样的问题我个人的看法是用flux architecture.
您创建了一个包装元素,它将所有信息分发给子元素。所有更改都通过主要组件进行。
<app-wrapper>
<component-x attr="[[someParam]]" />
<component-x attr="[[someParam]]" />
<component-x attr="[[someParam]]" />
</app-wrapper>
component-x
正在 app-wrapper
上触发更改值事件并且 app-wrapper
正在更新 someValue
,注意它是 one-way-binding.
有个component for this, which is implementing the redux
architecture, but its also possible to code your own. It's more or less the observer pattern
my-app.html试试这个。我看不出有什么理由不在这里使用 two-way 绑定。
<dom-module id="my-app">
<template>
<my-element my-prop="{{myProp}}"></my-element>
<my-element my-prop="{{myProp}}"></my-element>
</template>
<script>
Polymer({
is: 'my-app',
ready: function() {
this.myProp = 'test';
}
});
</script>
</dom-module>
尽管使用 properties
对象而不是 ready
回调为 myProp
提供默认值可能是更好的做法。示例:
Polymer({
is: 'my-app',
properties: {
myProp: {
type: String,
value: 'test'
}
});
我们创建了一个组件来同步不同实例之间的数据。我们的组件是:
<dom-module id="sync-data">
<template>
<p>Debug info: {scope:[[scope]], key:[[key]], value:[[value]]}</p>
</template>
<script>
(function () {
var items = []
var propagateChangeStatus = {}
var togglePropagationStatus = function (status) {
propagateChangeStatus[this.scope + '|' + this.key] = status
}
var shouldPropagateChange = function () {
return propagateChangeStatus[this.scope + '|' + this.key] !== false
}
var propagateChange = function (key, scope, value) {
if (shouldPropagateChange.call(this)) {
togglePropagationStatus.call(this, false)
var itemsLength = items.length
for (var idx = 0; idx < itemsLength; idx += 1) {
if (items[idx] !== this && items[idx].key === key && items[idx].scope === scope) {
items[idx].set('value', value)
}
}
togglePropagationStatus.call(this, true)
}
}
Polymer({
is: 'sync-data',
properties: {
key: {
type: String,
value: ''
},
scope: {
type: String,
value: ''
},
value: {
type: String,
notify: true,
observer: '_handleValueChanged',
value: ''
}
},
created: function () {
items.push(this)
},
_handleValueChanged: function (newValue, oldValue) {
this.typeof = typeof newValue
propagateChange.call(this, this.key, this.scope, newValue)
}
})
})()
</script>
</dom-module>
我们在这样的组件中使用它:
<sync-data
key="email"
scope="user"
value="{{email}}"></sync-data>
在另一个类似这样的组件中:
<sync-data
key="email"
scope="user"
value="{{userEmail}}"></sync-data>
通过这种方式,我们可以获得聚合物的事件和绑定的原生行为
我正在尝试在同一元素的实例之间同步我的一些 Web 组件属性,因此如果其中一个属性发生更改,则相同的 属性 会在所有实例中更新并具有相应的绑定和事件.
注意:我想使用Polymer Data System Concepts进行实例间的通信。
例子
我的-element.html
<dom-module id="my-element">
<script>
Polymer({
is: 'my-element',
properties: {
myProp: {
type: String,
notify: true
}
});
</script>
</dom-module>
我的-其他-element.html
<dom-module id="my-other-element">
<template>
<my-element my-prop="{{otherProp}}"></my-element>
</template>
<script>
Polymer({
is: 'my-other-element',
properties: {
otherProp: {
type: String,
notify: true,
readOnly: true
}
}
})
</script>
</dom-module>
我的-app.html
<dom-module id="my-app">
<template>
<my-element id="element"></my-element>
<my-other-element id="otherElement"
on-other-prop-changed="onPropChanged"
></my-other-element>
</template>
<script>
Polymer({
is: 'my-app',
attached: function () {
// should set 'myProp' to 'test' and trigger
// the event 'my-prop-changed' in all my-element instances
this.$.element.myProp = 'test'
},
onPropChanged: function (ev, detail) {
console.log(detail.value); // should print 'test'
console.log(this.$.element.myProp); // should print 'test'
console.log(this.$.otherElement.otherProp); // should print 'test'
}
});
</script>
</dom-module>
PD: 使用类似标准的模式和良好实践会很好。
tl;博士
我创建了一个自定义行为,可以同步具有 notify: true
的所有元素的属性。工作原型:JSBin.
目前,此原型不区分不同种类的元素,这意味着它只能同步同一自定义元素的实例 - 但这可以轻松更改。
您还可以定制行为,以便只同步所需的属性,而不是全部与 notify: true
同步。但是,如果您采用此路径,请注意您要同步的所有属性 必须 具有 notify: true
,因为该行为会侦听 <property-name>-changed
事件,该事件仅当 属性 具有 notify: true
.
详情
让我们从自定义 SyncBehavior
行为开始:
(function() {
var SyncBehaviorInstances = [];
var SyncBehaviorLock = false;
SyncBehavior = {
attached: function() {
// Add instance
SyncBehaviorInstances.push(this);
// Add listeners
for(var property in this.properties) {
if('notify' in this.properties[property] && this.properties[property].notify) {
// Watch all properties with notify = true
var eventHanler = this._eventHandlerForPropertyType(this.properties[property].type.name);
this.listen(this, Polymer.CaseMap.camelToDashCase(property) + '-changed', eventHanler);
}
}
},
detached: function() {
// Remove instance
var index = SyncBehaviorInstances.indexOf(this);
if(index >= 0) {
SyncBehaviorInstances.splice(index, 1);
}
// Remove listeners
for(var property in this.properties) {
if('notify' in this.properties[property] && this.properties[property].notify) {
// Watch all properties with notify = true
var eventHanler = this._eventHandlerForPropertyType(this.properties[property].type.name);
this.unlisten(this, Polymer.CaseMap.camelToDashCase(property) + '-changed', eventHanler);
}
}
},
_eventHandlerForPropertyType: function(propertyType) {
switch(propertyType) {
case 'Array':
return '__syncArray';
case 'Object':
return '__syncObject';
default:
return '__syncPrimitive';
}
},
__syncArray: function(event, details) {
if(SyncBehaviorLock) {
return; // Prevent cycles
}
SyncBehaviorLock = true; // Lock
var target = event.target;
var prop = Polymer.CaseMap.dashToCamelCase(event.type.substr(0, event.type.length - 8));
if(details.path === undefined) {
// New array -> assign by reference
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
instance.set(prop, details.value);
}
});
} else if(details.path.endsWith('.splices')) {
// Array mutation -> apply notifySplices
var splices = details.value.indexSplices;
// for all other instances: assign reference if not the same, otherwise call 'notifySplices'
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
var instanceReference = instance.get(prop);
var targetReference = target.get(prop);
if(instanceReference !== targetReference) {
instance.set(prop, targetReference);
} else {
instance.notifySplices(prop, splices);
}
}
});
}
SyncBehaviorLock = false; // Unlock
},
__syncObject: function(event, details) {
var target = event.target;
var prop = Polymer.CaseMap.dashToCamelCase(event.type.substr(0, event.type.length - 8));
if(details.path === undefined) {
// New object -> assign by reference
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
instance.set(prop, details.value);
}
});
} else {
// Property change -> assign by reference if not the same, otherwise call 'notifyPath'
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
var instanceReference = instance.get(prop);
var targetReference = target.get(prop);
if(instanceReference !== targetReference) {
instance.set(prop, targetReference);
} else {
instance.notifyPath(details.path, details.value);
}
}
});
}
},
__syncPrimitive: function(event, details) {
var target = event.target;
var value = details.value;
var prop = Polymer.CaseMap.dashToCamelCase(event.type.substr(0, event.type.length - 8));
SyncBehaviorInstances.forEach(function(instance) {
if(instance !== target) {
instance.set(prop, value);
}
});
},
};
})();
请注意,我使用 IIFE 模式隐藏了包含自定义元素所有实例的变量 my-element
。这是必不可少的,所以不要更改它。
如您所见,该行为由六个函数组成,即:
attached
,它将当前实例添加到实例列表中,并为具有notify: true
. 的所有属性注册侦听器
detached
,它从实例列表中删除当前实例并删除具有notify: true
. 的所有属性的侦听器
_eventHandlerForPropertyType
,其中 returns 函数 4-6 之一的名称,取决于 属性 类型。__syncArray
,同步实例之间的数组类型属性。请注意,我忽略了当前目标并实现了一个简单的锁定机制以避免循环。该方法处理两种情况:分配一个新数组和改变一个现有数组。__syncObject
,同步实例之间的对象类型属性。请注意,我忽略了当前目标并实现了一个简单的锁定机制以避免循环。该方法处理两种情况:分配一个新对象,以及更改现有对象的 属性。__syncPrimitive
,在实例之间同步属性的原始值。请注意,我忽略了当前目标以避免循环。
为了 test-drive 我的新行为,我创建了一个示例自定义元素:
<dom-module id="my-element">
<template>
<style>
:host {
display: block;
}
</style>
<h2>Hello [[id]]</h2>
<ul>
<li>propString: [[propString]]</li>
<li>
propArray:
<ol>
<template is="dom-repeat" items="[[propArray]]">
<li>[[item]]</li>
</template>
</ol>
</li>
<li>
propObject:
<ul>
<li>name: [[propObject.name]]</li>
<li>surname: [[propObject.surname]]</li>
</ul>
</li>
</ul>
</template>
<script>
Polymer({
is: 'my-element',
behaviors: [
SyncBehavior,
],
properties: {
id: {
type: String,
},
propString: {
type: String,
notify: true,
value: 'default value',
},
propArray: {
type: Array,
notify: true,
value: function() {
return ['a', 'b', 'c'];
},
},
propObject: {
type: Object,
notify: true,
value: function() {
return {'name': 'John', 'surname': 'Doe'};
},
},
},
pushToArray: function(item) {
this.push('propArray', item);
},
pushToNewArray: function(item) {
this.set('propArray', [item]);
},
popFromArray: function() {
this.pop('propArray');
},
setObjectName: function(name) {
this.set('propObject.name', name);
},
setNewObjectName: function(name) {
this.set('propObject', {'name': name, 'surname': 'unknown'});
},
});
</script>
</dom-module>
有1个String属性、1个Array属性、1个Object属性;全部 notify: true
。自定义元素还实现了 SyncBehavior
行为。
要将以上所有内容组合到一个工作原型中,您只需执行以下操作:
<template is="dom-bind">
<h4>Primitive type</h4>
propString: <input type="text" value="{{propString::input}}" />
<h4>Array type</h4>
Push to propArray: <input type="text" id="propArrayItem" /> <button onclick="_propArrayItem()">Push</button> <button onclick="_propNewArrayItem()">Push to NEW array</button> <button onclick="_propPopArrayItem()">Delete last element</button>
<h4>Object type</h4>
Set 'name' of propObject: <input type="text" id="propObjectName" /> <button onclick="_propObjectName()">Set</button> <button onclick="_propNewObjectName()">Set to NEW object</button> <br />
<script>
function _propArrayItem() {
one.pushToArray(propArrayItem.value);
}
function _propNewArrayItem() {
one.pushToNewArray(propArrayItem.value);
}
function _propPopArrayItem() {
one.popFromArray();
}
function _propObjectName() {
one.setObjectName(propObjectName.value);
}
function _propNewObjectName() {
one.setNewObjectName(propObjectName.value);
}
</script>
<my-element id="one" prop-string="{{propString}}"></my-element>
<my-element id="two"></my-element>
<my-element id="three"></my-element>
<my-element id="four"></my-element>
</template>
在这个原型中,我创建了 my-element
的四个实例。一个将 propString
绑定到输入,而其他则根本没有任何绑定。我创建了一个简单的表格,涵盖了我能想到的所有场景:
- 更改原始值。
- 将项目推入数组。
- 正在创建一个新数组(包含一项)。
- 正在从数组中删除一个项目。
- 设置对象属性.
- 正在创建新对象。
编辑
我更新了我的 post 和原型以解决以下问题:
- non-primitive 值的同步,即数组和对象。
- 正确地将 属性 名称从 Dash 大小写转换为 Camel 大小写(和 vice-versa)。
像这样的问题我个人的看法是用flux architecture.
您创建了一个包装元素,它将所有信息分发给子元素。所有更改都通过主要组件进行。
<app-wrapper>
<component-x attr="[[someParam]]" />
<component-x attr="[[someParam]]" />
<component-x attr="[[someParam]]" />
</app-wrapper>
component-x
正在 app-wrapper
上触发更改值事件并且 app-wrapper
正在更新 someValue
,注意它是 one-way-binding.
有个component for this, which is implementing the redux
architecture, but its also possible to code your own. It's more or less the observer pattern
my-app.html试试这个。我看不出有什么理由不在这里使用 two-way 绑定。
<dom-module id="my-app">
<template>
<my-element my-prop="{{myProp}}"></my-element>
<my-element my-prop="{{myProp}}"></my-element>
</template>
<script>
Polymer({
is: 'my-app',
ready: function() {
this.myProp = 'test';
}
});
</script>
</dom-module>
尽管使用 properties
对象而不是 ready
回调为 myProp
提供默认值可能是更好的做法。示例:
Polymer({
is: 'my-app',
properties: {
myProp: {
type: String,
value: 'test'
}
});
我们创建了一个组件来同步不同实例之间的数据。我们的组件是:
<dom-module id="sync-data">
<template>
<p>Debug info: {scope:[[scope]], key:[[key]], value:[[value]]}</p>
</template>
<script>
(function () {
var items = []
var propagateChangeStatus = {}
var togglePropagationStatus = function (status) {
propagateChangeStatus[this.scope + '|' + this.key] = status
}
var shouldPropagateChange = function () {
return propagateChangeStatus[this.scope + '|' + this.key] !== false
}
var propagateChange = function (key, scope, value) {
if (shouldPropagateChange.call(this)) {
togglePropagationStatus.call(this, false)
var itemsLength = items.length
for (var idx = 0; idx < itemsLength; idx += 1) {
if (items[idx] !== this && items[idx].key === key && items[idx].scope === scope) {
items[idx].set('value', value)
}
}
togglePropagationStatus.call(this, true)
}
}
Polymer({
is: 'sync-data',
properties: {
key: {
type: String,
value: ''
},
scope: {
type: String,
value: ''
},
value: {
type: String,
notify: true,
observer: '_handleValueChanged',
value: ''
}
},
created: function () {
items.push(this)
},
_handleValueChanged: function (newValue, oldValue) {
this.typeof = typeof newValue
propagateChange.call(this, this.key, this.scope, newValue)
}
})
})()
</script>
</dom-module>
我们在这样的组件中使用它:
<sync-data
key="email"
scope="user"
value="{{email}}"></sync-data>
在另一个类似这样的组件中:
<sync-data
key="email"
scope="user"
value="{{userEmail}}"></sync-data>
通过这种方式,我们可以获得聚合物的事件和绑定的原生行为