本系列为视频课程的学习笔记。原课程:Vue3 + vite + Ts + pinia + 实战 + 源码 +electron

globalProperties

Vue3没有Prototype属性,所以使用app.config.globalProperties代替去定义变量和函数。

Vue2:

1
Vue.prototype.$http = ()=>{}

Vue3:

1
2
const app = create App({})
app.config.globalProperties.$http = ()=>{}

另外,Vue3废除了filters,但可以用全局函数代替。

main.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//其他省略
const app = createApp(App)

app.config.globalProperties.$env = "dev"
app.config.globalProperties.$filters = {
format<T>(str:T) {
return `114514${str}`
}
}

type Filter = {
format<T>(str:T):string
}

// 不扩充类型的话使用时会报错
declare module "vue" {
export interface ComponentCustomProperties {
$env: string
$filters: Filter
}
}

App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<!-- 定义完后,可以直接在任何模板中访问到全局函数/变量 -->
<div>{{ $env }}</div>
<div>{{ $filters.format("1919810") }}</div>
</template>

<script setup lang="ts">
// 在ts内则需要通过组件实例获取
const app = getCurrentInstance()
console.log(app?.proxy?.$env)
console.log(app?.proxy?.$filters.format("1919810"))
</script>

源码

在Vue源码(/package/runtime-core/src/apiCreateApp.ts)中可以看到createApp的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
// 定义的很多属性在此初始化
export function createAppContext(): AppContext {
return {
app: null as any,
config: {
isNativeTag: NO,
performance: false,
// 全局函数变量在这呢
globalProperties: {},
optionMergeStrategies: {},
errorHandler: undefined,
warnHandler: undefined,
compilerOptions: {}
},
mixins: [],
components: {},
directives: {},
provides: Object.create(null),
optionsCache: new WeakMap(),
propsCache: new WeakMap(),
emitsCache: new WeakMap()
}
}

// 初始化函数
export function createAppAPI<HostElement>(
render: RootRenderFunction<HostElement>,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
// 返回项目里用到的createApp函数,接收一个根组件(通常是App.vue)
return function createApp(rootComponent, rootProps = null) {
if (!isFunction(rootComponent)) {
rootComponent = extend({}, rootComponent)
}

if (rootProps != null && !isObject(rootProps)) {
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
rootProps = null
}

// 初始化
const context = createAppContext()

// TODO remove in 3.4
// 开发团队说这块代码在3.4版本会移除
if (__DEV__) {
Object.defineProperty(context.config, 'unwrapInjectedRef', {
get() {
return true
},
set() {
warn(
`app.config.unwrapInjectedRef has been deprecated. ` +
`3.3 now alawys unwraps injected refs in Options API.`
)
}
})
}

const installedPlugins = new Set()

let isMounted = false

// 赋给app这个对象,填充这些属性和方法
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,

version,

get config() {
return context.config
},

set config(v) {
if (__DEV__) {
warn(
`app.config cannot be replaced. Modify individual options instead.`
)
}
},

// 注册一些插件
use(plugin: Plugin, ...options: any[]) {
if (installedPlugins.has(plugin)) {
__DEV__ && warn(`Plugin has already been applied to target app.`)
} else if (plugin && isFunction(plugin.install)) {
installedPlugins.add(plugin)
plugin.install(app, ...options)
} else if (isFunction(plugin)) {
installedPlugins.add(plugin)
plugin(app, ...options)
} else if (__DEV__) {
warn(
`A plugin must either be a function or an object with an "install" ` +
`function.`
)
}
return app
},

// 注册一些混入
mixin(mixin: ComponentOptions) {
if (__FEATURE_OPTIONS_API__) {
if (!context.mixins.includes(mixin)) {
context.mixins.push(mixin)
} else if (__DEV__) {
warn(
'Mixin has already been applied to target app' +
(mixin.name ? `: ${mixin.name}` : '')
)
}
} else if (__DEV__) {
warn('Mixins are only available in builds supporting Options API')
}
return app
},

// 注册一些全局组件
component(name: string, component?: Component): any {
if (__DEV__) {
validateComponentName(name, context.config)
}
if (!component) {
return context.components[name]
}
if (__DEV__ && context.components[name]) {
warn(`Component "${name}" has already been registered in target app.`)
}
context.components[name] = component
return app
},

// 注册一些指令
directive(name: string, directive?: Directive) {
if (__DEV__) {
validateDirectiveName(name)
}

if (!directive) {
return context.directives[name] as any
}
if (__DEV__ && context.directives[name]) {
warn(`Directive "${name}" has already been registered in target app.`)
}
context.directives[name] = directive
return app
},

// 挂载到DOM上,app.mount("#app")
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
if (!isMounted) {
// #5571
if (__DEV__ && (rootContainer as any).__vue_app__) {
warn(
`There is already an app instance mounted on the host container.\n` +
` If you want to mount another app on the same host container,` +
` you need to unmount the previous app by calling \`app.unmount()\` first.`
)
}
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
vnode.appContext = context

// HMR root reload
if (__DEV__) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer, isSVG)
}
}

if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer, isSVG)
}
isMounted = true
app._container = rootContainer
// for devtools and telemetry
;(rootContainer as any).__vue_app__ = app

if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = vnode.component
devtoolsInitApp(app, version)
}

return getExposeProxy(vnode.component!) || vnode.component!.proxy
} else if (__DEV__) {
warn(
`App has already been mounted.\n` +
`If you want to remount the same app, move your app creation logic ` +
`into a factory function and create fresh app instances for each ` +
`mount - e.g. \`const createMyApp = () => createApp(App)\``
)
}
},

// 卸载
unmount() {
if (isMounted) {
render(null, app._container)
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = null
devtoolsUnmountApp(app)
}
delete app._container.__vue_app__
} else if (__DEV__) {
warn(`Cannot unmount an app that is not mounted.`)
}
},

// 依赖注入
provide(key, value) {
if (__DEV__ && (key as string | symbol) in context.provides) {
warn(
`App already provides property with key "${String(key)}". ` +
`It will be overwritten with the new value.`
)
}

context.provides[key as string | symbol] = value

return app
},

runWithContext(fn) {
currentApp = app
try {
return fn()
} finally {
currentApp = null
}
}
})

if (__COMPAT__) {
installAppCompatProperties(app, context, render)
}

// 返回app
return app
}
}

在Vue源码(/package/runtime-core/src/component.ts)中可以看到app.proxy的来源。

1
2
3
4
5
// setupStatefulComponent函数内
// 用markRaw为对象添加__skip__属性跳过reactive代理,因为它里面自己就做了个代理(防止重复代理)
// 代理了instance.ctx,调用了PublicInstanceProxyHandlers方法
// ctx就是$开头的一些api
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))

在Vue源码(/package/runtime-core/src/componentPublicInstance.ts)中可以看到app.proxy调用的方法PublicInstanceProxyHandlers,其中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
else if (
// global properties
// 赋值全局函数变量
((globalProperties = appContext.config.globalProperties),
// 判断要读取的属性是否存在于globalProperties
hasOwn(globalProperties, key))
){
if (__COMPAT__) {
// 读取属性的描述符,兼容边缘情况
const desc = Object.getOwnPropertyDescriptor(globalProperties, key)!
// 如果有getter
if (desc.get) {
return desc.get.call(instance.proxy)
} else {
const val = globalProperties[key]
return isFunction(val)
? Object.assign(val.bind(instance.proxy), val)
: val
}
} else {
// 存在就返回
return globalProperties[key]
}