精读 Vue 官方文档系列 🎉
Why?
通过为 Vue 构造函数的原型对象 prototype 绑定属性或方法,来实现数据与功能的全局共享和复用。
对比 模块化代码复用
require/import 方式会更繁琐些,但能产生更清晰的依赖清单,更可维护,适合多人协作的大型应用。拓展原型的方式,使用起来更简单灵活,可以直接通过组件实例的 this 来获取这些拓展的成员 (property)。例如 this.$foo() ,并且所拓展的成员方法也能通过上下文对象来获取当前绑定的组件实例上的资源,例如 props、attrs、$options、计算属性、其它方法等。
当然,这种便利是以牺牲**“显性表达”**为代价的。
Vue.prototype.$http = axios;
如果对于这一背后机制缺乏了解的话,就很难清楚的知道 $http 方法的由来,让人感到混乱。
How?
为了避免与组件实例本身的 props、数据、方法、计算属性等重名而发生覆盖,为原型拓展的成员,它们的名称中应要加入一个特定的前缀标识,以用于作用域隔离。例如:$ 或者 Ω。
为了更好的管理 “实例上的 property”,我们应当通过“插件”的方式来使用。
替代方案
在没有模块系统 (比如 webpack 或 Browserify) 的应用中,存在一种任何重 JS 前端应用都常用的模式:一个全局的 App 对象。
var App = Object.freeze({
name: 'My App',
version: '2.1.4',
helpers: {
// 这是我们之前见到过的 `$reverseText` 方法
// 的一个纯函数版本
reverseText: function (text) {
return text
.split('')
.reverse()
.join('')
}
}
})
使用 Object.freeze() 冻结对象,可以阻止这个对象在未来被可能的修改。其效果类似于通过 const 定义的不可变常量。
TS 中的最佳实践
首先,使用 TypeScript 的模块补充 (module augmentation) 机制,修改 Vue 的类型声明文件,为其构造函数增加新实例的类型声明。
// types/shims-vue.d.ts
import {$f, $filters} from './filters.ts';
declare module 'vue/types/vue' {
interface Vue {
$filter: typeof $filters,
$f:typeof $f
}
}
然后,我们可以制作一个插件来实现要扩展的属性与方法:
//filter.ts
export const $filters = {
toUpperCase(v){
return v.toUpperCase();
}
};
export const $f = function (data:any, ...pipeLines) {
let value = data;
for(const pipeline of pipeLines) {
value = pipeline(value);
}
return value;
}
export defalut install(Vue){
Object.defineProperty(Vue.prototype, '$filters', {
get() {
return $filters
}
});
Object.defineProperty(Vue.prototype, '$f', {
get() {
return $f
}
});
}
最后使用插件。
import filters from './filters';
Vue.use(filters);
更多 Vue Typescript 模块补充的声明类型:
declare module 'vue/types/vue'{
interface Vue {} //为构造函数 prototype 上的实例成员声明类型。
interface VueConstuctor {} //为 Vue 构造函数声明全局静态的成员类型。
interface ComponentOptions <V extends Vue> {} //声明组件选项成员的类型声明。
}