渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合
VS
Vue2.x 主要改进tree-shaking
机制typeScript
支持composition-api
: 逻辑重用
2.6.12
版本,预计还会推出2.7.x
版本,当大家平滑过渡Vue CLi v4.5
以上yarn global add @vue/cli
# OR
npm i -g @vue/cli
# 检测当前版本号
vue --version // @vue/cli 4.5.6
vue create 项目名 // 选择 3.x 版本 其余选项和2.x一致
vue upgrade --next // 所有相关插件更新到最新版本
# 启动开发服务器
yarn serve
# 打包
yarn build
vue是一个web开发构建工具,基于原生ES Module
的能力加载模块,速度非常之快【大型项目webpack构建速度慢,有时候改一个东西要等好几秒,vite实现秒开】
# 创建项目
npm init vite-app 项目名
# 进入项目 安装依赖
npm i
# 启动项目
npm run dev
# 创建项目
yarn create vite-app 项目名
# 进入项目 安装依赖
yarn
# 启动项目
yarn dev
src
详解
# main.js
import { createApp } from 'vue' // 引入vue创建整个应用对象的方法 createApp
import App from './App.vue' // 引入根组件
createApp(App).mount('#app') // 把根组件传入 创建应用对象 挂载public/index.html 中id为app的div容器
// App.vue
# 和2.x对比 template可以写多个根节点 没有一个根节点的限制 代码提示工具还没有更新
<template>
<h1>欢迎使用Vue3.x</h1>
<div>我是内容</div>
</template>
# JS代码
<script>
export default {
}
</script>
# CSS样式
<style lang="less" scoped>
</style>
setup
函数是一个新的组件选项。作为在组件内使用 Composition API 【组合式API】的入口点。
setup
选项的写法是一个函数,和 data
选项相同。
创建组件实例,然后初始化 props,紧接着就好调用 setup
函数。从生命周期钩子函数的视角来看,它会在beforeCreate
之前就被调用。
export default {
name: "App",
setup() {
console.log("1.执行了setup..."); // 执行时机比较早 在beforeCreate之前
},
beforeCreate() {
console.log("2.执行beforeCreate..");
},
created() {
console.log("3.执行created..", this.a);
},
};
如果 setup
返回一个对象,则对象的属性会被组件模板的渲染上下文,可以直接在模板中使用。
<template>
<div>{{ count }} {{ obj.name }}</div>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
setup() {
const count = ref(0)
const obj = reactive({ name: '小貂蝉' })
// 暴露给模板
return {
count,
obj,
}
},
}
</script>
注意 setup
返回的ref在模板中会自动解开,可以直接使用值,不需要 .value
。
setup
函数接收 props 作为其第一个参数:
export default {
props: { // 外部传入参数
name: String,
},
setup(props) {
console.log("接收到的参数:", props.name);
},
};
</script>
注意 props
对象是响应式的,watchEffect
或 watch
会观察和响应 props 的更新:
export default {
props: {
name: String,
},
setup(props) {
// 当父组件的props传入的参数更新后,子组件props同步更新
watchEffect(() => {
console.log("接收到的参数:", props.name);
});
},
};
</script>
注意: 不要结构 props
对象,会失去响应性。
export default {
props: {
name: String,
},
setup({ name }) { // 解构了props props就不再是响应式数据了
watchEffect(() => {
console.log("接收到的参数:", name);
});
},
};
</script>
第二个参数提供了一个上下文对象,从原来2.x中的 this
选择地暴露一些属性
setup(props, context) {
console.log(context); // 输出结果如下
/*
attrs: { ... }
emit: { ... }
slots: { ... }
*/
},
this
在 setup
中不可用。
setup(props, context) {
console.log(this); // undefined
}
接收一个普通对象然后返回该普通对象的响应式代理。等同于 2.x 的 Vue.observable()
const obj = reactive({ name: '小貂蝉' }) // 该对象数据变为响应式数据
响应式转换是“深层次的”,会影响到对象内部所有嵌套属性。基于ES6的 Proxy 实现,返回的代理对象 不等于 原始对象。
// 之后对对象添加属性 删除属性 都会响应式更新dom
const user = reactive({
prop: {
name: "赵子龙",
age: 19,
},
compony: {
name: "德国",
departMent: "H5",
},
});
接收一个参数值并返回一个响应式且可改变的 ref
对象,ref
对象拥有一个指向内部值的属性 .value
setup() {
const count = ref(0) // count可以让一个数字变成响应式的ref对象
console.log( count.value ) // 0 响应ref对象,内部有一个value值
function add() {
count.value++
}
return {
count, // 返回的count直接使用在template模板中,就是响应式的数据
add // 点击事件触发add count发生变化 dom自动渲染更新
}
}
注意:
当 ref 响应对象 count
在 setup
中返回时,在 template
中渲染,不需要写 .value
<template>
<div>{{ count }} </div>
</template>
<script>
export default {
setup() {
return {
count:ref(0)
}
}
}
</script>
当 ref 作为一个 reactive 对象的属性被修改或者访问时,也不需要写 .value
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count += 1;
console.log(state.count) // 1
如果将一个新的 ref
赋值给现在的响应对象中的 ref
, 响应对象中的 ref
将会被替换掉
// 注意:如果将一个新的 ref 分配给现有的 ref,将替换旧的 ref
const count = ref(1);
console.log("原来的ref->", count.value);
const state = reactive({
count,
});
console.log("state.count->", state.count);
// 使用一个新的ref响应式对象 替换原来旧的ref响应式对象
const otherCount = ref(2);
state.count = otherCount;
console.log("替换后的->", state, state.count);
console.log("原来的->", count);
当 ref
嵌套在 reactive
响应对象 Object
中时,才不需要 .value
, 如果从 Array
或者 Map
等原生集合类中访问 ref
, 都是需要写 .value
的。
const arr = reactive([ref(0)]);
console.log(arr[0].value); // 0 需要写 .value 才能访问
const map = reactive(new Map([["foo", ref(100)]]));
console.log(map.get("foo").value); // 100 需要写 .value 才能访问
传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。
const count = ref(1);
const plusOne = computed(() => count.value + 1); // 计算属性
console.log(plusOne.value); // 2
plusOne.value += 1;
console.log(plusOne.value); // 2 默认只能获取 不能修改 修改没有效果
或者传入一个 拥有 get
和 set
的函数对象,创建一个可以手动修改的计算状态
const count = ref(1);
const plusOne = computed({
get: () => count.value,
set: (val) => {
count.value = val;
},
});
console.log(plusOne.value); // 1 获取值 通过get
plusOne.value = 2;
console.log(plusOne.value); // 2 设置值 通过set
传入一个对象(响应式或普通)或 ref,返回一个原始对象的__只读__代理,一个只读代理是“深层的”,对象内部任何嵌套的属性也是只读的。
const original = reactive({ count: 0 });
const copy = readonly(original);
watchEffect(() => {
// 依赖追踪 [函数立即执行 依赖的数据发生变化 也会重新执行]
console.log(copy.count); //0 10 立即执行了一次 数据变化后再执行一次
});
original.count += 10;
console.log(copy.count); // 10
copy.count++; // reactivity.esm-bundler.js?a1e9:297 Set operation on key "count" failed: target is readonly.
console.log(copy.count); // 10 copy是一个只读的代理对象 不能修改
立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。
const count = ref(0);
watchEffect(() => console.log(count.value)); // 0 1
setTimeout(() => {
count.value++; // 依赖的数据count改变 会重新执行 watchEffect中的回调函数
}, 100);
停止侦听
当 watchEffect
在组件的 setup()
函数或生命周期钩子函数被调用时,侦听器会被链接到该组件的生命周期,并在该组件卸载时自动停止。
在一些情况下,也可以显示调用返回值以停止侦听:
// 停止侦听
const count = ref(0);
const stop = watchEffect(() => {
console.log("count发生了变化:", count.value);
});
setInterval(() => {
if (count.value === 5) stop(); // 如果等于5 停止侦听 watchEffect回调函数的执行
count.value++;
}, 1000);
清除副作用
有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除(即完成之前状态已经改变 了)。所以侦听副作用传入的函数可以接收一个 onInvalidate
函数作为参数,用来注册清理失效时候的回调。当一下情况发生时,这个__失效回调__ 会被触发。
setup
或 生命周期钩子函数中使用了 watchEffect
, 则在卸组件时) const id = ref(0);
function preformAsyncOperation() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("123456");
}, 1000);
});
}
watchEffect((onInvalidate) => {
const token = preformAsyncOperation(id.value);
onInvalidate(() => {
// id 改变时 或 停止侦听时
// 取消之前的异步操作
console.log("执行");
});
});
副作用刷新的时机
Vue的响应式系统会缓存副作用函数,并且异步的刷新它们,这样可以避免同一个tick中多个状态改变导致的不必要的重复调用。在核心的具体实现中,组件的更新函数也是一个被侦听的副作用。当一个用户定义的副作用函数进入队列时,会在所有的组件更新后执行。
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
setup() {
const count = ref(0)
watchEffect(() => {
console.log(count.value)
})
return {
count,
}
},
}
</script>
注意:
count
会在初始运行时同步打印出来count
时,将在组件__更新后__ 执行副作用。mounted
之前执行的。因此,如果你希望在编写副作用函数时访问 DOM( 或 模板ref ), 请在 onMounted
钩子中执行。onMounted(() => {
watchEffect(() => {
// 可以访问DOM 或者 template refs
})
})
// 如:
onMounted(() => {
console.log("生命周期挂载后...");
watchEffect(() => {
console.log(count.value);
console.log(document.getElementById("box")); // 可以访问到DOM 放到onMounted外部获取到null
});
});
如果副作用函数需要同步或在组件更新之前运行,可以传递一个拥有 flush
属性的对象作为选项 ( 选项 post
);
// 同步运行
wetchEffect(
() => {
/* ... */
},
{
flush: 'sync'
}
)
// 组件更新前执行
watchEffect(
() => {
/* ... */
},
{
flush: 'pre'
}
)
watch
API完全等效于2.x this.$watch
( 以及 watch
中相应的选项 )。 watch
需要侦听特定的数据源,并在回调中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才会执行回调。
对比 watchEffect
, watch允许我们:
侦听单个数据源
侦听器的数据源可以使一个返回getter的函数,也可以说ref:
// 侦听一个getter
const state = reactive({ count: 0 });
watch(
() => state.count,
(count, prevCount) => {
console.log("count->", count, "preCount->", prevCount); // count-> 1 preCount-> 0
}
);
state.count++;
// 直接侦听一个ref
const count = ref(10);
watch(count, (count, prevCount) => {
console.log("count->", count, "preCount->", prevCount); // count-> 11 preCount-> 10
});
count.value++;
watcher
也可以侦听多个数据源watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
// foo bar 为当前的值 【新的值】
// prevFoo prevBar 为之前的值 【旧的值】
})
可以直接导入 onXXX
的函数来注册生命周期钩子函数:
import { onMounted, onUpdated, onUnmounted } from 'vue'
onMounted(() => {
console.log("onMounted");
});
onUpdated(() => {
console.log("onUpdated");
});
onUnmounted(() => {
console.log("onUnmounted");
});
这些生命周期钩子函数只能在 setup
期间同步使用。
组件实例上下文也是生命周期钩子函数同步执行期间设置的,因此,在卸载组件时,在生命周期钩子内部同步创建的侦听器和计算状态也将同步删除。
与2.x版本生命周期相对于的API
beforeCreate
- > setup
created
- > setup
beforeMount
-> onBeforeMount
mounted
- > onMounted
beforeUpdate
- > onBeforeUpdate
updated
- > onUpdated
beforeDestroy
- > onBeforeUnmount
destroyed
- > onUnmounted
errorCaptured
- > onErrorCaptured
新增的钩子函数
除了和2.x生命周期相同的周期之外,组合式API还提供了一下调试钩子函数
onRenderTracked
onRenderTriggered
两个钩子函数都接收一个 DebuggerEvent
, 与 watchEffect
参数选项中的 onTrack
和 onTrigger
类似
export default {
onRenderTrigger(e) {
debugger; // 检查哪个依赖数据导致组件重新渲染
}
}
Teleport提供了一种的方法,使我们可以控制要在DOM中哪个父对象下呈现HTML,而不必求助于全局状态或将其拆分为两个部分.
Tepeport可以让我们的组件在别的dom容器中呈现,而组件内部的状态不会发生任何变化。
# index.html
<div id="app"></div>
<!-- 传送目标 -->
<div id="teleport-target"></div>
<template>
<div class="app">
<h1>teleport demo</h1>
# 使用模态框组件
<Model></Model>
</div>
</template>
<script>
import Model from './components/model.vue' // 引入模态框组件
export default {
components: {
Model // 注册模态框组件
}
}
</script>
<style lang="less" scoped>
</style>
<template>
<div>
<button @click="showModel" class="btn">查看详情</button>
# 最终 teleport内部的内容 渲染到index.html中的容器 div#teleport-target中 实现传送
<!-- to 属性就是最终渲染的目标容器位置】 -->
<teleport to="#teleport-target">
<div v-show="visible" class="model">
<div class="model-wrapper">
<div class="content">内容</div>
<button @click="hideModel" >取消</button>
</div>
</div>
</teleport>
</div>
</template>
<script>
import { ref } from 'vue'; # 引入ref
export default {
setup() {
const visible = ref(false); # 定义响应式数据
const showModel = () => { # 显示模态框
visible.value = true;
}
const hideModel = () => { # 隐藏模态框
visible.value = false
}
# 暴露给组件
return {
visible,
showModel,
hideModel
}
}
}
</script>
<style lang="less" scoped>
# 模态框组件样式
.model {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0, 0.5);
z-index: 10;
.model-wrapper {
display: flex;
flex-direction: column;
padding: 20px;
width: 400px;
height: 300px;
background-color: #fff;
border-radius: 4px;
.content {
flex:1;
}
button {
height: 30px;
background-color: #fff;
border: 1px solid rgba(0,0,0,0.1);
box-shadow: 2px 2px 2px #ccc;
outline: none;
}
}
}
</style>
vue3.x中,组件支持一个template中有多个根节点,即Fragments
文档片段
<template>
<div>
<header>...</header>
<main>...</main>
<footer>...</footer>
</div>
</template>
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
provide()
和 inject()
可以实现嵌套组件之间的数据传递。这两个函数只能在 setup()
函数中使用。父级组件中使用 provide()
函数向下传递数据;子级组件中使用 inject()
获取上层传递过来的数据,无论层级多少,都可以获取父组件传递过来的数据。
# 根组件App.vue
<template>
<div id="app">
<h1>我是根组件 App.vue</h1>
<my-component1></my-component1>
</div>
</template>
<script>
import MyComponent1 from './components/my-component1.vue' // 引入组件1
import { provide } from 'vue' // 引入 provide
export default {
name: 'app',
setup() {
provide('initColor', 'red') // 给子组件传递数据
},
components: {
MyComponent1 // 注册组件
}
}
</script>
# 子组件1 【第一个层级】
<template>
<div>
// 使用父组件传递过来的数据
<h3 :style="{'background-color': color}">组件1</h3>
<hr />
<my-component2></my-component2>
</div>
</template>
<script>
import MyComponent2 from './my-component2.vue' // 引入子组件2
import { inject } from 'vue' // 引入inject
export default {
setup() {
const color = inject('initColor') // 获取父组件的数据
return {
color
}
},
components: {
MyComponent2 // 注册组件
}
}
</script>
# 子组件2
<template>
<div>
// 使用父组件传递过来的数据
<h5 :style="{backgroundColor: color}">组件2</h5>
</div>
</template>
<script>
// 1. 按需导入 inject
import { inject } from 'vue'
export default {
setup() {
const color = inject('initColor') // 获取父组件传递过来的数据
return {
color
}
}
}
</script>
<template>
<div id="app">
<h1>App 根组件</h1>
# 点击按钮 切换不同的颜色
<button @click="color='red'">红色</button>
<button @click="color='green'">绿色</button>
<button @click="color='pink'">粉色</button>
<hr />
# 使用组件1
<my-component1></my-component1>
</div>
</template>
<script>
# 引入组件1
import MyComponent1 from './components/my-component1.vue'
import { provide, ref } from 'vue' # 引入 provide 和 ref
export default {
name: 'app',
setup() {
const color = ref('red') # 响应式数据 color
provide('initColor', color) # 传递数据给子组件
return {
color
}
},
# 注册组件
components: {
MyComponent1
}
}
</script>
<template>
<div>
<h3 ref="title">组件1</h3>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const title = ref(null) // 设置响应式数据title
// 生命周期 onMounted 可以操作dom
onMounted(() => {
title.value.style.color = 'red'
})
return {
title
}
}
}
</script>
# App.vue 根组件
<template>
<div>
<h3>App 根组件</h3>
<button @click="getNumber">获取组件1中的count的值</button>
<!-- 组件1 -->
<my-component1 ref="comP1"></my-component1>
</div>
</template>
<script>
import { ref } from 'vue'
import MyComponent1 from './components/my-component1'
export default {
setup() {
const comP1 = ref(null) # 响应式数据comP1
# 获取数据的方法
const getNumber = () => {
console.log(comP1.value.count)
}
return {
comP1,
getNumber
}
},
components: {
MyComponent1
}
}
</script>
# 组件1
<template>
<div>
<h5>组件1 {{count}}</h5>
<button @click="count+=1">+1</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return {
count
}
}
}
</script>