- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在尝试使用 Vue 作为一个非常薄的层来将现有模型对象绑定(bind)到 View 。
下面是一个说明我的问题的玩具应用程序。我有一个 GainNode对象,来自网络音频 API。我想绑定(bind)它的 value
到 slider 。
这在 Angular 中是微不足道的。双向绑定(bind)适用于任何对象,无论是否是 Angular 组件的一部分。有没有办法在 Vue 中做类似的事情?
在实际应用中,我有大量以编程方式生成的对象。我需要将它们绑定(bind)到一个组件,例如<Knob v-for='channel in channels' v-model='channel.gainNode.gain.value'>
.
更新:我使用的是解决方法 #2(如下),它似乎工作得很好,直到我尝试 v-model
- 将两个组件绑定(bind)到相同的音频参数。然后它就无法正常工作,以我无法调试的完全神秘的方式。我最终放弃了,正在使用 getters/setters,这是更多的样板文件,但具有优势,你知道......实际工作。
class MyApp {
constructor() {
// core model which I'd prefer to bind to
this.audio = new AudioContext();
this.audioNode = this.audio.createGain();
this.audioNode.gain.value = .8; // want to bind a control to this
// attempts to add reactivity
this.reactiveWrapper = Vue.reactive(this.audioNode.gain);
this.refWrapper = Vue.ref(this.audioNode.gain.value);
}
get gainValue() { return this.audioNode.gain.value; }
set gainValue(value) { this.audioNode.gain.value = value; }
}
let appModel = new MyApp();
let app = Vue.createApp({
template: '#AppView',
data() { return {
// core model which I'd prefer to bind to
model: appModel,
// attempt to add reactivity
dataAliasAudioNode: appModel.audioNode }
}
});
app.mount('#mount');
<script type='text/x-template' id='AppView'>
<div>
model.audioNode.gain.value: {{model.audioNode.gain.value}}
</div>
<hr>
<div>
<div>Binding to getter/setter for <code>model.audioNode.gain.value</code> (works)</div>
<input type='range' min='0' max='1' step='.1' v-model='model.gainValue'>
</div>
<div>
<div>Binding directly to <code>model.audioNode.gain.value</code> (doesn't work)</div>
<input type='range' min='0' max='1' step='.1' v-model='model.audioNode.gain.value'>
</div>
<div>
<div>Binding through <code>model.reactiveWrapper</code> (doesn't work)</div>
<input type='range' min='0' max='1' step='.1' v-model='model.reactiveWrapper.value'>
</div>
<div>
<div>Binding through <code>model.refWrapper</code> (doesn't work)</div>
<input type='range' min='0' max='1' step='.1' v-model='model.refWrapper.value'>
</div>
<div>
<div>Binding through <code>dataAliasAudioNode.gain.value</code> (doesn't work)</div>
<input type='range' min='0' max='1' step='.1' v-model='dataAliasAudioNode.gain.value'>
</div>
</script>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id='mount'></div>
问题附录#1:在探索方法时,我发现(如前所述)如果我绑定(bind)到外部对象的嵌套部分(GainNode
网络音频API)它不是 react 性的,但如果我自己构建一个类似的外部对象,绑定(bind)到嵌套参数是 react 性的。这是示例代码:
// my version Web Audio API's AudioContext, GainNode, and AudioParam
class AudioParamX {
constructor() {
this._value = 0;
}
get value() { return this._value; }
set value(v) { this._value = v; }
}
class ValueParamX extends AudioParamX {
}
class GainNodeX {
constructor() {
this.gain = new ValueParamX();
}
}
class AudioContextX {
createGain() {
return new GainNodeX();
}
}
//==================================================================
class MyApp {
constructor() {
this.audio = new AudioContext();
this.audioNode = this.audio.createGain();
this.xaudio = new AudioContextX();
this.xaudioNode = this.xaudio.createGain();
}
}
let appModel = new MyApp();
let app = Vue.createApp({
template: '#AppView',
data() { return { model: appModel } }
});
app.mount('#mount');
<script type='text/x-template' id='AppView'>
<div>
model.xaudioNode.gain.value: {{model.xaudioNode.gain.value}}
</div>
<div>
model.audioNode.gain.value: {{model.audioNode.gain.value}}
</div>
<hr>
<div>
<div>Binding to <code>model.xaudioNode.gain.value</code> works.</div>
<input type='range' min='0' max='1' step='.05' v-model='model.xaudioNode.gain.value'>
</div>
<div>
<div>Binding to <code>model.audioNode.gain.value</code> doesn't. Why?</div>
<input type='range' min='0' max='1' step='.05' v-model='model.audioNode.gain.value'>
</div>
</script>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id='mount'></div>
解决方法#1:
因此,经过更多探索后,我提出了一种解决方法,可以减少 getter/setter 的样板文件。我要么:
ref
(不知道 Vue.ref
不起作用)或Proxy
对象并调用 $forceUpdate
当调用 setter 时。这两种方法都有效,缺点是我必须将代理公开为成员并绑定(bind)到它,而不是原始对象。但这比同时暴露 getter 和 setter 更好,而且它可以与 v-model
一起使用。 .
class MyApp {
createWrapper(obj, field) {
return {
get [field]() { return obj[field]; },
set [field](v) { obj[field] = v; }
}
}
createProxy(obj) {
let update = () => this.forceUpdate();
return new Proxy(obj, {
get(target, prop) { return target[prop] },
set(target, prop, value) {
update();
return target[prop] = value
}
});
}
watch(obj, prop) {
hookSetter(obj, prop, () => this.forceUpdate());
}
constructor() {
this.audio = new AudioContext();
// core model which I'd prefer to bind to
this.audioNode = this.audio.createGain();
this.audioNode.gain.value = .1; // want to bind a control to this
this.audioNode.connect(this.audio.destination);
// attempts to add reactivity
this.wrapper = this.createWrapper(this.audioNode.gain, 'value');
this.proxy = this.createProxy(this.audioNode.gain);
}
}
let appModel = new MyApp();
let app = Vue.createApp({
template: '<AppView :model="model" />',
data() { return { model: appModel } },
});
app.component('AppView', {
template: '#AppView',
props: ['model'],
mounted() {
this.model.forceUpdate = () => this.$forceUpdate();
}
})
app.mount('#mount');
<style>body { user-select: none; }</style>
<script type='text/x-template' id='AppView'>
<div>
<div>model.audioNode.gain.value: {{model.audioNode.gain.value}}</div>
<div>model.wrapper.value: {{model.wrapper.value}}</div>
<div>model.proxy.value: {{model.wrapper.value}}</div>
</div>
<hr>
<div>
<div>Binding directly to <code>model.audioNode.gain.value</code> (doesn't work)</div>
<input type='range' min='0' max='1' step='.05' v-model='model.audioNode.gain.value'>
</div>
<div>
<div>Binding through <code>model.wrapper.value</code> (works)</div>
<input type='range' min='0' max='1' step='.05' v-model='model.wrapper.value'>
</div>
<div>
<div>Binding through <code>model.proxy.value</code> (works)</div>
<input type='range' min='0' max='1' step='.05' v-model='model.proxy.value'>
</div>
</script>
<div id='mount'></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
解决方法 #2:
另一种解决方法是修补我要监视的访问器并调用 $forceUpdate
在里面。这具有最少 样板。我只是打电话watch(obj, prop)
并且该属性变为响应式(Reactive)。
根据我的口味,这是一个相当可接受的解决方法。但是,当我开始将内容移动到子组件中时,我不确定这些变通方案的效果如何。接下来我要试试看。我也不明白为什么Vue.reference
不做同样的事情。
我想尽可能以最原生的 Vue 方式来做这件事,这似乎是一个非常典型的用例。
class MyApp {
watch(obj, prop) {
hookObjectSetter(obj, prop, () => this.forceUpdate());
}
constructor() {
this.audio = new AudioContext();
// core model which I'd prefer to bind to
this.audioNode = this.audio.createGain();
this.audioNode.gain.value = .1; // want to bind a control to this
this.watch(this.audioNode.gain, 'value'); // make it reactive
}
}
let appModel = new MyApp();
let app = Vue.createApp({
template: '<AppView :model="model" />',
data() { return { model: appModel } },
});
app.component('AppView', {
template: '#AppView',
props: ['model'],
mounted() {
this.model.forceUpdate = () => this.$forceUpdate();
}
})
app.mount('#mount');
function hookObjectSetter(obj, prop, callback) {
let descriptor = Object.getOwnPropertyDescriptor(obj, prop);
if (!descriptor) {
obj = Object.getPrototypeOf(obj);
descriptor = Object.getOwnPropertyDescriptor(obj, prop);
}
if (descriptor && descriptor.configurable) {
let set = descriptor.set || (v => descriptor.value = v);
let get = descriptor.get || (v => descriptor.value);
Object.defineProperty(obj, prop, {
configurable: false, // prevent double-hooking; sorry anybody else!
get,
set(v) {
callback();
return set.apply(this, arguments);
},
});
}
}
<script type='text/x-template' id='AppView'>
<div>
<div>model.audioNode.gain.value: {{model.audioNode.gain.value}}</div>
</div>
<hr>
<div>
<div>Binding directly to <code>model.audioNode.gain.value</code> with custom `watch` (works)</div>
<input type='range' min='0' max='1' step='.05' v-model='model.audioNode.gain.value'>
</div>
</script>
<div id='mount'></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
最佳答案
你的问题很令人兴奋,所以我决定花几个小时找出答案。
首先,我们需要知道当 Vue 在模板上渲染一个值时会发生什么?让我们考虑一下这个模板:
{{ model.audioNode.gain.value }}
如果model
是响应式(Reactive)对象(由 reactive
、 ref
或 computed
... 创建),Vue 将创建一个 getter 将链上的每个对象转换为响应式(Reactive)。因此,这些以下对象将使用 Vue.reactive
转换为 react 形式功能:model.audioNode
, model.audioNode.gain
但只是一些可以转换为 react 对象的类型。这是 the code from Vue reactive package
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}
正如我们所见,键入除 Object
以外的其他类型, Array
, Map
, Set
, WeakMap
, 和 WeakSet
将无效。要知道你的对象是什么类型,你可以调用yourObject.toString()
(what Vue is actually using)。任何不修改 toString
的自定义类方法将是 Object
类型并且可以是 react 性的。在您的示例代码中 model
是object
类型,model.audioNode
类型是 GainNode
.所以它不能被转换成一个 react 对象并且改变它的属性不会触发 Vue 重新渲染。
那么为什么 setter 方法有效?
它实际上不起作用。让我们考虑一下这个片段:
class MyApp {
constructor() {
this.audio = new AudioContext();
this.audioNode = this.audio.createGain();
this.audioNode.gain.value = .8;
}
get gainValue() { return this.audioNode.gain.value; }
set gainValue(value) { this.audioNode.gain.value = value; }
}
let appModel = new MyApp();
let app = Vue.createApp({
template: '#AppView',
data() {
return {
model: appModel,
}
}
});
app.mount('#mount');
<script type='text/x-template' id='AppView'>
<div>
model.audioNode.gain.value: {{model.audioNode.gain.value}}
</div>
<hr>
<div>
<div>Binding to getter/setter for <code>model.gainValue</code> (does NOT work)</div>
<input type='range' min='0' max='1' step='.1' :value="model.audioNode.gain.value" @input="model.gainValue=$event.target.value">
</div>
</script>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id='mount'></div>
上面代码片段中的 setter 不起作用。让我们考虑另一个片段:
class MyApp {
constructor() {
this.audio = new AudioContext();
this.audioNode = this.audio.createGain();
this.audioNode.gain.value = .8;
}
get gainValue() { return this.audioNode.gain.value; }
set gainValue(value) { this.audioNode.gain.value = value; }
}
let appModel = new MyApp();
let app = Vue.createApp({
template: '#AppView',
data() {
return {
model: appModel,
}
}
});
app.mount('#mount');
<script type='text/x-template' id='AppView'>
<div>
model.audioNode.gain.value: {{model.audioNode.gain.value}}
</div>
<hr>
<div>
<div>Binding to getter/setter for <code>model.gainValue</code> (does work)</div>
<input type='range' min='0' max='1' step='.1' :value="model.gainValue" @input="model.gainValue=$event.target.value">
</div>
</script>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id='mount'></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id='mount'></div>
上面代码片段中的 setter 确实有效。看看那行 <input type='range' min='0' max='1' step='.1' :value="model.gainValue" @input="model.gainValue=$event.target.value">
这实际上是您使用 v-model="model.gainValue"
时发生的情况.它起作用的原因是行 :value="model.gainValue"
将随时触发 Vue 重新渲染 model.gainValue
已更新。并且 Vue 不是完全选择性的重新渲染。 所以当整个模板被重新渲染时 block {{ model.audioNode.gain.value }}
也会重新渲染。
为了证明 Vue 不是完全选择性的重新渲染,让我们考虑这个片段:
class MyApp {
constructor() {
this.audio = new AudioContext();
this.audioNode = this.audio.createGain();
this.audioNode.gain.value = .8;
}
get gainValue() { return this.audioNode.gain.value; }
set gainValue(value) { this.audioNode.gain.value = value; }
}
let appModel = new MyApp();
let app = Vue.createApp({
template: '#AppView',
data() {
return {
model: appModel,
anIndependentProperty: 1
}
},
methods: {
update(event){
this.model.audioNode.gain.value = event.target.value
this.anIndependentProperty = event.target.value
}
}
});
app.mount('#mount');
<script type='text/x-template' id='AppView'>
<div>
model.audioNode.gain.value: {{model.audioNode.gain.value}}
</div>
<div>
anIndependentProperty: {{anIndependentProperty}}
</div>
<hr>
<div>
<div>anIndependentProperty trigger re-render so the template will be updated</div>
<input type='range' min='0' max='1' step='.1' :value="model.audioNode.gain.value" @input="update">
</div>
</script>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id='mount'></div>
在上面的例子中 anIndependentProperty
是响应式(Reactive)的,它会在更新时触发 Vue 重新渲染。当 Vue 重新渲染模板 block 时 {{model.audioNode.gain.value}}
也会更新。
此解决方案仅适用于在模板中使用属性的情况。如果你想使用 computed
从您的类属性中,您必须使用 setter/getter 方法滚动。
class MyApp {
constructor() {
this.audio = new AudioContext();
this.audioNode = this.audio.createGain();
this.audioNode.gain.value = .8;
}
}
let appModel = new MyApp();
let app = Vue.createApp({
template: '#AppView',
data() {
return {
model: appModel,
reactiveControl: 0
}
},
});
app.mount('#mount');
<script type='text/x-template' id='AppView'>
<input type="hidden" :value="reactiveControl">
<div>
<div>Binding to <code>model.audioNode.gain.value (works):</code> {{model.audioNode.gain.value}} </div>
<input type='range' min='0' max='1' step='.1' :value="model.audioNode.gain.value" @input="model.audioNode.gain.value=$event.target.value; reactiveControl++">
</div>
<div>
<div>Binding to other property <code>model.audioNode.channelCount (works):</code> {{model.audioNode.channelCount}}</div>
<input type='range' min='1' max='32' step='1' :value="model.audioNode.channelCount" @input="model.audioNode.channelCount=$event.target.value; reactiveControl++">
</div>
You can bind to any property now...
</script>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id='mount'></div>
请注意这一行:
<input type="hidden" :value="reactiveControl">
每当reactiveControl
变量变化,模板会更新,其他变量也会更新。所以你只需要改变reactiveControl
的值每当您更新类属性时。
关于vue.js - Vue 绑定(bind)到外部对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73285075/
如何指示 webpack 排除所有 d3 模块? // does not work externals: { "d3-*": "d3" } 所以如果应用导入了d3-submod
这个问题在这里已经有了答案: 9年前关闭。 Possible Duplicate: What does “outer =>” really mean? 我在哪里可以找到有关信息 trait After
这是一个简单的循环,我正在尝试对性能进行基准测试。 var extremeLoop=function(n){ var time=new Date() var t=0; for(
问题+概述 下面是两个片段,其中包含最初隐藏的 div,然后通过单击 button 和 jQuery 的 .show() 显示。两个 div 都具有由外部样式表应用的 display: grid; 样
我有一个 HTML 页面和一个单独的 .js 文件,该文件包含在带有 的页面中标签。 这是我的 .js 文件: element = document.getElementById("test");
我在 linux 静态库项目中有 3 个文件,我想在两个类方法实现文件中使用的静态字段存在链接问题。我有 class1.h、class1main.cpp 和 class1utils.cpp。 clas
我正在尝试将颜色背景更改为默认背景颜色,当我点击输入框外 我尝试使用“null”或“none”但没有用? window.addEventListener('click', outsideClick);
我正在编写一个应用程序,要求用户在手机上选择各种类型的文件。我使用此代码启动文件选择器 Intent : Intent intent = new Intent(Intent.ACTION_GET_C
在 android 中,不可移动(内部)的外部存储和内部存储有什么区别?我不确定在哪里保存我的数据。我只需要保存一个人可以随时提取的游戏统计数据 谢谢 最佳答案 在许多较新的设备中,将不再有物理区别,
在 C++ 中,假设我们有这个头文件: myglobals.h #ifndef my_globals_h #define my_globals_h int monthsInYear = 12; #en
我正在尝试使用 externs 在 C++ 中连接到 Ada。这两种实现有什么区别? 实现A namespace Ada { extern "C" { int getN
这个问题在这里已经有了答案: Get selected element's outer HTML (30 个答案) 关闭 2 年前。 想象一下我们有这样的东西: Hello World 如果我们这样
假设我在模块的顶部有这个: Public Declare Function getCustomerDetails Lib "CustomerFunctions" () As Long 如果我从 VB6
我目前正在使用这段代码: var wordRandomizer = { run: function (targetElem) { var markup = this.creat
我们正在使用 SVN 试水,并以 Beanstalk 作为主机。我们的设置如下所示: 存储库:模块 模块一 模块二 模块 3 存储库:网站1 自定义网站代码 svn:对模块 1 的外部引用 svn:对
有没有办法在负载均衡器中设置自动外部 IP 分配给像谷歌这样的服务? 我在裸机上运行 Kubernetes。 谢谢 最佳答案 使用 nodePort 类型的服务,它会将您的服务绑定(bind)到所有节
是否有可能在 Controller 之外使用 generateUrl() 方法? 我尝试在带有 $this->get('router') 的自定义存储库类中使用它,但它没有用。 更新 我在这里找到了一
我目前正在尝试通过 Webpack 外部对象外部化 Angular 依赖项来缩短构建时间。到目前为止,我已经为 React 和其他小库实现了这一目标。 如果我只是移动 '@angular/compil
我想创建一个自动应用其他插件的插件(外部插件)。这要求在我称为“应用插件”之前为插件设置构建脚本依赖项。但是似乎我无法在插件中添加buildscript依赖项,或者得到了: 您不能更改处于未解析状态的
我是R包的创建者EnvStats . 有一个我经常使用的函数,叫做 stripChart .我刚开始学习ggplot2 ,并在过去几天里仔细研究了 Hadley 的书、Winston 的书、Stack
我是一名优秀的程序员,十分优秀!