开发者社区> 我是小助手> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

【译】Vue Patterns

简介: 大多數狀況下,你可以優先使用作用域插槽 (Scoped Slots) 之於渲染屬性 (Render Props),但是,在某些狀況下渲染屬性還是很有用的。
+关注继续查看



英文原版:learn-vuejs

中文翻譯:yoyoys

此頁面集結了許多有用的 Vue 實作模式、技術、技巧、以及有幫助的參考連結。

元件宣告

單文件組件(Single File Component, SFC) - 最為常見


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

<script>
export default {
  data() {
    return {
      text: 'Click me',
    };
  },
  methods: {
    handleClick() {
      console.log('clicked');
    },
  },
}
</script>

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

字串樣板 (String Template) (或是 es6 樣板字面值 (Template Literal))


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

渲染函式 (Render Function)


Vue.component('my-btn', {
  data() {
    return {
      text: 'Click me',
    };
  },
  methods: {
    handleClick() {
      console.log('clicked');
    },
  },
  render(h) {
    return h('button', {
      attrs: {
        class: 'btn-primary'
      },
      on: {
        click: this.handleClick,
      },
    });
  },
});

JSX


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

vue-class-component (使用 es6 classes)


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

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

@Component
export default MyBtn extends Vue {
  text = 'Click me';

  handleClick() {
    console.log('clicked');
  }
}
</script>

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

參考連結

元件條件渲染 (Component Conditional Rendering)

指令 (Directives) (v-if / v-else / v-else-if / v-show)



v-if

<h1 v-if="true">只在 v-if 值為 true 時渲染</h1>
v-if 與 v-else

<h1 v-if="true">只在 v-if 值為 true 時渲染</h1>
<h1 v-else>只在 v-if 值為 false 時渲染</h1>
v-else-if

<div v-if="type === 'A'">只在 `type` 等於 `A` 時渲染</div>
<div v-else-if="type === 'B'">只在 `type` 等於 `B` 時渲染</div>
<div v-else-if="type === 'C'">只在 `type` 等於 `C` 時渲染</div>
<div v-else>只在 `type` 不等於>fmf `A` 或 `B` 或 `C` 時渲染</div>
v-show

<h1 v-show="true">永遠都會渲染,但是只在 `v-show` 值為 true 時顯示</h1>
如果你需要同時在多個元素上面做條件式渲染,你可以在 <template> 元素上使用這些指令 (v-if / v-else / v-else-if /v-show)。 注意:<template> 元素不會實際渲染一個 DOM。

<template v-if="true">
  <h1>所有元素</h1>
  <p>都會被渲染成為 DOM</p>
  <p>除了 `template` 元素</p>
</template>

JSX

如果你在你的 Vue 應用程式中使用 JSX,你可以使用所有 javascript 語句,例如 if else 、 switch case 、三元運算 (ternary) 與 邏輯運算式 (logical operator)

if else 語句


export default {
  data() {
    return {
      isTruthy: true,
    };
  },
  render(h) {
    if (this.isTruthy) {
      return <h1>值為真時渲染</h1>;
    } else {
      return <h1>值為假時渲染</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} />;
    },
  }
};

你也可以透過物件的對應來簡化 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 />;
  },
};

三元運算子 (ternary operator)

export default {
  data() {
    return {
      isTruthy: true,
    };
  },
  render(h) {
    return (
      <div>
        {this.isTruthy ? (
          <h1>值為真時渲染</h1>
        ) : (
          <h1>值為假時渲染</h1>
        )}
      </div>
    );
  },
};

邏輯運算子 (logical operator)

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

參考連結

Difference Between v-if and v-show [With Video at End]

動態元件

使用 is 屬性在 <component> 元素上

範例 1 範例 2 範例 3


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

上面的範例,原有 <component> 中的元件,在切換元件的同時將會被消滅。 如果你需要切換後仍保留 <component> 中元件的實體,而不被消滅的話,可以包裹一個 <keep-alive> 標籤,如下:

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

元件組合

基本組合 (Basic Composition)



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

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

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

繼承 (Extends)

當你需要繼承一個單文件組件 (SFC) 時可以使用。


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

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

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

參考連結

Extending VueJS Components

混入 (Mixins)


// 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 deafult {
  mixins: [closableMixin],
  props: ['text']
};
</script>

參考連結

Practical use of Components and Mixins in Vue JS

預設插槽 (Slots (Default))


<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>

參考連結

Understanding Component Slots with Vue.js Composing Custom Elements With Slots And Named Slots

具名插槽(Named Slots)

BaseLayout.vue


<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

App.vue


<base-layout>
  <template slot="header">
    <h1>這裡是頁面標題</h1>
  </template>

  <p>一段文件主體內的文字</p>
  <p>另外一段文字</p>

  <template slot="footer">
    <p>一些聯絡資訊</p>
  </template>
</base-layout>

作用域插槽 (Scoped Slots)


<template>
  <ul>
    <li
      v-for="todo in todos"
      v-bind:key="todo.id"
    >
      <!-- 保留一個插槽供每一個 todo 使用,-->
      <!-- 並將 將 `todo` 物件作為插槽參數傳遞給它,供外部元件使用。-->
      <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>

參考資料

Getting Your Head Around Vue.js Scoped Slots Understanding scoped slots in Vue.js Scoped Component Slots in Vue.js The Trick to Understanding Scoped Slots in Vue.js The Power of Scoped Slots in Vue

渲染屬性 (Render Props)

大多數狀況下,你可以優先使用作用域插槽 (Scoped Slots) 之於渲染屬性 (Render Props),但是,在某些狀況下渲染屬性還是很有用的。

於單文件組件:

<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;

參考連結

參數傳遞 (Passing Props)

有時候你想要傳遞所有參數 (props) 與事件 (listeners) 到子元件,但又不想要宣告所有子元件的參數。 你可以直接將 $attrs 與 $listeners 綁定在子元件上。


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

<script>
export default {
  name: 'PassingPropsSample'
  props: {
    title: {
      type: String,
      default: 'Hello, Vue!'
    }
  }
};
</script>

在父元件上,你可以這樣做:


<template>
  <passing-props-sample
    title="Hello, Passing Props"
    childPropA="This props will properly mapped to <child-component />"
    @click="handleChildComponentClick"
  >
  </passing-props-sample>
</template>

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

export default {
  components: {
    PassingPropsSample
  },
  methods: {
    handleChildComponentClick() {
      console.log('child component clicked');
    }
  }
};
</script>

參考資料

高優先元件 (Higher Order Component, HOC)

參考連結

相依注入 (Dependency injection)

Vue 支援 提供 與 注入 (Provide / inject) 機制來傳遞一個物件到所有子代元件中,不管結構有多深,只要都基於同一個父代即可。 注意: provide 和 inject 並沒有響應能力 (reactive) ,除非你傳遞的物件本身就帶有響應能力。


<parent-component>  // 父元件
  <child-component>  // 子元件
    <grand-child-component></grand-child-component>  // 孫元件
  </child-component>
</ancestor-component>

上述的元件結構,若要從 父元件 取得資料,你必須要透過 參數(props) 傳遞資料到 子元件 與 孫元件 之中。 但如果 父元件 提供 (provide) 資料(或物件), 孫元件 可以透過宣告直接 注入(inject父元件 中所定義的資料(或物件)。

參考連結

提供 與 注入 (Provide / Inject)


// ParentComponent.vue

export default {
  provide: {
    theme: {
      primaryColor: 'blue',
    },
  },
};

// GrandChildComponent.vue

<template>
  <button :style="{ backgroundColor: primary && theme.primaryColor }">
    <slot></slot>
  </button>
</template>

<script>
export default {
  inject: ['theme'],
  props: {
    primary: {
      type: Boolean,
      default: true,
    },
  },
};
</script>

注入裝飾器模式 (@Provide / @Inject Decorator)


// ParentComponent.vue

import { Component, Vue, Provide } from 'vue-property-decorator';

@Component
export class ParentComponent extends Vue {
  @Provide
  theme = {
    primaryColor: 'blue',
  };
}


// GrandChildComponent.vue

<template>
  <button :style="{ backgroundColor: primary && theme.primaryColor }">
    <slot></slot>
  </button>
</template>

<script>
import { Component, Vue, Inject, Prop } from 'vue-property-decorator';

export class GrandChildComponent extends Vue {
  @Inject() theme;

  @Prop({ default: true })
  primary: boolean;
};
</script>

錯誤處理 (Handling Errors)

errorCaptured 事件



export default {
  name: 'ErrorBoundary',
  data() {
    return {
      error: false,
      errorMessage: '',
    };
  },
  errorCaptured (err, vm, info) {
    this.error = true;
    this.errorMessage = `${err.stack}\n\nfound in ${info} of component`;
    
    return false;
  },
  render (h) {
    if (this.error) {
      return h('pre', { style: { color: 'red' }}, this.errorMessage);
    }

    return this.$slots.default[0]
  }
};


<error-boundary>
  <another-component/>
</error-boundary>

範例

參考連結

生產力小技巧

讓監聽器在 created 事件時就有效


// 不要這樣做
created() {
  this.fetchUserList();
},
watch: {
  searchText: 'fetchUserList',
}


// 這樣做
watch: {
  searchText: {
    handler: 'fetchUserList',
    immediate: true,
  }
}

有用的連結

組建間的溝通

重構技巧

Vuex

Mobx

不須渲染的元件 (Renderless Component)

範例

目錄結構

小技巧

專案範例

不良示範 (反模式)

影片與音訊課程

付費課程


其他資訊



原文发布时间为:2018年07月02日
原文作者:掘金
本文来源: 掘金 如需转载请联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
设计模式之代理模式(Proxy Pattern)
1 代理模式定义 定义:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。 本篇文章主要介绍的是静态代理,关于动态代理请参考:设计模式之动态代理(dynamic proxy) 2 代理模式的作用 在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介
1223 0
设计模式之代理模式(Proxy Pattern)
1 代理模式定义 定义:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。 本篇文章主要介绍的是静态代理,关于动态代理请参考:设计模式之动态代理(dynamic proxy) 2 代理模式的作用 在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介
1125 0
乐在其中设计模式(C#) - 代理模式(Proxy Pattern)
原文:乐在其中设计模式(C#) - 代理模式(Proxy Pattern)[索引页][源码下载] 乐在其中设计模式(C#) - 代理模式(Proxy Pattern) 作者:webabcd 介绍 为其他对象提供一个代理以控制对这个对象的访问。
867 0
乐在其中设计模式(C#) - 提供者模式(Provider Pattern)
原文:乐在其中设计模式(C#) - 提供者模式(Provider Pattern)[索引页][源码下载] 乐在其中设计模式(C#) - 提供者模式(Provider Pattern) 作者:webabcd 介绍 为一个API进行定义和实现的分离。
1173 0
Net设计模式实例之外观模式(Façade Pattern)
一、外观模式简介(Brief Introduction) 外观模式,为子系统的一组接口提供一个统一的界面,此模式定义了一个高层接口,这一个高层接口使的子系统更加容易使用。   二、解决的问题(What To Solve) 1、分离不同的两个层        典型的分层例子是Net三层架构,界面层与业务逻辑层分离,业务逻辑层与数据访问层分类。
698 0
.NET设计模式-代理模式(Proxy Pattern)
代理模式(Proxy Pattern) ——.NET设计模式系列之十四 Terrylee,2006年5月 摘要:在软件系统中,有些对象有时候由于跨越网络或者其他的障碍,而不能够或者不想直接访问另一个对象,如果直接访问会给系统带来不必要的复杂性,这时候可以在客户程序和目标对象之间增加一层中间层,让代理对象来代替目标对象打点一切。
804 0
+关注
我是小助手
云栖直播
383
文章
6
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载