之前用了 vue3 rc版本做了一个 UI 库。
但是正式版 vue3 One Piece 已于9月18日晚已发布。
v3 中文文档和仓库也逐渐完善。
那么下面我们就看看 vue 3 到底做了哪些工作。
Vue2与Vue3的全局配置API变化区别
createApp
Vue2.x创建实例并且挂载DOM上
1 | import Vue from "vue"; |
Vue3新增api===>createApp 创建实例
createApp 会产生一个 app 实例,该实例拥有全局的可配置上下文
1 | import { createApp } from 'vue' |
createApp做了什么
1 | export const createApp = (() => { |
ensureRenderer方法追溯过去底添加patchclass+patchStyle等跟操作DOM相关的方法
ensureRenderer(创建虚拟DOM)一直追溯到createRenderer以及baseCreateRenderer,baseCreateRenderer方法涉及了虚拟DOM的创建更新DIFF算法
之后就是检查时候又mount是否挂载在DOM上
app对象上的方法:config、use、mixin、component、directive、mount、unmount、provide/inject
component
Vue2.x【注册或获取全局组件。注册还会自动使用给定的 id 设置组件的名称】
1 | // 注册组件,传入一个选项对象 (自动调用 Vue.extend) |
Vue3【注册或获取全局组件. 注册还会自动使用给定的 name组件 设置组件的名称】
全局组件
基本vue2写法一致
1 | import { createApp } from 'vue' |
config【app=createApp(App)】
devtools
配置是否允许 vue-devtools 检查代码。
开发版本默认为 true,生产版本默认为 false。
生产版本设为 true 可以启用检查。
1 | - Vue.config.devtools = true |
errorHandler
1 | - Vue.config.errorHandler = function (err, vm, info) { |
指定组件的渲染和观察期间未捕获错误的处理函数。
这个处理函数被调用时,可获取错误信息和 Vue 实例。
warnHandler
1 | - Vue.config.warnHandler = function (msg, vm, trace) { |
为 Vue 的运行时警告赋予一个自定义处理函数。
注意这只会在开发者环境下生效,在生产环境下它会被忽略。
globalProperties 【新增属性】
1 | app.config.globalProperties.foo = 'bar' |
添加可在程序内的任何组件实例中访问的全局属性。
当存在键冲突时,组件属性将优先替代掉Vue2.x的 Vue.prototype属性放到原型上的写法
1 | // Vue2.x |
isCustomElement 【新增属性】
- 替代掉Vue2.x的ignoredElements
1
2
3
4
5
6
7
8- Vue.config.ignoredElements = [
// 用一个 `RegExp` 忽略所有“ion-”开头的元素
// 仅在 2.5+ 支持
/^ion-/
]
// 一些组件以'ion-'开头将会被解析为自定义组件
+ app.config.isCustomElement = tag => tag.startsWith('ion-')
指定一个方法来识别在Vue之外定义的自定义组件(例如,使用Web Component API)。
如果组件符合这个条件,它就不需要本地或全局注册,Vue也不会抛出关于Unknown custom element的警告
注意,这个函数中不需要匹配所有原生HTML和SVG标记—Vue解析器会自动执行此检查
optionMergeStrategies
1 | const app = Vue.createApp({ |
定义自定义选项的合并策略。
合并策略接收在父实例options和子实例options,分别作为第一个和第二个参数。
上下文Vue实例作为第三个参数传递
【自定义选项合并策略】mixin
1 | const app = Vue.createApp({ |
optionMergeStrategies先获取到子实例的
$options
的mixin
而没有父实例【custom第一次改变从undefined
到goodbye
—>打印"goodbye!", undefined
】
父实例的options替换掉子实例的options替换掉子实例的options替换掉子实例的options
【custom第二次从goodbye到hello!—>打印了”hello”, “goodbye!”】
最后在打印app.config.optionMergeStrategies.custom返回的父实例的
$options
无论如何this.options.custom最后会返回合并策略的return的值
【使用场景利用父子组件的options,然后返回计算等操作得到所需要的值】
optionMergeStrategies合并$options变化
performance
1 | - Vue.config.performance=true; |
设置为 true 以在浏览器开发工具的性能/时间线面板中启用对组件初始化、编译、渲染和打补丁的性能追踪。
只适用于开发模式和支持 performance.mark API 的浏览器上。
directive
注册或获取全局指令。
1 | import { createApp } from 'vue' |
el: 指令绑定到的元素。这可以用来直接操作DOM。
binding【包含下列属性的对象】
instance:使用指令的组件的实例
value:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为 2
oldValue:指令绑定的前一个值,仅在 beforeUpdate
和 updated
钩子中可用。无论值是否改变都可用
arg:传给指令的参数,可选。例如 v-my-directive:foo
中,参数为 "foo"
modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为 { foo: true, bar: true }
dir:一个对象,在注册指令时作为参数传递; 举个例子,看下面指令
1 | app.directive('focus', { |
dir就是下面的对象
1 | { |
vnode
编译生成的虚拟节点
prevNode
前一个虚拟节点,仅在beforeUpdate和updated钩子中可用
tips:除了 el 之外,其它参数都应该是只读的,切勿进行修改。
如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行
mixin【基本Vue2.x一致】
- optionMergeStrategies 影响,可看上面
全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。
插件作者可以使用混入,向组件注入自定义的行为。不推荐在应用代码中使用。
mount【类似Vue2.x】
- 在所提供的DOM元素上挂载应用程序实例的根组件
1
2
3
4
5import { createApp } from 'vue'
const app = createApp({})
// 做一些准备
app.mount('#my-app')
provide/inject【Vue2.x一致】
该选项与
inject
一起使用,允许一个祖先组件作为其所有后代的依赖注入器,无论组件层次结构有多深,只要它们位于同一父链中就可以
provide
选项应该是一个对象或返回一个对象的函数。
该对象包含可注入其子孙的property
。
在该对象中你可以使用ES2015 Symbols
作为key
,但是只在原生支持Symbol
和Reflect.ownKeys
的环境下可工作。
如果在组件中两者都只能在当前活动组件实例的 setup() 中调用,详细请看依赖注入部分
1 | import { createApp } from 'vue' |
unmount【新增属性】
在所提供的DOM元素上卸载应用程序实例的根组件
1 | import { createApp } from 'vue' |
use【Vue2.x一致】
安装 Vue.js 插件。
如果插件是一个对象,必须提供 install 方法。
如果插件是一个函数,它会被作为 install 方法。
install 方法调用时,会将 Vue 作为参数传入。
当 install 方法被同一个插件多次调用,插件将只会被安装一次。
setup
setup
函数是一个新的组件选项。作为在组件内使用 Composition API
的入口点
注意
setup
返回的 ref 在模板中会自动解开,不需要写 .value【setup 内部需要.value】
调用时机
创建组件实例,然后初始化
props
,紧接着就调用setup
函数。
从生命周期钩子的视角来看,它会在beforeCreate
钩子之前被调用
如果
setup
返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文
参数
props
作为其第一个参数
注意
props
对象是响应式的,watchEffect
或watch
会观察和响应props
的更新不要解构props
对象,那样会使其失去响应性
1 | export default { |
第二个参数提供了一个上下文对象【从原来 2.x 中 this
选择性地暴露了一些 property(attrs/emit/slots)
】
attrs 和 slots 都是内部组件实例上对应项的代理,可以确保在更新后仍然是最新值。
所以可以解构,无需担心后面访问到过期的值
为什么props作为第一个参数?
组件使用 props 的场景更多,有时候甚至只使用 props
将 props 独立出来作为第一个参数,可以让 TypeScript 对 props 单独做类型推导,不会和上下文中的其他属性相混淆。
这也使得 setup 、 render 和其他使用了 TSX 的函数式组件的签名保持一致
this
在setup()
中不可用。
由于 setup() 在解析 2.x 选项前被调用,setup() 中的 this 将与 2.x 选项中的 this 完全不同。
同时在 setup() 和 2.x 选项中使用 this 时将造成混乱
1 | setup(props, { attrs }) { |
响应式系统 API
reactive
desc: 接收一个普通对象然后返回该普通对象的响应式代理【等同于 2.x 的 Vue.observable()
】
tips:Proxy对象是目标对象的一个代理器,任何对目标对象的操作(实例化,添加/删除/修改属性等等),都必须通过该代理器。
因此我们可以把来自外界的所有操作进行拦截和过滤或者修改等操作
响应式转换是“深层的”:会影响对象内部所有嵌套的属性。基于 ES2015 的 Proxy 实现,返回的代理对象不等于原始对象。建议仅使用代理对象而避免依赖原始对象
reactive 类的 api 主要提供了将复杂类型的数据处理成响应式数据的能力,其实这个复杂类型是要在object array map set weakmap weakset 这五种之中【如下源码,他会判断是否是五类以及是否被冻结】
1 | const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet]) |
因为是组合函数【对象】,所以必须始终保持对这个所返回对象的引用以保持响应性【不能解构该对象或者展开】
例如const { x, y } = useMousePosition()
或者return { ...useMousePosition() }
1 | function useMousePosition() { |
toRefs API 用来提供解决此约束的办法——它将响应式对象的每个 property 都转成了相应的 ref【把对象转成了ref】。
1 | function useMousePosition() { |
ref
接受一个参数值并返回一个响应式且可改变的 ref 对象。
ref 对象拥有一个指向内部值的单一属性 .value
1 | const count = ref(0) |
如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换
1 | const convert = <T extends unknown>(val: T): T => isObject(val) ? reactive(val) :val |
陷阱
setup 中return返回会自动解套【在模板中不需要.value】
1 | <template> |
ref 作为 reactive 对象的 property 被访问或修改时,也将自动解套 .value
1 | const count = ref(0) |
注意如果将一个新的 ref 分配给现有的 ref, 将替换旧的 ref
1 | /*创建一个新的ref*/ |
嵌套在 reactive Object 中时,ref 才会解套。
从 Array 或者 Map 等原生集合类中访问 ref 时,不会自动解套【自由数据类型是Object才会解套,array map set weakmap weakset集合类 访问 ref 时,不会自动解套】
1 | const arr = reactive([ref(0)]) |
心智负担上 ref vs reactive
在普通 JavaScript 中区别声明基础类型变量与对象变量时一样区别使用 ref 和 reactive
所有的地方都用 reactive,然后记得在组合函数返回响应式对象时使用 toRefs。
这降低了一些关于 ref 的心智负担
readonly
传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。
一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的【返回一个永远不会变的只读代理】【场景可以参数比对等】
1 | const original = reactive({ count: 0 }) |
reactive响应式系统工具集
isProxy
检查一个对象是否是由
reactive
或者readonly
方法创建的代理
isReactive
检查一个对象是否是由 reactive
创建的响应式代理
1 | import { reactive, isReactive } from 'vue' |
如果这个代理是由 readonly
创建的,但是又被 reactive
创建的另一个代理包裹了一层,那么同样也会返回 true
1 | import { reactive, isReactive, readonly } from 'vue' |
isReadonly
检查一个对象是否是由 readonly
创建的只读代理
reactive高级响应式系统API
toRaw
返回由
reactive
或readonly
方法转换成响应式代理的普通对象。
这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发更改。
不建议一直持有原始对象的引用【不建议赋值给任何变量】。请谨慎使用
被toRaw
之后的对象是没有被代理/跟踪的的普通对象
1 | const foo = {} |
markRaw
显式标记一个对象为“永远不会转为响应式代理”,函数返回这个对象本身。
【markRaw
传入对象,返回的值是永远不会被转为响应式代理的】
1 | const foo = markRaw({ |
被 markRaw
标记了,即使在响应式对象中作属性,也依然不是响应式的
1 | const bar = reactive({ foo }) |
markRaw
注意点
markRaw
和shallowXXX
一族的 API允许选择性的覆盖reactive或者readonly 默认创建的 “深层的” 特性【响应式】/或者使用无代理的普通对象
设计这种「浅层读取」有很多原因
一些值的实际上的用法非常简单,并没有必要转为响应式【例如三方库的实例/省市区json/Vue组件对象】
当渲染一个元素数量庞大,但是数据是不可变的,跳过 Proxy 的转换可以带来性能提升
这些 API 被认为是高级的,是因为这种特性仅停留在根级别,所以如果你将一个嵌套的,没有 markRaw 的对象设置为 reactive 对象的属性,在重新访问时,你又会得到一个 Proxy 的版本
在使用中最终会导致标识混淆的严重问题:执行某个操作同时依赖于某个对象的原始版本和代理版本(标识混淆在一般使用当中应该是非常罕见的
但是要想完全避免这样的问题,必须要对整个响应式系统的工作原理有一个相当清晰的认知)
1 | const foo = markRaw({ |
foo.nested没有被标记为(永远不会转为响应式代理),导致最后的值一个reactive
shallowReactive
只为某个对象的私有(第一层)属性创建浅层的响应式代理,不会对“属性的属性”做深层次、递归地响应式代理,而只是保留原样【第一层是响应式代理,深层次只保留原样(不具备响应式代理)】
1 | const state = shallowReactive({ |
shallowReadonly
类似于shallowReactive,区别是:
第一层将会是响应式代理【第一层修改属性会失败】,属性为响应式
深层次的对象属性可以修改,属性不是响应式
1 | const state = shallowReadonly({ |
ref
响应式系统工具集
unref
unref
是val = isRef(val) ? val.value : val
的语法糖
1 | unref(ref(0))===unref(0)===0 返回number |
toRef
toRef
可以用来为一个 reactive
对象的属性【某个属性区别toRefs每一个属性】创建一个 ref。
这个 ref 可以被传递并且能够保持响应性
1 | const state = reactive({ |
toRefs
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应
1 | const state = reactive({ |
可以通过toRefs
返回可解构的reactive,因为toRefs
包裹之后返回一一对应的ref属性
1 | function useFeatureX() { |
isRef
检查一个值是否为一个 ref 对象
ref 高级响应式系统API
customRef
用于自定义一个 ref,可以显式地控制依赖追踪和触发响应,接受一个工厂函数,两个参数分别是用于追踪的 track 与用于触发响应的 trigger,并返回一个一个带有 get 和 set 属性的对象【实际上就是手动 track追踪 和 trigger触发响应】
以下代码可以使得v-model防抖
1 | function useDebouncedRef(value, delay = 200) { |
shallowRef
创建一个 ref ,将会追踪它的 .value 更改操作,但是并不会对变更后的 .value 做响应式代理转换(即变更不会调用 reactive)
前面我们说过如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换,通过shallowRef创建的ref,将不会调用reactive【对象不会是响应式的】
1 | const refOne = shallowRef({}); |
triggerRef 【与shallowRef配合】
手动执行与shallowRef相关的任何效果
1 | const shallow = shallowRef({ |
Computed and watch【监控变化】
computed
传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象【默认传入的是get函数的对象】
传入一个拥有 get
和 set
函数的对象,创建一个可手动修改的计算状态
1 | const count = ref(1) |
watchEffect
立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数
computed与watchEffect区别:
computed计算属性可通过setup return,再模板中使用,watchEffect不能;
computed可以使用多个,并且对多个属性进行不同的响应计算,watchEffect会存在副作用
1 | const count = ref(0) |
停止观察
当在组件的setup()函数或生命周期钩子期间调用watchEffect时,监视程序会链接到组件的生命周期,并在卸载组件时自动停止
一般情况下watchEffect返回可以stop 操作,停止监听程序
1 | const stop = watchEffect(() => { |
副作用(函数式编程)
一个带有副作用的函数不仅只是简单的返回一个值,还干了一些其他的事情,比如:
修改一个变量
直接修改数据结构
设置一个对象的成员
抛出一个异常或以一个错误终止
打印到终端或读取用户的输入
读取或写入一个文件
在屏幕上绘画
buyCoffee的例子(p3):
函数只不过是需要返回一杯咖啡,可是却对费用进行了持久化操作(产生副作用),我们可以在buyCoffee方法返回咖啡时也把费用作为值一并返回,将费用这条记录交给其他程序来做持久化,以此来去除副作用
通过把这些副作用推到程序的外层,来转换任何带有副作用的函数(纯的内核和一层很薄的外围来处理副作用)
如果一个函数内外有依赖于外部变量或者环境时,常常我们称之为其有副作用
如果我们仅通过函数签名不打开内部代码检查并不能知道该函数在干什么,作为一个独立函数我们期望有明确的输入和输出,副作用是bug的发源地
作为程序员开发者应尽量少的开发有副作用的函数或方法,副作用也使得方法通用性下降不适合扩展和可重用性
清除副作用[^]: watchEffect
函数都是副作用
在一些时候监听函数将执行异步副作用【一个响应式依赖被修改了,会做其他事情】,这些响应需要在其失效时清除(例如在效果完成前状态改变)。
effect函数接收一个onInvalidate 函数作入参, 用来注册清理失效时的回调。
这个 invalidation函数 在什么时候会被调用:
监听函数重新被执行的时候【响应式依赖的数据被修改】
监听停止的时候(如果watchEffect在setup()或者生命周期函数中被使用的时候组件会被卸载)【停止观察】
1 | watchEffect(onInvalidate => { |
从上面看:我们之所以是通过传入一个函数去注册失效回调,而不是从回调返回它,是因为返回值对于异步错误处理很重要
1 | const data = ref(null) |
我们知道异步函数都会隐式地返回一个 Promise,但是清理副作用的函数必须要在 Promise 被 resolve 之前被注册。
另外,Vue 依赖这个返回的 Promise 来自动处理 Promise 链上的潜在错误
副作用刷新时机
Vue 的响应式系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个 tick 中多个状态改变导致的不必要的重复调用。
在核心的具体实现中, 组件的更新函数也是一个被侦听的副作用。
当一个用户定义的副作用函数进入队列时, 会在所有的组件更新后执行
1 | <template> |
count
会在初始运行时同步打印出来
更改 count
时,将在组件更新后执行副作用
初始化运行是在组件 mounted 之前执行的【你希望在编写副作用函数时访问 DOM(或模板 ref),请在 onMounted 钩子中进行】
1 | onMounted(() => { |
如果副作用需要同步或在组件更新之前重新运行,我们可以传递一个拥有 flush 属性的对象作为选项(默认为 ‘post’)
1 | // 同步运行 |
侦听器调试【响应式调试用的】
onTrack
和 onTrigger
选项可用于调试一个侦听器的行为。
当一个 reactive 对象属性或一个 ref 作为依赖被追踪时,将调用 onTrack
【调用次数为被追踪的数量】
依赖项变更会导致重新追踪依赖,从而onTrack
被调用【调用次数为被追踪的数量】
依赖项变更导致副作用被触发时,将调用 onTrigger
这两个回调都将接收到一个包含有关所依赖项信息的调试器事件。
建议在以下回调中编写 debugger 语句来检查依赖关系:【onTrack
和 onTrigger
仅在开发模式下生效】
1 | watchEffect( |
watch
watch API 完全等效于 2.x watch 中相应的选项。
watch 需要侦听特定的数据源,并在回调函数中执行副作用【默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调】
watch允许我们:
懒执行副作用
更明确哪些状态的改变会触发侦听器重新运行副作用
访问侦听状态变化前后的值
侦听单个数据源
侦听器的数据源可以是一个拥有返回值的 getter 函数,也可以是 ref:
1 | // 侦听一个 getter |
侦听多个数据源
watcher 也可以使用数组来同时侦听多个源
1 | watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { |
与 watchEffect
共享的行为
watch 和 watchEffect 在停止侦听, 清除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入),副作用刷新时机 和 侦听器调试 等方面行为一致
1 | watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar],onInvalidate) => { |
生命周期钩子函数
与 2.x 版本生命周期相对应的组合式 API
beforeCreate-> 使用 setup()created-> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
1 | import { onMounted, onUpdated, onUnmounted } from 'vue' |
这些生命周期钩子注册函数只能在 setup()
期间同步使用, 因为它们依赖于内部的全局状态来定位当前组件实例(正在调用 setup()
的组件实例), 不在当前组件下调用这些函数会抛出一个错误。
组件实例上下文也是在生命周期钩子同步执行期间设置的,因此,在卸载组件时,在生命周期钩子内部同步创建的侦听器和计算状态也将自动删除。
###新增的钩子函数
除了和 2.x 生命周期等效项之外,组合式 API 还提供了以下调试钩子函数:
onRenderTracked
onRenderTriggered
两个钩子函数都接收一个 DebuggerEvent
,与 watchEffect
参数选项中的 onTrack
和 onTrigger
类似:
1 | export default { |
Vue提供的内置组件
component 与Vue2.x一致
渲染一个“元组件”为动态组件。依 is
的值,来决定哪个组件被渲染。
1 | <!-- 动态组件由 vm 实例的 `componentId` property 控制 --> |
transition 与 Vue2.x 【基本】 一致有差异
Props新增:persisted - boolean
如果为true
,则表示这是一个转换,实际上不会插入/删除元素,而是切换显示/隐藏状态。transition
过渡挂钩被注入,但会被渲染器跳过。
相反,自定义指令可以通过调用注入的钩子(例如v-show)来控制过渡
enter-class—–>enter-from-classleave-class—–>leave-from-class
事件
before-appear
transition-group 与 Vue2.x 一致
slot 与 Vue2.x 一致
teleport 【新增组件】
Props
to - string
必填属性,必须是一个有效的query选择器,或者是元素(如果在浏览器环境中使用)中的内容将会被放置到指定的目标元素中
1 | <!-- 正确的 --> |
disabled - boolean
这是一个可选项 ,做一个是可以用来禁用的功能,这意味着它的插槽内容不会移动到任何地方,而是按没有teleport组件一般来呈现【默认为false】
1 | <teleport to="#popup" :disabled="displayVideoInline"> |
注意,这将移动实际的DOM节点,而不是销毁和重新创建,并且还将保持任何组件实例是活动的。
所有有状态HTML元素(比如一个正在播放的视频)将保持它们的状态。
【控制displayVideoInline并不是销毁重建,它保持实例是存在的,不会被注销】
关于Teleport 其他内容
Vue鼓励我们通过将UI和相关行为封装到组件中来构建UI。
我们可以将它们彼此嵌套在一起,以构建构成应用程序UI的树
但是,有时组件模板的一部分逻辑上属于这个组件,而从技术角度来看,最好将这一部分模板移到DOM中的其他地方,放到Vue应用程序之外
一个常见的场景是创建一个包含全屏模态的组件。
在大多数情况下,您希望模态的逻辑驻留在组件中,但是模态框的定位问题很快就很难通过CSS解决,或者需要更改组件的组成
考虑下面的HTML结构:
1 | <body> |
让我们看看 mode -button
该组件将有一个button
元素来触发模态的打开,还有一个div元素,其类为.modal,它将包含模态的内容和一个自关闭按钮
1 | const app = Vue.createApp({}); |
当在初始HTML结构中使用这个组件时,我们可以看到一个问题——模态被呈现在深嵌套的div中,模态的绝对位置以父div相对位置作为参考。
Teleport提供了一种干净的方式,允许我们控制DOM中希望在哪个父节点下呈现HTML片段,而不必诉诸全局状态或将其拆分为两个组件。
让我们修改我们的modal-button来使用并告诉Vue “teleport this HTML to the “body”标签”。
1 | app.component('modal-button', { |
与Vue组件一起使用
如果包含一个Vue组件,它将仍然是的父组件的逻辑子组件
1 | const app = Vue.createApp({ |
在这种情况下,即使在不同的地方呈现child-component,它仍将是parent-componen的子组件【而不是爷爷组件】,并将从其父组件接收一个name 的props
这也意味着来自父组件的注入如预期的那样工作,并且子组件将嵌套在Vue Devtools的父组件之下,而不是放在实际内容移动到的地方
对同一目标使用多次teleports
一个常见的用例场景是一个可重用的组件,该组件可能同时有多个活动实例。
对于这种场景,多个组件可以将它们的内容挂载到相同的目标元素。
这个顺序将是一个简单的附加—稍后的挂载将位于目标元素中较早的挂载之后。
1 | <teleport to="#modals"> |
依赖注入Provide / Inject
provide 和 inject 提供依赖注入,功能类似 2.x 的 provide/inject。
两者都只能在当前活动组件实例的 setup() 中调用
例如,如果我们想在根组件上提供一个book name,并将其inject到子组件上
1 | import { provide, inject } from 'vue' |
inject 接受一个可选的的默认值作为第二个参数。
如果未提供默认值,并且在 provide 上下文中未找到该属性,则 inject 返回 undefined。
如果我们需要提供或注入多个值,我们可以通过随后分别调用provide或inject来实现【多次调用】
1 | import { provide, inject } from 'vue' |
注入的响应性
可以使用 ref 或 reactive 来保证 provided 和 injected 之间值的响应
1 | import { ref, reactive } from 'vue' |
现在,当提供者组件上的book或year发生变化时,我们可以观察到它们在注入的组件上的变化。
警告:我们不建议改变一个被注入的反应性属性【子组件去修改数据流】,因为它会破坏Vue的单向数据流。
相反,尝试在提供值【父组件去修改】的地方改变值,或者提供一个方法来改变值
1 | import { ref, reactive } from 'vue' |
指令
v-text 【Vue2.x一致】
v-html【Vue2.x一致】
v-show【Vue2.x一致】
v-if【Vue2.x一致】
v-else【Vue2.x一致】
v-else-if【Vue2.x一致】
v-for【Vue2.x一致】
v-on【Vue2.x一致】
v-model【Vue2.x一致】
v-slot【Vue2.x一致】
v-cloak【Vue2.x一致】
v-once 【Vue2.x一致】
v-pre【Vue2.x一致】
v-bind 【Vue2.x 修饰符差异】
修饰符
.prop去除.sync去除
.camel 将 kebab-case attribute 名转换为 camelCase
v-is【新增】
注意:本节只影响在页面的HTML中直接编写Vue模板的情况
限制:原生html元素
使用:
使用in-DOM模板时,该模板应遵守本机HTML解析规则。 某些HTML元素对可以在其中显示哪些元素有限制,而某些元素(例如,和)只能 出现在某些其他元素内。 解决方法是,我们可以在这些元素上使用v-is指令【作用就是转成组件的名字】
警告:
v-is
功能 像一个动态2.x:is
绑定 所以要根据注册的名称渲染组件,它的值应该是一个JavaScript字符串
1 | <!-- 不正确的, 不会出现任何渲染 --> |
全局API
createApp
返回一个应用程序实例,提供了一个应用程序上下文。应用程序实例挂载的整个组件树共享相同的上下文
1 | const app = Vue.createApp({}) |
参数
该函数接收一个根组件选项对象作为第一个参数
1 | const app = Vue.createApp({ |
使用第二个参数,我们可以将根组件props 传递给应用
1 | <div id="app"> |
1 | interface Data { |
h
返回“虚拟节点”,通常缩写为VNode
:一个简单的对象,它包含描述Vue应该在页面上渲染何种类型的节点的信息,包括对任何子节点的描述。你可以手动阅读render functions
1 | render() { |
参数
接受三个参数tag, props and children
tag
:
类型:String | Object | Function | null
详情:一个HTML标签名,一个组件,一个异步组件或null。使用null将渲染成注释。此参数是必需的
props
类型:Object
详情:模板中使用的attributes、props 和events 对应的对象。可选
children
类型: String | Array | Object
详情:Children VNodes,使用h()构建,或使用字符串来获取“text VNodes”或带有槽的对象。可选
1 | const aaa = { |
1 | Vue.h( |
限制:VNodes 必须独一无二
组件树中的所有vnode必须是唯一的。这意味着下面的渲染函数是无效的
1 | render() { |
如果您确实想多次复制相同的元素/组件,则可以使用工厂函数进行复制。 例如,以下呈现函数是呈现20个相同段落的完美有效方法:
1 | render() { |
用普通的JavaScript替换模板特性
- v-if and v-for
在任何地方都可以用普通JavaScript轻松完成,Vue渲染functions 都不提供专有的替代方案。例如,在使用v-if和v-for的模板中1
2
3
4<ul v-if="items.length">
<li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p>1
2
3
4
5
6
7
8
9
10props: ['items'],
render() {
if (this.items.length) {
return Vue.h('ul', this.items.map((item) => {
return Vue.h('li', item.name)
}))
} else {
return Vue.h('p', 'No items found.')
}
} - v-model
v-model指令被扩展到modelValue
和onUpdate:modelValue
道具在模板编译期间,我们将不得不自己提供这些props1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17props: ['modelValue'],
render() {
return Vue.h(SomeComponent, {
modelValue: this.modelValue,
'onUpdate:modelValue': value => this.$emit('update:modelValue', value)
})
}
```
+ **v-on**
我们必须为事件处理程序提供一个适当的prop名称,例如,为了处理click事件,prop名称应该是onClick
```ts
render() {
return Vue.h('div', {
onClick: $event => console.log('clicked', $event.target)
})
} - 事件修饰符
对于.passive、.capture和.once事件修饰符,Vue提供了处理程序的对象语法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17render() {
return Vue.h('input', {
onClick: {
handler: this.doThisInCapturingMode,
capture: true
},
onKeyUp: {
handler: this.doThisOnce,
once: true
},
onMouseOver: {
handler: this.doThisOnceInCapturingMode, //事件
once: true, //是否触发一次
capture: true
},
})
}
对于所有其他事件和键修饰符,不需要特殊的API,因为我们可以在处理程序中使用事件方法
1 | render() { |
- Slots
你可以访问插槽内容this.$slots在VNodes数组的
1 | render() { |
1 | props: ['message'], |
使用render函数将槽传递给子组件
1 | render() { |
- JSX
如果我们要编写大量的渲染函数,编写这样的东西可能会让人感到痛苦特别是当模板版本相比之下如此简洁的时候1
2
3
4
5
6
7Vue.h(
'anchored-heading',
{
level: 1
},
[Vue.h('span', 'Hello'), ' world!']
)这就是为什么有一个Babel插件可以在Vue中使用JSX,让我们回到更接近模板的语法1
<anchored-heading :level="1"> <span>Hello</span> world! </anchored-heading>
1
2
3
4
5
6
7
8
9
10
11
12import AnchoredHeading from './AnchoredHeading.vue'
new Vue({
el: '#demo',
render() {
return (
<AnchoredHeading level={1}>
<span>Hello</span> world!
</AnchoredHeading>
)
}
})
defineComponent【组件】
在实现方面,defineComponent只会执行返回传递给它的对象的操作。
但是,就类型而言,返回的值具有人工渲染功能,TSX和IDE工具支持的构造函数的综合类型
参数
具有组件选项的对象
1 | import { defineComponent } from 'vue' |
defineAsyncComponent 【异步组件】
创建只在必要时加载的异步组件
参数
对于基本用法,defineAsyncComponent可以接受返回Promise的工厂函数。
当您从serve检索到组件定义时,应该调用Promise的解析回调。
您还可以调用reject(reason)来指示加载失败。
1 | import { defineAsyncComponent } from 'vue' |
在使用本地注册时,还可以直接提供返回Promise的函数
1 | import { createApp, defineAsyncComponent } from 'vue' |
对于高级用法,defineAsyncComponent可以接受一个对象
1 | const AsyncComp = defineAsyncComponent({ |
resolveComponent
警告resolveComponent只能在render或setup函数中使用。
允许通过名称解析组件,如果它在当前应用程序实例中可用。
如果找不到组件,返回组件或未定义组件
1 | app.component('MyComponent', { |
resolveDynamicComponent【解析活动的组件active】
resolveDynamicComponent只能在render
或setup
函数中使用。
允许使用与<component:is="">
相同的机制来解析组件。
返回解析的组件或一个新创建的VNode以组件名称作为节点标记的。
如果没有找到组件,会发出警告
resolveDirective
警告 resolveDirective只能在render
或setup
函数中使用。
允许通过名称解析指令,如果它在当前应用程序实例中可用。
返回一个Directive或 当没有找到的时候,返回undefined。
1 | app.directive('highlight', {}) |
withDirectives
警告 withDirectives只能在render或setup函数中使用。
允许应用指令到VNode。返回一个带有应用指令的VNode。
1 | const bar = resolveDirective('bar') |
nextTick
将回调延迟到下一个DOM更新周期之后执行。在更改了一些数据以等待DOM更新之后立即使用它
1 | setup() { |
实例方法methods
$watch
参数
{string | Function} source
{Function | Object} callback
{Object} [options]
{boolean} deep
{boolean} immediate
用法
观察组件实例上的响应式属性或computed函数的更改。
使用回调获取到给定属性的新值和旧值。
我们只能通过顶级data、prop或computed的属性名作为字符串的形式传递。
对于更复杂的表达式或嵌套属性,使用函数代替。
例子
1 | const app = Vue.createApp({ |
当监视的值是对象或数组时,对其属性或元素的任何更改都不会触发监视程序,因为它们引用相同的对象/数组
1 | const app = Vue.createApp({ |
$watch返回一个取消监视的函数,该函数停止触发回调
1 | const unwatch = vm.$watch('a', cb) |
Option: deep
检测对象内部嵌套的值更改,需要在options参数中传入deep: true。
注意,侦听数组突变并不需要这样做。
1 | vm.$watch('someObject', callback, { |
Option: immediate
在选项中传入immediate: true将立即用表达式的当前值触发回调
1 | vm.$watch('a', callback, { |
请注意,使用immediate选项,您将无法在第一个回调调用中取消监视给定的属性。
1 | //这个例子是错误的 |
如果你仍然想在回调中调用一个unwatch函数,你应该首先检查它的可用性
1 | const unwatch = vm.$watch( |
$emit 【一致】
$forceUpdate【一致】
$nextTick【一致】
实例 property
vm.$data 【一致】
vm.$props 【一致】
vm.$el 【一致】
vm.$options 【一致】
vm.$parent 【一致】
vm.$root【一致】
vm.$slots 【一致】
vm.$refs 【一致】
vm.$attrs 【一致】
废弃:vm.$childrenvm.$slotsvm.$scopedSlotsvm.$isServervm.$listeners
选项 / 组合
mixins 【一致】
extends【一致】
provide / inject【一致】
parent【废弃】
setup【新增】
选项 / 资源
directives【一致】
components【一致】filters【废弃】
选项 / 数据
data【一致】
props【一致】
computed【一致】
methods【一致】
watch【一致】
emits【新增】
详情
可以从组件发出的自定义事件的list/hash。
它具有基于数组的简单语法和允许配置事件验证的替代的基于对象的语法。
在基于对象的语法中,每个属性的值可以为null或验证函数。
验证函数将接收传递给emit调用的其他参数。
例如,如果调用this.emit(‘foo’,1),则foo的相应验证器将接收参数1。
验证器函数应返回一个布尔值,以指示事件参数是否有效。
1 | const app = Vue.createApp({}) |
提示: 在emit选项中列出的事件将不会被组件的根元素继承
- 本文作者: Jambo
- 版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!