VueJS 响应式绑定到模块导出

VueJS reactive binding to module export

我是 Vue 的新手,我正在尝试将组件值绑定到导出对象的 属性。初始值设置正确但不是反应性的。我不确定我使用的术语是否正确,但相关部分是

// Settings.js
export const settings = { showOverlay: true }

// Overlay.vue
<template>
  <div v-show="enabled"> Some stuff </div>
</template>

<script>
import { settings } from "../js/Settings.js";
export default {
  data() {
    return {
        enabled: settings.showOverlay
    };
  }
};
</script>

现在,我知道导出的对象 (settings) 是该对象的只读视图,因为这就是模块的工作方式,所以 Vue 可能无法将它的钩子放入其中。问题是,我希望此设置服务将设置设置为 "owned",该服务负责在页面加载之间保留值,但我不认为该服务必须知道组件想要观察一个值并注意在值更改时手动触发组件上的更新——我可能只是误解了我应该在这种情况下使用的模式。

这是用 Webpack / babel 构建的,如果有什么不同的话。

我现在感觉有点不好意思。根据我在你的问题中看到的一些语法,我陷入了一个小兔子洞,这导致了一大堆不必要的旋转。语法是这样的:

data() {
  return {
    enabled: settings.showOverlay
  };
}

出于某种原因,我将其解释为 "well sure, whenever enabled changes, settings.showOverlay is going to change because Vue is reactive"。

是的,没有。

在该代码中,settings.showOverlay 只是 enabled 属性 的 初始值 enabled 属性 将是 反应式 ,但它绝不会将值传递给设置对象。基本上,数据函数 returns 一个启用了 属性 的对象,它的初始值是 settings.showOverlay,然后 that 对象变成了一个反应对象。

如果您希望将在 Vue 中所做的更改传递给您的设置对象,那么您需要做的就是在 Vue 的数据对象上公开设置对象。

data() {
  return {
    settings,
  };
}

现在 如果你有这样的代码

<div v-show="settings.showOverlay"> Some stuff </div>
<button @click="settings.showOverlay= !settings.showOverlay"></button>

settings.showOverlay 不仅在 Vue 中是响应式的,在 settings 对象中也是如此。不需要我在下面跳过的任何箍 (/facepalm)。

FWIW 我相信我在评论中提到的一些链接指的是数据对象本身。数据对象需要是一个普通的 javascript 对象,不一定是它上面的所有属性。

换句话说,在

data() {
  return something
}

something 必须是普通 javascript 对象。

原答案

我在我的 Vue 应用程序中以多种方式完成了此操作。在我的第一个应用程序中,我想做同样的事情,将设置存储在一个外部模块中,该模块可以管理持久化设置并在我的 Vue 上公开这些设置。我最终写了一个看起来像这样的 class。

class Settings {
  constructor(){
    // read settings from persisted solution
  }
  get(key){
    // return "key" from settings
  }
  set(key){
    // set "key" in settings
  }
  save(){
    // save settings to persisted solution
  }
}

export default Settings

然后像这样在我的 Vue 中使用它。

import Settings from "./settings"

new Vue({
  data:{
    someSetting: Settings.get("someSetting")
  }
})

然后稍后触发 set() 和 save()。对我来说,每当触发路由更改时,我都会将所有设置设置回设置对象,然后保存。

听起来您正在导出一个具有 getter/setter 属性的对象,可能是这样的。

export const settings = { 
  overlay: stored.showOverlay,
  get showOverlay(){
    return this.overlay
  },
  set showOverlay(v){
    this.overlay = v
  }
}

触发 set 时可能会触发保存。与我上面描述的解决方案相比,我更喜欢这个想法。但是让它工作需要更多的工作。首先我尝试使用计算。

new Vue({
  computed:{
     showOverlay: {
       get(){ return settings.showOverlay }
       set(v) { settings.showOverlay = v }
     }
  }
})

但这并不完全有效,因为它没有反映对 Vue 的更改。这是有道理的,因为 Vue 并不真正知道值发生了变化。将 $forceUpdate 添加到 setter 也不起作用,我预计这是因为计算值的缓存性质。然而,将计算与数据 属性 结合使用, 确实 有效。

new Vue({
  data(){
    return {
      showOverlay_internal: settings.showOverlay
    }
  },
  computed:{
     showOverlay: {
       get(){ return this.showOverlay_internal }
       set(v) { 
         settings.showOverlay = v 
         this.showOverlayInternal = v
       }
     }
  }
})

这会同时更改 Vue 的状态并触发设置对象的更改(这反过来又会触发持久化)。

但是,该死的,工作量很大。

但有时要记住,我们用来实例化 Vue 的对象只是普通的 javascript 对象,我们可以操作它们。我想知道我是否可以编写一些代码来为我们创建数据 属性 和计算值。借鉴 Vuex,是的,我们可以。

我最后得到的是这个。

import {settings, mapSetting} from "./settings"

const definition = {
  name:"app"
}

mapSetting(definition, "showOverlay"

export default definition

mapSetting 完成了我们上面所做的所有工作。 showOverlay 现在是一个计算的 属性,它对 Vue 中的变化做出反应并更新我们的设置对象。目前唯一的缺点是它暴露了一个showOverlay_internal数据属性。我不确定这有多重要。可以改进一次映射多个属性。

这是我使用 localStorage 作为持久性媒介编写的完整代码。

function saveData(s){
  localStorage.setItem("settings", JSON.stringify(s))
}

let stored = JSON.parse(localStorage.getItem("settings"))
if (null == stored) {
  stored = {}
}

export const settings = { 
  overlay: stored.showOverlay,
  get showOverlay(){
    return this.overlay
  },
  set showOverlay(v){
    this.overlay = v
    saveData(this)
  }
}

function generateDataFn(definition, setting, internalName){
  let originalDataFn = definition.data
  return function(){
    let data = originalDataFn ? originalDataFn() : {}
    data[internalName] = settings[setting]
    return data
  }
}

function generateComputed(internalName, setting){
  return {
      get(){
        return this[internalName]
      },
      set(v){
        settings[setting] = v
        this[internalName] = v
      }
  }
}

export function mapSetting(definition, setting){
  let internalName = `${setting}_internal` 
  definition.data = generateDataFn(definition, setting, internalName)

  if (!definition.computed)
    definition.computed = {}

  definition.computed[setting] = generateComputed(internalName, setting)
}