[英]What is two way binding?
我读过很多关于 Backbone 不做双向绑定的文章,但我并不完全理解这个概念。
有人可以给我一个例子,说明两种方式绑定在 MVC 代码库中是如何工作的,以及它在 Backbone 中是如何工作的?
双向绑定只是意味着:
Backbone 没有 #2 的“内置”实现(尽管您当然可以使用事件侦听器来实现)。 其他框架(如 Knockout)会自动连接双向绑定。
在 Backbone 中,您可以通过将视图的“render”方法绑定到其模型的“change”事件来轻松实现#1。 要实现#2,您还需要向输入元素添加更改侦听器,并在处理程序中调用model.set
。
双向绑定意味着影响模型的任何与数据相关的更改都会立即传播到匹配的视图,并且视图中所做的任何更改(例如,由用户)都会立即反映在底层模型中. 当应用程序数据发生变化时,UI 也会发生变化,反之亦然。
这是在其上构建 Web 应用程序的一个非常可靠的概念,因为它使“模型”抽象成为可以在应用程序中的任何地方使用的安全的原子数据源。 比如说,如果绑定到视图的模型发生变化,那么无论发生什么,它的匹配 UI(视图)都会反映出来。 并且匹配的 UI(视图)可以安全地用作收集用户输入/数据的手段,从而使应用程序数据保持最新。
从开发人员的角度来看,一个好的双向绑定实现显然应该使模型和某些视图之间的这种连接尽可能简单。
说 Backbone 不支持双向绑定是非常不真实的:虽然它不是框架的核心功能,但可以很简单地使用 Backbone 的 Events 来执行。 对于简单的情况,它需要花费几行明确的代码; 并且对于更复杂的绑定可能变得非常危险。 这是一个简单的案例(未经测试的代码,为了说明而即时编写):
Model = Backbone.Model.extend
defaults:
data: ''
View = Backbone.View.extend
template: _.template("Edit the data: <input type='text' value='<%= data %>' />")
events:
# Listen for user inputs, and edit the model.
'change input': @setData
initialize: (options) ->
# Listen for model's edition, and trigger UI update
@listenTo @model, 'change:data', @render
render: ->
@$el.html @template(@model.attributes)
@
setData: (e) =>
e.preventDefault()
@model.set 'data', $(e.currentTarget).value()
model: new Model()
view = new View {el: $('.someEl'), model: model}
这是原始 Backbone 应用程序中非常典型的模式。 正如人们所见,它需要相当数量的(相当标准的)代码。
AngularJS和其他一些替代品( Ember 、 Knockout ……)提供双向绑定作为第一公民功能。 他们在一些 DSL 下抽象了许多边缘情况,并尽最大努力在他们的生态系统中集成双向绑定。 我们的示例使用 AngularJS 看起来像这样(未经测试的代码,见上文):
<div ng-app="app" ng-controller="MainCtrl">
Edit the data:
<input name="mymodel.data" ng-model="mymodel.data">
</div>
angular.module('app', [])
.controller 'MainCtrl', ($scope) ->
$scope.mymodel = {data: ''}
比较短!
但是,请注意,对于 Backbone 也确实存在一些成熟的双向绑定扩展(以原始的、主观的复杂性递减顺序): Epoxy 、 Stickit 、 ModelBinder ……
例如,Epoxy 的一件很酷的事情是,它允许您在模板 (DOM) 或视图实现 (JavaScript) 中声明您的绑定(模型属性 <-> 视图的 DOM 元素)。 有些人非常不喜欢在 DOM/模板中添加“指令”(例如 AngularJS 所需的 ng-* 属性,或者 Ember 的 data-bind 属性)。
以 Epoxy 为例,您可以将原始 Backbone 应用程序改造成这样的东西 (...):
Model = Backbone.Model.extend
defaults:
data: ''
View = Backbone.Epoxy.View.extend
template: _.template("Edit the data: <input type='text' />")
# or, using the inline form: <input type='text' data-bind='value:data' />
bindings:
'input': 'value:data'
render: ->
@$el.html @template(@model.attributes)
@
model: new Model()
view = new View {el: $('.someEl'), model: model}
总而言之,几乎所有“主流”JS 框架都支持双向绑定。 其中一些,例如 Backbone,确实需要一些额外的工作才能使其顺利运行,但这些都是相同的,它们并没有强制执行特定的方法来开始。 所以这真的是关于你的心态。
此外,您可能对Flux感兴趣,这是一种用于通过循环模式促进单向绑定的 Web 应用程序的不同架构。 它基于在任何数据更改时快速、全面地重新渲染 UI 组件的概念,以确保内聚性并使代码/数据流更容易推理。 在相同的趋势中,您可能想要检查 MVI(模型-视图-意图)的概念,例如Cycle 。
McGarnagle 有一个很好的答案,你会想接受他的,但我想我会提到(因为你问过)数据绑定是如何工作的。
它通常通过每当对数据进行更改时触发事件来实现,这会导致侦听器(例如 UI)被更新。
双向绑定通过执行两次此操作来工作,并注意确保您不会陷入事件循环(事件的更新导致另一个事件被触发)。
我想把它放在评论中,但它变得很长......
实际上emberjs
支持双向绑定,这是 javascript MVC 框架最强大的功能之一。 您可以查看它在其用户指南中提到binding
地方。
对于 emberjs,创建双向绑定是通过在末尾创建一个字符串 Binding 的新属性,然后从全局范围指定一个路径:
App.wife = Ember.Object.create({
householdIncome: 80000
});
App.husband = Ember.Object.create({
householdIncomeBinding: 'App.wife.householdIncome'
});
App.husband.get('householdIncome'); // 80000
// Someone gets raise.
App.husband.set('householdIncome', 90000);
App.wife.get('householdIncome'); // 90000
请注意,绑定不会立即更新。 Ember 会等到所有应用程序代码都运行完毕后再同步更改,因此您可以根据需要多次更改绑定属性,而无需担心值是瞬态时同步绑定的开销。
希望它有助于扩展所选的原始答案。
值得一提的是,有许多不同的解决方案提供了两种方式的绑定,并且播放效果非常好。
我对这个模型绑定器有一个愉快的体验 - https://github.com/theironcook/Backbone.ModelBinder 。 它提供了合理的默认值,还提供了大量自定义 jquery 选择器将模型属性映射到输入元素。
github上有更多扩展的主干扩展/插件列表
LemonadeJS 是另一个值得一提的微型库。 它有 7Kbytes 并且非常好地执行双向绑定。 您可以在没有依赖项或转译的情况下运行它。
<html>
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
<div id='root'></div>
<script>
let Input = function() {
let self = {
input: 'paul@beatles.com'
}
// Any change in the self.input will update the input and vice-versa.
let template = `<>
<h1>{{self.input}}</h1>
<input type='text' @bind='self.input' />
<input type='button' value='Update' onclick="self.input = 'New value'" />
</>`
return lemonade.element(template, self);
}
lemonade.render(Input, document.getElementById('root'));
</script>
</html>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.