ES6 课程概述⑤

组件_动态组件

基本使用

当我们在一个多标签的界面中,在不同组件之间进行动态切换是非常有用的。

<div id="app">
  <button v-for="page in pages" @click="pageCmp = page.cmp" :key="page.id">
    {{ page.name }}
  </button>
  <component :is="pageCmp"></component>
</div>
Vue.component("base-post", {
  data() {
    return {
      postCmp: "",
      posts: [
        { title: "标题1", content: { template: `<div>内容1</div>` }, id: 11 },
        { title: "标题2", content: { template: `<div>内容2</div>` }, id: 12 },
        { title: "标题3", content: { template: `<div>内容3</div>` }, id: 13 },
      ],
    };
  },
  mounted() {
    this.postCmp = this.posts[0].content;
  },
  template: `
    <div>
      <button
        v-for="post in posts"
        @click="postCmp = post.content"
        :key="post.id"
      >{{ post.title }}</button>
      <component :is="postCmp"></component>
    </div>
  `,
});
Vue.component("base-more", {
  template: `<div>更多内容</div>`,
});

const vm = new Vue({
  el: "#app",
  data: {
    pages: [
      { name: "博客", cmp: "base-post", id: 0 },
      { name: "更多", cmp: "base-more", id: 1 },
    ],
    pageCmp: "base-post",
  },
});

通过上面方法,我们可以实现组件间的切换,能够注意到的是:每一次切换标签时,都会创建一个新的组件实例,重新创建动态组件在更多情况下是非常有用的,但是在这个案例中,我们会更希望哪些标签的组件实例能够在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个<keep-alive>元素将动态组件包裹起来。如:

<!-- 失活的组件将会被缓存!-->
<keep-alive>
  <component v-bind:is="pageCmp"></component>
</keep-alive>

注意:<keep-alive> 要求被切换到的组件都有自己的名字,不论是通过组件的 name 选项还是局部/全局注册。

keep-alive

<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。
当组件在 <keep-alive> 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。

activated & deactivated

activated:keep-alive 组件激活时调用。
deactivated: keep-alive 组件停用时调用。

组件_处理边界情况

接下来我们要学习的都是和处理边界情况有关的功能,即一些需要对 Vue 的规则做一些小调整的特殊情况。需要注意的是,这些功能都是有劣势或危险场景的。

访问元素 & 组件

在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。不过也确实在一些情况下做这些事情是合适的。

访问根实例

在每个子组件中,可以通过 $root 访问根实例。

// Vue 根实例
new Vue({
  data: {
    foo: 1,
  },
  computed: {
    bar() {
      /* ... */
    },
  },
  methods: {
    baz() {
      /* ... */
    },
  },
});

所有的子组件都可以将这个实例作为一个全局 store 来访问或使用。

// 获取根组件的数据
this.$root.foo;

// 写入根组件的数据
this.$root.foo = 2;

// 访问根组件的计算属性
this.$root.bar;

// 调用根组件的方法
this.$root.baz();

在 demo 或在有少量组件的小型应用中使用是非常方便的。但是在大型应用里使用就会很复杂了。所以,我们还是要用 Vuex(后面会学)来管理应用的状态。

访问父级组件实例

在子组件中,可以通过 $parent 访问 父组件实例。这可以替代将数据以 prop 的方式传入子组件的方式。

如:

<cmp-parent>
  <cmp-a></cmp-a>
</cmp-parent>

若 cmp-parent 需要共享一个属性 share,它的所有子元素都需要访问 share 属性,在这种情况下 cmp-a 可以通过 this.$parent.share 的方式访问 share。

但是,通过这种模式构建出来的组件内部仍然容易出现问题。比如,我们在 cmp-a 中嵌套一个一个子组件 cmp-b,如:

<cmp-parent>
  <cmp-a>
    <cmp-b></cmp-b>
  </cmp-a>
</cmp-parent>

那么,在 cmp-b 组件中去访问 share 时,需要先查看一下,其父组件中是否存在 share,如果不存在,则在向上一级查找,落实到代码上为:

var share = this.$parent.share || this.$parent.$parent.share;

这样做,很快组件就会失控:触达父级组件会使应用更难调试和理解,尤其是当变更父组件数据时,过一段时间后,很难找出变更是从哪里发起的。

碰到上述情况,可以使用依赖注入解决。

依赖注入

在上面的例子中,利用 $parent 属性,没有办法很好的扩展到更深层级的嵌套组件上。这也是依赖注入的用武之地,它用到了两个新的实例选项:provide 和 inject。

provide 选项允许我们指定想要提供给后代组件的数据/方法,例如:

Vue.component("cmp-parent", {
  provide() {
    return {
      share: this.share,
    };
  },
  data() {
    return {
      share: "share",
    };
  },
  template: `<div>cmp-parent</div>`,
});

然后再任何后代组件中,我们都可以使用 inject 选项来接受指定想要添加在实例上的属性。

Vue.component("cmp-a", {
  inject: ["share"],
  template: `<div>cmp-a</div>`,
});

相比 $parent 来说,这个用法可以让我们在任意后代组件中访问 share,而不需要暴露整个 cmp-parent 实例。这允许我们更好的持续研发该组件,而不需要担心我们可能会改变/移除一些子组件依赖的东西。同时这些组件之间的接口是始终明确定义的,就和 props 一样。

实际上,你可以把依赖注入看作一部分“大范围有效的 prop”,除了:

  • 祖先组件不需要知道哪些后代组件使用它提供的属性
  • 后代组件不需要知道被注入的属性来自哪里

然而,依赖注入还是有负面影响的。它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。同时所提供的属性是非响应式的。这是出于设计的考虑,因为使用它们来创建一个中心化规模化的数据跟使用 $root 做这件事都是不够好的。如果你想要共享的这个属性是你的应用特有的,而不是通用化的,或者如果你想在祖先组件中更新所提供的数据,那么这意味着你可能需要换用一个像 Vuex 这样真正的状态管理方案了。

访问子组件实例或子元素

尽管存在 prop 和事件,但是有时候我们仍可能需要在 JS 里直接访问一个子组件,那么此时,我们可以通过 ref 特性为子组件赋予一个 ID 引用:

<my-cmp ref="cmp"></my-cmp>

这样就可以通过 this.$refs.cmp 来访问<my-cmp>实例。
ref 也可以 对指定 DOM 元素进行访问,如:

<input ref="input" />

那么,我们可以通过 this.$refs.input 来访问到该 DOM 元素。

当 ref 和 v-for 一起使用时,得到的引用将会是一个包含了对应数据源的这些子组件的数组。

注意:$refs 只会在组件渲染完成之后生效,并且它们不是响应式的。应该避免在模板或计算属性中访问 $refs。

程序化的事件侦听器

除了 v-on 和 $emit 外, Vue 实例在其事件接口中还提供了其它的方法。我们可以:

  • 通过 $on(eventName, eventHandler) 侦听一个事件
  • 通过 $once(eventName, eventHandler) 一次性侦听一个事件
  • 通过 $off(eventName, eventHandler) 停止侦听一个事件

这几个方法一般不会被用到,但是,当需要在一个组件实例上手动侦听事件时,他们是可以派的上用场的。

例如,有时我们会在组件中集成第三方库:

Vue.component("my-cmp", {
  // 一次性将这个日期选择器附加到一个输入框上
  // 它会被挂载到 DOM 上。
  mounted() {
    // Pikaday 是一个第三方日期选择器的库
    this.picker = new Pikaday({
      field: this.$refs.input,
      format: "YYYY-MM-DD",
    });
  },
  // 在组件被销毁之前,
  // 也销毁这个日期选择器。
  beforeDestroy() {
    this.picked.destroy();
  },
  template: `
    <div>
      <input type="text" ref="input" />
      <button @click="$destroy()">销毁组件</button>
    </div>
  `,
});

使用上面的方法,有两个潜在的问题:

  • 它需要在这个组件实例中保存这个 picker,如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。
  • 我们的建立代码独立于我们的清理代码,这使得我们比较难于程序化地清理我们建立的所有东西。

所有,我们可以通过程序化的侦听器解决这两个问题:

Vue.component("my-cmp", {
  mounted() {
    var picker = new Pikaday({
      field: this.$refs.input,
      format: "YYYY-MM-DD",
    });
    this.$once("hook:beforeDestroy", () => {
      picker.destroy();
    });
  },
  template: `
    <div>
      <input type="text" ref="input" />
      <button @click="$destroy()">销毁组件</button>
    </div>
  `,
});

使用了这个策略,我们还可以让多个输入框元素使用不同的 pikaday:

Vue.component("my-cmp", {
  mounted() {
    this.datePicker("inputA");
    this.datePicker("inputB");
  },
  methods: {
    datePicker(refName) {
      var picker = new Pikaday({
        field: this.$refs[refName],
        format: "YYYY-MM-DD",
      });
      this.$once("hook:beforeDestroy", () => {
        picker.destroy();
      });
    },
  },
  template: `
    <div>
      <input type="text" ref="inputA" />
      <input type="text" ref="inputB" />
      <button @click="$destroy()">销毁组件</button>
    </div>
  `,
});

注意,即便如此,如果你发现自己不得不在单个组件里做很多建立和清理的工作,最好的方式通常还是创建更多的模块化组件,在这个例子中,我们推荐创建一个可复用的 <input-datepicker> 组件。

循环引用

递归组件

组件是可以在它们自己的模板中调用自身的,不过它们只能通过 name 选项来做这件事:

name: "my-cmp";

不过当使用 Vue.component 全局注册一个组件时,全局的 ID 会自动设置为该组件的 name 选项。

Vue.component("my-cmp", {
  /** */
});

稍有不慎,递归组件就可能导致无限循环:

name: 'my-cmp',
template: `<div><my-cmp /></div>`

类似上述的组件将会导致“max stack size exceeded”错误,所以要确保递归调用是条件性的 (例如使用一个最终会得到 false 的 v-if)。

组件之间的循环引用

有时,在去构建一些组件时,会出现组件互为对方的后代/祖先:

Vue.component("cmp-a", {
  template: `
    <div>
      <cmp-b></cmp-b>
    </div>
  `,
});
Vue.component("cmp-b", {
  template: `
    <div>
      <cmp-a></cmp-a>
    </div>
  `,
});

此时,我们使用的是全局注册组件,并不会出现悖论,但是如果使用的为局部组件就会出现悖论。

但是即使用了全局注册组件,在使用 webpack 去导入组件时,也会出现一个错误:Failed to mount component: template or render function not defined。

模块系统发现它需要 A,但是首先 A 依赖 B,但是 B 又依赖 A,但是 A 又依赖 B,如此往复。这变成了一个循环,不知道如何不经过其中一个组件而完全解析出另一个组件。为了解决这个问题,我们需要给模块系统一个点:“A 反正是需要 B 的,但是我们不需要先解析 B。”

beforeCreate () {
  this.$options.components.CmpB = require('./tree-folder-contents.vue').default;
}

或者,在本地注册组件的时候,你可以使用 webpack 的异步 import:

components: {
  CmpB: () => import("./tree-folder-contents.vue");
}

模板定义的替代品

内联模板

在使用组件时,写上特殊的特性:inline-template,就可以直接将里面的内容作为模板而不是被分发的内容(插槽)。

<my-cmp inline-template>
  <div>
    <p>These are compiled as the component's own template.</p>
    <p>Not parent's transclusion content.</p>
  </div>
</my-cmp>

不过,inline-template 会让模板的作用域变得更加难以理解。所以作为最佳实践,请在组件内优先选择 template 选项或 .vue 文件里的一个 <template> 元素来定义模板。

X-Template

另一个定义模板的方式是在一个 <script> 元素中,并为其带上 text/x-template 的类型,然后通过一个 id 将模板引用过去。例如:

<script type="text/x-template" id="hello-world-template">
  <p>Hello hello hello</p>
</script>
Vue.component("hello-world", {
  template: "#hello-world-template",
});

这些可以用于模板特别大的 demo 或极小型的应用,但是其它情况下请避免使用,因为这会将模板和该组件的其它定义分离开。

控制更新

强制更新

当 更改了某个数据,页面未重新渲染时,可以调用 $forceUpdate 来做一次强制更新。

但是在做强制更新前,需要留意数组或对象的变更检测注意事项,99.9%的情况,都是在某个地方做错了事,如果做了上述检查,仍未发现问题,那么可以通过 $forceUpdate 来更新。

通过 v-once 创建低开销的静态组件

渲染普通的 HTML 元素在 Vue 中是非常快速的,但有的时候你可能有一个组件,这个组件包含了大量静态内容。在这种情况下,你可以在根元素上添加 v-once 特性以确保这些内容只计算一次然后缓存起来,就像这样:

Vue.component("terms-of-service", {
  template: `
    <div v-once>
      <h1>Terms of Service</h1>
      ... a lot of static content ...
    </div>
  `,
});

试着不要过度使用这个模式。当你需要渲染大量静态内容时,极少数的情况下它会给你带来便利,除非你非常留意渲染变慢了,不然它完全是没有必要的——再加上它在后期会带来很多困惑。例如,设想另一个开发者并不熟悉 v-once 或漏看了它在模板中,他们可能会花很多个小时去找出模板为什么无法正确更新。

组件_通信

prop

父组件传递数据给子组件时,可以通过特性传递。

推荐使用这种方式进行父->子通信。

$emit

子组件传递数据给父组件时,触发事件,从而抛出数据。

推荐使用这种方式进行子->父通信。

v-model

.sync

$attrs

祖先组件传递数据给子孙组件时,可以利用$attrs 传递。

demo 或小型项目可以使用$attrs 进行数据传递,中大型项目不推荐,数据流会变的难于理解。

$attrs 的真正目的是撰写基础组件,将非 Prop 特性赋予某些 DOM 元素。

$listeners

可以在子孙组件中执行祖先组件的函数,从而实现数据传递。

demo 或小型项目可以使用$listeners 进行数据传递,中大型项目不推荐,数据流会变的难于理解。

$listeners 的真正目的是将所有的事件监听器指向这个组件的某个特定的子元素。

$root

可以在子组件中访问实例的数据。

对于 demo 或非常小型的有少量组件的应用来说这是很方便的。中大型项目不适用。会使应用难于调试和理解。

$parent

可以在子组件中访问实例的数据。

对于 demo 或非常小型的有少量组件的应用来说这是很方便的。中大型项目不适用。会使应用难于调试和理解。

$children

可以在父组件中访问实例的数据。

对于 demo 或非常小型的有少量组件的应用来说这是很方便的。中大型项目不适用。会使应用难于调试和理解。

ref

可以在父组件中访问实例的数据。

$refs 只会在组件渲染完成之后生效,并且它们不是响应式的,适用于 demo 或小型项目。

provide & inject

祖先组件提供数据(provide),子孙组件按需注入(inject)。

会将组件的阻止方式,耦合在一起,从而使组件重构困难,难以维护。不推荐在中大型项目中使用,适用于一些小组件的编写。

eventBus(事件总线)

Vue.prototype.$bus = new Vue();
Vue.component("cmp-a", {
  data() {
    return {
      a: "a",
    };
  },
  methods: {
    onClick() {
      this.$bus.$on("click", this.a);
    },
  },
  template: `
    <div>
      <button @click="onClick">点击</button>
    </div>
  `,
});
Vue.component("cmp-a", {
  mounted() {
    this.$bus.$on("click", (data) => {
      console.log(data);
    });
  },
  template: `
    <div>b</div>
  `,
});

非父子组件通信时,可以使用这种方法,但仅针对于小型项目。中大型项目使用时,会造成代码混乱不易维护。

Vuex

状态管理,中大型项目时强烈推荐使用此种方式,日后再学~

混入

基础

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。
一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

var minxin = {
  created() {
    this.hello();
  },
  methods: {
    hello() {
      console.log("hello,我是混入中的函数");
    },
  },
};

Vue.component("my-cmp", {
  mixins: [mixin],
  template: `
    <div>xx</div>
  `,
});

选项合并

当组件和混入对象含有同名选项时,这些选项会以恰当的方式进行“合并”。

合并数据,以组件数据优先:

var mixin = {
  data () {
    return {
      msg: 'hello',
    }
  }
}
new Vue({
  mixins: [mixin],
  data: {
    msg: 'goodbye',
  },
  created: function () {
    console.log(this.msg)
})

合并钩子函数,将合并为一个数组。先调用混入对象的钩子,再调用组件自身钩子。

var mixin = {
  created() {
    console.log("混入对象钩子");
  },
};

new Vue({
  el: "#app",
  mixins: [mixin],
  created() {
    console.log("组件钩子");
  },
});

合并值为对象的选项,如 methods、components 等,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

全局混入

混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。

// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
  created() {
    var myOption = this.$options.myOption;
    if (myOption) {
      console.log(myOption);
    }
  },
});

new Vue({
  myOption: "hello!",
});

谨慎使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)。大多数情况下,只应当应用于自定义选项。

自定义指令

简介

我们可以自己写一个自定义指令去操作 DOM 元素,以达到代码复用的目的。注意,在 Vue 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

全局注册指令:

Vue.directive("focus", {
  /** */
});

局部注册指令

const vm = new Vue({
  el: "#app",
  directives: {
    focus: {
      /** */
    },
  },
});

使用:

<input v-focus></input>

例如,写一个自动聚焦的输入框:

Vue.directive("focus", {
  // 当被绑定的元素插入到DOM时执行
  inserted: function (el) {
    el.focus();
  },
});

此时,在 input 元素上使用 v-focus 指令就可以实现自动聚焦了。

钩子函数

自定义指令对象提供了钩子函数供我们使用,这些钩子函数都为可选。

bind

只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

inserted

被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。

update

所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前

componentUpdated

指令所在组件的 VNode 及其子 VNode 全部更新后调用。

unbind

只调用一次,指令与元素解绑时调用(被绑定的 Dom 元素被 Vue 移除)。

钩子函数参数

  • el: 指令所绑定的元素,可以用来直接操作 DOM。
  • binding:对象,包含以下属性:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2。
    • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
  • vnode:Vue 编译生成的虚拟节点。
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

练习

模拟 v-show

// 绑定的值为false,display为none,值为true,display为""
Vue.directive("myshow", {
  bind(el, binding, vnode, oldVnode) {
    var display = binding.value ? "" : "none";
    el.style.display = display;
  },
  update(el, binding, vnode, oldVnode) {
    var display = binding.value ? "" : "none";
    el.style.display = display;
  },
});

模拟 v-model

// 1. 通过绑定的数据,给元素设置value
// 2. 当触发input事件时,去更改数据的值
// 3. 更改数据后,同步input的value值
Vue.directive("mymodel", {
  bind(el, binding, vnode) {
    const vm = vnode.context;
    const { value, expression } = binding;
    el.value = value;

    el.oninput = function (e) {
      const inputVal = el.value;
      vm[expression] = inputVal;
    };
  },
  update(el, binding) {
    const { value } = binding;
    el.value = value;
  },
});

写一个 v-slice(截取文本框)

Vue.directive("slice", {
  bind(el, binding, vnode) {
    const vm = vnode.context;
    let { value, expression, arg, modifiers } = binding;

    if (modifiers.number) {
      value = value.replace(/[^0-9]/g, "");
    }

    el.value = value.slice(0, arg);
    vm[expression] = value.slice(0, arg);

    el.oninput = function (e) {
      let inputVal = el.value;

      if (modifiers.number) {
        inputVal = inputVal.replace(/[^0-9]/g, "");
      }

      el.value = inputVal.slice(0, arg);
      vm[expression] = inputVal.slice(0, arg);
    };
  },
  update(el, binding, vnode) {
    const vm = vnode.context;
    let { value, arg, expression, modifiers } = binding;

    if (modifiers.number) {
      value = value.replace(/[^0-9]/g, "");
    }

    el.value = value.slice(0, arg);
    vm[expression] = value.slice(0, arg);
  },
});

动态指令参数

指令的参数可以是动态的。如:v-directive:[arguments]="valueargument参数可以根据组件实例数据进行更新。

重写 v-slice

Vue.directive("slice", {
  bind(el, binding, vnode) {
    const vm = vnode.context;
    let { value, expression, arg, modifiers } = binding;

    if (modifiers.number) {
      value = value.replace(/[^0-9]/g, "");
    }

    el.value = value.slice(0, arg);
    vm[expression] = value.slice(0, arg);

    el.oninput = function (e) {
      let inputVal = el.value;

      if (modifiers.number) {
        inputVal = inputVal.replace(/[^0-9]/g, "");
      }

      el.value = inputVal.slice(0, arg);
      vm[expression] = inputVal.slice(0, arg);
    };
  },
  update(el, binding, vnode) {
    const vm = vnode.context;
    let { value, arg, expression, modifiers } = binding;

    if (modifiers.number) {
      value = value.replace(/[^0-9]/g, "");
    }

    el.value = value.slice(0, arg);
    vm[expression] = value.slice(0, arg);

    el.oninput = function (e) {
      let inputVal = el.value;

      if (modifiers.number) {
        inputVal = inputVal.replace(/[^0-9]/g, "");
      }

      el.value = inputVal.slice(0, arg);
      vm[expression] = inputVal.slice(0, arg);
    };
  },
});

函数简写

当想在 bind 和 update 中触发相同行为,而不关心其他钩子时,可以写成函数的形式:

Vue.directive("myshow", (el, binding) => {
  const { value } = binding;
  const display = value ? "" : "none";
  el.style.display = display;
});
Vue.directive("slice", (el, binding, vnode) => {
  const vm = vnode.context;
  let { value, expression, arg, modifiers } = binding;

  if (modifiers.number) {
    value = value.replace(/[^0-9]/g, "");
  }

  el.value = value.slice(0, arg);
  vm[expression] = value.slice(0, arg);

  el.oninput = function (e) {
    let inputVal = el.value;

    if (modifiers.number) {
      inputVal = inputVal.replace(/[^0-9]/g, "");
    }

    el.value = inputVal.slice(0, arg);
    vm[expression] = inputVal.slice(0, arg);
  };
});

对象字面量

如果自定义指令需要多个值,可以传入一个 JS 对象字面量。指令函数能够接受所有合法的 JS 表达式。

<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive("demo", function (el, binding) {
  console.log(binding.value.color); // => "white"
  console.log(binding.value.text); // => "hello!"
});

过滤器

自定义过滤器,用于一些常见的文本格式化。

过滤器可用在两个地方:双花括号插值 和 v-bind 表达式,添加在 JS 表达式的尾部,由“管道”符号表示:

<!-- 在双花括号中 -->
{{ message | filter }}

<!-- 在 v-bind 中 -->
<div v-bind:id="id | filter"></div>

定义过滤器

全局过滤器:

Vue.filter("filter", (value) => {});

局部过滤器:

filter () {
  return xxx;
}

参数

当过滤器形式为 msg | filter 时,filter 过滤器接收一个参数,参数为msg

当过滤器形式为 msg | filter('a')时,filter 过滤器接收两个参数,参数为msg, 'a'

过滤器串联

{
  {
    msg | filterA | filterB;
  }
}

在这个例子中,filterA 的参数为msg,filterB 的参数为 filterA。

练习

首字母大写

{{ content | capitalize }}
Vue.filter("capitalize", (value) => {
  if (!value) {
    return;
  }
  return value.charAt(0).toUpperCase() + value.slice(1);
});

数字中间加上逗号

{{ money | toMoney }}
Vue.filter("toMoney", (value) => {
  if (!value) {
    return;
  }
  return value.toLocaleString();
});

数字添加文字“万”

{{ likes | addWord }}
Vue.filter("addWord", (value) => {
  if (!value) {
    return;
  }

  if (value > 10000) {
    return (value / 10000).toFixed(1) + "万";
  }
  return value;
});

安装脚手架

安装@vue/cli

node 版本要求: >8.9,推荐使用 8.11.0 +。

关于旧版本:
如果在这之前已经全局安装了旧版本的 vue-cli(1.x 或 2.x),那么需要先卸载掉。
运行:npm uninstall vue-cli -gyarn global remove vue-cli

安装:

npm install -g @vue/cli
# OR
yarn global add @vue/cli

安装之后,可以在命令行中访问 vue 命令。

检查版本是否正确:

vue --version

快速原型开发

安装:

npm install -g @vue/cli-service-global
# OR
yarn global add @vue/cli-service-global

安装 vscode 插件

名字:Vetur。用于高亮.vue 文件代码

练习_树形组件

数据:

data: [
  {
    label: "一级 1",
    children: [
      {
        label: "二级 1-1",
        children: [
          {
            label: "三级 1-1-1",
          },
        ],
      },
    ],
  },
  {
    label: "一级 2",
    children: [
      {
        label: "二级 2-1",
        children: [
          {
            label: "三级 2-1-1",
          },
        ],
      },
      {
        label: "二级 2-2",
        children: [
          {
            label: "三级 2-2-1",
          },
        ],
      },
    ],
  },
  {
    label: "一级 3",
    children: [
      {
        label: "二级 3-1",
        children: [
          {
            label: "三级 3-1-1",
          },
        ],
      },
      {
        label: "二级 3-2",
        children: [
          {
            label: "三级 3-2-1",
          },
        ],
      },
    ],
  },
];

利用脚手架搭建项目

拉取 2.x 模板 (旧版本)

npm install -g @vue/cli-init
# `vue init` 的运行效果将会跟 `vue-cli@2.x` 相同
vue init webpack my-project
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值