migration-to-vue3.md 11.0 KB
Newer Older
Q
qiang 已提交
1 2 3 4 5 6 7 8
#### vue2 项目迁移 vue3,必须适配的部分

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

- main.js

  - 创建应用实例

inkwalk's avatar
inkwalk 已提交
9 10 11 12
    ::: preview

    > Vue2

Q
qiang 已提交
13 14 15 16 17 18 19 20 21 22
    ```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()
inkwalk's avatar
inkwalk 已提交
23
    ```
Q
qiang 已提交
24

inkwalk's avatar
inkwalk 已提交
25 26 27
    > Vue3

    ```JS
Q
qiang 已提交
28 29 30
    import App from './App'
    import { createSSRApp } from 'vue'
    export function createApp() {
inkwalk's avatar
inkwalk 已提交
31 32
      const app = createSSRApp(App)
      return {
Q
qiang 已提交
33
          app
inkwalk's avatar
inkwalk 已提交
34
      }
Q
qiang 已提交
35 36 37
    }
    ```

inkwalk's avatar
inkwalk 已提交
38 39
    :::

Q
qiang 已提交
40 41
  - 全局属性,例如:全局网络请求

inkwalk's avatar
inkwalk 已提交
42 43 44
    ```js
    // 之前 - Vue 2
    Vue.prototype.$http = () => {};
Q
qiang 已提交
45

inkwalk's avatar
inkwalk 已提交
46 47 48 49
    // 之后 - Vue 3
    const app = createApp({});
    app.config.globalProperties.$http = () => {};
    ```
Q
qiang 已提交
50 51 52

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

inkwalk's avatar
inkwalk 已提交
53 54 55 56 57 58 59 60 61 62
    ```js
    // 之前 - Vue 2
    import store from "./store";
    Vue.prototype.$store = store;

    // 之后 - Vue 3
    import store from "./store";
    const app = createApp(App);
    app.use(store);
    ```
Q
qiang 已提交
63 64 65 66 67 68 69 70

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

  ```html
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
F
fasttian 已提交
71 72 73 74
      <meta
        name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
      />
Q
qiang 已提交
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
      <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 模块规范

  - 导入模块, 例如:

    ```js
    // 之前 - Vue 2, 使用 commonJS
    var utils = require("../../../common/util.js");

    // 之后 - Vue 3, 只支持 ES6 模块
    import utils from "../../../common/util.js";
    ```

  - 模块导出,例如:

    ```js
    // 之前 - Vue 2, 依赖如使用 commonJS 方式导出
    module.exports.X = X;

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

- vuex 用法

inkwalk's avatar
inkwalk 已提交
110 111 112 113
  ::: preview

  > Vue2

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

  > Vue3
Q
qiang 已提交
125

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

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

Q
qiang 已提交
136 137 138 139 140
- 避免在同一元素上同时使用 v-if 与 v-for

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

- 生命周期的适配
F
fasttian 已提交
141 142 143

  在 Vue3 中组件卸载的生命周期被重新命名

Q
qiang 已提交
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
  - `destroyed` 修改为 `unmounted`
  - `beforeDestroy` 修改为 `beforeUnmount`

- 事件的适配

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

  **强烈建议使用`emits`记录每个组件发出的所有事件。**

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

  ```html
  <template>
    <button @click="onClick">OK</button>
  </template>
  <script>
F
fasttian 已提交
160 161 162 163 164 165 166 167
    export default {
      emits: ["click"],
      methods: {
        onClick() {
          this.$emit("click", "OK");
        },
      },
    };
Q
qiang 已提交
168 169 170 171
  </script>
  ```

- v-model 的适配
F
fasttian 已提交
172

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

  - 修改 modelValue
    用于自定义组件时,Vue3 v-model prop 和事件默认名称已更改 `props.value` 修改为 `props.modelValue` ,`event.value` 修改为 `update:modelValue`

    ```javascript
    export default {
      props: {
        // value:String,
        // 替换 value 为 modelValue
F
fasttian 已提交
183 184 185
        modelValue: String,
      },
    };
Q
qiang 已提交
186 187 188
    ```

- 事件返回
F
fasttian 已提交
189

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

  自定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件:

  ```html
F
fasttian 已提交
195 196 197
  <ChildComponent v-model="pageTitle" />

  <!-- 是以下的简写: -->
Q
qiang 已提交
198

F
fasttian 已提交
199 200 201 202 203
  <ChildComponent
    :modelValue="pageTitle"
    @update:modelValue="pageTitle = $event"
  />
  ```
Q
qiang 已提交
204 205 206 207

  若需要更改 model 名称,作为组件内 model 选项的替代,现在我们可以将一个 argument 传递给 v-model:

  ```html
F
fasttian 已提交
208 209 210 211 212
  <ChildComponent v-model:title="pageTitle" />

  <!-- 是以下的简写: -->

  <ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
Q
qiang 已提交
213 214 215 216 217 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)

inkwalk's avatar
inkwalk 已提交
219 220 221 222
  ::: preview

  > Vue2

Q
qiang 已提交
223 224 225 226 227 228 229 230
  ```html
  <!--  Vue2 支持的用法 -->
  <uni-nav-bar>
    <view slot="left" class="city">
      <!-- ... -->
    </view>
  </uni-nav-bar>
  ```
inkwalk's avatar
inkwalk 已提交
231 232
  
  > Vue3
Q
qiang 已提交
233 234 235 236 237 238 239 240 241 242 243 244

  ```html
  <!--  Vue3 支持的用法 -->
  <uni-nav-bar>
    <template v-slot:left>
      <view class="city">
        <!-- ... -->
      </view>
    </template>
  </uni-nav-bar>
  ```

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

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

D
DCloud_LXH 已提交
249
- 在 Vue3 中,处理 API `Promise 化` 调用结果的方式不同于 Vue2。[更多](https://uniapp.dcloud.io/api/#api-promise-化)
F
fasttian 已提交
250 251 252

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

    ::: preview

    > Vue2
F
fasttian 已提交
257 258

    ```js
inkwalk's avatar
inkwalk 已提交
259
    // Vue 2 转 Vue 3, 在 main.js 中写入以下代码即可
F
fasttian 已提交
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
    function isPromise(obj) {
      return (
        !!obj &&
        (typeof obj === "object" || typeof obj === "function") &&
        typeof obj.then === "function"
      );
    }

    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 已提交
286
    > Vue3
F
fasttian 已提交
287 288

    ```js
inkwalk's avatar
inkwalk 已提交
289
    // Vue 3 转 Vue 2, 在 main.js 中写入以下代码即可
F
fasttian 已提交
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
    function isPromise(obj) {
      return (
        !!obj &&
        (typeof obj === "object" || typeof obj === "function") &&
        typeof obj.then === "function"
      );
    }

    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 已提交
315 316 317

    :::

F
fasttian 已提交
318
  - uni-app 生命周期钩子在 Vue3 组合式 API 中的使用方式如下:
inkwalk's avatar
inkwalk 已提交
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 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369

    - 在 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'}
        });

        onShow(() => {
          console.log("B 页面 onShow");
        });
      </script>
      ```

      > 方法二

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

        export default {
          setup() {
            // onLoad 接受 A 页面传递的参数
            onLoad((option) => {
              console.log("B 页面 onLoad:", option); //B 页面 onLoad: {id: '1', name: 'uniapp'}
            });

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

      :::
F
update  
fasttian 已提交
376

F
fasttian 已提交
377 378 379
  - 在 Vue3 中,this 对象下的 `$mp` 调整为 `$scope`

  - 在 Vue3 中,如果 nvue 使用了 Vuex 的相关 API,需要在 main.js 的 createApp 的返回值中 return 一下 Vuex 示例:
inkwalk's avatar
inkwalk 已提交
380

F
fasttian 已提交
381
    ```js
382
    import Vuex from 'vuex'
F
fasttian 已提交
383
    export function createApp() {
inkwalk's avatar
inkwalk 已提交
384 385 386 387 388 389
      const app = createSSRApp(App);
      app.use(store);
      return {
        app,
        Vuex, // 如果 nvue 使用 vuex 的各种map工具方法时,必须 return Vuex
      };
F
fasttian 已提交
390
    }
F
update  
fasttian 已提交
391
    ```
inkwalk's avatar
inkwalk 已提交
392

F
update  
fasttian 已提交
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
  - App,小程序端源码调试,需要在 vite.config.js 中主动开启 sourcemap

    ```js
    import { defineConfig } from "vite";
    import uni from "@dcloudio/vite-plugin-uni";

    /**
     * @type {import('vite').UserConfig}
     */

    export default defineConfig({
      build: {
        sourcemap: true,
      },

      plugins: [uni()],
    });
    ```
F
update  
fasttian 已提交
411

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

  - vue3 支持的手机版本最低到多少?
inkwalk's avatar
inkwalk 已提交
415
    > vue3 支持的范围是:Android > 4.4, ios >= 10
416
  
417 418 419 420 421 422 423 424 425 426 427 428
  - vue3 nvue 暂不支持 recycle-list 组件

  - vue3 在 h5 平台发行时,为了优化包体积大小,会默认启动摇树,仅打包明确使用的api。如果要关闭摇树,可以在manifest.json中配置:
        
    ```json
    "h5": {
        "optimization": {
            "treeShaking": {
                "enable": false
            }
        }
    }
429
    ```
F
update  
fasttian 已提交
430
  ## 通过定义 props 来直接接收 url 传入的参数