Vue常用模式

作者: linc2046 | 来源:发表于2018-12-21 22:47 被阅读0次
Vue常用模式

组件定义

SFC 单文件组件

SFC

<template>
  <p class="demo">
    <button class="btn-primary" @click.prevent="handleClick">
      <slot></slot>(clicked - {{ count }})
    </button>
  </p>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    };
  },
  methods: {
    handleClick() {
      this.count++;
      console.log("clicked", this.count);
    }
  }
};
</script>

<style scoped>
.btn-primary {
  display: inline-block;
  font-size: 1.2rem;
  color: #fff;
  background-color: #3eaf7c;
  padding: 0.8rem 1.6rem;
  border-radius: 4px;
  transition: background-color 0.1s ease;
  box-sizing: border-box;
  border-bottom: 1px solid #389d70;
}
</style>

字符串模板或 ES6 模板字面量

Vue.component('my-btn', { template: `
<button class="btn-primary" @click.prevent="handleClick">
  <slot></slot>(clicked - {{ count }})
</button>
`, data() { return { count: 0, }; }, methods: { handleClick() { this.count++;
console.log('clicked', this.count); }, }, });

渲染函数

Vue.component('my-btn', { data() { return { count: 0, }; }, methods: {
handleClick() { this.count++; console.log('clicked', this.count); }, },
render(h) { return h( 'button', { attrs: { class: 'btn-primary', }, on: { click:
this.handleClick, }, }, this.$slots.default ); }, });

JSX 语法糖

Vue.component('my-btn', { data() { return { text: 'Click me', }; }, methods: {
handleClick() { console.log('clicked'); }, }, render() { return (
<button class="btn-primary" @click.prevent="handleClick">
  {this.$slots.default}(clicked - {{ count }})
</button>
); }, });

Vue 类组件装饰器

<template>
  <button class="btn-primary" @click.prevent="handleClick">
    <slot></slot>(clicked - {{ count }})
  </button>
</template>

<script>
import Vue from 'vue';
import Component from 'vue-class-component';

@Component
export default MyBtn extends Vue {
  count = 0;

  handleClick() {
    this.count++;
    console.log('clicked', this.count);
  }
}
</script>

<style scoped>
.btn-primary {
  background-color: blue;
}
</style>

参考

组件通信

Prop属性和事件

vue组件基本上遵循单向数据流原则,即数据通过props向下传递子组件,子组件向上触发事件。

props数据只读,因此无法子组件无法改变props。 当props变化时,子组件会自动重新渲染。(props是响应式数据源)

子组件只能向父组件触发事件,父组件接收事件,改变映射至子组件的props数据。

<template>
  <button @click="$emit('click');">{{ text }}</button>
</template>

<script>
export default {
  name: 'v-btn',
  props: {
    text: String,
  },
};
</script>
<template>
  <v-btn :text="buttonText" @click="handleClick"></v-btn>
</template>

<script>
export default {
  data() {
    return {
      clickCount: 0,
      buttonText: 'initial button text',
      },
    };
  methods: {
    handleClick() {
      this.buttonText = `Button clicked ${++this.clickCount}`;
      console.log('clicked', this.buttonText);
    },
  },
};
</script>

参考

https://vuejs.org/v2/guide/components-props.html
https://alligator.io/vuejs/component-communication/
https://www.smashingmagazine.com/2017/08/creating-custom-inputs-vue-js/
https://vegibit.com/vue-sibling-component-communication/
https://medium.com/fullstackio/managing-state-in-vue-js-23a0352b1c87
https://gambardella.info/2017/09/13/vue-js-communication-part-2-parent-child-components/

组件事件处理

参考

组件条件渲染

指令

v-if

<h1 v-if="true">Render only if v-if condition is true</h1>

v-ifv-else

<h1 v-if="true">Render only if v-if condition is true</h1>
<h1 v-else>Render only if v-if condition is false</h1>

v-else-if

<div v-if="type === 'A'">Render only if `type` is equal to `A`</div>
<div v-else-if="type === 'B'">Render only if `type` is equal to `B`</div>
<div v-else-if="type === 'C'">Render only if `type` is equal to `C`</div>
<div v-else>Render if `type` is not `A` or `B` or `C`</div>

v-show

<h1 v-show="true">
  Always rendered, but it should be visible only if `v-show` conditions is true
</h1>

如果想条件渲染多个元素,可以在模板template元素上使用条件指令。

注意模板元素仅仅是隐形包装,不会渲染成DOM。

<template v-if="true">
  <h1>All the elements</h1>
  <p>will be rendered into DOM</p>
  <p>except `template` element</p>
</template>

渲染函数或JSX语法糖

如果你在vue应用中使用渲染函数或jsx, 可以直接使用 if-elseswitch-case语句以及三元和逻辑运算符。

if-else语句

export default {
  data() {
    return {
      isTruthy: true,
    };
  },
  render(h) {
    if (this.isTruthy) {
      return <h1>Render value is true</h1>;
    } else {
      return <h1>Render value is false</h1>;
    }
  },
};

switch case语句

import Info from './Info';
import Warning from './Warning';
import Error from './Error';
import Success from './Success';

export default {
  data() {
    return {
      type: 'error',
    };
  },
  render(h) {
    switch (this.type) {
      case 'info':
        return <Info text={text} />;
      case 'warning':
        return <Warning text={text} />;
      case 'error':
        return <Error text={text} />;
      default:
        return <Success text={text} />;
    }
  },
};

或者你可以使用map对象简化switch-case

import Info from './Info';
import Warning from './Warning';
import Error from './Error';
import Success from './Success';

const COMPONENT_MAP = {
  info: Info,
  warning: Warning,
  error: Error,
  success: Success,
};

export default {
  data() {
    return {
      type: 'error',
    };
  },
  render(h) {
    const Comp = COMPONENT_MAP[this.type || 'success'];

    return <Comp />;
  },
};

三元运算符

export default {
  data() {
    return {
      isTruthy: true,
    };
  },
  render(h) {
    return (
      <div>
        {this.isTruthy ? (
          <h1>Render value is true</h1>
        ) : (
          <h1>Render value is false</h1>
        )}
      </div>
    );
  },
};

逻辑运算符

export default {
  data() {
    return {
      isLoading: true,
    };
  },
  render(h) {
    return <div>{this.isLoading && <h1>Loading ...</h1>}</div>;
  },
};

参考

https://vuejs.org/v2/guide/conditional.html
https://dzone.com/articles/difference-between-v-if-and-v-show-with-a-video

动态组件

带有is属性组件

<component :is="currentTabComponent"></component>

上例中,如果有不同组件渲染进入component组件,渲染过的组件将会销毁。

如果你想要component标签内部实例不被销毁,可以在外面再包一层keep-alive

<keep-alive> <component :is="currentTabComponent"></component> </keep-alive>

参考

https://vuejs.org/v2/guide/components.html#Dynamic-Components
https://vuejs.org/v2/guide/components-dynamic-async.html
https://medium.com/scrumpy/dynamic-component-templates-with-vue-js-d9236ab183bb

组件组合

第三方库

Proppy-组件函数式组合方案

基础组合

<template>
  <div class="component-b"><component-a></component-a></div>
</template>

<script>
import ComponentA from './ComponentA';

export default {
  components: {
    ComponentA,
  },
};
</script>

参考

https://vuejs.org/v2/guide/#Composing-with-Components

扩展

当你想扩展单文件组件时可以使用扩展:

<template>
  <button class="button-primary" @click.prevent="handleClick">
    {{ buttonText }}
  </button>
</template>

<script>
import BaseButton from './BaseButton';

export default {
  extends: BaseButton,
  props: ['buttonText'],
};
</script>

参考

https://vuejs.org/v2/api/#extends
https://medium.com/js-dojo/extending-vuejs-components-42fefefc688b

混入

// closableMixin.js
export default {
  props: {
    isOpen: {
      default: true,
    },
  },
  data: function() {
    return {
      shown: this.isOpen,
    };
  },
  methods: {
    hide: function() {
      this.shown = false;
    },
    show: function() {
      this.shown = true;
    },
    toggle: function() {
      this.shown = !this.shown;
    },
  },
};
<template>
  <div
    v-if="shown"
    class="alert alert-success"
    :class="'alert-' + type"
    role="alert"
  >
    {{ text }}
    <i class="pull-right glyphicon glyphicon-remove" @click="hide"></i>
  </div>
</template>

<script>
import closableMixin from './mixins/closableMixin';

export default {
  mixins: [closableMixin],
  props: ['text'],
};
</script>

参考

https://vuejs.org/v2/guide/mixins.html
http://www.qcode.in/practical-use-of-components-and-mixins-in-vue-js/

默认插槽

/* VBtn */
<template>
  <button class="btn btn-primary"><slot></slot></button>
</template>

<script>
export default {
  name: 'VBtn',
};
</script>
<template>
  <v-btn> <span class="fa fa-user"></span> Login </v-btn>
</template>

<script>
import VBtn from './VBtn';

export default {
  components: {
    VBtn,
  },
};
</script>

参考

https://vuejs.org/v2/guide/components-slots.html#Slot-Content
https://alligator.io/vuejs/component-slots/
https://alligator.io/web-components/composing-slots-named-slots/
https://alligator.io/vuejs/vue-abstract-components/

命名插槽

基础布局文件

/* base-layout */
<div class="container">
  <header><slot name="header"></slot></header>
  <main><slot></slot></main>
  <footer><slot name="footer"></slot></footer>
</div>

应用文件

<base-layout>
  <template slot="header">
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template slot="footer">
    <p>Here's some contact info</p>
  </template>
</base-layout>

参考

https://vuejs.org/v2/guide/components-slots.html#Named-Slots
https://medium.com/@fdietz/vue-js-component-composition-with-slots-eda311579218

域插槽

<template>
  <ul>
    <li v-for="todo in todos" v-bind:key="todo.id">
      <!-- We have a slot for each todo, passing it the -->
      <!-- `todo` object as a slot prop. -->
      <slot v-bind:todo="todo"> {{ todo.text }} </slot>
    </li>
  </ul>
</template>

<script>
export default {
  name: 'TodoList',
  props: {
    todos: {
      type: Array,
      default: () => [],
    },
  },
};
</script>
<template>
  <todo-list v-bind:todos="todos">
    <template slot-scope="{ todo }">
      <span v-if="todo.isComplete">✓</span> {{ todo.text }}
    </template>
  </todo-list>
</template>

<script>
import TodoList from './TodoList';

export default {
  components: {
    TodoList,
  },
  data() {
    return {
      todos: [
        { todo: 'todo 1', isComplete: true },
        { todo: 'todo 2', isComplete: false },
        { todo: 'todo 3', isComplete: false },
        { todo: 'todo 4', isComplete: true },
      ];
    };
  },
};
</script>

参考

https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots
https://medium.com/js-dojo/getting-your-head-around-vue-js-scoped-slots-281bf82a1e4e
https://medium.com/corebuild-software/understanding-scoped-slots-in-vue-js-db5315a42391
https://alligator.io/vuejs/scoped-component-slots/
https://adamwathan.me/the-trick-to-understanding-scoped-slots-in-vuejs/
https://pineco.de/power-scoped-slots-vue/
https://medium.com/@tkwebdev/building-a-list-keyboard-control-component-with-vue-js-and-scoped-slots-c74db4fcf84f

渲染属性

大部分场景可以用域插槽替代渲染属性,但有些场景渲染属性更有用。

使用SFC

<template>
  <div id="app"><Mouse :render="__render" /></div>
</template>

<script>
import Mouse from './Mouse.js';
export default {
  name: 'app',
  components: {
    Mouse,
  },
  methods: {
    __render({ x, y }) {
      return (
        <h1>
          The mouse position is ({x}, {y})
        </h1>
      );
    },
  },
};
</script>
<style>
* {
  margin: 0;
  height: 100%;
  width: 100%;
}
</style>

使用JSX

const Mouse = {
  name: 'Mouse',
  props: {
    render: {
      type: Function,
      required: true,
    },
  },
  data() {
    return {
      x: 0,
      y: 0,
    };
  },
  methods: {
    handleMouseMove(event) {
      this.x = event.clientX;
      this.y = event.clientY;
    },
  },
  render(h) {
    return (
      <div style={{ height: '100%' }} onMousemove={this.handleMouseMove}>
        {this.$props.render(this)}
      </div>
    );
  },
};

export default Mouse;

参考

https://vuejs.org/v2/guide/render-function.html
https://medium.com/@dillonchanis/leveraging-render-props-in-vue-7eb9a19c262d
https://medium.com/js-dojo/use-a-vue-js-render-prop-98880bc44e05
https://medium.com/js-dojo/using-react-style-callback-props-with-vue-pros-and-cons-e0ee7455695b

传递 Props 和监听器

有时需要向子组件传递属性和监听器,但不需要声明全部属性。

你可以在子组件中绑定$attrs$listeners,设置inheritAttrsfalse

外部div和子组件都会接收到设置的attributes。

传递Props

<template>
  <div>
    <h1>{{title}}</h1>
    <passing-props-child v-bind="$attrs" v-on="$listeners"></passing-props-child>
  </div>
</template>

<script>
import PassingPropsChild from './PassingPropsChild';

export default {
  components: {
    PassingPropsChild,
  },
  inheritAttrs: false,
  props: {
    title: {
      type: String,
      default: 'Hello, Vue!',
    },
  },
};
</script>

父组件

<template>
  <p class="demo">
    <passing-props
      title="This is from <passing-props />"
      childPropA="This is from <passing-props-child />"
      @click="handleClickPassingPropsChildComponent"
    >
    </passing-props>
  </p>
</template>

<script>
import PassingProps from './PassingProps';

export default {
  components: {
    PassingProps,
  },
  methods: {
    handleClickPassingPropsChildComponent() {
      console.log('This event comes from `<passing-props-child />`');
      alert('This event comes from `<passing-props-child />`');
    },
  },
};
</script>

参考

https://zendev.com/2018/05/31/transparent-wrapper-components-in-vue.html

高阶组件

参考

依赖注入

Vue支持 Provide和Inject机制为所有子代组件提供对象。

不管组件层次有多深,只要都在共同父组件下,机制都可以发挥作用。

注意provideinject绑定都不是响应式的,除非传递可观察对象。

<parent-component>
  <child-component>
    <grand-child-component></grand-child-component>
  </child-component>
</parent-component>

根据上例中组件层次,为了从父组件中获取数据,你应该将数据对象作为props传递给
子组件和孙组件。但是如果父组件提供数据对象,孙组件仅定义注入父组件提供的数据对象可以直接获取数据。

参考

Provide和Inject API

提示: 可以使用vue属性装饰器@Provide@Inject

主题提供者

<script>
export default {
  provide: {
    theme: {
      primaryColor: '#3eaf7c',
      secondaryColor: '#1FA2FF'
    },
  },
  render(h) {
    return this.$slots.default[0];
  },
};
</script>

带主题按钮

<template>
  <button class="btn" :style="{ color: '#fff', backgroundColor: (primary && theme.primaryColor) || (secondary && theme.secondaryColor)  }">
    <slot></slot>
  </button>
</template>

<script>
export default {
  inject: {
    theme: {
      default: {},
    },
  },
  props: {
    primary: {
      type: Boolean,
      default: false,
    },
    secondary: {
      type: Boolean,
      default: false,
    },
  },
};
</script>
<theme-provider>
  <p class="demo">
    <button class="btn">Normal Button</button>
    <theme-button secondary>Themed Button</theme-button>
  </p>
</theme-provider>

错误处理

errorCaptured 钩子函数

错误边界

<script>
export default {
  name: 'ErrorBoundary',
  data() {
    return {
      error: false,
      errorMessage: '',
    };
  },
  errorCaptured(err, vm, info) {
    this.error = true;
    this.errorMessage = `Sorry, error occured in ${info}`;

    return false;
  },
  render(h) {
    if (this.error) {
      return h('p', { class: 'demo bg-danger' }, this.errorMessage);
    }

    return this.$slots.default[0];
  },
};
</script>

抛出错误

<template>
  <p class="demo">
    <button class="btn btn-danger" @click.prevent="throwError()">Error Thrown Button ({{count}})</button>
  </p>
</template>

<script>
export default {
  data() {
    return {
      count: 0,
    };
  },
  watch: {
    count() {
      throw new Error('error');
    },
  },
  methods: {
    throwError() {
      this.count++;
    },
  },
};
</script>
<error-boundary> <throw-error></throw-error> </error-boundary>

参考

效率建议

创建时执行观察

// don't
created() {
  this.fetchUserList();
},
watch: {
  searchText: 'fetchUserList',
}
// do
watch: {
  searchText: {
    handler: 'fetchUserList',
    immediate: true,
  }
}

译者注

相关文章

  • Vue常用模式

    组件定义 SFC 单文件组件 SFC 字符串模板或 ES6 模板字面量 渲染函数 JSX 语法糖 Vue 类组件装...

  • Vue路由 ---- vue-router

    vue-router 中常用的 hash 和 history 路由模式实现原理吗? Two Point!!! sp...

  • VUE的总结文档

    VUE VUE是一个用MVVM模式来做项目的一个框架 首先 说一下Vue里面的常用指令 1、 v-model:将...

  • jquery模版 vue模版常用模版

    jquery常用 vue常用

  • vue基础(二) - Vuex

    简介 Vuex 是 专为 Vue 开发的状态管理模式。通常用于跨页面共享数据。 状态管理模式:它采用集中式的存储,...

  • Vue 非父子组件的数据传递

    Vue 复杂的组件之间传值有两种常用的方法,数据层框架vuex和(Bus/总线/发布订阅模式/观察者模式)。在这...

  • VIM个人使用小结

    三种模式介绍 普通模式个人常用操作 编辑模式个人常用操作 命令模式常用个人操作

  • Nginx | 常用配置

    Nginx 常用功能 常用配置详解 Nginx 部署 Vue 项目 将 Vue 项目build打包后生成的 ...

  • Vue 对数组常用操作

    Vue 数组常用操作

  • vue小总结

    MVVM模式: Vue就是基于MVVM模式实现的一套框架,在Vue中 路由 VUEX .vue文件

网友评论

    本文标题:Vue常用模式

    本文链接:https://www.haomeiwen.com/subject/zolbkqtx.html