vue2 更新到 vue3

首先卸载旧版本

npm uninstall vue-cli -g

然后安装新版本

npm install -g @vue/cli

项目创建方式

在vue3 中有 三种项目创建方式

  1. 使用脚手架 Vite

    在以往 Vue使用的是vue-cli脚手架, React使用 create-react-app脚手架,虽然脚手架工具不同,但是内部的打包工具还是webpack。而Vite脚手架的底层打包工具是 Rollup 支持打包多页应用程序和工具库,所以Vite脚手架能够在多个框架中都能够使用。

    Vite 为什么比webpack快启动快?

    在webpack中,每次启动都会把整个程序进行打包,而且热部署的时候会把相关的依赖再打包

    Vite 把项目分成了两个部分 依赖配置和源代码

    • 依赖配置 依赖部分不会经常变,所以Vite会提前把用到的依赖打包并保存到node_modules/.vite文件中,依赖的打包使用的是ESBuild工具,使用GO语言开发的所以速度要比JavaScript快得多。

      Vite 只有在 第一次启动/依赖变动/配置变动 才会重新打包依赖,打包完成之后通过http缓存到浏览器中

    • 源代码 直接运行在浏览器中,利用浏览器原生的ESM语法加载模块,当源代码发生变化时,模块热部署只会更新当前页面中的最少的代码

    缺点

    • HMR有时不会更新
    • 有些错误提示不友好

    使用Vite创建项目

    npm init vite@latest [项目名] -- --template vue

  2. 图形化创建

    vue ui

    vue3 中提供了一种图形化创建项目的的方式,运行命令之后, 就会启动一个本地服务,类似于控制台,所有的插件配置的修改都可以通过傻瓜式的方式

  3. vue create [项目名]

新特性

CompositionAPI

Vue2.x

vue2.x是使用的 Options API 也就是选项,在脚本中会定义 data,methods,computed等等属性,可以把他们看成一个一个的选项,然后使用这些选项进行开发。

当代码很少时,使用OptionsAPI开发会使代码结构很清晰。但是当代码量过多时,代码会变得非常臃肿,一个功能可能用到了多个选项属性,散落在各个地方,会使我们的代码变得难以阅读和维护。而CompositionAPI就是为了解决这个问题

Vue3.x

CompositionAPI 是基于函数组合的API 又叫组合式API,可以将一个功能中不同的代码放在一个函数中。还可以根据业务进行部分逻辑封装。为vue应用提供了更好的逻辑复用和代码组织 你

当然,vue2的mixin也可以实现,但是在mixin中,当多个mixin合在一起且其中有重名的变量时会发生错误,还有当代码量过多时会分不清哪一块逻辑属于哪一个mixin的问题。

CompositionAPI的使用

setup函数

  • setup 是使用 CompositionAPI 的入口,关于CompositionAPI的代码都写在setup函数中
  • setup 又是一个生命周期钩子函数,它会在 beforeCreate 之前调用
  • 在setup中,不能使用this
  • 在setup中定义的数据或方法,若需要在模板中使用则都必须要return出去

下面介绍一下Vue2 OptionsAPI 中 各个选项在 Vue3 中的实现

  • data

    data在vue2中用于定义响应式的数据引用,在vue3中则使用ref函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <template>
    <div id="app">
    <p>{{name}}</p>
    <p>{{age}}</p>
    </div>
    </template>

    <script>
    // 1. 导入ref 函数
    import { ref } from 'vue'

    export default {
    setup() {
    // 2. 使用ref函数定义数据,此时定义的name是一个响应式对象
    const name = ref('fish')
    const age = ref(18)

    // 3. 若需要在模板中使用name这个响应式对象,则需要把name 放在一个对象中并返回
    return { name, age }
    }
    }
    </script>
  • methods

    在vue3中定义方法,直接在setup函数中定义即可

    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
    <template>
    <div id="app">
    <p>{{name}}</p>
    <p>
    {{age}}
    <!-- 绑定事件 -->
    <button @click="addAge">+</button>
    </p>
    </div>
    </template>

    <script>
    import { ref } from 'vue'

    export default {
    setup() {
    const name = ref('fish')
    const age = ref(18)

    // 创建对应事件函数
    function addAge(){
    // age++; 此时的age是一个响应式对象,不是一个Number变量
    // 若需要修改它,则修改他的value属性
    age.value++
    }

    // 将函数返回
    return { name, age, addAge }
    }
    }
    </script>

  • computed

    vue3中的计算属性需要将 computed方法导入,然后传入回调函数进行数据处理

    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
    <template>
    <div id="app">
    <p>{{name}}</p>
    <p>
    <button @click="changeAge(-1)">-</button>
    {{age}}
    <button @click="changeAge(1)">+</button>
    </p>
    <p>出生年份 {{year}}</p>
    </div>
    </template>

    <script>
    // 导入 computed计算属性
    import { ref, computed } from 'vue'

    export default {
    setup() {
    const name = ref('fish')
    const age = ref(18)

    // 调用计算属性方法,并传入一个回调函数,进行数据处理
    const year = computed(()=>{
    return 2021 - age.value
    })
    function changeAge(value){
    age.value += value
    }

    // 将计算属性返回
    return { name, age, changeAge, year }
    }
    }
    </script>
  • watch
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
<template>
<div id="app">
<p>{{name}}</p>
<p>
<button @click="changeAge(-1)">-</button>
{{age}}
<button @click="changeAge(1)">+</button>
</p>
<p>出生年份 {{year}}</p>
</div>
</template>

<script>
// 导入 watch
import { computed, reactive, toRefs, watch } from 'vue'

export default {
setup() {
const data = reactive({
name: 'fish',
age: 18,
year: computed(()=>{
return 2021 - data.age
})
});

function changeAge(value){
data.age += value;
}

// 在watch中有两个参数,第一个是箭头函数用于返回监听的对象。第二个也是一个箭头函数,当监听对象变化时进行的一些处理
watch(()=> data.age, (newAge, oldAge)=>{
console.log(newAge);
debugger
})

return { ...toRefs(data), changeAge }
}
}
</script>
  • 生命周期

    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
    <template>
    <div id="app">
    </div>
    </template>

    <script>
    // 导入 生命周期钩子
    // 在vue3中 可用的钩子如下
    //onBeforeMount
    //onMounted
    //onBeforeUpdate
    //onUpdated
    //onBeforeUnmount
    //onUnmounted
    //onActivated
    //onDeactivated
    //onErrorCaptured
    import { onMounted, onUpdated, onUnmounted } from 'vue'

    export default {
    setup() {
    onBeforeMount(() => {
    // ...
    })
    onMounted(() => {
    // ...
    })
    onBeforeUpdate(() => {
    // ...
    })
    }
    }
    </script>

响应式对象

在上面的代码中可以看到,如果定义的数据或方法很多时,return语句就会很长。所以vue3提供了一个 reactive函数,可以定义一个响应式对象,然后把想要return的值都放在这个对象里面,使用了响应式对象,声明的时候就可以不使用ref

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
<template>
<div id="app">
<p>{{data.name}}</p>
<p>
<button @click="changeAge(-1)">-</button>
{{data.age}}
<button @click="changeAge(1)">+</button>
</p>
<p>出生年份 {{data.year}}</p>
</div>
</template>

<script>
// 导入 reactive
import { computed, reactive } from 'vue'

export default {
setup() {
const data = reactive({
// 直接声明即可
name: 'fish',
age: 18,
year: computed(()=>{
// 由于setup中没有this,所以访问属性时,需要使用data.属性名的方式访问
return 2021 - data.age
}),
changeAge(value){
data.age += value
}
})


// 最终只要将data对象返回即可
return { data }
}
}
</script>

当使用reactive时,需要在模板中使用 data.属性名 才可以取到值。我们可以使用 toRefs 函数

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
<template>
<div id="app">
<p>{{name}}</p>
<p>
<button @click="changeAge(-1)">-</button>
{{age}}
<button @click="changeAge(1)">+</button>
</p>
<p>出生年份 {{year}}</p>
</div>
</template>

<script>
// 导入 toRefs
import { computed, reactive, toRefs } from 'vue'

export default {
setup() {
const data = reactive({
name: 'fish',
age: 18,
year: computed(()=>{
return 2021 - data.age
})
});

function changeAge(value){
data.age += value;
}


// 使用toRefs函数将 响应式对象data变成普通对象data,然后还需要将toRefs解构,将普通对象data中的属性一个个导出去
return { ...toRefs(data), changeAge }
}
}
</script>

setup 的参数

  • props 用于接受参数

    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
    <template>
    <div id="app">
    <p>{{name}}</p>
    <p>
    <button @click="changeAge(-1)">-</button>
    {{age}}
    <button @click="changeAge(1)">+</button>
    </p>
    <p>出生年份 {{year}}</p>
    </div>
    </template>

    <script>
    import { computed, reactive, toRefs, watch } from 'vue'

    export default {
    props:{
    title: String
    },
    // 定义props接收参数

    setup(props) {
    // 然后传入setup中
    const data = reactive({
    name: 'fish',
    age: 18,
    year: computed(()=>{
    return 2021 - data.age
    })
    });

    function changeAge(value){
    data.age += value;
    }
    watch(()=> data.age, (newAge, oldAge)=>{
    console.log(newAge);
    console.log(props.title);
    // 随后即可取到props中的值
    })

    return { ...toRefs(data), changeAge }
    }
    }
    </script>
  • context

之前说到在setup中没有this,但是某些功能确实需要this时,可以使用context

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
<template>
<div id="app">
<p>{{name}}</p>
<p>
<button @click="changeAge(-1)">-</button>
{{age}}
<button @click="changeAge(1)">+</button>
</p>
<p>出生年份 {{year}}</p>
</div>
</template>

<script>
import { computed, reactive, toRefs, watch } from 'vue'

export default {
setup(context) {
// 传入context
const data = reactive({
name: 'fish',
age: 18,
year: computed(()=>{
return 2021 - data.age
})
});

function changeAge(value){
data.age += value;
}
watch(()=> data.age, (newAge, oldAge)=>{
console.log(newAge);
context.emit('age-changed', newAge)
// 向父组件暴露一个方法
})

return { ...toRefs(data), changeAge }
}
}
</script>

自定义指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//在 vue2 中,自定义指令是通过Vue的directive方法
<p v-highlight="'yellow'">以亮黄色高亮显示此文本</p>
Vue.directive('highlight', {
bind(el, binding, vnode) {
el.style.background = binding.value
}
})

// 在 vue3 中,由于有了app实例,所以直接在app上调用即可
<p v-highlight="'yellow'">以亮黄色高亮显示此文本</p>
const app = Vue.createApp({})

app.directive('highlight', {
beforeMount(el, binding, vnode) {
el.style.background = binding.value
}
})

变化较大的地方

  • 移除了 filter 过滤器,转而使用 计算属性代替

  • 移除了 $children ,如果需要访问子组件实例,建议使用 $refs

  • 移除了 Vue.extend

  • 在vue3 中可以有多个根节点

  • Vue.prototype 替换成 config.globalProperties

    1
    2
    3
    4
    5
    6
    // 之前 - Vue 2
    Vue.prototype.$http = () => {}

    // 之后 - Vue 3
    const app = createApp({})
    app.config.globalProperties.$http = () => {}
  • dom 相关操作

    1
    2
    3
    4
    5
    import { nextTick } from 'vue'

    nextTick(() => {
    // 一些和 DOM 有关的东西
    })
  • 注册组件

1
2
3
4
import ComA from './components/ComA.vue';
import { createApp } from 'vue';
const app = createApp(App);
app.component('com-a', ComA);