vue 3.0 初体验 (项目搭建)及新特性
Vue 3.0 项目初始化
第一步,安装 vue-cli:
npm install -g @vue/cli
安装成功后,我们即可使用 vue 命令,测试方法:
$ vue -V
@vue/cli 4.3.1
第二步,初始化 vue 项目:
vue create vue-next-test
输入命令后,会出现命令行交互窗口,这里我们选择 Manually select features:
Vue CLI v4.3.1 ? Please pick a preset: default (babel, eslint) ❯ Manually select features
随后我们勾选:Router、Vuex、CSS Pre-processors 和 Linter / Formatter,这些都是开发商业级项目必须的:
Vue CLI v4.3.1 ? Please pick a preset: Manually select features ? Check the features needed for your project: ◉ Babel ◯ TypeScript ◯ Progressive Web App (PWA) Support ◉ Router ◉ Vuex ◉ CSS Pre-processors ❯◉ Linter / Formatter ◯ Unit Testing ◯ E2E Testing
注意:Vue 3.0 项目目前需要从 Vue 2.0 项目升级而来,所以为了直接升级到 Vue 3.0 全家桶,我们需要在 Vue 项目创建过程中勾选 Router 和 Vuex,所以避免手动写初始化代码
升级 Vue 3.0 项目
目前创建 Vue 3.0 项目需要通过插件升级的方式来实现,vue-cli 还没有直接支持,我们进入项目目录,并输入以下指令:
cd vue-next-test
vue add vue-next
执行上述指令后,会自动安装 vue-cli-plugin-vue-next 插件(查看项目代码),该插件会完成以下操作:
- 安装 Vue 3.0 依赖
- 更新 Vue 3.0 webpack loader 配置,使其能够支持 .vue 文件构建(这点非常重要)
- 创建 Vue 3.0 的模板代码
- 自动将代码中的 Vue Router 和 Vuex 升级到 4.0 版本,如果未安装则不会升级
- 自动生成 Vue Router 和 Vuex 模板代码
完成上述操作后,项目正式升级到 Vue 3.0,注意该插件还不能支持 typescript,用 typescript 的同学还得再等等。(就是目前还不太支持TS)
Vue 3.0 基本特性体验
下面我们从项目开发的角度逐步体验 Vue 3.0 的开发流程
创建路由
项目开发中,我们通常需要创建新页面,然后添加路由配置,我们在 /src/views 目录下创建 Test.vue:
<template> <div class="test"> <h1>vue3.0 初体验</h1> <p>少年你的头发可还好,???? 哈哈哈哈哈</p> </div> </template> <script> export default { } </script> <style lang="less" scoped> .test { color: red; } </style>
之后在 /src/router/index.js 中创建路由配置:
import { createRouter, createWebHashHistory } from 'vue-router' import Home from '../views/Home.vue' const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/test', name: 'Test', component: () => import(/* webpackChunkName: "test" */ '../views/Test.vue') } ] const router = createRouter({ history: createWebHashHistory(), routes }) export default router
初始化 Vue Router 的过程与 3.0 版本变化不大,只是之前采用构造函数的方式,这里改为使用 createRouter 来创建 Vue Router 实例,配置的方法基本一致,配置完成后我们还需要在 App.vue 中增加链接到 Test.vue 的路由:
<template> <div id="app"> <div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> | <router-link to="/test">Test</router-link> </div> <router-view/> </div> </template>
启动项目:
npm run serve
状态和事件绑定
Vue 3.0 中定义状态的方法改为类似 React Hooks 的方法,下面我们在 Test.vue 中定义一个状态 count:
<template> <div class="test"> <h1>test count: {{count}}</h1> </div> </template> <script> import { ref } from 'vue' export default { setup () { const count = ref(0) return { count } } } </script>
Vue 3.0 中初始化状态通过 setup 方法,定义状态需要调用 ref 方法。接下来我们定义一个事件,用来更新 count 状态:
<template> <div class="test"> <h1>test count: {{count}}</h1> <button @click="add">add</button> </div> </template> <script> import { ref } from 'vue' export default { setup () { const count = ref(0) const add = () => { count.value++ } return { count, add } } } </script>
这里的 add 方法不再需要定义在 methods 中,但注意更新 count 值的时候不能直接使用 count++,而应使用 count.value++,更新代码后,点击按钮,count 的值就会更新了:
计算属性和监听器
Vue 3.0 中计算属性和监听器的实现依赖 computed 和 watch 方法:
<template> <div class="test"> <h1>test count: {{count}}</h1> <div>count * 2 = {{doubleCount}}</div> <button @click="add">add</button> </div> </template> <script> import { ref, computed, watch } from 'vue' export default { setup () { const count = ref(0) const add = () => { count.value++ }
//侦听一个getter watch(() => count.value, val => { console.log(`count is ${val}`) }) const doubleCount = computed(() => count.value * 2) return { count, doubleCount, add } } } </script>
计算属性 computed 是一个方法,里面需要包含一个回调函数,当我们访问计算属性返回结果时,会自动获取回调函数的值:
const doubleCount = computed(() => count.value * 2)
监听器 watch 同样是一个方法,它包含 2 个参数,2 个参数都是 function:
watch(() => count.value, val => { console.log(`count is ${val}`) })
第一个参数是监听的值,count.value 表示当 count.value 发生变化就会触发监听器的回调函数,即第二个参数,第二个参数可以执行监听时候的回调
如果是2 个以上的监听属性 就这样
watch( [refA, () => refB.value], ([a, b], [prevA, prevB]) => { console.log(`a is: ${a}`) console.log(`b is: ${b}`) } )
获取路由
Vue 3.0 中通过 getCurrentInstance 方法获取当前组件的实例,然后通过 ctx 属性获得当前上下文,ctx.$router 是 Vue Router 实例,里面包含了 currentRoute 可以获取到当前的路由信息
<script> import { getCurrentInstance } from 'vue' export default { setup () { const { ctx } = getCurrentInstance() console.log(ctx.$router.currentRoute.value) } } </script>
Vuex 集成
Vuex 的集成方法如下:
定义 Vuex 状态
第一步,修改 src/store/index.js 文件:
import Vuex from 'vuex' export default Vuex.createStore({ state: { test: { a: 1 } }, mutations: { setTestA(state, value) { state.test.a = value } }, actions: { }, modules: { } })
Vuex 的语法和 API 基本没有改变,我们在 state 中创建了一个 test.a 状态,在 mutations 中添加了修改 state.test.a 状态的方法: setTestA
引用 Vuex 状态
第二步,在 Test.vue 中,通过计算属性使用 Vuex 状态:
<template> <div class="test"> <h1>test count: {{count}}</h1> <div>count * 2 = {{doubleCount}}</div> <div>state from vuex {{a}}</div> <button @click="add">add</button> </div> </template> <script> import { ref, computed, watch, getCurrentInstance } from 'vue' export default { setup () { const count = ref(0) const add = () => { count.value++ } watch(() => count.value, val => { console.log(`count is ${val}`) }) const doubleCount = computed(() => count.value * 2) const { ctx } = getCurrentInstance() console.log(ctx.$router.currentRoute.value) const a = computed(() => ctx.$store.state.test.a) return { count, doubleCount, add, a } } } </script>
这里我们通过计算属性来引用 Vuex 中的状态:
const a = computed(() => ctx.$store.state.test.a)
ctx 是上节中我们提到的当前组件实例
更新 Vuex 状态
更新 Vuex 状态仍然使用 commit 方法,这点和 Vuex 3.0 版本一致:
<template> <div class="test"> <h1>test count: {{count}}</h1> <div>count * 2 = {{doubleCount}}</div> <div>state from vuex {{a}}</div> <button @click="add">add</button> <button @click="update">update a</button> </div> </template> <script> import { ref, computed, watch, getCurrentInstance } from 'vue' export default { setup () { const count = ref(0) const add = () => { count.value++ } watch(() => count.value, val => { console.log(`count is ${val}`) }) const doubleCount = computed(() => count.value * 2) const { ctx } = getCurrentInstance() console.log(ctx.$router.currentRoute.value) const a = computed(() => ctx.$store.state.test.a) const update = () => { ctx.$store.commit('setTestA', count) } return { count, doubleCount, add, a, update } } } </script>
这里我们点击 update a 按钮后,会触发 update 方法,此时会通过 ctx.$store.commit 调用 setTestA 方法,将 count 的值覆盖 state.test.a 的值。
1、setup()
setup() 函数是 vue3 中,专门为组件提供的新属性。它为我们使用 vue3 的 Composition API 新特性提供了统一的入口
-
调用时间
在创建组件实例时,在初始 prop 解析之后立即调用
setup
。在生命周期方面,它是在 beforeCreate 钩子之前调用的。 -
模板使用
如果
setup
返回一个对象,则该对象的属性将合并到组件模板的渲染上下文中。
2、ref()
ref 和 reactive 一样都是实现响应式数据的方法
由于 reactive 必须传递一个对象,所以导致我们再企业开发中,如果我们只想让某个变量实现响应式的时候非常麻烦,所以 Vue3 就提供了 ref 方法,实现对简单值的监听
ref 底层的本质还是 reactive 系统会自动根据我们给 ref 传入的值将他转换成 ref(xx) —— reactive({value: xx})
ref 注意点
a、在 VUE 的模板中使用 ref 的值不需要通过 value 获取 (Vue 会通过自动给 ref 的值加上 .value)
b、在 js 中使用 ref 的值必须使用 .value 获取
3、reactive() 声明单一对象 (注意:解构会破坏代理proxy即双向绑定)
reactive() 函数接收一个普通对象,返回一个响应式的数据对象, 想要使用创建的响应式数据也很简单,创建出来之后,在setup中return出去,直接在template中调用即可; 在reactive()函数中访问ref()函数创建的对象不用.value
4、isRef() 判断是否是通过ref()函数创建出来的对象
5、toRefs() 从组合函数返回反应对象
toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据
const state = reactive({ foo: 1, bar: 2 }) const stateAsRefs = toRefs(state) /* Type of stateAsRefs: { foo: Ref<number>, bar: Ref<number> } */ // ref 和 原始property “链接” state.foo++ console.log(stateAsRefs.foo.value) // 2 stateAsRefs.foo.value++ console.log(state.foo) // 3
当从合成函数返回响应式对象时,toRefs
非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行分解/扩散(注意:解构会破坏代理proxy即双向绑定):
function useFeatureX() { const state = reactive({ foo: 1, bar: 2 }) // 逻辑运行状态 // 返回时转换为ref return toRefs(state) } export default { setup() { // 可以在不失去响应性的情况下破坏结构 const { foo, bar } = useFeatureX() return { foo, bar } } }
6、computed() 计算属性 可创建只读,和可读可写两种
1) 只读 let fullName = computed(() => msg.value + '~' + name)
2)可读可写 let fullName2 = computed({ get: () => number.value + state.step, set: (v) => number.value = v })
7、watch()
watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源数据变更时才执行回调。
1) 监听 reactive( )声明的数据
单数据监听: watch( ( )=> reactive.name, (n, o)=>{ })
多数据监听: watch([( )=> reactive.name, ( )=> reactive.sex], ([name, sex], [oldName, oldSex])=> { })
2) 监听 ref( )声明的数据
单数据监听: watch(age,(n, o)=>{ })
多数据监听: watch([age, weight], ([newAge, newWeight], [oldAge, oldWeight])=> { })
3)取消监听
const stop = watch(age,(n, o)=>{ })
setTimeout(() => { stop() }, 1000) // 一秒后取消对age的监听
8、LifeCycle Hooks(新的生命周期)
onBeforeUnmount(() => { console.log('onBeforeUnmount') })
onUnmounted(() => { console.log('onUnmounted') })
onErrorCaptured(() => { console.log('onErrorCaptured') })
onRenderTracked(() => { console.log('onRenderTracked') })
onRenderTriggered(() => { console.log('onRenderTriggered') })
9、Template refs 通过refs 来回去真实dom元素
<标签 ref='x'></标签>
在2.x中取得h3的dom: this.$refs.x
在3.0中如下:
当使用组合式 API 时,reactive refs 和 template refs 的概念已经是统一的:
setup(){ let x=ref(null); 获取dom x.value return{ x } }
10、global Properties vue3.0 的全局配置
Vue 2.x 通过 Vue.prototype扩展
vue3.0 通过app.config.globalProperties.$api = 'http://www.baidu.com'
在setUp函数中通过:
const { ctx } = getCurrentInstance()
ctx.$api ( http://www.baidu.com )
11、Suspense
异步组件在默认情况下是可挂起的。这意味着如果它在父链中有一个 <Suspense>,它将被视为该 <Suspense> 的异步依赖。在这种情况下,加载状态将由 <Suspense> 控制,组件自身的加载、错误、延迟和超时选项将被忽略。异步组件可以选择退出 Suspense 控制,并通过在其选项中指定 suspensable:false,让组件始终控制自己的加载状态。
参考:https://www.cnblogs.com/yf-html/p/12753540.html
参考:https://www.jianshu.com/p/9e78d2d6aa34
官网:https://www.vue3js.cn/docs/zh/api/computed-watch-api.html#computed