vite脚手架创建项目
1、全局安装vite脚手架
npm install -g create-vite-app
2、使用脚手架创建项目
create-vite-app projectName
3、进入项目文件夹
cd projectName
4、安装依赖
npm install
5、启动vue3.0项目
npm run dev
升级Vue-cli
npm update -g @vue/cli
# OR
yarn global upgrade --latest @vue/cli
如果已经全局安装了旧版本的vue-cli(1.x或者2.x), 你需要先通过
npm uninstall vue-cli -g
//或
yarn global remove vue-cli
//卸载它,然后再使用
npm install -g @vue/cli
//或
yarn global add @vue/cli
//安装新的包
创建项目
npm install -g @vue/cli
vue create 项目名
cd 项目名
vue add vue-next
npm run serv
开始
在vue-cli3.0下安装 composition-api
npm install @vue/composition-api --save
# OR
yarn add @vue/composition-api
在使用任何 @vue/composition-api 提供的能力前,必须先通过 Vue.use()
进行安装
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'
Vue.use(VueCompositionApi)
mian.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import './App.scss'
const app = createApp(App)
app.use(router).use(store).mount('#app');
Vue-router
import router from './router';
import store from './store';
createApp(App).use(router).use(store).mount('#app');
import { createRouter, createWebHashHistory } from 'vue-router';
import Home from '../views/Home/index.vue';
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/home',
name: 'Home',
component: Home,
},
{
path: '/login',
name: 'Login',
component: () => import('../views/Login/index.vue'),
},
{
path: '/user',
name: 'User',
component: () => import('../views/User/index.vue'),
},
{
path: '/information',
name: 'Information',
component: () => import('../views/Information/index.vue'),
},
{
path: '/discover',
name: 'Discover',
component: () => import('../views/Discover/index.vue'),
}
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
router.beforeEach((to, from, next) => {
if(to.path === '/') {
next({ path: '/home' })
} else {
next()
}
})
export default router;
Vuex
import Vuex from 'vuex';
export default Vuex.createStore({
state: {
userInfo: {}
},
mutations: {
setUserInfo(state, info) {
state.userInfo = info
}
},
actions: {
},
});
页面 template
vue2.0里 template模板里只能有一个根标签, 而3.0可以使用多个根标签
createApp
返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。可以在 createApp 之后链式调用其它方法。该函数第一个参数接收一个根组件选项的对象,第二个参数可以将 props 传递给应用程序.
createApp(
App,
{
username: 'alex cheng' // 提供 props,App 组件内部通过 props: ['username'] 接收
}
).mount('#app')
Composition API
数据声明
声明单一基础数据类型时使用;定义的变量,函数都要 return 出来;
setup函数,是在beforecreate钩子之前完成的。所以无法使用data跟methods。 另外要注意的是setup是同步的,不是异步的。
//引入方式1
import { setup, ref } from 'vue'
//引入方式2
import { reactive } from '@vue/composition-api'
export default {
setup(props, context) {
let count = ref(0)
console.log(count.value) //0 , 内部获取时需要用.value来获取
console.log(isRef(count)) //true //isRef() 用来判断是不是ref对象
return {
count // ref返回的是一个响应式的对象
}
}
}
Reactive 声明单一对象时使用
import { setup, Reactive } from 'vue'
export default {
// 倒计时逻辑的Composition Function
const useCountdown = (initialCount) => {
const count = ref(initialCount)
const state = ref(false)
const start = (initCount) => {
state.value = true;
if (initCount > 0) {
count.value = initCount
}
if (!count.value) {
count.value = initialCount
}
const interval = setInterval(() => {
if (count.value === 0) {
clearInterval(interval)
state.value = false
} else {
count.value--
}
}, 1000)
}
return {
count,
start,
state
}
}
setup(props, context) {
const obj = reacttive({
data1: '123',
data2: {}
})
const state = reactive({count: 0}) // 创建数据
// 直接使用倒计时逻辑
const {count, start, state} = useCountdown(10)
const onClick = () => {
start()
}
return {
...torefs(obj)// 把obj转为响应式的ref,不然template里获取不到,
state,
count,
onClick,
state
}
}
}
watchEffect() 监听props
import { Reactive } from 'vue'
export default {
props: ['val'], //这里获取props和 2.0 一样,也可以用对象形式
setup(props, context) {
watchEffect(() => { //首次和props改变才会执行这里面的代码
console.log(props.val)
})
}
}
ref
ref()函数用来根据给定值创建一个响应式的数据对象,ref()函数的调用返回值是一个对象,这个对象上只包含一个value属性。
ref函数只能监听简单类型的变化,不能监听复杂类型的变化
导入相关ref相关函数
import { ref } from '@vue/composition-api'
创建响应式对象
setup() {
const count = ref(0)
return {
count,
name: ref('vue')
}
}
在template中访问响应式数据
{{count}}--- {{name}}
isRef
isRef的使用
,isRef()函数主要用来判断某个值是否为ref()创建出来的对象;应用场景:当需要展开某个可能为ref()创建出来的值得时候,例如:
import { isRef } from '@vue/composition-api'
const fooData = isRef(foo) ? foo.value : foo
toRefs
toRefs的使用,toRefs()函数可以将reactive()创建出来的响应式对象,转为普通对象,只不过这个对象上的属性节点都是以ref()类型的像是数据, 最常见应用场景
import { toRefs, reactive } from '@vue/composition-api'
setup() {
// 定义响应式数据对象
const state = reactive({
count: 0
})
// 定义页面上可用的事件处理函数
const increment = () => {
state.count++
}
// 在 setup 中返回一个对象供页面使用
// 这个对象中可以包含响应式的数据,也可以包含事件处理函数
return {
// 将 state 上的每个属性,都转化为 ref 形式的响应式数据
...toRefs(state),
// 自增的事件处理函数
increment
}
}
在template中就直接可以使用count属性和相对应的increment方法了,如果没有使用roRefs直接返回state那么就得通过state.xx来访问数据
当前的count值为:{{count}}
shallowReactive
听这个API的名称就知道,这是一个浅层的 reactive,难道意思就是原本的 reactive 是深层的呗,没错,这是一个用于性能优化的API
其实将 obj 作为参数传递给 reactive 生成响应式数据对象时,若 obj 的层级不止一层,那么会将每一层都用 Proxy 包装一次,我们来验证一下
import {reactive} from 'vue'
export default {
setup() {
const obj = {
a: 1,
first: {
b: 2,
second: {
c: 3
}
}
}
const state = reactive(obj)
console.log(state)
console.log(state.first)
console.log(state.first.second)
}
}
来看一下打印结果:
设想一下如果一个对象层级比较深,那么每一层都用 Proxy 包装后,对于性能是非常不友好的
接下来我们再来看看 shallowReactive
import {shallowReactive} from 'vue'
export default {
setup() {
const obj = {
a: 1,
first: {
b: 2,
second: {
c: 3
}
}
}
const state = shallowReactive(obj)
console.log(state)
console.log(state.first)
console.log(state.first.second)
}
}
来看一下打印结果:
结果非常的明了了,只有第一层被 Proxy 处理了,也就是说只有修改第一层的值时,才会响应式更新,代码如下:
{{ state.a }}
{{ state.first.b }}
{{ state.first.second.c }}
首先看一下被 shallowRef 包装过后是怎样的结构
我们先点击了第二个按钮,发现数据确实被改变了,但是视图并没随之更新;
于是点击了第一个按钮,即将整个 .value 重新赋值了,视图就立马更新了
这么一看,未免也太过麻烦了,改个数据还要重新赋值,不要担心,此时我们可以用到另一个API,叫做 triggerRef ,调用它就可以立马更新视图,其接收一个参数 state ,即需要更新的 ref 对象
我们来使用一下
triggerRef
{{ state.a }}
{{ state.first.b }}
{{ state.first.second.c }}
我们来看一下具体过程
可以看到,我们没有给 .value 重新赋值,只是在修改值后,调用了 triggerRef 就实现了视图的更新
toRaw
toRaw 方法是用于获取 ref 或 reactive 对象的原始数据的
先来看一段代码
{{ state.name }}
{{ state.age }}
来看看具体过程
我们改变了 reactive 对象中的数据,于是看到原始数据 obj 和被 reactive 包装过的对象的值都发生了变化,由此我们可以看出,这两者是一个引用关系
那么此时我们就想了,那如果直接改变原始数据 obj 的值,会怎么样呢?答案是: reactive 的值也会跟着改变,但是视图不会更新
由此可见,当我们想修改数据,但不想让视图更新时,可以选择直接修改原始数据上的值,因此需要先获取到原始数据,我们可以使用 Vue3 提供的 toRaw 方法
toRaw 接收一个参数,即 ref 对象或 reactive 对象
上述代码就证明了 toRaw 方法从 reactive 对象中获取到的是原始数据,因此我们就可以很方便的通过修改原始数据的值而不更新视图来做一些性能优化了
注意: 补充一句,当 toRaw 方法接收的参数是 ref 对象时,需要加上 .value 才能获取到原始数据对象
markRaw
markRaw 方法可以将原始数据标记为非响应式的,即使用 ref 或 reactive 将其包装,仍无法实现数据响应式,其接收一个参数,即原始数据,并返回被标记后的数据
我们来看一下代码
{{ state.name }}
{{ state.age }}
我们来看一下在被 markRaw 方法处理过后的数据是否还能被 reactive 包装成响应式数据
从图中可以看到,即使我们修改了值也不会更新视图了,即没有实现数据响应式
provide && inject
与 Vue2中的 provide 和 inject 作用相同,只不过在Vue3中需要手动从 vue 中导入
这里简单说明一下这两个方法的作用:
provide :向子组件以及子孙组件传递数据。接收两个参数,第一个参数是 key,即数据的名称;第二个参数为 value,即数据的值
inject :接收父组件或祖先组件传递过来的数据。接收一个参数 key,即父组件或祖先组件传递的数据名称
假设这有三个组件,分别是 A.vue 、B.vue 、C.vue,其中 B.vue 是 A.vue 的子组件,C.vue 是 B.vue 的子组件
// A.vue
// B.vue
// C.vue
获取标签元素
最后再补充一个 ref 另外的作用,那就是可以获取到标签元素或组件
在Vue2中,我们获取元素都是通过给元素一个 ref 属性,然后通过 this.$refs.xx 来访问的,但这在Vue3中已经不再适用了
接下来看看Vue3中是如何获取元素的吧
div元素
获取元素的操作一共分为以下几个步骤:
先给目标元素的 ref 属性设置一个值,假设为 el
然后在 setup 函数中调用 ref 函数,值为 null,并赋值给变量 el,这里要注意,该变量名必须与我们给元素设置的 ref 属性名相同
把对元素的引用变量 el 返回(return)出去
补充:设置的元素引用变量只有在组件挂载后才能访问到,因此在挂载前对元素进行操作都是无效的
接收props数据
在props中定义当前组件允许外界传递过来的参数名称:
props: {
name: String
}
通过setup函数的第一个形参,接收 props 数据:
setup(props) {
console.log(props.name)
}
context形参
setup函数的第二个形参是一个上下文对象,就是vue2.x中的this,在vue 3.x中,它们的访问方式如下
setup(props, context) {
context.slots
context.emit
context.refs
}
watchEffect
在追踪其依赖时立即运行一个函数,并在依赖发生变化时重新运行
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> logs 0
setTimeout(() => {
count.value++
// -> logs 1
}, 100)
取消观察
接口会返回一个函数,该函数用以取消观察。
const stop = watchEffect(() => {
/* ... */
})
// later
stop() // 取消之后对应的watchEffect不会再执行
清除副作用(side effect)
为什么需要清除副作用?有这样一种场景,在watch中执行异步操作时,在异步操作还没有执行完成,此时第二次watch被触发,这个时候需要清除掉上一次异步操作。
watch(onInvalidate => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id has changed or watcher is stopped.
// invalidate previously pending async operation
token.cancel()
})
})
watch提供了一个onInvalidate的副作用清除函数,该函数接收一个函数,在该函数中进行副作用清除。在以下情况中调用:
watch的callback即将被第二次执行时。
watch被停止时,即组件被卸载之后
接口还支持配置一些选项以改变默认行为,配置选项可通过watch的最后一个参数传入。
flush
当同一个tick中发生许多状态突变时,Vue会缓冲观察者回调并异步刷新它们,以避免不必要的重复调用。
默认的行为是:当调用观察者回调时,组件状态和DOM状态已经同步。
这种行为可以通过flush来进行配置,flush有三个值,分别是post(默认)、pre与sync。sync表示在状态更新时同步调用,pre则表示在组件更新之前调用。
watchEffect(
() => {
/* ... */
},
{
flush: 'sync'
}
)
onTrack与onTrigger
用于调试:
onTrack:在reactive属性或ref被追踪为依赖时调用。
onTrigger: 在watcher的回调因依赖改变而触发时调用。
watchEffect(
() => {
/* side effect */
},
{
onTrigger(e) {
debugger
}
}
)
onTrack与onTrigger仅适用于开发模式。
mixin
import {ref} from 'vue'
export const useMyMinxin = {
const name = ref('qimukakax')
const getToken = () => {
const params = 'qimukakax'
getToken()
}
}
将对象混入当前的模版
components
readonly
readonly函数接收一个对象(普通对象或者reactive对象)或者ref,并返回一个只读的原始参数对象代理,在参数对象改变时,返回的代理对象也会相应改变。如果传入的是reactive或ref响应对象,那么返回的对象也是响应的。
简而言之就是给传入的对象新建一个只读的副本。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 原始对象的变化会出发副本的watch
console.log(copy.count)
})
// 原始对象改变,副本的值也会改变
original.count++
// 副本不可更改
copy.count++ // warning!
watch监听器
监听单个数据
import { Reactive } from 'vue'
export default {
setup(props, context) {
let count1 = ref(0)
let state = reactive({
count2: 0
)
//监听reactive类型
watch(
() => state.count2, //这里是你要监听的数据
(count, preCount) => { //count新值, preCount旧值
console.log('') //这里是监听数据变化后执行的函数
}, {lazy: false}//在第一次创建不监听)
//监听ref类型
watch(count1,(count, preCount) => { //count新值, preCount旧值
console.log('') //这里是监听数据变化后执行的函数
})
return {
count
...toRefs(state)
}
}
}
监听多个数据
import { setup, Reactive } from 'vue'
export default {
setup(props, context) {
let count1 = ref(0)
let name1 = ref(0)
let state = reactive({
count2: 0,
name2: 'yangy'
)
//监听多个reactive类型
watch(
[() => state.count2, () => state.name2]
([count, name], [preCount, preName]) => { //count新值, preCount旧值
console.log('') //这里是监听数据变化后执行的函数
},
{
lazy: false
})//在第一次创建不监听
//监听ref类型
watch(
[count2, name2]
([count, name], [preCount, preName]) => { //count新值, preCount旧值
console.log('') //这里是监听数据变化后执行的函数
}, {lazy: false}//在第一次创建不监听)
return {
count,
...toRefs(state)
}
}
}
具体使用
使用前导入 import { watch } from '@vue/composition-api'
const count = ref(0)
// 定义watch只要count的值变化,就会触发watch的回调
// watch 会在创建的时候自动调用一次
watch(() => console.log(count.value))
setTimeout(() => {
count.value++
}, 1000)
监听指定数据
// 定义reactive数据源
const state = reactive({ count: 0 })
// 监视 state.count 这个数据节点的变化
watch(() => state.count, (now, prev) => {
console.log(now, prev)
})
/ 定义ref数据源
const count = ref(0)
// 监视count这个数据的变化
watch(count, (now, prev) => {
console.log(now, prev)
})
computed
计算属性 可创建只读,和可读可写两种
computed() 用来创建计算属性,computed() 函数的返回值是一个 ref 的实例。使用 computed 之前需要按需导入:
import { computed } from '@vue/composition-api'
创建只读的计算属性,在调用computed()函数的时候,传入一个function函数,可以得到一个只读的计算属性。
// 创建一个响应式数据
const count = ref(1)
// 根据count的值创建一个响应式的计算属性, 它会根据ref自动计算并且返回一个新的ref
const computedCount = computed(() => count.value + 1 )
console.log(computedCount.value) // 打印 2
computedCount.value++ //报错
创建可读可写的计算属性
在调用computed()函数的时候传入一个包含get和set的对象,就可以得到一个可读可写的计算属性了。
// 创建一个响应式数据
const count = ref(1)
// 根据count的值创建一个响应式的计算属性, 它会根据ref自动计算并且返回一个新的ref
const computedCount = computed({
get: () => count.value + 1,
set: (val) => count.value = val - 1
} )
computedCount.count = 6
console.log(count.value) // 打印 5
import { setup, Reactive } from 'vue'
export default {
setup(props, context) {
let count = ref(0)
setup () {
const count = ref(0)
const addCount = computed(() => count.value + 1) //只读 ,count.value变化后执行加1
const addCount2 = computed({
get:() => count.value + 1,
set: (value) => count.value = value
})
// addCount2.value = 10 //赋值方法
return {
count,
addCount,
addCount2
}
}
}
}
import { setup } from 'vue'
export default {
setup(props, context) {
//这里的props大家应该知道是啥吧,父组件传的值
// context是什么?
//在setup()里我们不能用this
//所以vue2.0里的 this.$emit, this.$psrent, this.$refs在这里都不能用了。
//context就是对这些参数的集合
//context.attrs
//context.slots
//context.parent 相当于2.0里 this.$psrent
//context.root 相当于2.0里 this
//context.emit 相当于2.0里 this.$emit
//context.refs 相当于2.0里 this.$refs
...
}
}
provide / inject
provide 和 inject 启用依赖注入。只有在使用当前活动实例的 setup() 期间才能调用这两者。
// 父组件
setup() {
const globalData = reactive({
name: 'alex.cheng'
})
provide('globalData', globalData)
}
// 子/孙 组件
setup() {
const globalData = inject('globalData')
}
注意: 可以在 setup 中获取,也可以在生命周期(onMounted/onBeforeMount)中获取。但是不能在方法中获取,比如点击事件在方法中打印,会是 undefined,并且Vue会给出警告。
function showInject () {
// 获取顶层组件的数据
const globalObj = inject('globalObj')
console.log(globalObj)
}
defineComponent
顾名思义,这是一个定义组件的方法,传递一个包含组件选项的对象
import { defineComponent, h } from 'vue'
const DefineComp = defineComponent({
data () {
return {
count: 11
}
},
render () {
return h(
'h1',
{
class: 'define-comp'
},
`${this.count}`
)
}
})
或者是一个setup函数(函数名将作为组件名来使用)
import { defineComponent, ref } from 'vue'
const HelloWorld = defineComponent(function HelloWorld() {
const count = ref(0)
return { count }
})
getCurrentInstance
getCurrentInstance 支持访问内部组件实例,用于高阶用法或库的开发。只能在 setup 或生命周期钩子中调用。
注意: 如需在 setup 或生命周期钩子外使用,请先在 setup 中调用 getCurrentInstance() 获取该实例然后再使用。
const MyComponent = {
setup() {
const internalInstance = getCurrentInstance() // works
const id = useComponentId() // works
const handleClick = () => {
getCurrentInstance() // doesn't work
useComponentId() // doesn't work
internalInstance // works
}
onMounted(() => {
getCurrentInstance() // works
})
return () =>
h(
'button',
{
onClick: handleClick
},
`uid: ${id}`
)
}
}
// 在组合式函数中调用也可以正常执行
function useComponentId() {
return getCurrentInstance().uid
}
LifeCycle Hooks(生命周期)
在新版中的生命周期需要按需导入,并且只能写setup()函数中。
使用onBeforeMount, onMounted, updated相关生命周期,使用前导入相关api方法
相关每个生命周期方法都是传入一个function函数。
vue2.x与新版Composition API之间的映射关系
beforeCreate -> setup
created -> setup
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
provide & inject(数据共享)
provide()和 inject()可以实现嵌套组件之间的数据传递。这个两个函数只能在setup()函数中使用。父级组件中使用provide()函数可以使数据向下传递,子组件中使用inject()接收上层传过来的数据。
实现代码:
根组件(父组件)parent.vue
子组件1 ChildOne.vue
{{globalName}}
子组件2 ChildTwo.vue
{{globalName}}
provide函数被共享的数据可以使ref和reactive定义的响应式数据,用法类似
template refs(元素组件)
我们知道在vue2.x中获取页面元素的DOM可以通过ref写在页面元素上然后在js中通过$refs.x来拿取当前元素的DOM元素信息,操作DOM,在composition-api中我们通过提供的ref方法传入一个null并且定义与页面元素上ref相对应。
代码实现:
Hello Word
Suspense 组件
在开始介绍 Vue 的 Suspense 组件之前,我们有必要先了解一下 React 的 Suspense 组件,因为他们的功能类似。
React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise,该 Promise 需要 resolve 一个 default export 的 React 组件。
import React, { Suspense } from 'react';
const myComponent = React.lazy(() => import('./Component'));
function MyComponent() {
return (
Loading... }>