简体   繁体   English

使用 SFC 绑定到外部 object 的 Vue

[英]Vue binding to external object with SFC

I asked a very similar question a few weeks ago, trying to bind a UI control to a web audio AudioParam object in a reactive manner.几周前我问了一个非常相似的问题,试图以反应方式将 UI 控件绑定到 web 音频AudioParam object。 The only thing I could make work reliably was using a getter/setter on my model object.我唯一可以可靠地工作是在我的 model object 上使用 getter/setter。 That wasn't too bad when not using the composition API.当不使用组合 API 时,这并不算太糟糕。 Here's that in action:实际操作:

 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 { 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 (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 to <code>reactive(model.audioNode.gain)</code> (doesn't work)</div> <input type='range' min='0' max='1' step='.1' v-model='model.reactiveWrapper.value'> </div> <div> <div>Binding to <code>ref(model.audioNode.gain.value)</code> (doesn't work)</div> <input type='range' min='0' max='1' step='.1' v-model='model.refWrapper.value'> </div> </script> <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script> <div id='mount'></div>

Now I'm trying to use the composition API in a SFC (single file component).现在我正在尝试在 SFC(单文件组件)中使用组合 API。 I'm having the same issue, but this time the solution requires even more boilerplate.我遇到了同样的问题,但这次解决方案需要更多样板文件。 Unfortunately this can't be a runnable snippet, so you'll have to take my word that, exactly as in the example above, trying to use reactive on the AudioParam doesn't work:不幸的是,这不能是一个可运行的片段,所以你必须相信我的话,就像上面的例子一样,尝试在 AudioParam 上使用reactive式不起作用:

<script setup>
import { reactive, computed } from "vue";

let audio = new AudioContext();
let volume = audio.createGain(); // I want to bind to this

// this doesn't work
let reactiveGain = reactive(volume.gain);

// this also doesn't work
const gainComputed = computed({
  get: () => volume.gain.value,
  set: val => volume.gain.value = val,
})

// This does.
// Is it possible to achieve this effect without all this boilerplate?
// Also, such that I can still use `v-model` in my template?
class Model {
  set gain(value) { volume.gain.value = value; }
  get gain() { return volume.gain.value; }
}
let model = reactive(new Model());

</script>

<template>
  <div>
    Volume: {{ model.gain.toFixed(2) }}
  </div>

  <div>
    This works
    <input type="range" min="0" max="1" step=".01" v-model="model.gain">
  </div>

  <div>
    This doesn't
    <input type="range" min="0" max="1" step=".01" v-model="reactiveGain.value">
  </div>

  <div>
    This doesn't
    <input type="range" min="0" max="1" step=".01" v-model="gainComputed">
  </div>
</template>

Is there a better way to reactively bind to objects outside of Vue?有没有更好的方法来响应绑定到 Vue 之外的对象?

OK, the best I've been able to come up with is this.好的,我能想到的最好的就是这个。 I'll post it as a potential answer to my question, but wait to see if someone has something better before I accept it.我会将其发布为我的问题的潜在答案,但在我接受之前,请等待看看是否有人有更好的东西。

I created a class for wrapping an AudioParam :我创建了一个 class 用于包装AudioParam

import { reactive } from "vue";

export function wrapReactive(obj, field) {
    return reactive({
      set value(value) { obj[field] = value; },
      get value() { return obj[field]; }
    });
}
<script setup>
import { wrapReactive } from "./wrapReactive.mjs";

let audio = new AudioContext();
let volume = audio.createGain();

let gain = wrapReactive(volume.gain, 'value');
</script>

<template>
  <div>
    Volume: {{ gain.value.toFixed(2) }}
  </div>

  <div>
    <input type="range" min="0" max="1" step=".01" v-model="gain.value">
  </div>
</template>

The problem with this kind of binding is that it works in one direction, it's possible to update original volume.gain.value on Vue model changes, but it's impossible to update Vue model when volume.gain class instance changes value internally.这种绑定的问题是它在一个方向上工作,可以在 Vue model 更改时更新原始volume.gain.value ,但是当volume.gain ZA2F2ED2ED4F8EBC2CAB6DZC2 实例在内部更改value时无法更新 Vue model。 Given that volume.gain is an instance of AudioParam , value can change based on defined constraints, eg minValue .鉴于volume.gainAudioParam的一个实例, value可以根据定义的约束而改变,例如minValue This would require to replace these native classes with extended reactivity-aware classes, this depends on specific classes whether it's possible and how this needs to be done.这需要用扩展的反应感知类替换这些本地类,这取决于特定的类是否可能以及需要如何完成。 volume.gain is read-only, in the case of AudioParam replacing gain with fully reactive instance of MyAudioParam would require to extend the whole hierarchy of classes, starting from AudioContext . volume.gain是只读的,在AudioParam MyAudioParam完全反应实例替换gain的情况下,需要从AudioContext开始扩展类的整个层次结构。

Vue 3 provides limited support for the reactivity of classes. Vue 3 对类的反应性提供了有限的支持。 reactive transforms own enumerable properties into reactive ones. reactive将自己的可枚举属性转换为反应式属性。 The rest of members cannot be expected to be affected automatically.不能指望成员的 rest 会自动受到影响。 reactive can have destructive effect on classes that weren't specifically written with it in mind. reactive可能会对未专门编写的类产生破坏性影响。

Third-party classes may be implemented in arbitrary ways as long as it fits their normal use, while their use with reactive cannot be considered one, and their ability to provide reactivity needs to be determined by trial and error.第三方类可以以任意方式实现,只要它适合它们的正常使用,而它们与reactive的使用不能被认为是一种,它们提供响应式的能力需要通过反复试验来确定。

It's evident that native behaviour of volume.gain.value already relies on get / set to make AudioParam react to value changes, so value is not own enumerable property but a descriptor on AudioParam.prototype .很明显, volume.gain.value的本机行为已经依赖于get / set来使AudioParam对值的变化做出反应,因此value不是自己的可枚举属性,而是AudioParam.prototype上的描述符。 The original implementation didn't work because volume.gain.value is not reactive in neither of these cases and its changes cannot cause a view to be updated.最初的实现不起作用,因为volume.gain.value在这两种情况下都不是反应式的,并且它的更改不会导致视图被更新。 reactive shouldn't be used on it because it won't help to make value reactive but can have negative effects on class instance based on its implementation.不应在其上使用reactive ,因为它无助于使value具有响应性,但会根据其实现对 class 实例产生负面影响。

A class isn't needed for reactive(new Model()) . reactive(new Model())不需要 class 。 Vue reactivity was primarily designed to be used with plain objects. Vue 反应性主要设计用于普通对象。

Considering that the binding is one way (model value changes update native value behind it, but not vice versa), it's correct to explicitly define local state and update native value on its changes.考虑到绑定是一种方式(模型值更改会更新其背后的本机值,但反之则不然),明确定义本地 state 并根据其更改更新本机值是正确的。 If this is a common case, it can be a helper to reduce boilerplate:如果这是一种常见情况,它可以帮助减少样板:

let createPropModel = (obj, key) => {
  let state = ref(obj[key]);

  watch(state, v => { obj[key] = v }, { immediate: true });

  return state;
};

let gainRef = createPropModel(volume.gain, 'value');

Alternatively, this can be done with writable computed, but it should involve local state too, and the implementation is less straightforward:或者,这可以通过可写计算来完成,但它也应该涉及本地 state,并且实现不那么简单:

let createPropModel = (obj, key) => {
  let state = ref(obj[key]);

  return computed({
    get: () => state.value,
    set: (v) => { state.value = v; obj[key] = v },
  });
};

let gainRef = createPropModel(volume.gain, 'value');

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM