migration-to-vue3.md 12.0 KB
Newer Older
inkwalk's avatar
inkwalk 已提交
1
<!-- #### vue2 项目迁移 vue3,必须适配的部分 -->
Q
qiang 已提交
2 3 4

以下列举迁移到 vue3,必须适配的几个点,vue2 项目才能正常运行在 vue3 上。更多查看完整的[非兼容特性列表](https://github.com/vuejs/vue-next/tree/master/packages/vue-compat#incompatible)

inkwalk's avatar
inkwalk 已提交
5
## main.js
Q
qiang 已提交
6

inkwalk's avatar
inkwalk 已提交
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
创建应用实例

::: preview

> Vue2

```JS
// 之前 - Vue 2
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false    // vue3 不再需要
App.mpType = 'app'    // vue3 不再需要
const app = new Vue({
...App
})
app.$mount()
```

> Vue3

```JS
import App from './App'
import { createSSRApp } from 'vue'
30
// 不能修改导出的 createApp 方法名,不能修改从 Vue 中导入的 createSSRApp。
inkwalk's avatar
inkwalk 已提交
31 32 33 34 35 36 37 38 39 40
export function createApp() {
  const app = createSSRApp(App)
  return {
      app
  }
}
```

:::

inkwalk's avatar
inkwalk 已提交
41 42 43
## 全局属性

例如:全局网络请求
inkwalk's avatar
inkwalk 已提交
44 45 46 47 48 49 50 51 52 53

```js
// 之前 - Vue 2
Vue.prototype.$http = () => {};

// 之后 - Vue 3
const app = createApp({});
app.config.globalProperties.$http = () => {};
```

inkwalk's avatar
inkwalk 已提交
54 55 56
## 插件使用

例如:使用 vuex 的 store
inkwalk's avatar
inkwalk 已提交
57 58 59 60 61 62 63 64 65 66 67 68

```js
// 之前 - Vue 2
import store from "./store";
Vue.prototype.$store = store;

// 之后 - Vue 3
import store from "./store";
const app = createApp(App);
app.use(store);
```

inkwalk's avatar
inkwalk 已提交
69 70 71
## 项目根目录必需创建 index.html 文件

粘贴复制如下内容:
inkwalk's avatar
inkwalk 已提交
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />
    <title></title>
    <!--preload-links-->
    <!--app-context-->
  </head>
  <body>
    <div id="app"><!--app-html--></div>
    <script type="module" src="/main.js"></script>
  </body>
</html>
```

inkwalk's avatar
inkwalk 已提交
93
## 只支持使用 ES6 模块规范
inkwalk's avatar
inkwalk 已提交
94

inkwalk's avatar
inkwalk 已提交
95 96 97
commonJS 需改为 ES6 模块规范

### 模块导入
inkwalk's avatar
inkwalk 已提交
98

inkwalk's avatar
inkwalk 已提交
99 100 101
```js
// 之前 - Vue 2, 使用 commonJS
var utils = require("../../../common/util.js");
Q
qiang 已提交
102

inkwalk's avatar
inkwalk 已提交
103 104 105
// 之后 - Vue 3, 只支持 ES6 模块
import utils from "../../../common/util.js";
```
Q
qiang 已提交
106

inkwalk's avatar
inkwalk 已提交
107
### 模块导出
Q
qiang 已提交
108

inkwalk's avatar
inkwalk 已提交
109 110 111
```js
// 之前 - Vue 2, 依赖如使用 commonJS 方式导出
module.exports.X = X;
Q
qiang 已提交
112

inkwalk's avatar
inkwalk 已提交
113 114 115
// 之后 - Vue 3, 只支持 ES6 模块
export default { X };
```
Q
qiang 已提交
116

inkwalk's avatar
inkwalk 已提交
117
## vuex 用法
Q
qiang 已提交
118

inkwalk's avatar
inkwalk 已提交
119
::: preview
Q
qiang 已提交
120

inkwalk's avatar
inkwalk 已提交
121
> Vue2
Q
qiang 已提交
122

inkwalk's avatar
inkwalk 已提交
123 124 125 126 127 128 129 130 131
```js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
  state: {},
});
export default store;
```
Q
qiang 已提交
132

inkwalk's avatar
inkwalk 已提交
133
> Vue3
Q
qiang 已提交
134

inkwalk's avatar
inkwalk 已提交
135 136 137 138 139 140 141
```js
import { createStore } from "vuex";
const store = createStore({
  state: {},
});
export default store;
```
Q
qiang 已提交
142

inkwalk's avatar
inkwalk 已提交
143
:::
Q
qiang 已提交
144

inkwalk's avatar
inkwalk 已提交
145
## 避免在同一元素上同时使用 v-if 与 v-for
Q
qiang 已提交
146

inkwalk's avatar
inkwalk 已提交
147
而 Vue3 中,v-if 总是优先于 v-for 生效。以上写法将会在 Vue3 中与预期不符合,由于语法上存在歧义,建议避免在同一元素上同时使用两者([更多](https://v3.cn.vuejs.org/guide/migration/v-if-v-for.html#%E6%A6%82%E8%A7%88))。
inkwalk's avatar
inkwalk 已提交
148

inkwalk's avatar
inkwalk 已提交
149
## 生命周期的适配
inkwalk's avatar
inkwalk 已提交
150

inkwalk's avatar
inkwalk 已提交
151
在 Vue3 中组件卸载的生命周期被重新命名
inkwalk's avatar
inkwalk 已提交
152

inkwalk's avatar
inkwalk 已提交
153 154
- `destroyed` 修改为 `unmounted`
- `beforeDestroy` 修改为 `beforeUnmount`
Q
qiang 已提交
155

inkwalk's avatar
inkwalk 已提交
156
## 事件的适配
Q
qiang 已提交
157

inkwalk's avatar
inkwalk 已提交
158
Vue3 现在提供了一个`emits`选项,类似于现有`props`选项。此选项可用于定义组件可以向其父对象发出的事件, [更多](https://v3.cn.vuejs.org/guide/migration/emits-option.html#overview)
Q
qiang 已提交
159

inkwalk's avatar
inkwalk 已提交
160
**强烈建议使用`emits`记录每个组件发出的所有事件。**
F
fasttian 已提交
161

inkwalk's avatar
inkwalk 已提交
162
这一点特别重要,因为去除了`.native`修饰符。`emits` 现在在未使用声明的事件的所有侦听器都将包含在组件的中`$attrs`,默认情况下,该侦听器将绑定到组件的根节点。
F
fasttian 已提交
163

inkwalk's avatar
inkwalk 已提交
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
```html
<template>
  <button @click="onClick">OK</button>
</template>
<script>
  export default {
    emits: ["click"],
    methods: {
      onClick() {
        this.$emit("click", "OK");
      },
    },
  };
</script>
```
Q
qiang 已提交
179

D
DCloud_LXH 已提交
180 181 182 183 184 185 186 187
### Vue3 项目部分小程序端事件延迟或调用失败
可在执行事件的元素上添加 `data-eventsync="true"` 属性以解决此问题,如:
```html
<template>
  <button @click="onClick" data-eventsync="true">OK</button>
</template>
```

inkwalk's avatar
inkwalk 已提交
188
## v-model 的适配
Q
qiang 已提交
189

inkwalk's avatar
inkwalk 已提交
190
Vue3 的 v-model 相对 Vue2 来说 ,有了较大的改变。可以使用多 `model`,相应语法也有变化。[更多](https://v3.cn.vuejs.org/guide/migration/v-model.html#%E6%A6%82%E8%A7%88)
Q
qiang 已提交
191

inkwalk's avatar
inkwalk 已提交
192
### 修改 modelValue
Q
qiang 已提交
193

inkwalk's avatar
inkwalk 已提交
194
用于自定义组件时,Vue3 v-model prop 和事件默认名称已更改 `props.value` 修改为 `props.modelValue` ,`event.value` 修改为 `update:modelValue`
Q
qiang 已提交
195

inkwalk's avatar
inkwalk 已提交
196 197 198 199 200 201 202 203 204
```javascript
export default {
  props: {
    // value:String,
    // 替换 value 为 modelValue
    modelValue: String,
  },
};
```
Q
qiang 已提交
205

inkwalk's avatar
inkwalk 已提交
206
## 事件返回
Q
qiang 已提交
207

inkwalk's avatar
inkwalk 已提交
208
将之前的 `this.$emit('input')` 修改为 `this.$emit('update:modelValue')` ,vue3 中将省略这一步骤
Q
qiang 已提交
209

inkwalk's avatar
inkwalk 已提交
210
自定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件:
F
fasttian 已提交
211

inkwalk's avatar
inkwalk 已提交
212 213
```html
<ChildComponent v-model="pageTitle" />
Q
qiang 已提交
214

inkwalk's avatar
inkwalk 已提交
215
<!-- 是以下的简写: -->
Q
qiang 已提交
216

inkwalk's avatar
inkwalk 已提交
217 218 219 220 221
<ChildComponent
  :modelValue="pageTitle"
  @update:modelValue="pageTitle = $event"
/>
```
F
fasttian 已提交
222

inkwalk's avatar
inkwalk 已提交
223
若需要更改 model 名称,作为组件内 model 选项的替代,现在我们可以将一个 argument 传递给 v-model:
Q
qiang 已提交
224

inkwalk's avatar
inkwalk 已提交
225 226
```html
<ChildComponent v-model:title="pageTitle" />
Q
qiang 已提交
227

inkwalk's avatar
inkwalk 已提交
228
<!-- 是以下的简写: -->
Q
qiang 已提交
229

inkwalk's avatar
inkwalk 已提交
230 231
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
```
F
fasttian 已提交
232

inkwalk's avatar
inkwalk 已提交
233
## 插槽的适配
F
fasttian 已提交
234

inkwalk's avatar
inkwalk 已提交
235
Vue3 将不支持 `slot="xxx"` 的用法 ,请使用 `v-slot:xxx` 用法。[更多](https://v3.cn.vuejs.org/guide/component-slots.html#%E5%85%B7%E5%90%8D%E6%8F%92%E6%A7%BD)
Q
qiang 已提交
236

inkwalk's avatar
inkwalk 已提交
237
::: preview
Q
qiang 已提交
238

inkwalk's avatar
inkwalk 已提交
239
> Vue2
Q
qiang 已提交
240

inkwalk's avatar
inkwalk 已提交
241 242 243 244 245 246 247 248
```html
<!--  Vue2 支持的用法 -->
<uni-nav-bar>
  <view slot="left" class="city">
    <!-- ... -->
  </view>
</uni-nav-bar>
```
inkwalk's avatar
inkwalk 已提交
249

inkwalk's avatar
inkwalk 已提交
250
> Vue3
inkwalk's avatar
inkwalk 已提交
251

inkwalk's avatar
inkwalk 已提交
252 253 254 255 256
```html
<!--  Vue3 支持的用法 -->
<uni-nav-bar>
  <template v-slot:left>
    <view class="city">
Q
qiang 已提交
257 258
      <!-- ... -->
    </view>
inkwalk's avatar
inkwalk 已提交
259 260 261
  </template>
</uni-nav-bar>
```
Q
qiang 已提交
262

inkwalk's avatar
inkwalk 已提交
263
:::
inkwalk's avatar
inkwalk 已提交
264

inkwalk's avatar
inkwalk 已提交
265
## 不再支持过滤器
F
fasttian 已提交
266

inkwalk's avatar
inkwalk 已提交
267
从 Vue 3.0 开始,过滤器已删除,不再支持,建议用方法调用或计算属性替换它们。[更多](https://v3.cn.vuejs.org/guide/migration/filters.html#%E6%A6%82%E8%A7%88)
F
fasttian 已提交
268

inkwalk's avatar
inkwalk 已提交
269 270 271
## API `Promise 化` 调用结果的方式

在 Vue3 中,处理 API `Promise 化` 调用结果的方式不同于 Vue2。[更多](https://uniapp.dcloud.io/api/#api-promise-化)
inkwalk's avatar
inkwalk 已提交
272

inkwalk's avatar
inkwalk 已提交
273 274
- Vue3 中,调用成功会进入 then 方法,调用失败会进入 catch 方法
- Vue2 中,调用无论成功还是失败,都会进入 then 方法,返回数据的第一个参数是错误对象,第二个参数是返回数据
inkwalk's avatar
inkwalk 已提交
275

inkwalk's avatar
inkwalk 已提交
276
### 转换方法
F
fasttian 已提交
277

inkwalk's avatar
inkwalk 已提交
278
::: preview
F
fasttian 已提交
279

inkwalk's avatar
inkwalk 已提交
280 281 282 283 284 285 286 287 288 289 290
> Vue2

```js
// Vue 2 转 Vue 3, 在 main.js 中写入以下代码即可
function isPromise(obj) {
  return (
    !!obj &&
    (typeof obj === "object" || typeof obj === "function") &&
    typeof obj.then === "function"
  );
}
inkwalk's avatar
inkwalk 已提交
291

inkwalk's avatar
inkwalk 已提交
292 293 294 295 296 297 298 299 300 301 302 303
uni.addInterceptor({
  returnValue(res) {
    if (!isPromise(res)) {
      return res;
    }
    return new Promise((resolve, reject) => {
      res.then((res) => {
        if (res[0]) {
          reject(res[0]);
        } else {
          resolve(res[1]);
        }
inkwalk's avatar
inkwalk 已提交
304
      });
inkwalk's avatar
inkwalk 已提交
305 306 307 308
    });
  },
});
```
F
fasttian 已提交
309

inkwalk's avatar
inkwalk 已提交
310
> Vue3
F
fasttian 已提交
311

inkwalk's avatar
inkwalk 已提交
312 313 314 315 316 317 318 319 320
```js
// Vue 3 转 Vue 2, 在 main.js 中写入以下代码即可
function isPromise(obj) {
  return (
    !!obj &&
    (typeof obj === "object" || typeof obj === "function") &&
    typeof obj.then === "function"
  );
}
inkwalk's avatar
inkwalk 已提交
321

inkwalk's avatar
inkwalk 已提交
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
uni.addInterceptor({
  returnValue(res) {
    if (!isPromise(res)) {
      return res;
    }
    const returnValue = [undefined, undefined];
    return res
      .then((res) => {
        returnValue[1] = res;
      })
      .catch((err) => {
        returnValue[0] = err;
      })
      .then(() => returnValue);
  },
});
```

:::

inkwalk's avatar
inkwalk 已提交
342
## 生命周期钩子的组合式 API 使用方式
inkwalk's avatar
inkwalk 已提交
343 344 345

在 Vue3 组合式 API 中,也需要遵循 uni-app 生命周期钩子规范, 如 onLaunch 等应用生命周期仅可在 App.vue 中监听,使用中请注意生命周期钩子的适用范围。[查看全部生命周期钩子](https://uniapp.dcloud.net.cn/collocation/frame/lifecycle)

inkwalk's avatar
inkwalk 已提交
346
只能在 `<script setup>` 单文件语法糖或 `setup()` 方法中使用生命周期钩子,以 A 页面跳转 B 页面传递参数为例:
inkwalk's avatar
inkwalk 已提交
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367

::: preview

> 方法一

```js
// 从 A 页面跳转 B 页面时传递参数 ?id=1&name=uniapp,xxx 为跳转的页面路径
//uni.navigateTo({
//  url: 'xxx?id=1&name=uniapp'
//})

// 方法一:在 B 页面 <script setup> 中
<script setup>
  import {
    onLoad,
    onShow
  } from "@dcloudio/uni-app";

  // onLoad 接受 A 页面传递的参数
  onLoad((option) => {
    console.log("B 页面 onLoad:", option); //B 页面 onLoad: {id: '1', name: 'uniapp'}
inkwalk's avatar
inkwalk 已提交
368
  });
inkwalk's avatar
inkwalk 已提交
369

inkwalk's avatar
inkwalk 已提交
370 371 372 373 374
  onShow(() => {
    console.log("B 页面 onShow");
  });
</script>
```
inkwalk's avatar
inkwalk 已提交
375

inkwalk's avatar
inkwalk 已提交
376
> 方法二
F
update  
fasttian 已提交
377

inkwalk's avatar
inkwalk 已提交
378 379 380 381 382 383 384
```js
// 方法二:在 B 页面 setup() 中
<script>
  import {
    onLoad,
    onShow,
  } from "@dcloudio/uni-app";
F
fasttian 已提交
385

inkwalk's avatar
inkwalk 已提交
386 387 388 389 390 391
  export default {
    setup() {
      // onLoad 接受 A 页面传递的参数
      onLoad((option) => {
        console.log("B 页面 onLoad:", option); //B 页面 onLoad: {id: '1', name: 'uniapp'}
      });
inkwalk's avatar
inkwalk 已提交
392

inkwalk's avatar
inkwalk 已提交
393 394 395
      onShow(() => {
        console.log("B 页面 onShow");
      });
F
fasttian 已提交
396
    }
inkwalk's avatar
inkwalk 已提交
397 398 399 400 401 402 403 404 405
  }
</script>
```

:::

## `$mp` 调整为 `$scope`

在 Vue3 中,this 对象下的 `$mp` 调整为 `$scope`
inkwalk's avatar
inkwalk 已提交
406 407 408 409

## 在 nvue 使用 Vuex

在 Vue3 中,如果 nvue 使用了 Vuex 的相关 API,需要在 main.js 的 createApp 的返回值中 return 一下 Vuex 示例:
inkwalk's avatar
inkwalk 已提交
410 411 412 413 414 415 416 417 418 419 420 421

  ```js
  import Vuex from "vuex";
  export function createApp() {
    const app = createSSRApp(App);
    app.use(store);
    return {
      app,
      Vuex, // 如果 nvue 使用 vuex 的各种map工具方法时,必须 return Vuex
    };
  }
  ```
inkwalk's avatar
inkwalk 已提交
422

inkwalk's avatar
inkwalk 已提交
423 424 425
## 需主动开启 sourcemap

App,小程序端源码调试,需要在 vite.config.js 中主动开启 sourcemap
F
update  
fasttian 已提交
426

inkwalk's avatar
inkwalk 已提交
427 428 429
  ```js
  import { defineConfig } from "vite";
  import uni from "@dcloudio/vite-plugin-uni";
F
update  
fasttian 已提交
430

inkwalk's avatar
inkwalk 已提交
431 432 433
  /**
   * @type {import('vite').UserConfig}
   */
F
update  
fasttian 已提交
434

inkwalk's avatar
inkwalk 已提交
435 436 437 438
  export default defineConfig({
    build: {
      sourcemap: true,
    },
F
update  
fasttian 已提交
439

inkwalk's avatar
inkwalk 已提交
440 441 442
    plugins: [uni()],
  });
  ```
F
update  
fasttian 已提交
443

inkwalk's avatar
inkwalk 已提交
444
## 小程序平台中监听原生的点击事件
inkwalk's avatar
inkwalk 已提交
445

inkwalk's avatar
inkwalk 已提交
446 447
在 vue3 的小程序平台中,监听原生的点击事件可以先使用 tap。
在 vue3 中,移除了.native 修饰符,所以编译器无法预知 click 是要触发原生事件,还是组件的自定义事件,故并未转换成小程序的 tap 事件。
inkwalk's avatar
inkwalk 已提交
448

inkwalk's avatar
inkwalk 已提交
449
## vue3 支持的手机版本最低到多少?
inkwalk's avatar
inkwalk 已提交
450

451
  > vue3 支持的范围是:Android > 4.4(具体因系统 webview 版本而异,原生安卓系统升级过系统 webview 一般 5.0 即可,国产安卓系统未使用 x5 内核时一般需 7.0 以上), ios >= 10
inkwalk's avatar
inkwalk 已提交
452

d-u-a's avatar
d-u-a 已提交
453 454 455
  > Android < 4.4,配置 X5 内核支持,首次需要联网下载,可以配置下载 X5 内核成功后启动应用,[详情](https://uniapp.dcloud.net.cn/collocation/manifest.html#appwebview)


inkwalk's avatar
inkwalk 已提交
456
## vue3 nvue 暂不支持 recycle-list 组件
inkwalk's avatar
inkwalk 已提交
457

inkwalk's avatar
inkwalk 已提交
458 459
vue3 nvue 暂不支持 recycle-list 组件

inkwalk's avatar
inkwalk 已提交
460
## h5 平台发行时,会默认启动摇树
inkwalk's avatar
inkwalk 已提交
461

inkwalk's avatar
inkwalk 已提交
462
vue3 在 h5 平台发行时,为了优化包体积大小,会默认启动摇树,仅打包明确使用的 api,
inkwalk's avatar
inkwalk 已提交
463
如果要关闭摇树,可以在 manifest.json 中配置:
inkwalk's avatar
inkwalk 已提交
464

inkwalk's avatar
inkwalk 已提交
465 466 467 468 469 470 471 472 473
```json
"h5": {
    "optimization": {
        "treeShaking": {
            "enable": false
        }
    }
}
```
inkwalk's avatar
inkwalk 已提交
474

inkwalk's avatar
inkwalk 已提交
475
## 通过 props 来获取页面参数@url-search-params
inkwalk's avatar
inkwalk 已提交
476

inkwalk's avatar
inkwalk 已提交
477
vue3 全平台新增:通过 props 来获取页面参数的使用方式
inkwalk's avatar
inkwalk 已提交
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509

<!--方式1-->

```html
<script setup>
  // 页面可以通过定义 props 来直接接收 url 传入的参数
  // 如:uni.navigateTo({ url: '/pages/index/index?id=10' })
  const props = defineProps({
    id: String,
  });
  console.log("id=" + props.id); // id=10
</script>
```

<!--方式2-->

```html
<script>
  // 页面可以通过定义 props 来直接接收 url 传入的参数
  // 如:uni.navigateTo({ url: '/pages/index/index?id=10' })
  export default {
    props: {
      id: {
        type: String,
      },
    },
    setup(props) {
      console.log("id=" + props.id); // id=10
    },
  };
</script>
```
inkwalk's avatar
inkwalk 已提交
510 511 512 513

## 小程序和App端不支持插值方式定义国际化@vue-i18n

因运行平台限制,目前在小程序和 App 端不支持插值方式定义国际化,需要使用 Messages Functions 定义国际化信息,[参考文档](https://vue-i18n.intlify.dev/guide/advanced/function.html)
514

inkwalk's avatar
inkwalk 已提交
515 516 517
示例:

```js
inkwalk's avatar
inkwalk 已提交
518 519 520 521 522
const messages = {
  en: {
    greeting: ({ named }) => `hello, ${named('name')}!`
  }
}
inkwalk's avatar
inkwalk 已提交
523
```