Краткое изложение и сущность стека технологии Vue2

Vue.js React.js HTML Vuex

Вью — этосильно инкапсулированный,из коробки,Стек интерфейсных фреймворков, который можно комбинировать с веб-пакетом для скомпилированной внешней разработки или напрямую подключать к автоматизированным инструментам, таким как gulp и grunt.全局windowиспользовать. Эта статья была написана в начале выпуска Vue2.4.x, и последняя версия, используемая в настоящее время автором в производственной среде, — 2.5.2. Имея опыт разработки нескольких интерфейсных проектов с тяжелым взаимодействием, автор всесторонне проанализировал, обобщил и аннотировал стек технологии Vue в сочетании с официальными документами, поэтому эту статью можно использовать в качестве дополнительного чтения к официальному руководству по Vue2. Студентам, у которых нет опыта разработки Vue2, рекомендуется временно прочитать эту статью после прохождения официального руководства.

В версиях после Vue2.2.x фреймворк Vue и его функции стека технологий становятся все более совершенными.По сравнению с комбинацией React+Reflux/Redux/MobX, Vue ближе к техническим спецификациям W3C (например, реализация все еще находится на стадии проекта W3C.<template>,<slot>,isи другие новые функции, обеспечивающие хорошую и простую в использовании среду написания шаблонов), а технологический стек и экосистема с открытым исходным кодом стали более полными и простыми в настройке, интегрируя множество мест в React, требующих ручного кодирования, в лучшие практики и абстрагируя их в простой синтаксический сахар (такой как в Vuexstoreмодульность), позволяя разработчикам всегда сосредотачиваться на самой бизнес-логике.

По сравнению с Angular2, структура API Vue2 является более лаконичной и может свободно комбинироваться с TypeScript или ECMAScript6.Это не зависит от конкретного языка предварительной обработки для получения наилучшего пользовательского опыта.Характеристики самого фреймворка не заставляют полагаться на различный крутой синтаксический сахар. Vue2 в целом оченьЛегкийтехнологический стек, дизайн и реализация точно соответствуют техническим спецификациям W3C, уделяя особое внимание обработкеКомпонентизация шаблона HTML,Разделение событий и данных по областям,Многоуровневая связь компонентовТри ключевых вопроса одностраничной фронтенд-разработки. В процессе написания этой статьи описываются сходства, различия и сравнения интерфейсных фреймворков, таких как Angular и React, для справки разработчиков, блуждающих по выбору различных фронтенд-технологий.

Vue против Углового

составной

Идея дизайна Angular имитирует концепцию многослойности MVC в веб-разработке на Java.ControllerВырезать и контролировать область страницы, затем передатьServiceЧтобы добиться повторного использования, это своего рода страницапортретИдея послойной развязки. Vue позволяет разработчикам абстрагировать страницу на несколько независимых компонентов, то есть DOM-структуру страницы.горизонтальныйСокращение, полное повторное использование функций и контроль объема посредством сборки компонентов. Каждый компонент обеспечивает толькоpropsКак единый интерфейс и использование Vuex дляstate treeуправления, чтобы облегчить связь и синхронизацию состояния между компонентами.

составной

Angular доступен с версии 1.6.x.component()Методы иComponent Routerчтобы обеспечить компонентный опыт разработки, но по-прежнему необходимо полагаться наcontrollerа такжеserviceПо сути, он до сих пор не избавился от оков вертикальной многоуровневой идеи MVC.

Двустороннее связывание и реактивное связывание

Vue перебирает все свойства объекта данных и передает собственныйObject.defineProperty()метод преобразует эти свойства вgetter/setter(Поддерживает только браузеры IE9 и выше.). Vue внутренне отслеживает зависимости через эти геттеры/сеттеры и инициирует соответствующие изменения при изменении свойств, тем самым завершая двустороннюю привязку моделей к представлениям. Вызывается автоматически при создании экземпляра каждого компонента Vue.$watch()Пройдите через себя и запишите его как зависимость.Когда эти зависимости сеттера срабатывают, Watcher уведомляется о необходимости пересчитать новое значение, а затем запускает компонент VUE.render()Функция повторно отображает компонент.

Жизненный цикл реактивных привязок

В отличие от двусторонней привязки данных Aangular, компоненты Vue не могут обнаруживать добавление и удаление свойств данных после создания экземпляра, потому что компоненты Vue выполняют обработку получения/установки свойств при создании экземпляра, поэтому свойства объектов данных должны быть созданы. правильно. Следовательно, то, что предоставляет Vue, не является истинной двусторонней привязкой, более точное описание должно бытьОдносторонняя привязка, быстрое обновление, и Angular может пройти$scopeВлияет на привязку данных к представлению, а также может управляться через слой представления.$scopeСвойства объекта на , принадлежат реальному смыслуДвусторонняя привязка вида и модели.

var vm = new Vue({
  data:{
    a:1
  }
})
vm.a = 1  // 响应的
vm.b = 2 // 非响应的

Поэтому Vue не позволяет добавлять новые динамические реактивные свойства корневого уровня для уже созданных компонентов (То есть атрибуты, непосредственно монтируемые под данными), но вы можете использоватьVue.set(object, key, value)метод добавления реактивных свойств.

Vue.set(vm.someObject, "b", 2)
// vm.$set()实例方法是Vue.set()全局方法的别名
this.$set(this.someObject, "b",2)
// 使用Object.assign()或_.extend()也可以添加响应式属性,但是需要创建同时包含原属性、新属性的对象,从而有效触发watch()方法
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

Vue обновляет DOM асинхронно.После наблюдения за изменениями данных Vue открывает очередь и буферизует ее в том же цикле событий (цикл событий Vue называетсяtick[tɪk] п. Все изменения данных, происходящие в марке, жетоне). Если один и тот же наблюдатель запускается несколько раз, он будет помещен в эту очередь только один раз.

Vue будет передавать собственный JavaScript внутриPromise.then,MutationObserver,setTimeout(fn, 0)для выполнения наблюдателя в асинхронной очереди.

В сценариях, требующих ручного управления DOM, чтобы обновить DOM после того, как Vue отреагирует на изменения данных, вы можете вручную вызватьVue.nextTick(callback), и поместите логику операции DOM в функцию обратного вызова, чтобы гарантировать выполнение операции DOM после завершения адаптивного обновления.

<div id="example">{{message}}</div>
<script>
// 使用Vue实例上的.$nextTick()
var vue = new Vue({
  el: "#example",
  data: {
    message: "123"
  }
})
vue.message = "new message" // 更改数据
vue.$el.textContent === "new message" // false
vue.nextTick(function () {
  vm.$el.textContent === "new message" // true
})
</script>
<script>
// 组件内使用vm.$nextTick(),不需要通过全局Vue,且回调函数中this自动指向当前Vue实例
Vue.component("example", {
  template: "<span>{{ message }}</span>",
  data: function () {
    return {
      message: "没有更新"
    }
  },
  methods: {
    updateMessage: function () {
      this.message = "更新完成"
      console.log(this.$el.textContent) // 没有更新
      this.$nextTick(function () {
        console.log(this.$el.textContent) // 更新完成
      })
    }
  }
})
</script>

виртуальный DOM

Vritual DOMЭта концепция была впервые введена React.Это схема сравнения дифференциации объектов DOM, которая абстрагирует объекты DOM в объекты Virtual DOM (То есть результат рендеринга функцией render()), затем сравните виртуальный DOM с помощью алгоритма различия и верните разницу, и, наконец, примените возвращенный объект различия к реальному узлу DOM с помощью алгоритма исправления.

Виртуальные объекты DOM в Vue называютсяVNode(templateСодержимое будет скомпилировано в функцию render(), а функция render() получит функцию createElement() и, наконец, вернет объект VNode), а алгоритм исправления взят из другого проекта с открытым исходным кодом.snabbdom, который сопоставляет операции реального DOM с операциями виртуального DOM и повышает производительность за счет сокращения количества операций с реальным DOM.

➜  vdom git:(dev) tree
├── create-component.js
├── create-element.js
├── create-functional-component.js
├── helpers
│   ├── extract-props.js
│   ├── get-first-component-child.js
│   ├── index.js
│   ├── is-async-placeholder.js
│   ├── merge-hook.js
│   ├── normalize-children.js
│   ├── resolve-async-component.js
│   └── update-listeners.js
├── modules
│   ├── directives.js
│   ├── index.js
│   └── ref.js
├── patch.js
└── vnode.js

Начальная точка дизайна VNode такая же, как и у Angular.$digestЦикл аналогичен, как черезУменьшите количество операций с реальным DOM для повышения производительности., но реализация Vue более легкая, отказ от Angular обеспечивает двустороннюю привязку$apply(),$eval()Инкапсулировать функции, выборочно реализованные в Angular$compile(),$watch()аналогичная функция.

Опции для объектов Vue

путем вызова конструктораnew Vue()проходить вoptionобъект для создания экземпляра Vue.

var vm = new Vue({
  // 数据
  data: "声明需要响应式绑定的数据对象",
  props: "接收来自父组件的数据",
  propsData: "创建实例时手动传递props,方便测试props",
  computed: "计算属性",
  methods: "定义可以通过vm对象访问的方法",
  watch: "Vue实例化时会调用$watch()方法遍历watch对象的每个属性",
  // DOM
  el: "将页面上已存在的DOM元素作为Vue实例的挂载目标",
  template: "可以替换挂载元素的字符串模板",
  render: "渲染函数,字符串模板的替代方案",
  renderError: "仅用于开发环境,在render()出现错误时,提供另外的渲染输出",
  // 生命周期钩子
  beforeCreate: "发生在Vue实例初始化之后,data observer和event/watcher事件被配置之前",
  created: "发生在Vue实例初始化以及data observer和event/watcher事件被配置之后",
  beforeMount: "挂载开始之前被调用,此时render()首次被调用",
  mounted: "el被新建的vm.$el替换,并挂载到实例上之后调用",
  beforeUpdate: "数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前",
  updated: "数据更改导致虚拟DOM重新渲染和打补丁之后被调用",
  activated: "keep-alive组件激活时调用",
  deactivated: "keep-alive组件停用时调用",
  beforeDestroy: "实例销毁之前调用,Vue实例依然可用",
  destroyed: "Vue实例销毁后调用,事件监听和子实例全部被移除,释放系统资源",
  // 资源
  directives: "包含Vue实例可用指令的哈希表",
  filters: "包含Vue实例可用过滤器的哈希表",
  components: "包含Vue实例可用组件的哈希表",
  // 组合
  parent: "指定当前实例的父实例,子实例用this.$parent访问父实例,父实例通过$children数组访问子实例",
  mixins: "将属性混入Vue实例对象,并在Vue自身实例对象的属性被调用之前得到执行",
  extends: "用于声明继承另一个组件,从而无需使用Vue.extend,便于扩展单文件组件",
  provide&inject: "2个属性需要一起使用,用来向所有子组件注入依赖,类似于React的Context",
  // 其它
  name: "允许组件递归调用自身,便于调试时显示更加友好的警告信息",
  delimiters: "改变模板字符串的风格,默认为{{}}",
  functional: "让组件无状态(没有data)和无实例(没有this上下文)",
  model: "允许自定义组件使用v-model时定制prop和event",
  inheritAttrs: "默认情况下,父作用域的非props属性绑定会应用在子组件的根元素上。当编写嵌套有其它组件或元素的组件时,可以将该属性设置为false关闭这些默认行为",
  comments: "设为true时会保留并且渲染模板中的HTML注释"
});

Обычно используются экземпляры Vue.vm(View Model) переменная, чтобы назвать его.

свойство вычислено вычислено

Вкладывание слишком большого количества бизнес-логики в выражения шаблонов HTML может сделать шаблоны слишком тяжелыми и сложными в обслуживании. Таким образом, вы можете разделить более сложные выражения в шаблоне на вычисляемые свойства для вычисления.

<!-- 不使用计算属性 -->
<div id="example">
  {{ message.split("").reverse().join("") }}
</div>
<!-- 将表达式抽象到计算属性 -->
<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
<script>
  var vm = new Vue({
    el: "#example",
    data: {
      message: "Hello"
    },
    computed: {
      reversedMessage: function () {
        return this.message.split("").reverse().join("")
      }
    }
  })
</script>

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

Вычисляемые свойства по умолчанию имеют толькоgetterметод, но вы можете настроитьsetterметод.

<script>
... ... ...
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + " " + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(" ")
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
... ... ...
// 下面语句触发setter方法,firstName和lastName也会被相应更新
vm.fullName = "John Doe"
</script>

Наблюдатель за собственностью

С помощью свойства watch вы можете вручную наблюдать за изменениями данных в экземпляре Vue и, конечно же, вы также можете вызвать методvm.$watchдостичь той же цели.

<div id="watch-example">
  <p>Ask a yes/no question: <input v-model="question"></p>
  <p>{{ answer }}</p>
</div>
<script>
  var watchExampleVM = new Vue({
    el: "#watch-example",
    data: {
      question: "",
      answer: "I cannot give you an answer until you ask a question!"
    },
    watch: {
      // 如果question发生改变,该函数就会运行
      question: function (newQuestion) {
        this.answer = "Waiting for you to stop typing..."
        this.getAnswer()
      }
    },
    methods: {
      // _.debounce是lodash当中限制操作频率的函数
      getAnswer: _.debounce(
        function () {
          if (this.question.indexOf("?") === -1) {
            this.answer = "Questions usually contain a question mark. ;-)"
            return
          }
          this.answer = "Thinking..."
          var vm = this
          axios.get("https://yesno.wtf/api")
            .then(function (response) {
              vm.answer = _.capitalize(response.data.answer)
            })
            .catch(function (error) {
              vm.answer = "Error! Could not reach the API. " + error
            })
        },
        // 这是用户停止输入等待的毫秒数
        500
      )
    }
  })
</script>

Гибкость использования атрибута наблюдения заключается в том, что при отслеживании изменений данных вы можете выполнять некоторую обработку перехода, например установку промежуточных состояний.

Миксины со смешанными свойствами

Используется для повторного использования указанного объекта миксина в компоненте Vue.

// mixin对象
var mixin = {
  created: function () {
    console.log("混合对象的钩子被调用")
  },
  methods: {
    foo: function () {
      console.log("foo")
    },
    conflicting: function () {
      console.log("from mixin")
    }
  }
}
// vue属性
var vm = new Vue({
  mixins: [mixin],
  created: function () {
    console.log("组件钩子被调用")
  },
  methods: {
    bar: function () {
      console.log("bar")
    },
    conflicting: function () {
      console.log("from self")
    }
  }
})
// => "混合对象的钩子被调用"
// => "组件钩子被调用"
vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"

Свойства объекта опции компонента с тем же именем будут объединены в массив и вызывают последовательность, а свойства в объекте Mixin будут называться первыми. Если значение атрибута объекта опции компонента является объектом, атрибуты в микне будут игнорироваться.

Функция рендеринга render()

Используется для создания VNode, эта функция получаетcreateElement()метод в качестве первого параметра, после вызова метода он вернет виртуальный DOM (то есть VNode).

использовать выражение напрямую или вrender()пройти через функциюcreateElement()Для ручного рендеринга Vue автоматически поддерживаетblogTitleРеактивные обновления свойств.

<h1>{{ blogTitle }}</h1>
<script>
render: function (createElement) {
  return createElement("h1", this.blogTitle)
}
</script>

Если компонент является функциональным компонентом, функция render() также принимает параметр контекста для предоставления контекстной информации для функциональных компонентов, не имеющих экземпляра.

Реализовать виртуальный DOM с помощью функции render() сложно, поэтому вы можете использовать плагин Babel.babel-plugin-transform-vue-jsxПримените синтаксис JSX в функции render().

import AnchoredHeading from "./AnchoredHeading.vue"
new Vue({
  el: "#demo",
  render (h) {
    return (
      <AnchoredHeading level={1}>
        <span>Hello</span> world!
      </AnchoredHeading>
    )
  }
})

Глобальный API объекта Vue

Vue.extend(options)                 // 通过继承一个option对象来创建一个Vue实例。
Vue.nextTick([callback, context])   // 在下次DOM更新循环结束之后执行延迟回调。
Vue.set(target, key, value)         // 设置对象的属性,如果是响应式对象,将会触发视图更新。
Vue.delete(target, key)             // 删除对象的属性,如果是响应式对象,将会触发视图更新。
Vue.directive(id, [definition])     // 注册或获取全局指令。
Vue.filter(id, [definition])        // 注册或获取全局过滤器。
Vue.component(id, [definition])     // 注册或获取全局组件。
Vue.use(plugin)                     // 安装Vue插件。
Vue.mixin(mixin)                    // 全局注册一个mixin对象。
Vue.compile(template)               // 在render函数中编译模板字符串。
Vue.version                         // 提供当前使用Vue的版本号。

Vue.mixin(mixin)

Использование глобальных миксинов повлияет на всесоздать послеЭкземпляр Vue.

// 为自定义选项myOption注入一个处理器。
Vue.mixin({
  created: function () {
    var myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})
new Vue({
  myOption: "hello!"
})
// => "hello!"

Vue.directive(id, [definition])

Vue позволяет регистрировать пользовательские директивы для операций с базовым DOM.

Vue.directive("focus", {
  bind: function() {
    // 指令第一次绑定到元素时调用,只会调用一次,可以用来执行一些初始化操作。
  },
  inserted: function (el) {
    // 被绑定元素插入父节点时调用。
  },
  update: function() {
    // 所在组件的VNode更新时调用,但是可能发生在其子VNode更新之前。
  },
  componentUpdated: function() {
    // 所在组件VNode及其子VNode全部更新时调用。
  },
  unbind: function() {
    // 指令与元素解绑时调用,只会被调用一次。
  }
})

Обмен данными между хуками может осуществляться черезHTMLElementизdatasetатрибут (т. е. в теге HTML черезdata-определяемые форматом свойства).

Вышеупомянутая функция ловушки имеет следующие параметры:

  • el: HTML-элемент, связанный с директивой, может использоваться для прямого управления DOM.
  • vnode: виртуальный узел, сгенерированный компиляцией Vue.
  • oldVnode: предыдущий виртуальный узел, только вupdate,componentUpdatedВ наличии крючки.
  • привязка: объект, содержащий следующие свойства:
    • name: название инструкции, не включаяv-приставка.
    • value: значение привязки директивы, например.v-my-directive="1 + 1"серединаvalueЗначение2.
    • oldValue: предыдущее значение, к которому была привязана директива, только еслиupdate,componentUpdatedВ наличии крючки.
    • выражение: строковая форма связанного значения, например.v-my-directive="1 + 1"средиexpressionценность"1 + 1".
    • arg: параметр, передаваемый инструкции, напримерv-my-directive:fooсерединаargЗначение"foo".
    • модификаторы: объект, содержащий модификаторы, например.v-my-directive.foo.barизmodifiersЗначение{foo: true, bar: true}.

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

Vue.filter(id, [definition])

Vue может выполнять некоторое обычное форматирование текста, определяя фильтры, которые можно использовать в интерполяции усов и выражениях v-bind, а также использовать символ вертикальной черты при использовании.|добавляется в конце выражения.

<!-- in mustaches -->
{{ message | capitalize }}
<!-- in v-bind -->
<div v-bind:id="rawId | formatId"></div>
<!-- capitalize filter -->
<script>
  new Vue({
    filters: {
      capitalize: function (value) {
        if (!value) return ""
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
      }
    }
  })
</script>

Фильтры можно использовать последовательно или передавать в параметрах.

<span>{{ message | filterA | filterB }}</span>
<span>{{ message | filterA("arg1", arg2) }}</span>

Vue.use(plugin)

Vue добавляет некоторые глобальные функции через плагины, а плагины Vue переопределяют их.install()метод, первый параметр методаVue构造器, второй параметр является необязательнымoption对象:

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或属性
  Vue.myGlobalMethod = function () {}
  // 2. 添加全局资源
  Vue.directive("my-directive", {
    bind (el, binding, vnode, oldVnode) {}
  })
  // 3. 注入组件
  Vue.mixin({
    created: function () {}
  })
  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {}
}

Глобальный методVue.use()Чтобы использовать указанный плагин, вы также можете передать объект опции при его использовании.

Vue.use(MyPlugin, {someOption: true})

Плагины, такие как vue-router, будут автоматически вызываться, когда обнаружат, что Vue является глобальным объектом.Vue.use(), если в среде модуля CommonJS вам нужно явно вызватьVue.use().

Свойства и методы экземпляра

Экземпляры Vue предоставляют список префиксов$свойства экземпляра и методы .

let vm = new Vue();
vm = {
  // Vue实例属性的代理
  $data: "被watch的data对象",
  $props: "当前组件收到的props",
  $el: "Vue实例使用的根DOM元素",
  $options: "当前Vue实例的初始化选项",
  $parent: "父组件Vue对象的实例",
  $root: "根组件Vue对象的实例",
  $children: "当前实例的直接子组件",
  $slots: "访问被slot分发的内容",
  $scopedSlots: "访问scoped slots",
  $refs: "包含所有拥有ref注册的子组件",
  $isServer: "判断Vue实例是否运行于服务器",
  $attrs: "包含父作用域中非props的属性绑定",
  $listeners: "包含了父作用域中的v-on事件监听器",
  // 数据
  $watch: "观察Vue实例变化的表达式、计算属性函数",
  $set: "全局Vue.set的别名",
  $delete: "全局Vue.delete的别名",
  // 事件
  $on: "监听当前实例上的自定义事件,事件可以由vm.$emit触发",
  $once: "监听一个自定义事件,触发一次之后就移除监听器",
  $off: "移除自定义事件监听器",
  $emit: "触发当前实例上的事件",
  // 生命周期
  $mount: "手动地挂载一个没有挂载的Vue实例",
  $forceUpdate: "强制Vue实例重新渲染,仅影响实例本身和插入插槽内容的子组件",
  $nextTick: "将回调延迟到下次DOM更新循环之后执行",
  $destroy: "完全销毁一个实例",
}

Свойство $refs

сынобозначение компонентаrefПосле атрибута можно передатьотецкомпонент$refsСвойства экземпляра для доступа к нему.

<div id="parent">
  <user-profile ref="profile"></user-profile>
</div>
<script>
var parent = new Vue({ el: "#parent" })
var child = parent.$refs.profile // 访问子组件
</script>

$refs будет заполнен после рендеринга компонента.Он не отвечает и используется только как план на случай непредвиденных обстоятельств для прямого доступа к дочерним компонентам, поэтомуИзбегайте использования $refs в шаблонах или вычисляемых свойствах..

Жизненный цикл

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

жизненный цикл компонента

Не используйте стрелочные функции для свойств и обратных вызовов экземпляра Vue, напримерcreated: () => console.log(this.a)илиvm.$watch("a", newValue => this.myMethod()). Поскольку this функции стрелки привязано к родительскому контексту и не указывает на сам экземпляр Vue, поэтому в предыдущем кодеthis.aилиthis.myMethodбудетundefined.

Манипуляции с DOM через jQuery можно поместить вMountedПрогрессивное свойство, т. е. когда сборка завершена Vue при монтировании в DOM.

Привязка данных

Слой просмотра Vue черезMustache["mʌstæʃ]Синтаксис реактивно привязан к свойству данных в экземпляре Vue, а также через встроенные директивы.v-onceВыполните одностороннюю привязку илиv-htmlДиректива выводит связанную строку в виде HTML, хотя это уязвимо для XSS-атак.

<span>Message: {{ result }}</span>
<span v-once>一次性绑定: {{ msg }}</span>
<div v-html="rawHtml"></div>

Усы нельзя использовать для атрибутов HTML, в этом случае вам нужно прибегнуть кv-bindинструкция.

<div v-bind:id="dynamicId"></div>
<button v-bind:disabled="isButtonDisabled">Button</button>

Привязать класс и стиль HTML

Прямое управлениеclassа такжеstyleАтрибуты являются общим требованием при разработке внешнего интерфейса, Vue передаетv-bind:classа такжеv-bind:styleДирективы улучшают эти две операции целенаправленным образом.

v-bind:class

Связывание HTMLclassАтрибуты.

<!-- Vue对象中的data -->
<script>
  ... ...
  data: {
    isActive: true,
    hasError: false,
    classObject: {
      active: true,
      "text-danger": false
    }
  }
  ... ...
</script>
<!-- 直接绑定class到一个对象 -->
<div v-bind:class="classObject"></div>
<!-- 直接绑定class到对象的属性 -->
<div class="static" v-bind:class="{ active: isActive, "text-danger": hasError }"></div>
<!-- 渲染结果 -->
<div class="static active"></div>

Вы можете передать массив вv-bind:classТем самым устанавливая несколько атрибутов класса одновременно.

<!-- Vue对象中的data -->
<script>
  ... ...
  data: {
    activeClass: "active",
    errorClass: "text-danger"
  }
  ... ...
</script>
<!-- 绑定class到计算属性 -->
<div v-bind:class="[activeClass, errorClass]"></div>
<!-- 渲染结果 -->
<div class="active text-danger"></div>
<!-- 使用三目运算符,始终添加errorClass,只在isActive为true时添加activeClass -->
<div v-bind:class="[isActive ? activeClass : "", errorClass]"></div>
<!-- 在数组中使用对象可以避免三目运算符的繁琐 -->
<div v-bind:class="[{ active: isActive }, errorClass]"></div>

при использовании на пользовательских компонентахclassсвойства, эти свойства будут добавлены к корневому элементу компонента, эта возможность также относится кv-bind:class.

<!-- 声明一个组件 -->
<script>
  Vue.component("my-component", {
    template: "<p class="foo bar">Hi</p>",
    data: {
      isActive: true
    },
  })
</script>
<!-- 添加2个class属性 -->
<my-component class="baz boo"></my-component>
<!-- 渲染结果 -->
<p class="foo bar baz boo">Hi</p>
<!-- 使用v-bind:class -->
<my-component v-bind:class="{ active: isActive }"></my-component>
<!-- 渲染结果 -->
<p class="foo bar active">Hi</p>

v-bind:style

Связывание HTMLstyleАтрибуты.

<script>
  ... ...
  data: {
    styleObject: {
      color: "red",
      fontSize: "13px"
    },
    styleHeight: {
      height: 10rem;
    }
    styleWidth: {
      width: 20rem;
    }
  }
  ... ...
</script>
<div v-bind:style="styleObject"></div>
<!-- 使用数组可以将多个样式合并到一个HTML元素上面 -->
<div v-bind:style="[styleHeight, styleWidth]"></div>

использоватьv-bind:styleVue автоматически добавитпрефикс префикс, общие префиксы префиксов следующие:

  • -webkit-Chrome, Safari, новые версии Opera, все браузеры iOS (включая Firefox для iOS), почти все браузеры с ядром WebKit.
  • -moz-Для браузера Фаерфокс.
  • -o-Старые версии Opera, не использующие ядро ​​WebKit.
  • -ms-Браузеры Microsoft IE и Edge.

Использование выражений JavaScript

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

<span>{{ number + 1 }}</span>
<button>{{ ok ? "YES" : "NO" }}</button>
<p>{{ message.split("").reverse().join("") }}</p>
<div v-bind:id=""list-" + id"></div>
<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}
<!-- if流程控制属于多个表达式,因此不会生效,但可以使用三元表达式 -->
{{ if (ok) { return message } }}

Двусторонняя привязка данных v-модели

v-modelИнструкция по сутиv-onа такжеv-bindподслащенный синтаксис, директива получаетvalue属性, запускает новое значение, когда есть новое значениеinput事件.

<!-- 使用v-model的版本 -->
<input v-model="something">
<!-- 使用v-on和v-bind的版本 -->
<input v-bind:value="something"
       v-on:input="something = $event.target.value">
<!-- 也可以自定义输入域的响应式绑定 -->
<custom-input
  v-bind:value="something"
  v-on:input="something = arguments[0]">
</custom-input>

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

встроенные директивы

с участиемv-Префикс, который будет реагировать на DOM при изменении значения выражения. Команды могут быть получены с последующим:указанопараметр(Получается атрибутом arg внутри инструкции), или с.началомодификатор(Указывает, что директива связана особым образом).

<p v-if="seen">Hello world!</p>
<!-- 绑定事件 -->
<a v-bind:href="url"></a>
<!-- 绑定属性 -->
<a v-on:click="doSomething">
<!-- .prevent修饰符会告诉v-on指令对于触发的事件调用event.preventDefault() -->
<form v-on:submit.prevent="onSubmit"></form>

Vue этоv-bindа такжеv-onЭти две часто используемые директивы обеспечивают сокращенные формы:а также@.

<!-- v-bind -->
<a v-bind:href="url"></a>
<a :href="url"></a>
<!-- v-on -->
<a v-on:click="doSomething"></a>
<a @click="doSomething"></a>

В настоящее время Vue находится в2.4.2Версия предоставляет следующие встроенные директивы:

<html
  v-text = "更新元素的textContent"
  v-html = "更新元素的innerHTML"
  v-show = "根据表达式的true/false,切换HTML元素的display属性"
  v-for = "遍历内部的HTML元素"
  v-pre = "跳过表达式渲染过程,可以显示原始的Mustache标签"
  v-cloak = "保持在HTML元素上直到关联实例结束编译,可以隐藏未编译的Mustache"
  v-once = "只渲染元素和组件一次"
></html>
<!-- 根据表达式的true和false来决定是否渲染元素 -->
<div v-if="type === "A"">A</div>
<div v-else-if="type === "B"">B</div>
<div v-else-if="type === "C"">C</div>
<div v-else>Not A/B/C</div>
<!-- 动态地绑定属性或prop到表达式 -->
<p v-bind:attrOrProp
  .prop = "被用于绑定DOM属性"
  .camel = "将kebab-case特性名转换为camelCase"
  .sync = "语法糖,会扩展成一个更新父组件绑定值的v-on监听器"
></p>
<!-- 绑定事件监听器 -->
<button
  v-on:eventName
  .stop = "调用event.stopPropagation()"
  .prevent = "调用event.preventDefault()"
  .capture = "添加事件监听器时使用capture模式"
  .self = "当事件是从监听器绑定的元素本身触发时才触发回调" 
  .native = "监听组件根元素的原生事件"-
  .once = "只触发一次回调"
  .left = "点击鼠标左键触发"
  .right = "点击鼠标右键触发"
  .middle = "点击鼠标中键触发"
  .passive = "以{passive: true}模式添加监听器"
  .{keyCode | keyAlias} = "触发特定键触事件"
>
</button>
<!-- 表单控件的响应式绑定 -->
<input 
  v-model
  .lazy = "取代input监听change事件"
  .number = "输入字符串转为数字"
  .trim = "过滤输入的首尾空格" />

компоненты

Компоненты могут расширять функциональность HTML-элементов и инкапсулировать многократно используемый код. в состоянии пройтиVue.component( id, [definition] )Зарегистрируйтесь или получите глобальные компоненты.

// 注册组件,传入一个扩展过的构造器
Vue.component("my-component", Vue.extend({ ... }))
// 注册组件,传入一个option对象(会自动调用Vue.extend)
Vue.component("my-component", { ... })
// 获取注册的组件(始终返回构造器)
var MyComponent = Vue.component("my-component")

Следующий код создает экземпляр Vue и помещает пользовательский компонентmy-componentМонтировать в HTML.

<script>
  // 注册自定义组件
  Vue.component("my-component", {
    template: "<div>A custom component!</div>"
  })
  // 创建Vue根实例
  new Vue({
    el: "#example"
  })
</script>
<!-- 原始模板 -->
<div id="example">
  <my-component></my-component>
</div>
<!-- 渲染结果 -->
<div id="example">
  <div>A custom component!</div>
</div>
  • является собственностью

Выражения Vue не отображаются до тех пор, пока браузер не проанализирует HTML, но такие вещи, как<ul> <ol> <table> <select>ограничивает элементы HTML, которые можно обернуть, в то время как<option>Может отображаться только внутри определенных элементов HTML, что приводит к неправильному отображению выражений Vue. Таким образом, Vue предоставляетisкак псевдоним свойства для решения проблемы.

<!-- 不正确的方式 -->
<table>
  <my-row>...</my-row>
</table>
<!-- 使用is的正确方式 -->
<table>
  <tr is="my-row"></tr>
</table>
  • данные должны быть функцией

Vue.component()Свойство входящих данных не может быть объектом, оно должно быть функцией. Цель этого состоит в том, чтобы избежать повторного использования компонента в нескольких местах одного и того же шаблона, просто возврат объекта приведет к тому, что данные между компонентами будут загрязнены друг другом.Эту проблему можно полностью избежать, возвращая новые данные объект каждый раз через функцию.

Vue.component("simple-counter", {
  template: "<button v-on:click="counter += 1">{{ counter }}</button>",
  data: function () {
    return {
      a: "",
      b: ""
    }
  }
});
  • Связь между родительским и дочерним компонентами

родительский компонент черезpropsПередайте данные дочерним компонентам, а дочерние компоненты передаютeventsОтправить сообщение родительскому компоненту, т.е.props down, events up.

Взаимодействие компонентов родитель-потомок

props

Хотя область действия каждого компонента независима, к нему можно получить доступ черезprops属性Передать данные дочерним компонентам, что являетсяОднонаправленный поток данныхформа проявления.

Vue.component("child", {
  // 声明props
  props: ["message"],
  // 和data属性一样,prop也可以在vm通过this.message进行引用
  template: "<span>{{ message }}</span>"
})

Не изменяйте реквизиты внутри подкомпонентов, это вызовет ошибку в фоновом режиме.

изменение имени

Поскольку HTML не чувствителен к регистру, kebab-case(горб) Стиль имени props, в сборке будет в стиле camelCased(разделены тире) стиль принят.

<!-- camelCase in JavaScript -->
<script>
Vue.component("child", {
  props: ["myMessage"],
  template: "<span>{{ myMessage }}</span>"
})
<script>
<!-- kebab-case in HTML -->
<child my-message="hello!"></child>

динамический реквизит

в состоянии пройтиv-bindДирективы, которые реактивно связывают данные родительского компонента с реквизитами дочернего компонента. Когда данные родительского компонента изменяются, это изменение также распространяется на дочерний компонент.

<div>
  <input v-model="parentMsg">
  <br>
  <child v-bind:my-message="parentMsg"></child>
</div>

использоватьv-bindЗначение параметра может быть проанализировано как выражение JavaScript, иначе все входящие реквизиты будут рассматриваться дочерним компонентом как строковый тип.

<!-- 传递的是字符串"1" -->
<comp some-prop="1"></comp>
<!-- 传递实际的 number -->
<comp v-bind:some-prop="1"></comp>

Проверить реквизиты

Для реквизита компонента можно указать правила валидации, если поступающие данные не соответствуют требованиям, Vue выдаст соответствующее предупреждение, что может эффективно повысить надежность компонента.

Vue.component("example", {
  props: {
    // 基础类型检测
    propA: Number,
    // 多种类型
    propB: [String, Number],
    // 必传且是字符串
    propC: {
      type: String,
      required: true
    },
    // 数字,有默认值
    propD: {
      type: Number,
      default: 100
    },
    // 数组或对象的默认值由1个工厂函数返回
    propE: {
      type: Object,
      default: function () {
        return { message: "hello" }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
});

propsПроверка выполняется перед созданием экземпляра компонента.

Нереквизитные свойства компонента

Компоненты могут получать любые входящие атрибуты, и эти атрибуты будут добавлены к корневому элементу HTML-шаблона компонента (Независимо от того, определено ли это в реквизитах).

<!-- 带有属性的自定义组件 -->
<bs-date-input
  data-3d-date-picker="true"
  class="date-picker-theme-dark">
</bs-date-input>
<!-- 渲染出来的组件,class属性被合并 -->
<input type="date" data-3d-date-picker="true" class="form-control date-picker-theme-dark">

Свойства, передаваемые от родительского компонента к дочернему, могут переопределять свойства самого дочернего компонента, что приводит к повреждению и загрязнению дочернего компонента.

мероприятие

Дочерние компоненты могут взаимодействовать с родительскими компонентами через пользовательские события Vue.

Каждый экземпляр Vue реализует следующий API, но вы не можете прослушивать всплывающее событие дочерних компонентов напрямую через $on, а должны использовать директиву v-on.

  1. $on(eventName)прослушать событие
  2. $emit(eventName)триггерное событие

$onа также$emitнетaddEventListenerа такжеdispatchEventпсевдоним.

<div id="counter-event-example">
  <p>{{ total }}</p>
  <button-counter v-on:increment="incrementTotal"></button-counter>
  <button-counter v-on:increment="incrementTotal"></button-counter>
</div>
<script>
  Vue.component("button-counter", {
    template: "<button v-on:click="incrementCounter">{{ counter }}</button>",
    data: function () {
      return {
        counter: 0
      }
    },
    methods: {
      // 子组件事件
      incrementCounter: function () {
        this.counter += 1
        this.$emit("increment") //向父组件冒泡事件
      }
    },
  })
  new Vue({
    el: "#counter-event-example",
    data: {
      total: 0
    },
    methods: {
      // 父组件事件
      incrementTotal: function () {
        this.total += 1
      }
    }
  })
</script>
  • .nativeмодификатор

Разработчики также могут прослушивать собственные события в корневом элементе компонента, что требует помощи.nativeмодификатор.

<my-component v-on:click.native="doTheThing"></my-component>
  • .syncмодификатор

во ВьюpropsПо сути, реактивное связывание не может быть выполнено, чтобы предотвратить разрушение одностороннего потока данных и привести к тому, что несколько дочерних компонентов загрязнят состояние родительского компонента. Но в производственных условияхpropsПотребность в реактивных привязках реальна. Поэтому Vue будет.syncМодификатор инкапсулирован как синтаксис с сахарной оболочкой.После того, как родительский компонент использует модификатор в реквизитах дочернего компонента, родительский компонент автоматически привязывает реквизитыv-onсобытие, дочерний компонент будет сообщать родительскому компоненту при прослушивании изменений реквизита$emitобновить событие, чтобы родительский компонентpropsВозможность синхронизации с дочерними компонентами.

<!-- 使用.sync修饰符 -->
<comp :foo.sync="bar"></comp>
<!-- 被自动扩展为如下形式,该组件的子组件会通过this.$emit("update:foo", newValue)显式触发更新事件 -->
<comp :foo="bar" @update:foo="val => bar = val"></comp>
  • Параллельная связь компонентов

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

var bus = new Vue()
// 触发组件A中的事件
bus.$emit("id-selected", 1)
// 在组件B监听事件
bus.$on("id-selected", function (id) {
  ... ... ...
})

Лучшим способом является использование библиотеки управления состоянием потока, такой как VueX или Redux.

slot

Вы можете смешать содержимое родительского компонента с шаблоном дочернего компонента, и вы можете использовать его в дочернем компоненте<slot>Слот, который является содержимым родительского компонента.

Содержимое шаблона родительского компонента компилируется в области родительского компонента, а содержимое шаблона дочернего компонента компилируется в области дочернего компонента.

анонимный слот

Когда дочерний компонент имеет только один без атрибутов<slot>, весь фрагмент содержимого родительского компонента будет вставлен в позицию DOM, где находится слот, заменив саму метку слота.

<!-- 子组件my-component的模板 -->
<div>
  <h2>Child</h2>
  <slot>
    父组件没有需要插入的内容时显示
  </slot>
</div>
<!-- 父组件模板中使用my-component -->
<div>
  <h1>Parent</h1>
  <child>
    <p>Content 1</p>
    <p>Content 2</p>
  </child>
</div>
<!-- 渲染结果 -->
<div>
  <h1>Parent</h1>
  <div>
    <h2>Child</h2>
    <p>Content 1</p>
    <p>Content 2</p>
  </div>
</div>

<slot>Содержимое тега компилируется в области действия дочернего компонента и отображается только тогда, когда родительскому компоненту нечего вставлять.

именованный слот

в состоянии пройти<slot>элементальnameсвойства, чтобы настроить способ распространения контента.

<!-- 子组件 -->
<div id="app">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
<!-- 父组件 -->
<app>
  <div slot="header">Header</div>
  <p>Content 1</p>
  <p>Content 2</p>
  <div slot="footer">Footer</div>
</app>
<!-- 渲染结果 -->
<div id="app">
  <header>
    <div>Header</div>
  </header>
  <main>
    <p>Content 1</p>
    <p>Content 2</p>
  </main>
  <footer>
    <p>Footer</p>
  </footer>
</div>

Анонимный слот действует как слот для фрагмента родительского компонента без соответствующего содержимого.

слот с прицелом

дочерний компонент черезpropsпередавать данные в<slot>слот, родительский компонент используетscopeатрибут<template>для представления шаблона, представляющего текущий слот,scopeПеременная, соответствующая значению, получит объект реквизита, переданный дочерним компонентом.

<!-- 子组件通过props传递数据给插槽 -->
<div class="child">
  <slot text="hello from child"></slot>
</div>
<!-- 父组件使用带有scope属性的<template> -->
<div class="parent">
  <child>
    <template scope="props">
      <span>hello from parent</span>
      <span>{{ props.text }}</span>
    </template>
  </child>
</div>
<!-- 渲染结果 -->
<div class="parent">
  <div class="child">
    <span>hello from parent</span>
    <span>hello from child</span>
  </div>
</div>

функциональный компонент

то есть без гражданства (нет данных) нет экземпляра (нет этого контекста) компоненты, накладные расходы на рендеринг невелики и не будут отображаться вVue devtoolsсреди.

Vue.component("my-component", {
  functional: true,
  // 通过提供context参数为没有实例的函数组件提供上下文信息
  render: function (createElement, context) {},
  // Props可选
  props: {}
})

динамические компоненты

использовать<component>элемент и динамически привязать егоisАтрибут, который позволяет нескольким компонентам использовать одну и ту же точку монтирования объекта Vue и обеспечивать динамическое переключение.

<script>
var vm = new Vue({
  el: "#example",
  data: {
    currentView: "home"
  },
  components: {
    home: { /* ... */ },
    posts: { /* ... */ },
    archive: { /* ... */ }
  }
})
</script>
<component v-bind:is="currentView">
  <!-- 组件在vm.currentview变化时改变! -->
</component>

Если вам нужно сохранить переключаемый компонент в памяти, сохранить его состояние и избежать повторного рендеринга, вы можете использовать встроенный в Vuekeep-aliveинструкция.

<keep-alive>
  <component :is="currentView">
    <!-- 非活动组件将被缓存! -->
  </component>
</keep-alive>

Компоненты загружаются асинхронно

Vue позволяет определять компоненты как фабричные функции для асинхронного разрешения определений компонентов. Vue запускает фабричную функцию только тогда, когда компонент выполняет рендеринг, и кэширует результат для последующего рендеринга. Фабричная функция, определяющая компонент, получит разрешение(Вызывается при получении параметров компонента Vue, загруженных с сервера.) и отклонить (Вызывается, когда не удалось загрузить параметры удаленного компонента Vue.) функция обратного вызова в качестве параметра.

Vue.component("async-example", function (resolve, reject) {
  setTimeout(function () {
    // 将组件定义传递到resolve回调函数当中
    resolve({
      template: "<div>I am async!</div>"
    })
  }, 1000)
})

Можно комбинировать с предоставленным Webpackвырезание кодаФункция извлекает объект параметров компонента Vue в отдельный файл JavaScript для обеспечения асинхронной загрузки по запросу.

// 使用webpack的require()来进行异步代码块切割
Vue.component("async-webpack-example", function (resolve) {
  require(["./my-async-component"], resolve)
})
// 使用webpack的import()来进行异步代码块切割
Vue.component(
  "async-webpack-example", () => import("./my-async-component")
)

Начиная с Vue 2.3.0, асинхронный компонент можно определить следующим образом.

const AsyncWebpackExample = () => ({
  component: import("./MyComp.vue"),   // 需要加载的组件
  loading: LoadingComp,                // loading时渲染的组件
  error: ErrorComp,                    // 出错时渲染的组件
  delay: 200,                          // 渲染loading组件前的等待时间(默认:200ms)
  timeout: 3000                        // 最长等待时间,超出则渲染error组件(默认:Infinity)
})

Чтобы использовать этот метод записи на компонент маршрутизации, вам необходимо использовать Vue-маршрутизатор версии 2.4.0 или более поздней версии.

Циклические ссылки на компоненты

Циклическая ссылка, то есть два компонента ссылаются друг на друга, например, в следующем кодеtree-folder,tree-folder-contentsДва компонента становятся родительским или дочерним узлом друг друга одновременно, если вы используете инструмент модульного управления Webpack.requiring/importingкомпонент, он сообщитFailed to mount component: template or render function not defined.ошибка.

<template>
  <p>
    <span>{{ folder.name }}</span>
    <tree-folder-contents :children="folder.children"/>
  </p>
</template>
<template>
  <ul>
    <li v-for="child in children">
      <tree-folder v-if="child.children" :folder="child"/>
      <span v-else>{{ child.name }}</span>
    </li>
  </ul>
</template>

потому чтоtree-folder,tree-folder-contentsПосле обращения друг к другу невозможно определить порядок загрузки компонентов в бесконечный цикл, поэтому необходимо заранее указать приоритет загрузки компонентов вебпака. Чтобы решить проблему циклической ссылки компонентов Vue в приведенном выше примере, вы можетеtree-folderкомпонентbeforeCreate()Проблемы, вызванные регистрацией в функции жизненного циклаtree-folder-contentsкомпоненты.

beforeCreate: function () {
  this.$options.components.TreeFolderContents = require("./tree-folder-contents.vue").default
}

Соглашение об именах компонентов

Может использоваться при именовании компонентов в JavaScript.kebab-case,camelCase,PascalCase, но только в шаблонах HTMLkebab-caseФормат.

<kebab-cased-component></kebab-cased-component>
<camel-cased-component></camel-cased-component>
<pascal-cased-component></pascal-cased-component>
<!-- 也可以通过自关闭方式使用组件 -->
<kebab-cased-component />
<script>
components: {
  "kebab-cased-component": {},
  "camelCasedComponent": {},
  "PascalCasedComponent": {}
}
</script>

Рекомендуется передать в JavaScriptPascalCaseспособ объявления компонентов в HTML черезkebab-caseспособ использования компонентов.

компонентная рекурсия

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

// 局部注册
new Vue({
  el: "#my-component",
  name: "my-component",
  template: "<div><my-component></my-component></div>"
})
// 全局注册
Vue.component("my-component", {
  // name: "my-component", 可以省略name属性
  template: "<div><my-component></my-component></div>"
})

Когда компонент рекурсивно появляется в бесконечном цикле, он предложитmax stack size exceededошибка, поэтому вам нужно убедиться, что рекурсивные операции имеют условие завершения (Например, используйте v-if и верните false).

шаблон компонента

  • Может использоваться на компонентах Vueinline-templateатрибут, компонент будет отображать встроенный HTML-контент как шаблон для самого компонента, а не какslotраспространяемый контент.
<my-component inline-template>
  <div>
    <p>These are compiled as the component"s own template.</p>
    <p>Not parent"s transclusion content.</p>
  </div>
</my-component>
  • также через<script>Использование на этикеткеtype="text/x-template"а такжеidатрибут для определения встроенного шаблона.
<script type="text/x-template" id="hello-world-template">
  <p>Hello hello hello</p>
</script>
<script>
Vue.component("hello-world", {
  template: "#hello-world-template"
})
</script>

Управление состоянием Vuex

Vuex — это режим управления состоянием, специально предназначенный для приложений Vue.Ядром каждого приложения Vuex являетсяstore(склад), который загружает приложениеstate(условие) контейнеры, обычно только один на приложениеstoreпример.

Процесс выполнения Vuex

вексstateявляется отзывчивым, т.е.storeсерединаstateПри возникновении изменений соответствующие компоненты также будут обновлены, измененыstoreсредиstateЕдинственный способ - податьmutations.

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})
store.commit("increment")       // 通过store.state来获取状态对象
console.log(store.state.count)  // 通过store.commit()改变状态

State

отstoreЗалезайstateСамый простой способ - вернуть указанное в вычисляемом свойствеstate,в любое времяstateКогда происходит изменение, вычисляемое свойство выполняется повторно, и связанная с ним модель DOM обновляется.

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return store.state.count
    }
  }
}

Vuex предоставляетstoreвариант, будетstateиз корневого компонентаинъекцияв каждый подкомпонент, чтобы избежать частыхimport store.

// 父组件中注册store属性
const app = new Vue({
  el: "#app",
  store: store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>`
})
// 子组件,store会注入到子组件,子组件可通过this.$store进行访问
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

Vuex предоставляетmapState()Вспомогательная функция, чтобы избежать использования несколькихstateВ случае , объявите вычисляемое свойство несколько раз.

// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from "vuex"
export default {
  computed: mapState({
    count: state => state.count,
    // 传递字符串参数"count"等同于`state => state.count`
    countAlias: "count",
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}
// 当计算属性名称与state子节点名称相同时,可以向mapState传递一个字符串数组
computed: mapState([
  "count" // 映射this.count到store.state.count
])

mapState()Функция возвращаетstateОбъект связанного вычисляемого свойства, здесь вы можете использовать оператор расширения объекта ES6....Сравните этот объект с собственным компонентом Vue.computedсвойства объединены.

computed: {
  localComputed () {},
  ...mapState({})
}

Vuex позволяетstoreопределено вgetters(Вычисляемые свойства, которые можно рассматривать как хранилища),gettersВозвращаемое значение кэшируется в соответствии с его зависимостями и будет пересчитываться только в том случае, если значение зависимости изменилось. Метод получаетstateВ качестве первого параметра другиеgettersкак второй параметр. прямо вstoreзвонитьgettersчтобы получить указанное расчетное значение.

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: "...", done: true },
      { id: 2, text: "...", done: false }
    ]
  },
  getters: {
    doneTodos: (state, getters) => {
      return state.todos.filter(todo => todo.done)
    }
  }
})
// 获取doneTodos = [{ id: 1, text: "...", done: true }]
store.getters.doneTodos

Это упрощает основуstoreсуществующий вstateпроизводный новыйstate, чтобы избежать избыточности кода при повторном использовании в нескольких компонентах.

computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodos // 现在可以方便的在Vue组件使用store中定义的doneTodos
  }
}

предоставлено VuexmapGetters()Вспомогательная функция будетstoreсерединаgettersСопоставляется с локальными вычисляемыми свойствами.

import { mapGetters } from "vuex"
export default {
  computed: {
    // 使用对象展开运算符将getters混入computed计算属性
    ...mapGetters([
      "doneTodosCount",
      doneCount: "doneTodosCount" // 映射store.getters.doneTodosCount到别名this.doneCount
    ])
  }
}

Mutations

Единственный способ изменить хранилище в состоянии — отправить мутацию ([mjuː”teɪʃ(ə)n] п. Изменение), мутации аналогичны пользовательским событиям со строковым типом события и функцией обратного вызова (Получение состояния в качестве параметра - это то место, где состояние изменяется).

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    // 触发类型为increment的mutation时被调用
    increment (state) {
      state.count++ // 变更状态
    }
  }
})
// 触发mutation
store.commit("increment")

можно через магазинcommit()Метод запускает указанные мутации, которые также можно передать черезstore.commit()Передать параметры в мутацию.

// commit()
store.commit({
  type: "increment",
  amount: 10
})
// store
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

Рекомендуется использовать константы для типов событий мутации и размещать эти константы в отдельных файлах для облегчения управления и предотвращения дублирования.

// mutation-types.js
export const SOME_MUTATION = "SOME_MUTATION"
// store.js
import Vuex from "vuex"
import { SOME_MUTATION } from "./mutation-types"
const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // 可以通过ES6的计算属性命名特性去使用常量作为函数名
    [SOME_MUTATION] (state) {
      // mutate state
    }
  }
})

mutation()Должна быть синхронной функцией, потому что devtool не может отслеживать обратные вызовы.stateПроизведена асинхронная модификация.

Компоненты Vue могут использоватьthis.$store.commit("xxx")Отправьте мутацию или используйтеmapMutations()Поместите компонент Vue вmethodsкарты наstore.commitвызов (необходимо ввести в корневом узлеstore).

import { mapMutations } from "vuex"
export default {
  methods: {
    ...mapMutations([
      "increment" // 映射this.increment()为this.$store.commit("increment")
    ]),
    ...mapMutations({
      add: "increment" // 映射this.add()为this.$store.commit("increment")
    })
  }
}

Actions

Действие используется для отправки изменения, а действие может содержать асинхронные операции. Функция Action принимает экземпляр с теми же методами и свойствами, что и экземпляр хранилища.contextобъект, поэтому к нему можно получить доступ, вызвавcontext.commitотправить мутацию или черезcontext.stateа такжеcontext.gettersчтобы получить государство и быту.

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit("increment")
    }
  }
})

В производственной среде код может быть упрощен с использованием параметров деструктации ES6.

actions: {
  // 直接向action传递commit方法
  increment ({ commit }) {
    commit("increment")
  }
}

ActionPassstore.dispatch()способ распространения,mutationв котором толькоСинхронизироватьоперация, при этомactionможно сделать внутриасинхронныйоперация. Ниже приведен пример корзины покупок с несколькими мутациями, распределенными в коде и асинхронными операциями API.

actions: {
  checkout ({ commit, state }, products) {
    
    const savedCartItems = [...state.cart.added]  // 把当前购物车的物品备份起来
    commit(types.CHECKOUT_REQUEST)                // 发出结账请求,然后清空购物车
    // 购物Promise分别接收成功和失败的回调
    shop.buyProducts(
      products,
      () => commit(types.CHECKOUT_SUCCESS),                  // 成功操作
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)   // 失败操作
    )
  }
}

можно использовать компонентыthis.$store.dispatch("xxx")отправить действие или использоватьmapActions()компонентаmethodsкарты наstore.dispatch(Необходимо вводить в корневой узелstore).

import { mapActions } from "vuex"
export default {
  methods: {
    ...mapActions([
      "increment"       // 映射this.increment()为this.$store.dispatch("increment")
    ]),
    ...mapActions({
      add: "increment"  // 映射this.add()为this.$store.dispatch("increment")
    })
  }
}

store.dispatchможет справитьсяactionвозвращается функцией обратного вызоваPromise,а такжеstore.dispatchсам по-прежнему возвращаетPromise.

actions: {
  // 定义一个返回Promise对象的actionA
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit("someMutation") // 触发mutation
        resolve()
      }, 1000)
    })
  },
  // 也可以在actionB中分发actionA
  actionB ({ dispatch, commit }) {
    return dispatch("actionA").then(() => {
      commit("someOtherMutation") // 触发另外一个mutation
    })
  }
}
// 现在可以分发actionA
store.dispatch("actionA").then(() => {
  ... ... ...
})

Вы можете испытать функции асинхронной обработки ES7.async/awaitсовмещать действия.

actions: {
  async actionA ({ commit }) {
    commit("gotData", await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch("actionA") //等待actionA完成
    commit("gotOtherData", await getOtherData())
  }
}

Module

В случае единого дерева состояний для всего приложения все состояния будут централизованы в одном объекте хранилища, поэтому хранилище может сильно раздуться. Поэтому Vuex позволяет разрезать хранилище на модули (module), каждый модуль имеет свой собственныйstate,mutation,action,getterили даже вложенные подмодули.

const moduleA = {
  state: {},
  mutations: {},
  actions: {},
  getters: {}
}
const moduleB = {
  state: {},
  mutations: {},
  actions: {}
}
const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})
store.state.a // moduleA的状态
store.state.b // moduleB的状态

внутри модуляmutations()а такжеgetters()Первый полученный параметр — это локальный объект состояния модуля.

const moduleA = {
  state: { count: 0 },
  mutations: {
    increment (state) {
      state.count++ // 这里的state是模块的局部状态
    }
  },
  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

Среди внутренних действий модуля можно пройтиcontext.stateполучить местное состояние иcontext.rootStateПолучить глобальное состояние.

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit("increment")
      }
    }
  }
}

внутри модуляgetters()метод, который может получать глобальное состояние через свой третий параметр.

const moduleA = {
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

строгий режим

В строгом режиме, если изменение состояния не вызваноmutation()функция вызывает выдачу ошибки. Просто создайтеstoreпри входящемstrict: trueвключить строгий режим.

const store = new Vuex.Store({
  strict: true
})

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

const store = new Vuex.Store({
  strict: process.env.NODE_ENV !== "production"
})

В строгом режиме использование инструкции v-model для состояния, принадлежащего Vuex, вызовет ошибку.В этом случае вам нужно вручную привязать значение и прослушать события ввода и изменения, а также вручную отправить действие в обратном вызове события. . Другой способ сделать это — напрямую переопределить методы get и set вычисляемых свойств.

Суммировать

  1. Состояние уровня приложения должно быть централизовано водин объект магазинасередина.
  2. представитьmutationединственный способ изменить состояние, и процессСинхронизироватьиз.
  3. асинхронныйлогика должна быть заключена вactionв.

Webpack Vue Loader

vue-loaderэто загрузчик Webpack, предоставляемый сообществом Vue с открытым исходным кодом для.vueОднофайловые компоненты Postfix преобразуются в модули JavaScript, каждый.vueОднофайловый компонент может включать в себя следующие части:

  1. Один<template>.
  2. Один<script>.
  3. несколько<style>.
<template>只能有1个</template>
<script>只能有1个</script>
<style>可以有多个</style>
<style>可以有多个</style>
<style>可以有多个</style>

Область действия CSS

К.vueкомпонент одного файла<style>Добавить на этикеткуscopedсвойства, которые позволяют<style>Стили в теге применяются только к текущему компоненту. При использовании области действия селектор стиля должен попытаться использовать класс или идентификатор, чтобы улучшить производительность рендеринга страницы.

<!-- 单文件组件定义 -->
<style scoped>
.example {
  color: red;
}
</style>
<template>
  <div class="example">Hank</div>
</template>
<!-- 转换结果 -->
<style>
.example[data-v-f3f3eg9] {
  color: blue;
}
</style>
<template>
  <div class="example" data-v-f3f3eg9>Hank</div>
</template>

Ленты можно использовать одновременно в одном компонентеscopedс атрибутом и без атрибута<style/>, которые используются для определения личных стилей компонентов и глобальных стилей соответственно.

Модульность CSS

в одном файловом компоненте.vueиз<style>добавить на ярлыкmoduleproperties, чтобы включить модульность CSS.CSS Modulesдля модульной композиции CSS,vue-loaderБыла интегрирована функция модульности CSS.

<style module>
.red {
  color: red;
}
.bold {
  font-weight: bold;
}
</style>

Модуль CSS внедряет компонент Vue с именем$styleВычисляемые свойства, которые реализованы в компоненте<template/>использовать динамическийclassсвойство для привязки.

<template>
  <p :class="$style.red">
    This should be red
  </p>
</template>

анимация

Vue вставляет, обновляет, удаляетDOMКогда доступны следующие способы отображения записи (entering) и уходи (leaving) Эффекты перехода.

  1. Применяйте классы в переходах и анимации CSS.
  2. DOM непосредственно управляется в функции перехода ловушки.
  3. Используйте библиотеки анимации CSS, JavaScript, такие какAnimate.css,Velocity.js.

переходный компонент

Vue предоставляет встроенные компоненты<transition/>Чтобы добавить эффекты анимации перехода к элементам HTML и компонентам Vue, вы можетеУсловное отображение(использоватьv-ifилиv-show),динамические компоненты,Показать корневой узел компонентаРендеринг в деле.<transition/>В основном используется для обработки одного узла или одновременного рендеринга одного из нескольких узлов.

Имя класса автоматически переключаемого класса

Введите компонент или HTML (entering) и уходи (leaving) в эффекте перехода Vue автоматически переключится и применит шесть имен классов, показанных на рисунке ниже.

Класс для автоматического переключения эффектов перехода

можно использовать<transition/>изnameатрибут для автоматического создания имен переходных классов, например, в следующем примереname: "fade"автоматически расширится до.fade-enter,.fade-enter-activeЖдать,nameПо умолчанию имя класса свойства по умолчанию —v.

<div id="demo">
  <button v-on:click="show = !show"> Toggle </button>
  <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
</div>
<script>
new Vue({
  el: "#demo",
  data: {
    show: true
  }
})
</script>
<style>
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s
}
.fade-enter, .fade-leave-to {
  opacity: 0
}
</style>

Пользовательское имя класса CSS

комбинироватьAnimate.cssПри использовании вы можете<transition/>Среди них имя класса настраивается с помощью следующих атрибутов.

<transition
  enter-class = "animated"
  enter-active-class = "animated"
  enter-to-class = "animated"
  leave-class = "animated"
  leave-active-class = "animated"
  leave-to-class = "animated">
</transition>

Пользовательские хуки JavaScript

комбинироватьVelocity.jsПри использовании установите функцию ловушки в свойстве через v-on.

<transition
  v-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:after-enter="afterEnter"
  v-on:enter-cancelled="enterCancelled"
  v-on:before-leave="beforeLeave"
  v-on:leave="leave"
  v-on:after-leave="afterLeave"
  v-on:leave-cancelled="leaveCancelled">
</transition>
<script>
// ...
methods: {
  beforeEnter: function (el) {},
  enter: function (el, done) { done() },
  afterEnter: function (el) {},
  enterCancelled: function (el) {},
  beforeLeave: function (el) {},
  leave: function (el, done) { done() },
  afterLeave: function (el) {},
  leaveCancelled: function (el) {} // 仅用于v-show
}
</script>

Явно задайте продолжительность перехода

можно использовать<transition>Вверхduration属性Установите явную продолжительность перехода в миллисекундах.

<transition :duration="1000"> Hank </transition>
<!-- 可以分别定制进入、移出的持续时间 -->
<transition :duration="{ enter: 500, leave: 800 }"> Hank </transition>

Переход при первом рендеринге компонента

пройти через<transition>Вверхappear属性Задает анимацию перехода при первом отображении узла компонента.

<!-- 自定义CSS类名 -->
<transition
  appear
  appear-class="custom-appear-class"
  appear-to-class="custom-appear-to-class"
  appear-active-class="custom-appear-active-class">
</transition>
<!-- 自定义JavaScript钩子 -->
<transition
  appear
  v-on:before-appear="customBeforeAppearHook"
  v-on:appear="customAppearHook"
  v-on:after-appear="customAfterAppearHook"
  v-on:appear-cancelled="customAppearCancelledHook">
</transition>

Эффекты перехода для HTML-элементов

Ключевое свойство компонента Vue

Атрибут key в основном используется в алгоритме виртуального DOM Vue для различения старых и новых VNodes и не используется явно.key, Vue будет использовать самый производительный алгоритм автоматического сравнения. явное использованиеkey, будет основываться наkeyИзменения для изменения порядка элементов и удаления несуществующихkeyЭлементы. Дочерние элементы с одним и тем же родительским элементом должны иметь уникальныеkey, потому что повторяетсяkeyприведет к ошибкам рендеринга.

<ul>
  <!-- 最常见的用法是在使用v-for的时候 -->
  <li v-for="item in items" :key="item.id">...</li>
</ul>
поочередный переход элементов

Доступно через Vuev-ifа такжеv-elseСвойства для достижения альтернативных переходов нескольких компонентов, наиболее распространенным эффектом перехода является список и сообщение, описывающее, когда список пуст.

<transition>
  <table v-if="items.length > 0">
    <!-- ... -->
  </table>
  <p v-else>Sorry, no items found.</p>
</transition>

При переключении элементов с одинаковыми именами в Vue необходимо передать ключевое словоkeyВ качестве отличительного знака, в противном случае Vue будет заменять содержимое только внутри одного и того же тега из соображений эффективности, поэтому<transition>Настройки одноименных элементов в компонентахkeyЯвляетсяЛучшие практики.

<transition>
  <button v-if="isEditing" key="save"> Save </button>
  <button v-else key="edit"> Edit </button>
</transition>

В некоторых сценариях это можно сделать, задав одному и тому же HTML-элементуkeyсвойства устанавливают разные состояния вместо подробногоv-ifа такжеv-else.

<!-- 通过v-if和v-else来实现 -->
<transition>
  <button v-if="isEditing" key="save"> Save </button>
  <button v-else key="edit"> Edit </button>
</transition>
<!-- 设置动态的key属性来实现 -->
<transition>
  <button v-bind:key="isEditing"> {{ isEditing ? "Save" : "Edit" }} </button>
</transition>

А для использования несколькихv-ifМногоэлементные переходы также могут быть динамическими.keyсвойства сильно упрощаются.

<!-- 多个v-if实现的多元素过渡 -->
<transition>
  <button v-if="docState === "saved"" key="saved"> Edit </button>
  <button v-if="docState === "edited"" key="edited"> Save </button>
  <button v-if="docState === "editing"" key="editing"> Cancel </button>
</transition>
<!-- 通过动态key属性可以大幅简化模板代码 -->
<transition>
  <button v-bind:key="docState"> {{ buttonMessage }} </button>
</transition>
<script>
...
computed: {
  buttonMessage: function () {
    switch (this.docState) {
      case "saved": return "Edit"
      case "edited": return "Save"
      case "editing": return "Cancel"
    }
  }
}
</script>

Эффект перехода компонента Vue

Переходы между несколькими компонентами Vue не нужно использовать.keyсвойства, просто используйтединамические компонентыВот и все.

<transition name="component-fade" mode="out-in">
  <component v-bind:is="view"></component>
</transition>
<script>
new Vue({
  el: "#transition-components-demo",
  data: {
    view: "v-a"
  },
  components: {
    "v-a": {
      template: "<div>Component A</div>"
    },
    "v-b": {
      template: "<div>Component B</div>"
    }
  }
})
<script>
<style>
.component-fade-enter-active, .component-fade-leave-active {
  transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to {
  opacity: 0;
}
<style>

Выберите режим перехода для элементов HTML или компонентов Vue.

<transition>запись по умолчанию (enter) и уходи (leave) поведение происходит одновременно, поэтому, когда несколько элементов HTML или компонентов Vue, которые необходимо переключать и отображать, находятся в одном и том же положении, этот одновременный переход входа и выхода не может удовлетворить все потребности, Vue может пройти<transition-gruop>компонентmodeсвойства, чтобы выбрать следующие режимы перехода.

  • in-out: сначала сменяется новый элемент, а затем после завершения смещается отображаемый в данный момент элемент.
  • out-in: Текущий отображаемый элемент переходит первым, а затем после завершения переходит новый элемент.
<transition name="fade" mode="out-in">
  <button v-if="docState === "saved"" key="saved"> Edit </button>
  <button v-if="docState === "edited"" key="edited"> Save </button>
  <button v-if="docState === "editing"" key="editing"> Cancel </button>
</transition>

компонент переходной группы

<transition-group>Используется для установки эффектов перехода для нескольких элементов HTML или компонентов Vue, отличных от<transition>, компонент будет отображаться как реальный<span>элемент, но разработчики также могут передавать<transition-group>компонентtagАтрибуты заменяются другими допустимыми элементами HTML.<transition-group>Элементы внутри компонента должны предоставлять уникальныйkeyстоимость имущества.

<div id="list-demo" class="demo">
  <button v-on:click="add">Add</button>
  <button v-on:click="remove">Remove</button>
  <transition-group name="list" tag="p">
    <span v-for="item in items" v-bind:key="item" class="list-item">
      {{ item }}
    </span>
  </transition-group>
</div>
<script>
new Vue({
  el: "#list-demo",
  data: {
    items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
    nextNum: 10
  },
  methods: {
    randomIndex: function () {
      return Math.floor(Math.random() * this.items.length)
    },
    add: function () {
      this.items.splice(this.randomIndex(), 0, this.nextNum++)
    },
    remove: function () {
      this.items.splice(this.randomIndex(), 1)
    },
  }
})
</script>
<style>
.list-item {
  display: inline-block;
  margin-right: 10px;
}
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter, .list-leave-to {
  opacity: 0;
  transform: translateY(30px);
}
</style>

<transition-group>Реализованный эффект перехода по списку. При добавлении или удалении HTML-элемента другие соседние HTML-элементы мгновенно перемещаются на новую позицию, и этот процесс не является плавным переходом. Для решения этой проблемы,<transition-group>поставкаv-moveфункция для переопределенияВо время мобильного переходаИспользуемое имя класса CSS. Включите эту функцию, вы можете пройтиnameсвойства задаются вручную (в примере нижеname="flip-list"а также.flip-list-move), также можно использовать напрямуюmove-classАтрибуты.

<div id="flip-list-demo" class="demo">
  <button v-on:click="shuffle">Shuffle</button>
  <transition-group name="flip-list" tag="ul">
    <li v-for="item in items" v-bind:key="item">
      {{ item }}
    </li>
  </transition-group>
</div>
<script>
new Vue({
  el: "#flip-list-demo",
  data: {
    items: [1,2,3,4,5,6,7,8,9]
  },
  methods: {
    shuffle: function () {
      this.items = _.shuffle(this.items)
    }
  }
})
</script>
<style>
.flip-list-move {
  transition: transform 1s;
}
<style>

в состоянии пройтиОтзывчивыйсвязывание<transition>а также<transition-gruop>Атрибут имени компонента, чтобы можно было достичь эффекта динамического перехода в соответствии с состоянием самого компонента.

<transition v-bind:name="transitionName"></transition>