这是我参与8月更文挑战的第7天,活动详情查看: 8月更文挑战
这是一个趋势~~~~
所以,为了一系列,,,,,不得不学一下。学习新东西,过程还是蛮有意思的,毕竟,吹牛还是用得上的。。。
动机:
类型支持更好
利于 tree-shaking
API简化、一致性:render函数,sync修饰符,指令定义等
复用性:composition api
性能优化:响应式、编译优化
扩展性:自定义渲染器
1、可以先cdn引入,体验下。可参考官方文档 v3.vuejs.org/
<div id="app"> <h1>{{title}}</h1> </div> <script src="https://unpkg.com/vue@next"></script> <script> const { createApp } = Vue // new Vue // 函数式:类型支持ts const app = createApp({ // 统一api,一致性 data(){ return { title: '嘿嘿,vue3!' } } }).mount('#app') </script>
好处:
1、函数式,对类型支持很好。对ts支持很好,对比用options会产生很多this,减少this的使用;
摇树优化,即 Tree Shaking;
通过函数调用,知道程序中用到了哪些依赖,打包的时候把这些用到的函数打包进去。那些在源码中存在,但是却没有调用的代码就像树叶一样摇掉。这样打包体积减小。消灭静态方法。多个实例之间不会相互污染。
2、api简化,一致性
首次挂载做3件事:
1、根组件实例化;
2、初始化根组件;setupComponent函数,类似vue2中 this._init()
3、安装渲染函数(render函数)副作用
(ps: 副作用:如果对一个响应式数据有依赖,数据发生变化,会再次将这个副作用函数执行一遍,即:安装副作用。effect(fn): 添加副作用函数,这样里面的相关的响应式数据如果发生变化,那么fn会再次执行。)
compile 在我们无感知的情况下,做到了按需更新;
所以编译优化,就是代码在打包上线之前进行的优化,vue中把template转化成render函数,期间的源码大家去vue-next里搜baseCompile
const ast = baseParse(template) // 把template解析成ast 也就是一棵树
transform(ast,option) // 优化,比如标记和转化vue的特定语法
return genrate(ast) // 生成的render 函数
vue3.0亮点优势:
Performance : 重写了虚拟Dom的实现、编译模板的优化、更⾼效的组件初始
化。性能更⽐Vue 2.0强
Tree shaking support : 可以将⽆⽤模块“剪辑”,仅打包需要的,vue功能按
需引⼊
Composition API : 组合API,可与现有的 Options API⼀起使⽤,混⼊(mixin)
将不再作为推荐使⽤
Fragment, Teleport, Suspense : “碎⽚”,Teleport即Protal传送⻔,“悬
念”,⽀持async setup()
Better TypeScript support : ⽤TypeScript编写的库,可以享受到⾃动的类
型定义提示,提供类型检查,⾃动补全等功能
Custom Renderer API : 暴露了⾃定义渲染API
Composition API 详解
⼀、组件初始⼊⼝ setup
新的组件选项。作为在组件内使⽤ Composition API 的⼊⼝。
调⽤时机:创建组件实例,然后初始化 props ,紧接着就调⽤setup 函数。在
created 之前执⾏。
<script>
import { ref, reactive } from 'vue'
export default {
setup(props, ctx) {
const count = ref(0)
const object = reactive({ foo: 'bar' })
// 暴露给模板
return {
count,
object
}
}
}
</script>
参数说明
props : 接收props 数据
ctx(或者context):组件上下⽂对象,包含⼀些常⽤的属性,在vue2.x 中
通过this访问的
ctx.attrs、ctx.slots、ctx.parent、ctx.root、ctx.emit、ctx.refs
注:
1、this 在 setup() 中不可⽤
2、props 对象是响应式的, watchEffect 或 watch 会观察和响应 props 的
更新
3、不要解构 props 对象,那样会使其失去响应性。不要在组件内修改props
⼆、响应式系统 API
reactive: 创建响应式数据对象,等同于 2.x 的 Vue.observable()
setup() {
const state = reactive({ message: 'Hello Vue3!!' })
msgReverse = () => {
state.message = state.message.split('').reverse().join('')
}
return {
state,
msgReverse
}
}
响应式转换是“深层的”:会影响对象内部所有嵌套的属性。基于 Proxy 实现。
ref 和 isRef: 创建⼀个响应式的 ref 对象拥有⼀个指向内部值的单⼀属性
.value
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
computed: 创建计算属性
1、传⼊⼀个 getter 函数,返回⼀个默认不可⼿动修改的ref对象
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误!
2、传⼊⼀个拥有 get 和 set 函数的对象,创建⼀个可⼿动修改的计算状态
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
watch 和 watchEffct : 创建数据watch监听
1、watchEffect,如果响应性的属性有变更,就会触发这个函数。
watchEffect(() => console.log(count.value, 'count.value'))
// const stopWatchEffect = watchEffect(() =>
console.log(count.value, 'count.value'))
// stopWatchEffect()
2、watch,等效2.0⾥⾯的this.$watch
a、侦听单个数据源
watch(count, (val, oldVal) => {
console.log(val, 'val')
console.log(oldVal, 'oldVal')
console.log(count.value, 'count.value')
})
// const stop = watch(count, (val, oldVal) => console.log(val,
'val'))
// stop()
b、侦听多个数据源
watch([count, state], ([countVal, msgVal], [countOldVal,
msgOldVal]) => {
console.log(countVal, 'countVal')
console.log(msgVal, 'msgVal')
})
c、两者⽐较
1、watchEffect 是⽴即执⾏,watch 为懒执⾏,仅在侦听的源变更才执⾏
回调
2、watch 可访问侦听状态变化前后的值,更加明确状态的改变
v-modle: 数据双向绑定
三、⽣命周期钩⼦函数
可以直接导⼊ onXXX ⼀类的函数来注册⽣命周期钩⼦
import { onMounted, onUpdated, onUnmounted } from 'vue'
const MyComponent = {
setup() {
onMounted(() => {
console.log('mounted!')
})
onUpdated(() => {
console.log('updated!')
})
onUnmounted(() => {
console.log('unmounted!')
})
}
}
⽣命周期钩⼦函数依赖于内部的全局状态来定位当前组件实例,只能在
setup() 期间同步使⽤。
与 2.x 版本⽣命周期对⽐映射
四、模板 Refs
1. 在 setup()中创建⼀个ref()对象并返回;
2. 在⻚⾯上为元素添加ref属性,并设置属性值与创建的ref对象名称相同;
3. 当前⻚⾯渲染完成后(onMounted),可通过该ref对象获取到⻚⾯中对应的dom
元素。
<template> <div ref="root"></div>
</template> <script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// 在渲染完成后, 这个 div DOM 会被赋值给 root ref 对象
console.log(root.value) // <div/>
})
return {
root,
}
},
}
</script>
五、依赖注⼊ provide 和 inject
在祖先组件中使⽤provide()函数向下传递数据;在后代组件中使⽤inject()函数获取
上层传递传递过来的数据,两个函数只能在 setup()函数中使⽤
// ⽗组件
<template> <div class="hello"> <button @click="colorRef='red'">红</button> <button @click="colorRef='yellow'">⻩</button>
</div>
</template> <script>
import { provide, ref} from 'vue'
export default {
setup(){
// 向后代传数据 不会响应式更新
provide('color','yellow');
// 传递响应式的数据
let colorRef = ref('red')
provide('colorRef',colorRef);
return{
colorRef
}
}
}
</script>
//⼦或孙⼦组件
<template> <div class="grandson"> <h1 :style="{color:color}">孙组件1</h1> <h1 :style="{color:colorRef}">孙组件1</h1>
</div>
</template> <script>
import { inject } from 'vue'
export default {
setup() {
const color = inject('color')
const colorRef = inject('colorRef')
return {
color,
colorRef
}
}
}
</script>
六、公共代码的复⽤(mixin的替代⽅案)
- mixin 的缺陷
1. mixin把公共的逻辑抽取到⼀个独⽴⽂件,当项⽬过于复杂,mixin中的代码和
外部的代码存在命名冲突的时候会被覆盖,相同的⽣命周期函数也会被覆盖,
代码难以维护,容易出现bug。
2. 存在多个mixin 的时候,容易互相影响,来源不清晰,
-
代码示例
// useResizeMin.js import { ref, onMounted, onUnmounted } from 'vue' export default () => { const width = ref(window.innerWidth) // 默认值 const height = ref(window.innerHeight) // 默认值 const onUpdate = () => { width.value = window.innerWidth height.value = window.innerHeight } onMounted(() => { window.addEventListener('resize', onUpdate) }) onUnmounted(() => { window.removeEventListener('resize', onUpdate) }) return { width, height } } // 组件内使⽤ // 这样就很清晰width这些数据是从哪⾥维护的,且不会冲突其变量 const { width, height } = useResizeMin()
附加⼀:插件开发: vue2 很多插件都是向 this 注⼊property, 例如Vue-Router 注
⼊ this.router ,Vuex 注⼊ this.$store 。⽽vue3 不再使
⽤ this ,插件将在内部利⽤ provide 和 inject 并暴露⼀个组合函数。 以vuex为
例,插件内部代码:
const StoreSymbol = Symbol()
export function provideStore(store) {
provide(StoreSymbol, store)
}
export function useStore() {
const store = inject(StoreSymbol)
if (!store) {
// 抛出错误,不提供 store
}
return store
}
// vue-router
function useRouter() {
return vue.inject(routerKey);
}
function useRoute() {
return vue.inject(routeLocationKey);
}
// vuex
function useStore (key) {
if ( key === void 0 ) key = null;
return vue.inject(key !== null ? key : storeKey)
}
开发者在项⽬中使⽤:
// 在根组件中提供 store
const App = {
setup() {
provideStore(store)
},
}
const Child = {
setup() {
const store = useStore()
// 使⽤ store
},
}
附加⼆:Ref 和 Reactive 的选择
1. 区分开普通的基本类型 和 响应式值的引⽤;
2. 读写 ref 的操作⽐普通值更冗余,需要访问 .value ;
3. 通过 reactive 创建的对象不能被解构或展开,会失去响应性;
4. 最佳实践需要在项⽬中不断尝试。
现有vue2 项⽬逐步迁移vue3
-
安装 @vue/composition-api
// 安装npm 包依赖 npm i @vue/composition-api // 在项⽬⼊⼝ main.js 或者 main.ts 引⼊ import CompositionApi from '@vue/composition-api' Vue.use(CompositionApi)
-
setup 中可使⽤的函数与vue3 基本类似
-
vue-router 和 vuex 的使⽤
const { store } = ctx.root const route = computed(() => ctx.root.store.getters.avatar) let name = computed(() => $store.getters.name)