更好的阅读体验请点击 : advanced-front-end
// vue 2.x
<ant-input v-model="inputValue">ant-input>
// vue 3.x
<ant-input v-model:inputValue="inputValue">ant-input>
v-model 指令在 vue 2.x 和 vue 3.0 存在一些差别
只执行一次
优先于 created 执行
接收两个参数:props
、context
props
为组件传参绑定的属性
props
是响应性的值。如果使用 ES6 对其进行结构会破坏响应性结构,可以使用 toRefs
函数在 setup()
中操作
import {
toRefs } from 'vue'
setup(props) {
const {
title } = toRefs(props)
console.log(title.value)
}
爸爸你可以给,但是要不要得看我
另需要注意的是: 在给某个组件传入参数时,例如 v-model:foo="'foo'"
或者 v-bind:bar="'bar'"
, 都需要在子组件的 props 中注册,才能在 setup()
的 props
中获取到。
都说到 props
了,那就继续聊一聊 props 和 v-model 的变化
在 Vue 2.2 版本中添加了 model
Option,用于自定义组件在使用 v-model
时定制 prop
和 event
,但是如果需要在组件上同步绑定多个值,在已使用 v-model
的前提下,就只能使用 v-bind.sync
了, 但是 v-bind.sync
和 v-model
做着同样的事情,所以为什么不把他们合并到一个中呢?所以 Vue 3.0 就丰富了 v-model
语法糖的建设,可以使用
绑定多个值,实现数据的双向绑定。rfcs-0011-v-model-api-change
v-model不携带参数时,编译代码如下:
h(Comp, {
modelValue: foo,
'onUpdate:modelValue': value => (foo = value)
})
绑定了 modelValue
值,生命周期函数 onUpdate
时更新 ‘`v-model’ 绑定的值
v-model不携带参数时,编译代码如下:
h(Comp, {
value: foo,
'onUpdate:value': value => (foo = value)
})
可以绑定了多个 value
值,生命周期函数 onUpdate
时更新 v-model
绑定的值
既然说到了 v-model,v-model
还可以自定义修饰符, 如 v-model.capitalize
,但是这都是基于组件级别的修饰符,不是添加 全局或者类似于 v-model.trim
到处可用的 修饰符。
看完回头思考下,上述说到这么多关于 v-model
的语法糖,对于组件来说,不都是基于 单项数据流 的 值传递 ( props ) 和 **事件传播 ( $emit )**么?
感悟:
框架 --> 基础建筑 ,语法糖 --> 上层建筑,
原生JS --> 基础建筑, 框架 --> 上层建筑
所以,作为初中级前端,应该扎实 原生js 能力,不仅仅是 API 的使用,需要懂得背后的原理,和用于构建上层建筑的架构建设,你看,在 Vue 这样流行的框架上,作者的 JS 能力可不可以说是登峰造极呢?
context
对象包含三个属性 slots、attr、emit
slots: 作用域插槽
attr: 属性
emit: 事件传播函数
与 prop
不同,context 是普通对象,不是响应式的,slots 和 attr 的值会在组件时更新而更新,如果需要监听 slots
、‘attr’ 的更新触发的副作用,建议在 setup()
函数中添加 onUpdated
函数监听副作用
1212
如果setup
返回一个对象,则可以在组件的模板中访问该对象的属性
{
{ readersNumber }} {
{ book.title }}
setup
还可以返回一个渲染函数,该渲染函数可以直接使用在同一作用域中声明的反应状态。
可以把 script
标签中的代码独立到一个 js 文件中,作为一个 函数式组件 应用
// MyBook.vue
setup()
中的 this
不是当前组件实例,实际打印发现为 undefined
, 不建议 setup()
与 Option API 混用,可能会造成混乱。
Options API | Hook inside inside setup |
---|---|
beforeCreate |
Not needed* |
created |
Not needed* |
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeUnmount |
onBeforeUnmount |
unmounted |
onUnmounted |
errorCaptured |
onErrorCaptured |
renderTracked |
onRenderTracked |
renderTriggered |
onRenderTriggered |
Vue.observable()
import {
reactive } from 'vue';
setup() {
const state = reactive({
org: {
name: "W3C",
},
});
return {
state
}
},
需要搞清楚一点的是,reactive API 返回的响应式对象 state,在 setup()
中访问可以直接 state.org.name 访问到值,不需要添加 .value
,在 template 中访问也不用 .vaule
{
{ state }}
{
{ state.org.name }}
.value
ref
作为渲染上下文的属性返回并在模板中进行访问时,它将自动解构为内部值。无需.value
在模板中追加:reactive
方法进行深层响应转换
{
{ obj }}
{
{ count }}
可以看到:
ref()
返回一个 RefImpl 对象
可以看到 ref()
函数传入一个对象,返回值需要用 .value
访问,obj.value
返回的是一个 Proxy 代理响应式对象
const count = ref(0)
const state = reactive({
count,
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
const otherCount = ref(2)
state.count = otherCount // 修改了 count 值指向
console.log(state.count) // 2
console.log(count.value) // 1
Object
中时,ref 才会解套。从 Array
或者 Map
等原生集合类中访问 ref 时,不会自动解套:const arr = reactive([ref(0)])
// 这里需要 .value
console.log(arr[0].value)
const map = reactive(new Map([['foo', ref(0)]]))
// 这里需要 .value
console.log(map.get('foo').value)
ref
时传递泛型参数来覆盖默认推导: 这点我还没看懂const foo = ref<string | number>('foo') // foo 的类型: Ref
foo.value = 123 // 能够通过!
<template>
<h1>{
{
count }}</h1>
</template>
import {
toRefs } from 'vue';
setup() {
const state = reactive({
count: 0
})
}
return {
...toRefs(state)
}
const route = useRoute()
watch(()=> route.path, newValue => {
state.seletctedKeys = [newValue]
})
const seletctedKeys = computed(()=>{
return [route.path]
})
effect、watch、watchEffect 的区别?
// watching a getter
const state = reactive({
count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// directly watching a ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
观察多个数据源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
const stop = watchEffect(() => {
/* ... */
})
// later
stop()
在 rfcs 中就有提到 data()
将不在支持作为一个对象,只能作为一个 function
返回一个对象。虽然在 Vue 2.x 中可以返回一个状态进行状态共享,但是这势必会引发一些问题,而且如果要实现这种状态共享,function 完全可以替代,通过 function
返回对象,v-bind
给子组件就可以实现状态共享,使用 object 会对逻辑造成紊乱,并且需要示例去说明,更加重了学习者的心智负担!所以在 Vue 3.0 中不再支持 object
方式,强行使用编译不会通过,并且给出警告:[Vue warn]: data() should return an object.
Detailed design
在 Vue2.x 的版本中,在 Vue.prototype 定义了全局 API,如下:这会导致 Vue 库的整体体积较大,部署生产时即使未用到的 API ,也会被打包。
import Vue from 'vue'
Vue.nextTick(() => {
})
const obj = Vue.observable({
})
Vue 3.0 在平衡功能和包体积大小做了一定的努力,力图在 Vue 包做到更新并且不限制其功能。Vue 3.0 中使用了 组合式 API,通过 ES Modules 的静态分析的设计,与现代的 捆绑器 webpack 和 rollup 相结合,Tree-shaking 中剔除了那些未在项目使用却导出 ES Module API,如此,用户只会使用到那些 import
的 API.
需要注意的是: 在使用模块打包器构建 ES Module 包时可以 Tree-shaking,在打包 UMD 构建包时还是会全局打包 API 至 Vue 全局变量中.
example:
<body>
<div id="app">
<h1>Move the #content with the portal componenth1>
<teleport to="#endofbody">
<div id="content">
<p>
this will be moved to #endofbody.<br />
Pretend that it's a modal
p>
<Child />
div>
teleport>
div>
<div id="endofbody">div>
<script>
new Vue({
el: "#app",
components: {
Child: {
template: "Placeholder" }
}
});
script>
body>
result:
<div id="app">
div>
<div id="endofbody">
<div id="content">
<p>
this will be moved to #endofbody.<br />
Pretend that it's a modal
p>
<div>Placeholderdiv>
div>
div>
Detailed design
[1] vuejs-rfcs. https://github.com/vuejs/rfcs
[2] composition-api https://composition-api.vuejs.org/zh/
[3] Vue 3.0. https://v3.vuejs.org/api/application-config.html