migration-to-vue3.md 10.7 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 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
创建应用实例

::: 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'
export function createApp() {
  const app = createSSRApp(App)
  return {
      app
  }
}
```

:::

## 全局属性,例如:全局网络请求

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

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

## 插件使用,例如:使用 vuex 的 store

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

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

## 项目根目录必需创建 index.html 文件,粘贴复制如下内容:

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

## 只支持使用 ES6 模块规范,commonJS 需改为 ES6 模块规范

### 导入模块, 例如:
inkwalk's avatar
inkwalk 已提交
89

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

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

inkwalk's avatar
inkwalk 已提交
98
### 模块导出,例如:
Q
qiang 已提交
99

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

inkwalk's avatar
inkwalk 已提交
104 105 106
// 之后 - Vue 3, 只支持 ES6 模块
export default { X };
```
Q
qiang 已提交
107

inkwalk's avatar
inkwalk 已提交
108
## vuex 用法
Q
qiang 已提交
109

inkwalk's avatar
inkwalk 已提交
110
::: preview
Q
qiang 已提交
111

inkwalk's avatar
inkwalk 已提交
112
> Vue2
Q
qiang 已提交
113

inkwalk's avatar
inkwalk 已提交
114 115 116 117 118 119 120 121 122
```js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
  state: {},
});
export default store;
```
Q
qiang 已提交
123

inkwalk's avatar
inkwalk 已提交
124
> Vue3
Q
qiang 已提交
125

inkwalk's avatar
inkwalk 已提交
126 127 128 129 130 131 132
```js
import { createStore } from "vuex";
const store = createStore({
  state: {},
});
export default store;
```
Q
qiang 已提交
133

inkwalk's avatar
inkwalk 已提交
134
:::
Q
qiang 已提交
135

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

inkwalk's avatar
inkwalk 已提交
138
> 而 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 已提交
139

inkwalk's avatar
inkwalk 已提交
140
## 生命周期的适配
inkwalk's avatar
inkwalk 已提交
141

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

inkwalk's avatar
inkwalk 已提交
144 145
- `destroyed` 修改为 `unmounted`
- `beforeDestroy` 修改为 `beforeUnmount`
Q
qiang 已提交
146

inkwalk's avatar
inkwalk 已提交
147
## 事件的适配
Q
qiang 已提交
148

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

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

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

inkwalk's avatar
inkwalk 已提交
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
```html
<template>
  <button @click="onClick">OK</button>
</template>
<script>
  export default {
    emits: ["click"],
    methods: {
      onClick() {
        this.$emit("click", "OK");
      },
    },
  };
</script>
```
Q
qiang 已提交
170

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

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

inkwalk's avatar
inkwalk 已提交
175
### 修改 modelValue
Q
qiang 已提交
176

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

inkwalk's avatar
inkwalk 已提交
179 180 181 182 183 184 185 186 187
```javascript
export default {
  props: {
    // value:String,
    // 替换 value 为 modelValue
    modelValue: String,
  },
};
```
Q
qiang 已提交
188

inkwalk's avatar
inkwalk 已提交
189
## 事件返回
Q
qiang 已提交
190

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

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

inkwalk's avatar
inkwalk 已提交
195 196
```html
<ChildComponent v-model="pageTitle" />
Q
qiang 已提交
197

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

inkwalk's avatar
inkwalk 已提交
200 201 202 203 204
<ChildComponent
  :modelValue="pageTitle"
  @update:modelValue="pageTitle = $event"
/>
```
F
fasttian 已提交
205

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

inkwalk's avatar
inkwalk 已提交
208 209
```html
<ChildComponent v-model:title="pageTitle" />
Q
qiang 已提交
210

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

inkwalk's avatar
inkwalk 已提交
213 214
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
```
F
fasttian 已提交
215

inkwalk's avatar
inkwalk 已提交
216
## 插槽的适配
F
fasttian 已提交
217

inkwalk's avatar
inkwalk 已提交
218
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 已提交
219

inkwalk's avatar
inkwalk 已提交
220
::: preview
Q
qiang 已提交
221

inkwalk's avatar
inkwalk 已提交
222
> Vue2
Q
qiang 已提交
223

inkwalk's avatar
inkwalk 已提交
224 225 226 227 228 229 230 231
```html
<!--  Vue2 支持的用法 -->
<uni-nav-bar>
  <view slot="left" class="city">
    <!-- ... -->
  </view>
</uni-nav-bar>
```
inkwalk's avatar
inkwalk 已提交
232

inkwalk's avatar
inkwalk 已提交
233
> Vue3
inkwalk's avatar
inkwalk 已提交
234

inkwalk's avatar
inkwalk 已提交
235 236 237 238 239
```html
<!--  Vue3 支持的用法 -->
<uni-nav-bar>
  <template v-slot:left>
    <view class="city">
Q
qiang 已提交
240 241
      <!-- ... -->
    </view>
inkwalk's avatar
inkwalk 已提交
242 243 244
  </template>
</uni-nav-bar>
```
Q
qiang 已提交
245

inkwalk's avatar
inkwalk 已提交
246
:::
inkwalk's avatar
inkwalk 已提交
247

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

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

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

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

inkwalk's avatar
inkwalk 已提交
257
### 转换方法
F
fasttian 已提交
258

inkwalk's avatar
inkwalk 已提交
259
::: preview
F
fasttian 已提交
260

inkwalk's avatar
inkwalk 已提交
261 262 263 264 265 266 267 268 269 270 271
> 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 已提交
272

inkwalk's avatar
inkwalk 已提交
273 274 275 276 277 278 279 280 281 282 283 284
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 已提交
285
      });
inkwalk's avatar
inkwalk 已提交
286 287 288 289
    });
  },
});
```
F
fasttian 已提交
290

inkwalk's avatar
inkwalk 已提交
291
> Vue3
F
fasttian 已提交
292

inkwalk's avatar
inkwalk 已提交
293 294 295 296 297 298 299 300 301
```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 已提交
302

inkwalk's avatar
inkwalk 已提交
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
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);
  },
});
```

:::

## uni-app 生命周期钩子在 Vue3 组合式 API 中的使用方式

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

### 只能在 `<script setup>` 单文件语法糖或 `setup()` 方法中使用生命周期钩子,以 A 页面跳转 B 页面传递参数为例:

::: 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 已提交
349
  });
inkwalk's avatar
inkwalk 已提交
350

inkwalk's avatar
inkwalk 已提交
351 352 353 354 355
  onShow(() => {
    console.log("B 页面 onShow");
  });
</script>
```
inkwalk's avatar
inkwalk 已提交
356

inkwalk's avatar
inkwalk 已提交
357
> 方法二
F
update  
fasttian 已提交
358

inkwalk's avatar
inkwalk 已提交
359 360 361 362 363 364 365
```js
// 方法二:在 B 页面 setup() 中
<script>
  import {
    onLoad,
    onShow,
  } from "@dcloudio/uni-app";
F
fasttian 已提交
366

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

inkwalk's avatar
inkwalk 已提交
374 375 376
      onShow(() => {
        console.log("B 页面 onShow");
      });
F
fasttian 已提交
377
    }
inkwalk's avatar
inkwalk 已提交
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
  }
</script>
```

:::

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

在 Vue3 中,this 对象下的 `$mp` 调整为 `$scope`
## 在 Vue3 中,如果 nvue 使用了 Vuex 的相关 API,需要在 main.js 的 createApp 的返回值中 return 一下 Vuex 示例:

  ```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 已提交
400

inkwalk's avatar
inkwalk 已提交
401
## App,小程序端源码调试,需要在 vite.config.js 中主动开启 sourcemap
F
update  
fasttian 已提交
402

inkwalk's avatar
inkwalk 已提交
403 404 405
  ```js
  import { defineConfig } from "vite";
  import uni from "@dcloudio/vite-plugin-uni";
F
update  
fasttian 已提交
406

inkwalk's avatar
inkwalk 已提交
407 408 409
  /**
   * @type {import('vite').UserConfig}
   */
F
update  
fasttian 已提交
410

inkwalk's avatar
inkwalk 已提交
411 412 413 414
  export default defineConfig({
    build: {
      sourcemap: true,
    },
F
update  
fasttian 已提交
415

inkwalk's avatar
inkwalk 已提交
416 417 418
    plugins: [uni()],
  });
  ```
F
update  
fasttian 已提交
419

inkwalk's avatar
inkwalk 已提交
420 421 422
## 在 vue3 的小程序平台中,监听原生的点击事件可以先使用 tap

在 vue3 中,移除了.native 修饰符,所以编译器无法预知 click 是要触发原生事件,还是组件的自定义事件,故并未转换成小程序的 tap 事件
inkwalk's avatar
inkwalk 已提交
423

inkwalk's avatar
inkwalk 已提交
424
## vue3 支持的手机版本最低到多少?
inkwalk's avatar
inkwalk 已提交
425 426 427

  > vue3 支持的范围是:Android > 4.4, ios >= 10

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

inkwalk's avatar
inkwalk 已提交
430 431 432 433 434 435 436 437 438 439 440 441 442 443
vue3 nvue 暂不支持 recycle-list 组件

## vue3 在 h5 平台发行时,为了优化包体积大小,会默认启动摇树,仅打包明确使用的 api

如果要关闭摇树,可以在 manifest.json 中配置:
```json
"h5": {
    "optimization": {
        "treeShaking": {
            "enable": false
        }
    }
}
```
inkwalk's avatar
inkwalk 已提交
444 445 446

## url-search-params

inkwalk's avatar
inkwalk 已提交
447
vue3 全平台新增:通过 props 来获取页面参数的使用方式
inkwalk's avatar
inkwalk 已提交
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479

<!--方式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>
```