Реактивная привязка VueJS к экспорту модуля

Я новичок в 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 не может вставить в него свои крючки. Дело в том, что я хочу, чтобы этот параметр «принадлежал» этой службе настроек, которая отвечает за сохранение значений между загрузками страниц, но я не чувствую, что служба должна знать, что компонент хочет просмотреть value и позаботьтесь о ручном запуске обновлений компонента при изменении значения - я, вероятно, просто неправильно понимаю шаблон, который я должен использовать для таких случаев.

Это создается с помощью Webpack/babel, если это имеет значение.


person Coderer    schedule 19.06.2017    source источник
comment
Я думаю, вы ищете некоторую информацию о состоянии, я бы взглянул на vuex   -  person dops    schedule 19.06.2017
comment
Одна из причин, по которой я начал использовать Vue, заключается в том, что он должен быть легким, и вы должны иметь возможность внедрять его понемногу за раз. Я пока не хотел сокращать все состояние моего приложения до хранилища, специфичного для Vue (и я думаю, что потрачу больше времени на переключение на глобальную модель шины сообщений, чем сэкономил бы, используя Vue в первую очередь). Разве нет способа использовать немного Vue?   -  person Coderer    schedule 19.06.2017
comment
Я думаю, что более широкая проблема может заключаться в том, что я ищу объекты со свойствами, которые уже используют геттер/сеттер - я считаю, что под капотом экспортированные модули предоставляют представление только для чтения, скрывая фактические свойства и выставляя только сгенерированный геттер. Итак, если у меня есть объект и я хочу наблюдать за геттером, есть ли хороший шаблон для этого?   -  person Coderer    schedule 19.06.2017


Ответы (1)


Я чувствую себя немного застенчивым в данный момент. Я спустился в маленькую кроличью нору, основываясь на некотором синтаксисе, который я увидел в вашем вопросе, и это привело к целому ряду ненужных вращений. Синтаксис был таким:

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

Что, по какой-то причине, я интерпретировал как «конечно, всякий раз, когда enabled изменяется, settings.showOverlay будет меняться, потому что Vue реактивен».

Да, нет.

В этом коде settings.showOverlay — это просто начальное значение для свойства enabled. Свойство enabled будет реактивным, но оно никоим образом не будет передавать значения объекту настроек. По сути, функция данных возвращает объект с включенным свойством, начальное значение которого равно settings.showOverlay, а затем этот объект превращается в реактивный объект.

Если вы хотите, чтобы изменения, сделанные в 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 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 к установщику тоже не работает, я полагаю, из-за кэширования вычисляемых значений. Однако использование вычисляемого в сочетании со свойством данных действительно работает.

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)
}
person Bert    schedule 19.06.2017
comment
Это действительно исчерпывающий ответ - спасибо за вашу тяжелую работу! Я думаю, что пока отложу свой порт Vue. Я думаю, вы можете согласиться с тем, что приведенное выше решение делает много танцев для поддержки привязки, но у меня есть другая (не ориентированная на Vue) логика, которую мне пришлось бы использовать Vue-ify, чтобы она вообще работала, и я просто сейчас нет времени. Может быть, я еще почитаю о Vuex позже, когда придет время полностью переписать. - person Coderer; 20.06.2017
comment
@Coderer Это справедливое заявление. Я провел некоторые поиски по этому вопросу после того, как нашел решение, потому что оно кажется слишком сложным. Vue действительно хочет работать с простыми объектами в данных. Тем не менее, Vue, как правило, является фреймворком, который, по моему мнению, легче всего интегрируется в другие проекты. Я сторонник воздержания от Vuex до тех пор, пока он вам не понадобится. Я думаю, что именно этот случай, когда ваши свойства вашего внешнего объекта уже являются геттерами/сеттерами, является основным источником боли здесь. Я постоянно использую объекты с методами и простыми свойствами. - person Bert; 20.06.2017
comment
@Coderer Эван Вы также кратко рассказываете об этом здесь. Все методы прототипа игнорируются. Использование непростых объектов в качестве состояния является кроличьей норой для сложности и не поддерживается дизайном. - person Bert; 20.06.2017
comment
Эта цитата точно отражает мою проблему, спасибо. Я думаю, что мне не хватает хорошего примера/образца для подражания. Я написал не только этот модуль настроек, есть классы из библиотеки, которую я использую, и мне нужно привязать (логически) к их свойствам, двунаправленно. Я думаю, что если я взломаю это, это также решит мою проблему с настройками. - person Coderer; 21.06.2017
comment
Я пошел дальше и задал другой вопрос. Может быть, это более четкое изложение моей более крупной проблемы? - person Coderer; 21.06.2017
comment
@Coderer Я сделал действительно глупую ошибку. Проверьте обновленный ответ выше. - person Bert; 22.06.2017
comment
Есть ли место для размещения Fiddle, который работает с операторами импорта ES6, возможно, путем имитации поведения Webpack или подобного? Я полагаю, что ваш обновленный ответ не будет работать в этом случае, потому что data() { return { settings:settings }; } означает, что data является простым объектом, а data.settings — это сгенерированное только для чтения представление объекта, которое действительно существует только в глобальном пространстве имен Settings.js из-за как работает import. - person Coderer; 22.06.2017
comment
@Coderer Попробуйте это. Нажатие кнопки переключения и обновление между щелчками сохранит настройку. webpackbin.com/bins/-KnFHKDZGHZdc0P-7BTH - person Bert; 22.06.2017
comment
Это фантастика, большое спасибо, Берт. Я предполагаю, что мое понимание природы импорта только для чтения ошибочно — если я могу добавлять свойства к экспортируемому объекту, то что в нем доступно только для чтения? Впрочем, это другой вопрос. Я буду следовать предложенному вами подходу. - person Coderer; 26.06.2017