Демистификация двусторонней привязки Vue

.NET внешний интерфейс JavaScript Vue.js

Когда вам нужно что-то ввести в Vue, вы, естественно, подумаете об использовании<input v-model="xxx" />способ достижения двусторонней привязки. Ниже приведен самый простой пример

<div id="app">
    <h2>What's your name:</h2>
    <input v-model="name" />
    <div>Hello {{ name }}</div>
</div>
new Vue({
    el: "#app",
    data: {
       	name: ""
    }
});

Демонстрация JsFiddle

jsfiddle.net/0okxhc6f/

Затем будет отображено то, что вводится в поле ввода этого примера. Это нативная пара Vue<input>Это также типичный пример двусторонней передачи данных между родительским и дочерним компонентами. ноv-modelЭто новая функция, добавленная в Vue 2.2.0, до этого Vue поддерживал только односторонний поток данных.

Односторонний поток данных в Vue

Односторонний поток данных Vue подобен React.Родительский компонент может передавать данные дочернему компоненту, устанавливая свойства (Props) дочернего компонента.Если родительский компонент хочет получить данные дочернего компонента, он должен зарегистрируйте событие с дочерним компонентом, и дочерний компонент счастлив Когда это событие запускается, данные передаются. Подводя итог в одном предложении, свойства передают данные вниз, а события передают данные вверх.

В приведенном выше примере, если вы не используетеv-modelэто должно выглядеть так

<input :value="name" @input="name = $event.target.value" />

Поскольку обработка событий написана во встроенном режиме, часть сценария не нужно изменять. Но в большинстве случаев событие вообще определяется как метод, код будет намного сложнее

<input :value="name" @input="updateName" />
new Vue({
    // ....
    methods: {
        updateName(e) {
            this.name = e.target.value;
        }
    }
})

Из примера вышеv-modelЭто экономит много кода, и самое главное, что можно определить на один обработчик событий меньше. такv-modelФактические засушливые события включают

  • использоватьv-bind(который:) односторонняя привязка к свойству (пример::value="name")
  • связыватьinputсобытие (т.@input) в реализованный по умолчанию обработчик событий (пример:@input=updateName
  • Этот обработчик событий по умолчанию изменяет связанные данные на основе значения, переданного объектом события (пример:this.name = e.target.value)

пользовательские компонентыv-model

Vue инкапсулирует нативные компоненты, поэтому<input>Срабатывает при вводеinputмероприятие. Но как насчет пользовательских компонентов? Вот пример списка задач с помощью шаблона JsFiddle Vue.

Шаблон Vue для JsFiddle

Нажмите на логотип JsFilddle и выберите шаблон Vue на всплывающей панели выше.

Стандартный код состоит из двух частей, HTML и Vue(js), и выглядит следующим образом:

<div id="app">
  <h2>Todos:</h2>
  <ol>
    <li v-for="todo in todos">
      <label>
        <input type="checkbox"
          v-on:change="toggle(todo)"
          v-bind:checked="todo.done">

        <del v-if="todo.done">
          {{ todo.text }}
        </del>
        <span v-else>
          {{ todo.text }}
        </span>
      </label>
    </li>
  </ol>
</div>
new Vue({
  el: "#app",
  data: {
    todos: [
      { text: "Learn JavaScript", done: false },
      { text: "Learn Vue", done: false },
      { text: "Play around in JSFiddle", done: true },
      { text: "Build something awesome", done: true }
    ]
  },
  methods: {
  	toggle: function(todo){
    	todo.done = !todo.done
    }
  }
})

Определить компоненты Todo

Шаблон Vue JsFiddle реализует отображение списка задач по умолчанию, данные фиксируются, а весь контент заполняется в одном шаблоне. Первое, что нам нужно сделать, это превратить отдельный Todo в подкомпонент. Поскольку его нельзя записать в виде нескольких файлов в JsFiddle, компонент используетVue.component()Определено в скрипте, в основном, чтобы поставить<li>Выньте эту часть содержимого:

Vue.component("todo", {
    template: `
<label>
    <input type="checkbox" @change="toggle" :checked="isDone">
    <del v-if="isDone">
        {{ text }}
    </del>
    <span v-else>
        {{ text }}
    </span>
</label>
`,
    props: ["text", "done"],
    data() {
        return {
            isDone: this.done
        };
    },
    methods: {
        toggle() {
            this.isDone = !this.isDone;
        }
    }
});

Первоначально определено в приложенииtoggle()Метод также был немного изменен и определен в компоненте.toggle()При вызове он изменит, завершен он или нетdoneценность . Но из-заdoneопределяется вpropsАтрибуты не могут быть назначены напрямую, поэтому для определения данных используется первый метод, рекомендованный должностным лицом.isDone, инициализируетсяthis.doneи использовать внутри компонентаisDoneчтобы контролировать, завершено ли это состояние.

Шаблон и код соответствующего раздела App значительно уменьшены:

<div id="app">
    <h2>Todos:</h2>
    <ol>
        <li v-for="todo in todos">
            <todo :text="todo.text" :done="todo.done"></todo>
        </li>
    </ol>
</div>
new Vue({
    el: "#app",
    data: {
        todos: [
            { text: "Learn JavaScript", done: false },
            { text: "Learn Vue", done: false },
            { text: "Play around in JSFiddle", done: true },
            { text: "Build something awesome", done: true }
        ]
    }
});

Демонстрация JsFiddle

jsfiddle.net/0okxhc6f/1/

Но пока данные все еще односторонние. С точки зрения эффекта, установка флажка может привести к эффекту зачеркивания, но все эти динамические изменения находятся вtodoКомпонент делается внутри, и нет проблем с привязкой данных.

Добавить счетчик в список задач

чтобыtodoИзменения состояния внутри компонента могут отображаться в списке задач.Мы добавляем счетчик в список задач, чтобы показать количество задач, которые были выполнены. Потому что это числоtodoВлияние внутреннего состояния (данных) компонента, требующегоtodoИзменения внутренних данных отражаются в его родительском компоненте, так чтоv-modelиспользования.

Номер в названии, который мы должныn/mформа, например2/4Обозначает в общей сложности 4 задачи, 2 из которых выполнены. Это требует модификации шаблона и части кода Todo List, добавленияcountDoneа такжеcountДва вычисляемых свойства:

<div id="app">
    <h2>Todos ({{ countDone }}/{{ count }}):</h2>
    <!-- ... -->
</div>
new Vue({
    // ...
    computed: {
        count() {
            return this.todos.length;
        },
        countDone() {
            return this.todos.filter(todo => todo.done).length;
        }
    }
});

Теперь количество отображается, но теперь изменение состояния задачи не влияет на количество. Мы хотим, чтобы изменения дочернего компонента влияли на данные родительского компонента.v-modelО нем я расскажу позже, начнем с самого распространенного метода, событий:

  • Подсборкаtodoсуществуетtoggle()средний триггерtoggleсобытие будетisDoneкак параметр события
  • родительский компонент является дочерним компонентомtoggleобработчик событий определения события
Vue.component("todo", {
    //...
    methods: {
        toggle(e) {
            this.isDone = !this.isDone;
            this.$emit("toggle", this.isDone);
        }
    }
});
<!-- #app 中其它代码略 -->
<todo :text="todo.text" :done="todo.done" @toggle="todo.done = $event"></todo>

Здесь@toggleСвязывание — это выражение. потому что здесьtodoявляется временной переменной, если вmethodsТрудно связать эту временную переменную, определив специальную функцию обработки событий в прошлом (конечно, можно определить общий метод, вызвав его).

Функции обработчика событий, которые обычно непосредственно соответствуют обрабатываемым вещам, например определениеonToggle(e), связанный как@toggle="onToggle". В этом случае его нельзя передатьtodoкак параметр.

Обычный метод, который можно определить какtoggle(todo, e), который вызывается как выражение вызова функции в определении события:@toggle="toggle(todo, $event)"。它和todo.done = $event` Общее выражение.

Обратите внимание на разницу между ними: первая — связанная функция-обработчик (ссылка), а вторая — связанное выражение (вызов).

Теперь ожидаемый эффект был достигнут через метод события

** Демонстрация скрипта Js **

jsfiddle.net/0okxhc6f/2/

изменено вv-model

Ранее мы говорили об использованииv-modelПонял, теперь его преобразовывать. Обратите внимание на реализациюv-modelнесколько элементов

  • дочерний компонент черезvalueОпора принимает ввод
  • Дочерний компонент запускаетсяinputВыход события с параметрами массива
  • используется в родительском компонентеv-modelсвязывать
Vue.component("todo", {
    // ...
    props: ["text", "value"],   // <-- 注意 done 改成了 value
    data() {
        return {
            isDone: this.value    // <-- 注意 this.done 改成了 this.value
        };
    },
    methods: {
        toggle(e) {
            this.isDone = !this.isDone;
            this.$emit("input", this.isDone);  // <-- 注意事件名称变了
        }
    }
});
<!-- #app 中其它代码略 -->
<todo :text="todo.text" v-model="todo.done"></todo>

.syncРеализовать другие привязки данных

Введение Vue 2.2.0 упоминалось ранее.v-modelхарактеристика. По какой-то причине его входное свойствоvalue, но выходное событие вызываетсяinput.v-model,value,inputМежду этими тремя именами нет буквально никакой связи. Хотя это может показаться немного странным, дело не в этом, дело в том, что элемент управления может привязываться только к одному свойству в обоих направлениях?

Vue 2.3.0 представлен.syncмодификаторы используются для измененияv-bind(который:), чтобы сделать его двусторонним. Это тоже синтаксический сахар, добавляющий.syncПривязка декорированных данных будет выглядеть какv-modelОдин и тот же обработчик событий автоматически зарегистрирован для назначения значений в связанные данные. Этот подход также требует дочерних компонентов, чтобы вызвать определенные события. Однако имя этого события имеет отношение к имени свойства связывания. Он добавляется до имени свойства связывания.update:приставка.

Например<sub :some.sync="any" />подкомпонентыsomeсвойства и родительский компонентanyПривязка данных, дочерний компонент должен пройти$emit("update:some", value)чтобы вызвать изменение.

В приведенном выше примере используйтеv-modelСвязывание всегда кажется немного неловким, потому чтоv-modelБуквальное значение состоит в том, чтобы связать значение в обоих направлениях и указать, является ли оно неполным.doneНа самом деле это состояние, а не значение. Поэтому мы снова модифицируем его, все еще используяdoneэто имя свойства (вместоvalue),пройти через.syncдля достижения двусторонней привязки.

Vue.component("todo", {
    // ...
    props: ["text", "done"],   // <-- 恢复成 done
    data() {
        return {
            isDone: this.done    // <-- 恢复成 done
        };
    },
    methods: {
        toggle(e) {
            this.isDone = !this.isDone;
            this.$emit("update:done", this.isDone);  // <-- 事件名称:update:done
        }
    }
});
<!-- #app 中其它代码略 -->
<!-- 注意 v-model 变成了 :done.sync,别忘了冒号哟 -->
<todo :text="todo.text" :done.sync="todo.done"></todo>

** Демонстрация скрипта Js **

jsfiddle.net/0okxhc6f/3/

Демистификация двусторонней привязки Vue

Из приведенного выше описания, я думаю, все должны были понять, что двусторонняя привязка Vue на самом деле завершается обычной односторонней привязкой и комбинацией событий, но черезv-modelа также.syncФункция обработчика по умолчанию зарегистрирована для обновления данных. В исходном коде Vue есть такой абзац

// @file: src/compiler/parser/index.js

if (modifiers.sync) {
    addHandler(
        el,
        `update:${camelize(name)}`,
        genAssignmentCode(value, `$event`)
    )
}

Как видно из этого кода,.syncПри двусторонней привязке компилятор добавитupdate:${camelize(name)}Функция обработчика события для назначения данных (genAssignmentCodeбуквально означает код, который генерирует присваивание).

Перспектива

В настоящее время двусторонняя привязка Vue также должна обеспечивать возврат данных путем запуска событий. Между этим и многими ожидаемыми доходами от назначений все еще существует определенный разрыв. Есть две основные причины этого разрыва

  1. Необходимо вернуть данные через события
  2. Свойства (реквизит) не могут быть назначены

В текущей версии Vue упрощение может быть достигнуто за счет определения вычисляемых свойств, таких как

computed: {
    isDone: {
        get() {
            return this.done;
        },
        set(value) {
            this.$emit("update:done", value);
        }
    }
}

По правде говоря, довольно утомительно определять больше имен переменных с одинаковым значением и разными именами. Я надеюсь, что Vue сможет сократить этот процесс с помощью определенных технических средств в будущих версиях, таких как добавление объявлений свойств (Prop).syncвариант, просто объявитеsync: trueмогут быть напрямую назначены и автоматически запущеныupdate:xxxмероприятие.

Конечно, как фреймворк, при решении проблемы нам также нужно учитывать влияние на другие функции и расширяемость фреймворка, так что во что в итоге превратится двухсторонняя привязка, мы подождем и увидим для Vue 3.0.