Object.defineProperty => Proxy
重构了虚构 DOMOptionApi => Composition API
setup
是干啥的?setup
实际上是一个组件的入口,它运行在组件被实例化时候,props
属性被定义之后,实际上等价于 2 版本的 beforeCreate
和 Created
这两个生命周期。setup
承受两个参数,第一个参数是props
,另一个参数是context
,
setup(props, ctx) {console.log(props, ctx)
}
let Child = {template: `<div>{{title}}</div>`,
setup(props, context) {console.log(props)
}
}
let App = {
template: `
<div class="container">
<Child title="test props"/>
</div>`,
components: {Child}
}
Vue.createApp().mount(App, '#app')
reactive
const {reactive, toRefs} = Vue
let App = {
template: `
<div class="container">
count: {{count}}
<button @click="handlerCountAdd"> Click ++ </button>
</div>`,
setup() {const state = reactive({ count: 0})
const handlerCountAdd = () => {state.count++}
return {...toRefs(state), handlerCountAdd }
}
}
Vue.createApp().mount(App, '#app')
toRefs
vue3 提供的 ref 让咱们有机会创立单个的响应式的对象,在 setup 函数中 return 进来之后,在模板中可间接拜访
const App = {
template: `
<div class="container">
{{value}}
</div>`,
setup() {const value = ref(1)
return {value}
}
}
Vue.createApp().mount(App, '#app')
const App = {
template: `
<div class="container">
{{state.value}}
</div>`,
setup() {const state = reactive({ value: 'reactive'})
return {state}
}
}
Vue.createApp().mount(App, '#app')
const App = {
template: `
<div class="container">
{{value}}
</div>`,
setup() {const state = reactive({ value: 'reactive'})
return toRefs(state)
}
}
Vue.createApp().mount(App, '#app')
反转字符串 demo
let App = {
template: `
<div class="container">
value: <input v-model="value"/>
<br/>
rvalue: {{rvalue}}
</div>`,
setup() {
const state = reactive({
value: '',
rvalue: computed(() =>
state.value
.split('')
.reverse()
.join('')
)
})
return toRefs(state)
}
}
Vue.createApp().mount(App, '#app')
数据响应式
在 Vue3 中实现数据响应式的计划由 Vue2 中的 Object.defineProperty
换成了 Proxy
,对于数据响应式的 Api 上边说到了一些,还剩下effect
和watch
没有提及到,effect
是数据响应式中重要的一部分,watch
和 computed
都是基于 effect
的.
let App = {
template: `
<div class="container">
count: {{count}}
<button @click="handlerCountAdd"> Click ++ </button>
</div>`,
setup() {const state = reactive({ count: 0, value: 1})
const handlerCountAdd = () => {state.count++}
watch(() => state.count,
val => {console.log('watch', state.count)
console.log('watch', state.value)
}
)
effect(() => {console.log('effect', state.count)
console.log('effect', state.value)
})
return {...toRefs(state), handlerCountAdd }
}
}
Vue.createApp().mount(App, '#app')
effect
在响应式数据变动的时候就会执行,执行次数依据响应式数据的个数来决定
let App = {
template: `
<div class="container">
<button @click="handlerCountAdd"> Click ++ </button>
</div>`,
setup() {const r = ref(1)
const s = ref(1)
const t = ref(1)
const handlerCountAdd = () => {
r.value *= 1
s.value *= 2
t.value *= 3
}
effect(() => {console.log('effect', [r.value, s.value, t.value])
})
return {handlerCountAdd}
}
}
Vue.createApp().mount(App, '#app')
而 watch
则点击一次,只会触发执行一次
let App = {
template: `
<div class="container">
<button @click="handlerCountAdd"> Click ++ </button>
</div>`,
setup() {const state = reactive({ count: 0, value: 1})
const r = ref(1)
const s = ref(1)
const t = ref(1)
const handlerCountAdd = () => {
r.value *= 1
s.value *= 2
t.value *= 3
}
watch([r, s, t], val => {console.log('watch', val)
})
return {handlerCountAdd}
}
}
Vue.createApp().mount(App, '#app')
生命周期
beforeCreate => setup(代替)
created => setup(代替)
beforeMount => onBeforeMount
mounted => onMounted
beforeUpdate => onBeforeUpdate
updated => onUpdated
beforeDestroy => onBeforeUnmount
destroyed => onUnmounted
errorCaptured => onErrorCaptured
全局配置
Vue2.x
创立实例并且挂载 DOM
上
import Vue from "vue";
import App from './App.vue'
new Vue({render: (h) => h(App)
}).$mount("#app");
Vue3 新增api===>createApp
创立实例
createApp 会产生一个 app 实例,该实例领有全局的可配置上下文
import {createApp} from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
component
Vue2.x
【注册或获取全局组件。注册还会主动应用给定的 id 设置组件的名称】
// 注册组件,传入一个选项对象 (主动调用 Vue.extend)
Vue.component('my-component', { /* ... */})
// 获取注册的组件 (始终返回结构器)
var MyComponent = Vue.component('my-component')
Vue3
【注册或获取全局组件注册还会主动应用给定的 name
组件 设置组件的名称】全局组件
根本 vue2 写法统一
import {createApp} from 'vue'
const app = createApp({})
// 注册组件,传入一个选项对象
app.component('my-component', {/* ... */})
// 获取注册的组件 (始终返回结构器)
const MyComponent = app.component('my-component', {})
globalProperties【新增属性】
app.config.globalProperties.foo = 'bar'
app.component('child-component', {mounted() {console.log(this.foo) // 'bar'
}
})
增加可在程序内的任何组件实例中拜访的全局属性。当存在键抵触时,组件属性将优先
代替掉 Vue2.x
的 Vue.prototype
属性放到原型上的写法
// Vue2.x
Vue.prototype.$http = () => {}
// Vue3
const app = Vue.createApp({})
app.config.globalProperties.$http = () => {}
isCustomElement
【新增属性】
代替掉 Vue2.x
的ignoredElements
- 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
const app = Vue.createApp({mounted() {console.log(this.$options.hello)
}
})
app.config.optionMergeStrategies.hello = (parent, child, vm) => {return `Hello, ${child}`
}
app.mixin({hello: 'Vue'})
// 'Hello, Vue
定义自定义选项的合并策略。
合并策略接管在父实例 options
和∗∗
子实例 ∗∗options
和子实例options 和∗∗子实例∗∗options,别离作为第一个和第二个参数。上下文 Vue 实例作为第三个参数传递
【自定义选项合并策略】mixin
const app = Vue.createApp({custom: 'hello!'})
app.config.optionMergeStrategies.custom = (toVal, fromVal) => {console.log(fromVal, toVal)
// => "goodbye!", undefined
// => "hello!", "goodbye!"
return fromVal || toVal
}
app.mixin({
custom: 'goodbye!',
created() {console.log(this.$options.custom) // => "hello!"
}
})
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.custom 最初会返回合并策略的 return 的值【应用场景利用父子组件的 options.custom 最初会返回合并策略的 return 的值【应用场景利用父子组件的 options, 而后返回计算等操作失去所须要的值】optionMergeStrategies 合并 $options 变动
directive
import {createApp} from 'vue'
const app = createApp({})
// 注册
app.directive('my-directive', {
// 指令的生命周期
// 在绑定元素的父组件被挂载之前调用
beforeMount(el, binding, vnode) {},
// 在挂载绑定元素的父组件时调用
mounted(el, binding, vnode) {},
// 在更新蕴含组件的 VNode 之前调用
beforeUpdate(el, binding, vnode, prevNode) {},
// 组件的 VNode 及其子组件的 VNode 更新之后调用
updated(el, binding, vnode, prevNode) {},
// 在卸载绑定元素的父组件之前调用
beforeUnmount(el, binding, vnode) {},
// 在卸载绑定元素的父组件时调用
unmounted(el, binding, vnode) {}})
// 注册 (指令函数)
app.directive('my-directive', (el, binding, vnode, prevNode) => {// 这里将会被 `mounted` 和 `updated` 调用})
// getter,返回已注册的指令
const myDirective = app.directive('my-directive')
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:一个对象,在注册指令时作为参数传递; 举个例子,看上面指令
app.directive('focus', {mounted(el) {el.focus()
}
})
dir 就是上面的对象
{mounted(el) {el.focus()
}
}
vnode
编译生成的虚构节点
prevNode
前一个虚构节点,仅在 beforeUpdate 和 updated 钩子中可用
tips: 除了 el 之外,其它参数都应该是只读的,切勿进行批改。如果须要在钩子之间共享数据,倡议通过元素的 dataset 来进行
mount
【相似 Vue2.x】
在所提供的 DOM
元素上挂载应用程序实例的根组件
import {createApp} from 'vue'
const app = createApp({})
// 做一些筹备
app.mount('#my-app')
provide/inject
【Vue2.x 统一】
该选项与 inject 一起应用,容许一个先人组件作为其所有后辈的依赖注入器,无论组件层次结构有多深,只有它们位于同一父链中就能够
provide 选项应该是一个对象或返回一个对象的函数。该对象蕴含可注入其子孙的 property。在该对象中你能够应用 ES2015 Symbols 作为 key,然而只在原生反对 Symbol 和 Reflect.ownKeys 的环境下可工作。如果在组件中两者都只能在以后流动组件实例的 setup() 中调用,具体请看依赖注入局部
import {createApp} from 'vue'
const app = createApp({
provide: {user: 'John Doe'}
})
app.component('user-card', {inject: ['user'],
template: `
<div>
{{user}}
</div>
`
})
unmount【新增属性】
在所提供的 DOM 元素上卸载应用程序实例的根组件
import {createApp} from 'vue'
const app = createApp({})
// 做一些必要的筹备
app.mount('#my-app')
// 应用程序将在挂载后 5 秒被卸载
setTimeout(() => app.unmount('#my-app'), 5000)
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 对象,那样会使其失去响应性
export default {
props: {name: String,},
setup(props) {console.log(props.name)
watchEffect(() => {console.log(`name is: ` + props.name)
})
},
}
第二个参数提供了一个上下文对象【从原来 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 时将造成凌乱
setup(props, { attrs}) {
// 一个可能之后回调用的签名
function onClick() {console.log(attrs.foo) // 肯定是最新的援用,没有失落响应性
}
}
响应式零碎 API
reactive
desc: 接管一个一般对象而后返回该一般对象的响应式代理【等同于 2.x 的 Vue.observable()】tips:Proxy 对象是指标对象的一个代理器,任何对指标对象的操作(实例化,增加 / 删除 / 批改属性等等),都必须通过该代理器。因而咱们能够把来自外界的所有操作进行拦挡和过滤或者批改等操作
响应式转换是“深层的”:会影响对象外部所有嵌套的属性。基于 ES2015 的 Proxy 实现,返回的代理对象不等于原始对象。倡议仅应用代理对象而防止依赖原始对象
reactive 类的 api 次要提供了将简单类型的数据处理成响应式数据的能力,其实这个简单类型是要在 object array map set weakmap weakset 这五种之中【如下源码,他会判断是否是五类以及是否被解冻】
因为是组合函数【对象】,所以必须始终保持对这个所返回对象的援用以放弃响应性【不能解构该对象或者开展】例如 const {x, y} = useMousePosition()或者 return { ...useMousePosition() }
function useMousePosition() {
const pos = reactive({
x: 0,
y: 0,
})
return pos
}
toRefs API 用来提供解决此束缚的方法——它将响应式对象的每个 property 都转成了相应的 ref【把对象转成了 ref】。function useMousePosition() {
const pos = reactive({
x: 0,
y: 0,
})
return toRefs(pos)
}
// x & y 当初是 ref 模式了!
const {x, y} = useMousePosition()
ref
承受一个参数值并返回一个响应式且可扭转的 ref 对象。ref 对象领有一个指向外部值的繁多属性 .value
const count = ref(0)
console.log(count.value) // 0
如果传入 ref 的是一个对象,将调用 reactive 办法进行深层响应转换
陷阱
setup 中 return 返回会主动解套【在模板中不须要.value】
ref 作为 reactive 对象的 property 被拜访或批改时,也将主动解套 .value
const count = ref(0)
/* 当做 reactive 的对象属性 ---- 解套 */
const state = reactive({count,})
/* 不须要.value*/
console.log(state.count) // 0
/* 批改 reactive 的值 */
state.count = 1
/* 批改了 ref 的值 */
console.log(count.value) // 1
留神如果将一个新的 ref 调配给现有的 ref,将替换旧的 ref
/* 创立一个新的 ref*/
const otherCount = ref(2)
/* 赋值给 reactive 的旧的 ref,旧的会被替换掉 */
state.count = otherCount
/* 批改 reactive 会批改 otherCount*/
console.log(state.count) // 2
/* 批改 reactive 会 count 没有被批改 */
console.log(count.value) // 1
嵌套在 reactive Object 中时,ref 才会解套。从 Array 或者 Map 等原生汇合类中拜访 ref 时,不会主动解套【自在数据类型是 Object 才会解套,array map set weakmap weakset 汇合类 拜访 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 vs reactive
在一般 JavaScript 中区别申明根底类型变量与对象变量时一样区别应用 ref 和 reactive
所有的中央都用 reactive,而后记得在组合函数返回响应式对象时应用 toRefs。这升高了一些对于 ref 的心智累赘
readonly
传入一个对象(响应式或一般)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象外部任何嵌套的属性也都是只读的【返回一个永远不会变的只读代理】【场景能够参数比对等】const original = reactive({count: 0})
const copy = readonly(original)
watchEffect(() => {
// 依赖追踪
console.log(copy.count)
})
// original 上的批改会触发 copy 上的侦听
original.count++
// 无奈批改 copy 并会被正告
copy.count++ // warning!
reactive 响应式零碎工具集
isProxy
查看一个对象是否是由 reactive 或者 readonly 办法创立的代理
isReactive
查看一个对象是否是由 reactive 创立的响应式代理
import {reactive, isReactive} from 'vue'
const state = reactive({name: 'John'})
console.log(isReactive(state)) // -> true
如果这个代理是由 readonly 创立的,然而又被 reactive 创立的另一个代理包裹了一层,那么同样也会返回 true
import {reactive, isReactive, readonly} from 'vue'
const state = reactive({name: 'John'})
// 用 readonly 创立一个只读响应式对象 plain
const plain = readonly({name: 'Mary'})
//readonly 创立的,所以 isReactive 为 false
console.log(isReactive(plain)) // -> false
// reactive 创立的响应式代理对象包裹一层 readonly,isReactive 也是 true,isReadonly 也是 true
const stateCopy = readonly(state)
console.log(isReactive(stateCopy)) // -> true
isReadonly
查看一个对象是否是由 readonly 创立的只读代理
reactive 高级响应式零碎 API
toRaw
返回由 reactive 或 readonly 办法转换成响应式代理的一般对象。这是一个还原办法,可用于长期读取,拜访不会被代理 / 跟踪,写入时也不会触发更改。不倡议始终持有原始对象的援用【** 不倡议赋值给任何变量 **】。请审慎应用
被 toRaw 之后的对象是没有被代理 / 跟踪的的一般对象
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
console.log(toRaw(reactiveFoo) !== reactiveFoo) // true
markRaw
显式标记一个对象为“永远不会转为响应式代理”,函数返回这个对象自身。【markRaw 传入对象,返回的值是永远不会被转为响应式代理的】const foo = markRaw({name: 'Mary'})
console.log(isReactive(reactive(foo))) // false
被 markRaw 标记了,即便在响应式对象中作属性,也仍然不是响应式的
const bar = reactive({foo})
console.log(isReactive(bar.foo)) // false
markRaw 留神点
markRaw 和 shallowXXX 一族的 API 容许选择性的笼罩 reactive 或者 readonly 默认创立的 "深层的" 个性【响应式】/ 或者应用无代理的一般对象
设计这种「浅层读取」有很多起因
一些值的实际上的用法非常简单,并没有必要转为响应式【例如三方库的实例 / 省市区 json/Vue 组件对象】当渲染一个元素数量宏大,然而数据是不可变的,跳过 Proxy 的转换能够带来性能晋升
这些 API 被认为是高级的,是因为这种个性仅停留在根级别,所以如果你将一个嵌套的,没有 markRaw 的对象设置为 reactive 对象的属性,在从新拜访时,你又会失去一个 Proxy 的版本,在应用中最终会导致标识混同的重大问题:执行某个操作同时依赖于某个对象的原始版本和代理版本(标识混同在个别应用当中应该是十分常见的,然而要想完全避免这样的问题,必须要对整个响应式零碎的工作原理有一个相当清晰的认知)。const foo = markRaw({nested: {},
})
const bar = reactive({
// 只管 `foo` 己经被标记为 raw 了, 但 foo.nested 并没有
nested: foo.nested,
})
console.log(foo.nested === bar.nested) // false
foo.nested 没有被标记为(永远不会转为响应式代理),导致最初的值一个 reactive
shallowReactive
只为某个对象的公有(第一层)属性创立浅层的响应式代理,不会对“属性的属性”做深层次、递归地响应式代理,而只是保留原样【第一层是响应式代理,深层次只保留原样(不具备响应式代理)】const state = shallowReactive({
foo: 1,
nested: {bar: 2,},
})
// 变更 state 的自有属性是响应式的【第一档次响应式】state.foo++
// ... 但不会深层代理【深层次不是响应式】(渲染性能)
isReactive(state.nested) // false
state.nested.bar++ // 非响应式
shallowReadonly
相似于 shallowReactive,区别是:第一层将会是响应式代理【第一层批改属性会失败】,属性为响应式
深层次的对象属性能够批改,属性不是响应式
const state = shallowReadonly({
foo: 1,
nested: {bar: 2,},
})
// 变更 state 的自有属性会失败
state.foo++
// ... 然而嵌套的对象是能够变更的
isReadonly(state.nested) // false
state.nested.bar++ // 嵌套属性仍然可批改
ref 响应式零碎工具集
unref
unref 是 val = isRef(val) ? val.value : val 的语法糖
unref(ref(0))===unref(0)===0 返回 number
function useFoo(x: number | Ref<number>) {const unwrapped = unref(x) // unwrapped 肯定是 number 类型
}
toRef
toRef 能够用来为一个 reactive 对象的属性【某个属性区别 toRefs 每一个属性】创立一个 ref。这个 ref 能够被传递并且可能放弃响应性
const state = reactive({
foo: 1,
bar: 2,
})
//reactive 获取单个属性转为 ref【fooRef 只是一个代理】const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
toRefs
把一个响应式对象转换成一般对象,该一般对象的每个 property 都是一个 ref,和响应式对象 property 一一对应
const state = reactive({
foo: 1,
bar: 2,
})
const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型如下:
{
foo: Ref<number>,
bar: Ref<number>
}
*/
// ref 对象 与 原属性的援用是 "链接" 上的
state.foo++
console.log(stateAsRefs.foo) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
能够通过 toRefs 返回可解构的 reactive,因为 toRefs 包裹之后返回一一对应的 ref 属性
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2,
})
// 对 state 的逻辑操作
// 返回时将属性都转为 ref
return toRefs(state)
}
export default {setup() {
// 能够解构,不会失落响应性
const {foo, bar} = useFeatureX()
return {
foo,
bar,
}
},
}
isRef
查看一个值是否为一个 ref 对象
ref 高级响应式零碎 API
customRef
用于自定义一个 ref,能够显式地管制依赖追踪和触发响应,承受一个工厂函数,两个参数别离是用于追踪的 track 与用于触发响应的 trigger,并返回一个一个带有 get 和 set 属性的对象【实际上就是手动 track 追踪 和 trigger 触发响应】以下代码能够使得 v -model 防抖
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {get() {
/* 初始化手动追踪依赖考究什么时候去触发依赖收集 */
track()
return value
},
set(newValue) {
/* 批改数据的时候会把上一次的定时器革除【防抖】*/
clearTimeout(timeout)
timeout = setTimeout(() => {
/* 把新设置的数据给到 ref 数据源 */
value = newValue
/* 再有依赖追踪的前提下触发响应式 */
trigger()}, delay)
},
}
})
}
setup() {
return {
/* 裸露返回的数据加防抖 */
text: useDebouncedRef('hello'),
}
}
shallowRef
创立一个 ref,将会追踪它的 .value 更改操作,然而并不会对变更后的 .value 做响应式代理转换(即变更不会调用 reactive)后面咱们说过如果传入 ref 的是一个对象,将调用 reactive 办法进行深层响应转换, 通过 shallowRef 创立的 ref, 将不会调用 reactive【对象不会是响应式的】const refOne = shallowRef({});
refOne.value = {id: 1};
refOne.id == 20;
console.log(isReactive(refOne.value),refOne.value);//false {id: 1}
triggerRef【与 shallowRef 配合】手动执行与 shallowRef 相干的任何成果
const shallow = shallowRef({greet: 'Hello, world'})
// 第一次运行打印 "Hello, world"
watchEffect(() => {console.log(shallow.value.greet)
})
// 这不会触发成果,因为 ref 是 shallow
shallow.value.greet = 'Hello, universe'
// 打印 "Hello, universe"
triggerRef(shallow)
Computed and watch【监控变动】
computed
传入一个 getter 函数,返回一个默认不可手动批改的 ref 对象【默认传入的是 get 函数的对象】传入一个领有 get 和 set 函数的对象,创立一个可手动批改的计算状态
const count = ref(1)
/* 不反对批改【只读的】*/
const plusOne = computed(() => count.value + 1)
plusOne.value++ // 谬误!/*【可更改的】*/
const plusOne = computed({get: () => count.value + 1,
set: (val) => {count.value = val - 1},
})
plusOne.value = 1
console.log(count.value) // 0
watchEffect
立刻执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时从新运行该函数
computed 与 watchEffect 区别:computed 计算属性可通过 setup return,再模板中应用,watchEffect 不能;computed 能够应用多个,并且对多个属性进行不同的响应计算,watchEffect 会存在副作用
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 打印出 0
setTimeout(() => {
count.value++
// -> 打印出 1
}, 100)
进行察看
当在组件的 setup()函数或生命周期钩子期间调用 watchEffect 时,监督程序会链接到组件的生命周期,并在卸载组件时主动进行
个别状况下 watchEffect 返回能够 stop 操作,进行监听程序
const stop = watchEffect(() => {/* ... */})
// 进行监听程序
stop()
副作用(函数式编程)
一个带有副作用的函数不仅只是简略的返回一个值,还干了一些其余的事件,比方:
批改一个变量
间接批改数据结构
设置一个对象的成员
抛出一个异样或以一个谬误终止
打印到终端或读取用户的输出
读取或写入一个文件
在屏幕上绘画
buyCoffee 的例子(p3):函数只不过是须要返回一杯咖啡,可是却对费用进行了长久化操作(产生副作用),咱们能够在 buyCoffee 办法返回咖啡时也把费用作为值一并返回,将费用这条记录交给其余程序来做长久化,以此来去除副作用 ====》通过把这些副作用推到程序的外层,来转换任何带有副作用的函数(纯的内核和一层很薄的外围来解决副作用)如果一个函数内外有依赖于内部变量或者环境时,经常咱们称之为其有副作用,如果咱们仅通过函数签名不关上外部代码查看并不能晓得该函数在干什么,作为一个独立函数咱们冀望有明确的输出和输入,副作用是 bug 的发源地,作为程序员开发者应尽量少的开发有副作用的函数或办法,副作用也使得办法通用性降落不适宜扩大和可重用性
革除副作用
[^]: watchEffect 函数都是副作用
在一些时候监听函数将执行异步副作用【一个响应式依赖被批改了,会做其余事件】,这些响应须要在其生效时革除(例如在成果实现前状态扭转)。effect 函数接管一个 onInvalidate 函数作入参,用来注册清理生效时的回调。这个 invalidation 函数 在什么时候会被调用:监听函数从新被执行的时候【响应式依赖的数据被批改】监听进行的时候(如果 watchEffect 在 setup()或者生命周期函数中被应用的时候组件会被卸载)【进行察看】watchEffect(onInvalidate => {
/* 这是个异步操作 */
const token = performAsyncOperation(id.value)//id 依赖
onInvalidate(() => {// id 被批改了或者监听进行了会触发 token.cancel()事件【这块区域的代码】.
// 这里是异步事件的话,后面的 peding 的异步操作有效【这里的异步事件只执行一次】token.cancel()/* 异步操作 */
console.log('onInvalidate')
})
})
从下面看:咱们之所以是通过传入一个函数去注册生效回调,而不是从回调返回它(如 React useEffect
中的形式),是因为返回值对于异步错误处理很重要
const data = ref(null)
watchEffect(async onInvalidate => {onInvalidate(() => {...}) // 咱们在 Promise 的 resolves 之前注册清理函数(cleanup function)
data.value = await fetchData(props.id)
})
咱们晓得异步函数都会隐式地返回一个 Promise,然而清理副作用的函数必须要在 Promise 被 resolve 之前被注册。另外,Vue 依赖这个返回的 Promise 来主动解决 Promise 链上的潜在谬误
副作用刷新机会
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})
})
如果副作用须要同步或在组件更新之前从新运行,咱们能够传递一个领有 flush 属性的对象作为选项(默认为 'post')// 同步运行
watchEffect(() => {/* ... */},
{flush: 'sync',}
)
// 组件更新前执行
watchEffect(() => {/* ... */},
{flush: 'pre',}
)
侦听器调试【响应式调试用的】
onTrack 和 onTrigger 选项可用于调试一个侦听器的行为。当一个 reactive 对象属性或一个 ref 作为依赖被追踪时,将调用 onTrack【调用次数为被追踪的数量】依赖项变更会导致从新追踪依赖,从而 onTrack 被调用【调用次数为被追踪的数量】依赖项变更导致副作用被触发时,将调用 onTrigger
这两个回调都将接管到一个蕴含无关所依赖项信息的调试器事件。倡议在以下回调中编写 debugger 语句来查看依赖关系:【onTrack 和 onTrigger 仅在开发模式下失效】watchEffect(() => {/* 副作用的内容 */},
{onTrigger(e) {
/* 副作用依赖批改 */
debugger
},
onTrack(e) {
/* 副作用依赖批改 */
debugger
},
}
)
watch
watch API 齐全等效于 2.x watch 中相应的选项。watch 须要侦听特定的数据源,并在回调函数中执行副作用【默认状况是懒执行的,也就是说仅在侦听的源变更时才执行回调】watch 容许咱们:
懒执行副作用
更明确哪些状态的扭转会触发侦听器从新运行副作用
拜访侦听状态变动前后的值
侦听单个数据源
侦听器的数据源能够是一个领有返回值的 getter 函数,也能够是 ref:
// 侦听一个 getter
const state = reactive({count: 0})
watch(() => state.count,
(count, prevCount) => {/* ... */}
)
// 间接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {/* ... */})
侦听多个数据源
watcher 也能够应用数组来同时侦听多个源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {/* ... */})
与 watchEffect 共享的行为
watch 和 watchEffect 在进行侦听, 革除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入),副作用刷新机会 和 侦听器调试 等方面行为统一
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar],onInvalidate) => {
/* ... */
onInvalidate(() => {...})
},
{onTrigger(e) {
/* 副作用依赖批改 */
debugger
},
onTrack(e) {
/* 副作用依赖批改 */
debugger
},
})
生命周期钩子函数
与 2.x 版本生命周期绝对应的组合式 API
beforeCreate -> 应用 setup()
created -> 应用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
import {onMounted, onUpdated, onUnmounted} from 'vue'
setup() {onMounted(() => {console.log('mounted!')
})
onUpdated(() => {console.log('updated!')
})
onUnmounted(() => {console.log('unmounted!')
})
}
这些生命周期钩子注册函数只能在 setup() 期间同步应用,因为它们依赖于外部的全局状态来定位以后组件实例(正在调用 setup() 的组件实例), 不在以后组件下调用这些函数会抛出一个谬误。组件实例上下文也是在生命周期钩子同步执行期间设置的,因而,在卸载组件时,在生命周期钩子外部同步创立的侦听器和计算状态也将主动删除。新增的钩子函数
除了和 2.x 生命周期等效项之外,组合式 API 还提供了以下调试钩子函数:onRenderTracked
onRenderTriggered
两个钩子函数都接管一个 DebuggerEvent,与 watchEffect 参数选项中的 onTrack 和 onTrigger 相似:
export default {onRenderTracked(e){
debugger
// 查看有响应和追踪的依赖性
},
onRenderTriggered(e) {
debugger
// 查看哪个依赖性导致组件从新渲染
},
}
Vue 提供的内置组件
component 与 Vue2.x 统一
渲染一个“元组件”为动静组件。依 is 的值,来决定哪个组件被渲染。<!-- 动静组件由 vm 实例的 `componentId` property 管制 -->
<component :is="componentId"></component>
<!-- 也可能渲染注册过的组件或 prop 传入的组件 -->
<component :is="$options.components.child"></component>
transition 与 Vue2.x【根本】统一有差别
Props 新增:persisted - boolean 如果为 true,则示意这是一个转换,实际上不会插入 / 删除元素,而是切换显示 / 暗藏状态。transition 过渡挂钩被注入,但会被渲染器跳过。相同,自定义指令能够通过调用注入的钩子(例如 v -show)来管制过渡
enter-class----->enter-from-class
leave-class----->leave-from-class
事件
before-appear
transition-group 与 Vue2.x 统一
slot 与 Vue2.x 统一
teleport【新增组件】Props
to - string 必填属性,必须是一个无效的 query 选择器,或者是元素(如果在浏览器环境中应用)。中的内容将会被搁置到指定的指标元素中
<!-- 正确的 -->
<teleport to="#some-id" />
<teleport to=".some-class" />
/* 元素 */
<teleport to="[data-teleport]" />
<!-- 谬误的 -->
<teleport to="h1" />
<teleport to="some-string" />
disabled - boolean 这是一个可选项,做一个是能够用来禁用的性能,这意味着它的插槽内容不会挪动到任何中央,而是按没有 teleport 组件个别来出现【默认为 false】<teleport to="#popup" :disabled="displayVideoInline">
<h1>999999</h1>
</teleport>
留神,这将挪动理论的 DOM 节点,而不是销毁和从新创立,并且还将放弃任何组件实例是流动的。所有有状态 HTML 元素 (比方一个正在播放的视频) 将放弃它们的状态。【管制 displayVideoInline 并不是销毁重建,它放弃实例是存在的,不会被登记】
对于 Teleport 其余内容
Vue 激励咱们通过将 UI 和相干行为封装到组件中来构建 UI。咱们能够将它们彼此嵌套在一起,以构建形成应用程序 UI 的树
然而,有时组件模板的一部分逻辑上属于这个组件,而从技术角度来看,最好将这一部分模板移到 DOM 中的其余中央,放到 Vue 应用程序之外
一个常见的场景是创立一个蕴含全屏模态的组件。在大多数状况下,您心愿模态的逻辑驻留在组件中,然而模态框的定位问题很快就很难通过 CSS 解决,或者须要更改组件的组成
思考上面的 HTML 构造:<body>
<div style="position: relative;">
<h3>Tooltips with Vue 3 Teleport</h3>
<div>
<modal-button></modal-button>
</div>
</div>
</body>
让咱们看看 mode -button
该组件将有一个 button 元素来触发模态的关上,还有一个 div 元素,其类为.modal,它将蕴含模态的内容和一个自敞开按钮
const app = Vue.createApp({});
app.component('modal-button', {
template: `
<button @click="modalOpen = true">
Open full screen modal!
</button>
<div v-if="modalOpen" class="modal">
<div>
I'm a modal!
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
`,
data() {
return {modalOpen: false}
}
})
当在初始 HTML 构造中应用这个组件时,咱们能够看到一个问题——模态被出现在深嵌套的 div 中,模态的相对地位以父 div 绝对地位作为参考。Teleport 提供了一种洁净的形式,容许咱们管制 DOM 中心愿在哪个父节点下出现 HTML 片段,而不用诉诸全局状态或将其拆分为两个组件。让咱们批改咱们的 modal-button 来应用并通知 Vue "teleport this HTML to the"body"标签"。app.component('modal-button', {
template: `
<button @click="modalOpen = true">
Open full screen modal! (With teleport!)
</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a teleported modal!
(My parent is "body")
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
</teleport>
`,
data() {
return {modalOpen: false}
}
})
与 Vue 组件一起应用
如果蕴含一个 Vue 组件,它将依然是的父组件的逻辑子组件
const app = Vue.createApp({
template: `
<h1>Root instance</h1>
<parent-component />
`
})
app.component('parent-component', {
template: `
<h2>This is a parent component</h2>
<teleport to="#endofbody">
<child-component name="John" />
</teleport>
`
})
app.component('child-component', {props: ['name'],
template: `
<div>Hello, {{name}}</div>
`
})
在这种状况下,即便在不同的中央出现 child-component,它仍将是 parent-componen 的子组件【而不是爷爷组件】,并将从其父组件接管一个 name 的 props
这也意味着来自父组件的注入如预期的那样工作,并且子组件将嵌套在 Vue Devtools 的父组件之下,而不是放在理论内容挪动到的中央
对同一指标应用屡次 teleports
一个常见的用例场景是一个可重用的组件,该组件可能同时有多个流动实例。对于这种场景,多个组件能够将它们的内容挂载到雷同的指标元素。这个程序将是一个简略的附加—稍后的挂载将位于指标元素中较早的挂载之后。<teleport to="#modals">
<div>A</div>
</teleport>
<teleport to="#modals">
<div>B</div>
</teleport>
<!-- result-->
<div id="modals">
<div>A</div>
<div>B</div>
</div>
依赖注入 Provide / Inject
provide 和 inject 提供依赖注入,性能相似 2.x 的 provide/inject。两者都只能在以后流动组件实例的 setup() 中调用
例如,如果咱们想在根组件上提供一个 book name,并将其 inject 到子组件上
import {provide, inject} from 'vue'
const RootComponent = {setup() {provide('book', 'Vue 3 guide')
}
}
const MyBook = {setup() {
const book = inject(
'book',
'Eloquent Javascript' /* 选项的默认值,如果父组件不提供值就返回默认 */
)
return {book}
}
}
inject 承受一个可选的的默认值作为第二个参数。如果未提供默认值,并且在 provide 上下文中未找到该属性,则 inject 返回 undefined。如果咱们须要提供或注入多个值,咱们能够通过随后别离调用 provide 或 inject 来实现【屡次调用】import {provide, inject} from 'vue'
const RootComponent = {setup() {provide('book', 'Vue 3 guide')
provide('year', '2020')
}
}
const MyBook = {setup() {
const book = inject(
'book',
'Eloquent Javascript' /* 选项的默认值,如果父组件不提供值就返回默认 */
)
const year = inject('year')
return {
book,
year
}
}
}
注入的响应性
能够应用 ref 或 reactive 来保障 provided 和 injected 之间值的响应
import {ref, reactive} from 'vue'
// 提供者
setup() {
const book = reactive({
title: 'Vue 3 Guide',
author: 'Vue Team'
})
const year = ref('2020')
/* 提供 reactive 响应式 */
provide('book', book)
/* 提供 ref 响应式 */
provide('year', year)
}
// 消费者
setup() {const book = inject('book')
const year = inject('year')
/* 响应式 */
return {book, year}
}
当初,当提供者组件上的 book 或 year 发生变化时,咱们能够察看到它们在注入的组件上的变动。正告: 咱们不倡议扭转一个被注入的反馈性属性【子组件去批改数据流】,因为它会毁坏 Vue 的单向数据流。相同,尝试在提供值【父组件去批改】的中央扭转值,或者提供一个办法来扭转值
import {ref, reactive} from 'vue'
// in provider
setup() {
const book = reactive({
title: 'Vue 3 Guide',
author: 'Vue Team'
})
function changeBookName() {book.title = 'Vue 3 Advanced Guide'}
provide('book', book)
provide('changeBookName', changeBookName)
}
// in consumer
setup() {const book = inject('book')
const changeBookName = inject('changeBookName')
return {book, changeBookName}
}
指令
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-bind【Vue2.x 修饰符差别】修饰符
.prop 去除
.sync 去除
.camel 将 kebab-case attribute 名转换为 camelCase
v-model【Vue2.x 统一】v-slot【Vue2.x 统一】v-cloak【Vue2.x 统一】v-once【Vue2.x 统一】v-pre【Vue2.x 统一】v-is【新增】留神: 本节只影响在页面的 HTML 中间接编写 Vue 模板的状况
限度:原生 html 元素
应用:应用 in-DOM 模板时,该模板应恪守本机 HTML 解析规定。某些 HTML 元素(例如,,和)对能够在其中显示哪些元素有限度,而某些元素(例如,和)只能 呈现在某些其余元素内。解决办法是,咱们能够在这些元素上应用 v -is 指令【作用就是转成组件的名字】正告 v -is 性能 像一个动静 2.x :is 绑定 所以要依据注册的名称渲染组件,它的值应该是一个 JavaScript 字符串
<!-- 不正确的, 不会呈现任何渲染 -->
<tr v-is="blog-post-row"></tr>
<!-- 正确 -->
<tr v-is="'blog-post-row'"></tr>
全局 API
createApp
返回一个应用程序实例, 提供了一个应用程序上下文。应用程序实例挂载的整个组件树共享雷同的上下文
const app = Vue.createApp({})
参数
该函数接管一个根组件选项对象作为第一个参数
const app = Vue.createApp({data() {
return {...}
},
methods: {...},
computed: {...}
setup(){...}
...
})
应用第二个参数,咱们能够将根组件 props 传递给利用
<div id="app">
<!-- 这里将会显示 'Evan' -->
{{username}}
</div>
const app = Vue.createApp(
{props: ['username']
},
{username: 'Evan'}
)
h
返回“虚构节点”,通常缩写为 VNode: 一个简略的对象,它蕴含形容 Vue 应该在页面上渲染何种类型的节点的信息,包含对任何子节点的形容。你能够手动浏览 render functions
render() {return Vue.h('h1', {}, 'Some title')
}
参数
承受三个参数 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”或带有槽的对象。可选
const aaa = {
props: {someProp: String},
setup(props) {console.log(props, "dsadasdasddasds");
},
render() {
return h(
"h2",
// {Object}props
// 与 props,attributes 和 events 绝对应的对象
// 咱们将在 template 中应用。// 可选的。{style: {"font-size": "20px",
color: "#136"}},
[this.someProp,this.$slots.default()]);
}
};
app.component("anchored-heading", {render() {
return h(
/*
// {String | Object | Function | null}标签
// HTML 标记名称,组件,异步组件或 null。// 应用 null 将渲染正文。// 必填
*/
"h" + this.level, // tag name
// {Object}props
// 与 props,attributes 和 events 绝对应的对象
// 咱们将在 template 中应用。// 可选的。{},
// {String | Array | Object} children
// 应用 `h()` 构建的子级 VNode,// 或应用字符串获取“文本 VNodes”或
// 具备插槽的对象。// 可选的。[
"Some text comes first.",
h("h1", "A headline"),
h(aaa, {someProp: "foobar"})
] );},
});
Vue.h(
'a',
{
name: headingId,
href: '#' + headingId
},
this.$slots.default())
])
限度
VNodes 必须举世无双
组件树中的所有 vnode 必须是惟一的。这意味着上面的渲染函数是有效的
render() {const myParagraphVNode = Vue.h('p', 'hi')
return Vue.h('div', [
// 示意诧异 - 正本复制 VNodes!
myParagraphVNode, myParagraphVNode
])
}
如果您的确想屡次复制雷同的元素 / 组件,则能够应用工厂函数进行复制。例如,以下出现函数是出现 20 个雷同段落的完满无效办法:render() {
return Vue.h('div',
Array.apply(null, { length: 20}).map(() => {return Vue.h('p', 'hi')
})
)
}
用一般的 JavaScript 替换模板个性
v-if and v-for
在任何中央都能够用一般 JavaScript 轻松实现,Vue 渲染 functions 都不提供专有的代替计划。例如,在应用 v -if 和 v -for 的模板中
<ul v-if="items.length">
<li v-for="item in items">{{item.name}}</li>
</ul>
<p v-else>No items found.</p>
==>
props: ['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 道具在模板编译期间,咱们将不得不本人提供这些 props
props: ['modelValue'],
render() {
return Vue.h(SomeComponent, {
modelValue: this.modelValue,
'onUpdate:modelValue': value => this.$emit('update:modelValue', value)
})
}
v-on
咱们必须为事件处理程序提供一个适当的 prop 名称,例如,为了解决 click 事件,prop 名称应该是 onClick
render() {
return Vue.h('div', {onClick: $event => console.log('clicked', $event.target)
})
}
事件修饰符
对于.passive、.capture 和.once 事件修饰符,Vue 提供了处理程序的对象语法
render() {
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,因为咱们能够在处理程序中应用事件办法
render() {
return Vue.h('input', {
onKeyUp: event => {
// 如果收回事件的元素不存在,则停止事件绑定到的元素
if (event.target !== event.currentTarget) return
// 同时如果按下的键不是 enter 键 key (13)以及 shift 键没有按下
if (!event.shiftKey || event.keyCode !== 13) return
// 进行事件流传
event.stopPropagation()
// 阻止此元素的默认 keyup 处理程序
event.preventDefault()
// ...
}
})
}
Slots
你能够拜访插槽内容 this.$slots 在 VNodes 数组的
render() {
// `<div><slot></slot></div>`
return Vue.h('div', {}, this.$slots.default())
}
props: ['message'],
render() {
// `<div><slot :text="message"></slot></div>`
return Vue.h('div', {}, this.$slots.default({text: this.message}))
}
应用 render 函数将槽传递给子组件
render() {// `<div><child v-slot="props"><span>{{ props.text}}</span></child></div>`
return Vue.h('div', [Vue.h('child', {}, {
// 通过 `slots' 作为子对象
// in the form of {name: props => VNode | Array<VNode>}
default: (props) => Vue.h('span', props.text)
})
])
}
JSX
如果咱们要编写大量的渲染函数,编写这样的货色可能会让人感到痛苦
Vue.h(
'anchored-heading',
{level: 1},
[Vue.h('span', 'Hello'), 'world!']
)
特地是当模板版本相比之下如此简洁的时候
<anchored-heading :level="1"> <span>Hello</span> world! </anchored-heading>
这就是为什么有一个 Babel 插件能够在 Vue 中应用 JSX,让咱们回到更靠近模板的语法
import AnchoredHeading from './AnchoredHeading.vue'
new Vue({
el: '#demo',
render() {
return (<AnchoredHeading level={1}>
<span>Hello</span> world!
</AnchoredHeading>
)
}
})
defineComponent【组件】在实现方面,defineComponent 只会执行返回传递给它的对象的操作。然而,就类型而言,返回的值具备人工渲染性能,TSX 和 IDE 工具反对的构造函数的综合类型
参数
具备组件选项的对象
import {defineComponent} from 'vue'
const MyComponent = defineComponent({data() {return { count: 1}
},
methods: {increment() {this.count++}
}
})
defineAsyncComponent【异步组件】创立只在必要时加载的异步组件
参数
对于根本用法,defineAsyncComponent 能够承受返回 Promise 的工厂函数。当您从 serve 检索到组件定义时,应该调用 Promise 的解析回调。您还能够调用 reject(reason)来批示加载失败。import {defineAsyncComponent} from 'vue'
const AsyncComp = defineAsyncComponent(() =>
/* 或者 */
import('./components/AsyncComponent.vue')
/* 或者 */
new Promise((resolve, reject) => {
/* 能够 reject*/
resolve({template: '<div>I am async!</div>'})
})
)
app.component('async-component', AsyncComp)
在应用本地注册时,还能够间接提供返回 Promise 的函数
import {createApp, defineAsyncComponent} from 'vue'
createApp({
// ...
components: {AsyncComponent: defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
)
}
})
对于高级用法,defineAsyncComponent 能够承受一个对象
const AsyncComp = defineAsyncComponent({
// 工厂函数
loader: () => import('./Foo.vue')
// 加载异步组件时应用的组件
loadingComponent: LoadingComponent,
// 加载失败的时候应用的组件
errorComponent: ErrorComponent,
// 在显示加载组件之前提早。默认值:200 ms。delay: 200,
// 如果超时,将显示谬误组件
// 存在 timeout 并且超过这个工夫. 默认值: 无穷
timeout: 3000,
// 返回布尔值的函数,批示当加载器 promise rejects 时异步组件是否应该重试
retryWhen: error => error.code !== 404,
// 容许的最大重试次数
maxRetries: 3,
// 定义组件是否可挂载
suspensible: false
})
resolveComponent
正告 resolveComponent 只能在 render 或 setup 函数中应用。容许通过名称解析组件,如果它在以后应用程序实例中可用。如果找不到组件,返回组件或未定义组件
如果找不到组件,返回组件或未定义组件【组件】app.component('MyComponent', {/* ... */})
const MyComponent = resolveComponent('MyComponent')
resolveDynamicComponent【解析流动的组件 active】resolveDynamicComponent 只能在 render 或 setup 函数中应用。容许应用与 <component:is=""> 雷同的机制来解析组件。返回解析的组件或一个新创建的 VNode 以组件名称作为节点标记的。如果没有找到组件,会收回正告
resolveDirective
正告 resolveDirective 只能在 render 或 setup 函数中应用。容许通过名称解析指令,如果它在以后应用程序实例中可用。返回一个 Directive 或 当没有找到的时候,返回 undefined。app.directive('highlight', {})
render(){const highlightDirective = resolveDirective('highlight')
}
withDirectives
正告 withDirectives 只能在 render 或 setup 函数中应用。::: 容许利用指令到 VNode。返回一个带有利用指令的 VNode。const bar = resolveDirective('bar')
return withDirectives(h('div'), [[bar, this.y]
])
createRenderer *【待】nextTick
将回调提早到下一个 DOM 更新周期之后执行。在更改了一些数据以期待 DOM 更新之后立刻应用它
setup() {const message = ref('Hello!')
const changeMessage = async newMessage => {
message.value = newMessage
/* 期待 DOM 更新 */
await nextTick()
console.log('Now DOM is updated')
}
}
实例办法 methods
$watch
参数
{string | Function} source
{Function | Object} callback
{Object} [options]
{boolean} deep
{boolean} immediate
用法
察看组件实例上的响应式属性或 computed 函数的更改。应用回调获取到给定属性的新值和旧值。咱们只能通过顶级 data、prop 或 computed 的属性名作为字符串的模式传递。对于更简单的表达式或嵌套属性,应用函数代替。例子
const app = Vue.createApp({data() {
return {
a: 1,
b: 2,
c: {
d: 3,
e: 4
}
}
},
created() {
// 顶级属性名 a
this.$watch('a', (newVal, oldVal) => {// 做一些事})
// 察看监督单个嵌套属性
this.$watch(() => this.c.d,
(newVal, oldVal) => {// 做一些事}
)
// 监控简单表达式
this.$watch(
// 每当表达式 `this.a + this.b` 产生不同的后果时
// 处理程序将被调用。这就如同咱们在看 computed 属性
// 而不定义计算属性自身
() => this.a + this.b,
(newVal, oldVal) => {// 做一些事}
)
}
})
当监督的值是对象或数组时,对其属性或元素的任何更改都不会触发监督程序,因为它们援用雷同的对象 / 数组
const app = Vue.createApp({data() {
return {
article: {text: 'Vue is awesome!'},
comments: ['Indeed!', 'I agree']
}
},
created() {this.$watch('article', () => {console.log('Article changed!')
})
this.$watch('comments', () => {console.log('Comments changed!')
})
},
methods: {
// 这些办法不会触发观察者,因为咱们仅更改了对象 / 数组的属性,
// 并不是 Object/Array 自身
changeArticleText() {this.article.text = 'Vue 3 is awesome'},
addComment() {this.comments.push('New comment')
},
// 这些办法会触发观察者,因为咱们残缺替换了对象 / 数组
changeWholeArticle() {this.article = { text: 'Vue 3 is awesome'}
},
clearComments() {this.comments = []
}
}
})
$watch 返回一个勾销监督的函数,该函数进行触发回调
const unwatch = vm.$watch('a', cb)
// later, teardown the watcher
unwatch()
Option: deep
检测对象外部嵌套的值更改,须要在 options 参数中传入 deep: true。留神,侦听数组渐变并不需要这样做。vm.$watch('someObject', callback, {deep: true})
vm.someObject.nestedValue = 123
// 触发回调
Option: immediate
在选项中传入 immediate: true 将立刻用表达式的以后值触发回调
vm.$watch('a', callback, {immediate: true})
//“callback”被立刻触发,以后值为“a”
请留神,应用 immediate 选项,您将无奈在第一个回调调用中勾销监督给定的属性。// 这个例子是谬误的
const unwatch = vm.$watch(
'value',
function() {doSomething()
unwatch()},
{immediate: true}
)
如果你依然想在回调中调用一个 unwatch 函数,你应该首先查看它的可用性
const unwatch = vm.$watch(
'value',
function() {doSomething()
if (unwatch) {unwatch()
}
},
{immediate: true}
)
$emit【统一】$forceUpdate【统一】$nextTick【统一】实例 property
vm.$data【统一】vm.$props【统一】vm.$el【统一】vm.$options【统一】vm.$parent【统一】vm.$root【统一】vm.$slots【统一】vm.$refs【统一】vm.$attrs【统一】废除:
vm.$children
vm.$slots
vm.$scopedSlots
vm.$isServer
vm.$listeners
选项 / 组合
mixins【统一】extends【统一】provide / inject【统一】parent【废除】setup【新增】详情见上
选项 / 资源
directives【统一】components【统一】filters【废除】选项 / 数据
data【统一】props【统一】computed【统一】methods【统一】watch【统一】emits【新增】详情
能够从组件收回的自定义事件的 list/hash。它具备基于数组的简略语法和容许配置事件验证的代替的基于对象的语法。在基于对象的语法中,每个属性的值能够为 null 或验证函数。验证函数将接管传递给 emit 调用的其余参数。例如,如果调用 this.emit 调用的其余参数。例如,如果调用 this.emit 调用的其余参数。例如,如果调用 this.emit('foo',1),则 foo 的相应验证器将接管参数 1。验证器函数应返回一个布尔值,以批示事件参数是否无效。const app = Vue.createApp({})
// 数组语法
app.component('todo-item', {emits: ['check'],
created() {this.$emit('check')
}
})
// 对象语法
app.component('reply-form', {
emits: {
// 有效
click: null,
// 无效
submit: payload => {if (payload.email && payload.password) {return true} else {console.warn(`Invalid submit event payload!`)
return false
}
}
}
})
提醒 在 emit 选项中列出的事件将不会被组件的根元素继承。
vue 都是函数
createApp
const app = createApp(App)
app.use(store)
app.use(router)
app.mount('#app')
传了两个属性
v-model:selectKeys = "selectKeys"
import {reactive,toRef} from 'vue
export default{setup(props,ctx){
// 默认执行一次
// 页面应用 state.selectKeys
const state = reactive({ //attr slots emit
selectKeys:0
})
//1. 间接应用
return {selectKeys:state.selectKeys}
//2. 导出, 页面上间接应用,数据响应式还带解构
return {...toRefs(state)
}
onMounted(()=>{})
}
}
监控路由变动
import {reactive,toRef,watch} from 'vue
import {useRoute} from 'vue-router'
export default{setup(props,ctx){
const state = reactive({ //attr slots emit
selectKeys:0
})
//1.watch 监控路由变动
watch(()=>route.path,(newValue)=>{state.selectKeys = [newValue]
})
//2.computed 监控路由变动
const selectKeys = computed(()=>{return [route.path]
})
return {selectKeys}
}
}
vuex
import {reactive,toRef,watch ,computed} from 'vue'
import {useRoute} from 'vue-router'
export default{setup(props,ctx){const route = userRoute()
const store = useStore()
const state = reactive({ //attr slots emit
selectKeys:0
})
//1.watch 监控路由变动
watch(()=>route.path,(newValue)=>{state.selectKeys = [newValue]
})
//2.computed 监控路由变动
const selectKeys = computed(()=>{return [route.path]
})
//ref 把一般值变成包装后的构造, 将属性变成响应式
// ref(store.getters.allTime)
return {
selectKeys,
allTime:ref(store.getters.allTime)
}
}
}
//store.js
import {createStore} from 'vuex
export default {state:{},
getters:{allTime:()=>{return 0}
},
mutations:{ },
actions:{ },
modules:{}}
组件通信
import {reactive,toRef,watch ,computed} from 'vue'
import {useRoute} from 'vue-router'
import moment from 'moment'
export default{setup(props,ctx){
const state = reactive({
form:{date:moment(Date.now()).format('YYYY-MM-DD')
}
})
// 办法函数
const onSubmit =()=>{
// 传给父组件
this.$emit('handlePlan',state.form)
}
return {...toRefs(state),
onSubmit
}
}
}
// 父组件
<Child @handlePlan="handlePlan" />
import {reactive,toRef,watch ,computed} from 'vue'
import {useRoute} from 'vue-router'
import moment from 'moment'
export default{setup(props,ctx){
const state = reactive({
form:{date:moment(Date.now()).format('YYYY-MM-DD')
}
})
const handlePlan = (plan)=>{console.log(plan)
}
return {handlePlan}
}
}
环境变量
VUE_APP_URL = 'http://www.xxx.com:3000'
封装 api
import axios from 'axios
const instance = axios.create({
baseURL:process.env.VUE_APP_URL,
timeout:3000
})
instance.interceptors.request.use((config)=>{return config})
instance.interceptors.response.use((res)=>{return res.data.data},err=>{return Promise.reject(err)
})
export function request(opts){return instance(opts)
}
//request.js
import {request} from '../utils/axios'
export function getPlanList(){return request({url:'/plan',method:'get'})
}
export function addPlan(data){return request({url:'/plan',method:'post',data})
}
export function deletePlan(){return request({url:'/plan',method:'delete',params:{id}})
}
//action_type.js
export const SET_PLAN_LIST = 'SET_PLAN_LIST'
export const ADD_PLAN = 'ADD_PLAN'
export const DELETE_PLAN = 'DELETE_PLAN'
//store.js
import {createStore} from 'vuex'
export * as types from './action_type'
import * as api from './request'
export default {state:{},
getters:{allTime:()=>{return 0}
},
mutations:{[type.ADD_PLAN](state,payload){state.planList = [...state.planList,payload]
},
[type.DELETE_PLAN](state,payload){
state.planList.filter(item=>{return item._id !=payload._id})
},
[type.SET_PLAN_LIST](state,payload){},},
actions:{
//restful api 依据不同办法返回不同的资源
async [type.ADD_PLAN]({commit},payload){let plan = await api.addPlan(payload)
commit(type.ADD_PLAN,plan)
},
async [type.DELETE_PLAN]({commit},payload){let plan = await api.deletePlan(payload)
commit(type.DELETE_PLAN,plan)
},
async [type.SET_PLAN_LIST]({commit},payload){let plan = await api.getPlanList(payload)
commit(type.SET_PLAN_LIST,plan)
},
},
modules:{}}
应用数据
import {reactive,toRef,watch ,onMounted,onUpdated,compile,computed} from 'vue'
import {useStore} from 'vuex'
import moment from 'moment'
import * as types from '@/store/action_types'
export default{setup(props,ctx){const store = useStore()
// const state = reactive({
// planList:store.state.planList // 这样取的是默认值
// })
onMounted(()){store.dispatch(types.SET_PLAN_LIST)
}
// 工夫格式化办法
const formatDate = (value)=>{return moment(value).format('YYYY-MM-DD')
}
return {...toRefs(state.store),
formatDate
}
}
}
简版 vue
//1. 创立虚构节点,将虚构节点转化为实在节点
//2. 组件的实现 setup
//3.reactive api 实现 effect
//4.diff 算法
//5.vite
let {render} = Vue
const state = {count:0}
const vnode = {
tag:'div',
props:{color:'red'},
children:[
{
tag:'p',
props:{color:'blue},
children:['vue@3- 计数器']
},
{
tag:'p',
props:{onClick:()=>{alert(state.count)
}
}
children:['vue@3- 计数器']
}
]
}
render(vnode,app)
export function render(vnode,container){
// 渲染页面的办法叫 patch
//1. 第一次渲染 2.dom-diff
patch(null,vnode,container)
}
/**
* n1 老的虚构节点
* n2 新的虚构节点
* container 容器
*/
function patch(n1,n2,container){
// 组件的虚构节点是一个对象,tag 是一个对象
// 如果是组件,tag 可能是个对象
// 后续 diff 能够执行这个办法
if(typeof n2.tag ==='string'){
// 标签
mountElement(n2,container)
}else if(typeof n2.tag==='object'){}}
function mountElement(vnode,container){const { tag,children,props} = vnode
// 虚构节点和实在节点做映射关系
let el = (vnode.el = nodeOps.createElement(tag))
if(Array.isArray(children)){mountChild(children,el)
}else{nodeOps.hostSetElementText(el,children)
}
container.insert(el,container)
}
function mountChild(children,container){for(var i=0;i<children.length;i++){let child = children[i]
patch(null,child,container)
}
}
// 节点操作方法
exoprt const nodeOps = {
// 插入元素节点
insert(child,parent,anchor){if(anchor){parent.insertBefore(child,anchor)
}else{parent.appendChild(child)
}
},
// 移除节点
remove(child){
const parent = child.parentNode;
parent && parent.removeChild(child)
},
// 创立节点
createElement(tag){return document.createElement(tag)
},
// 设置文本内容
hostSetElementText(el,text){el.textContent = text}
}
1.Vue3 尝鲜
1.Vue3 文档【Vue2 迁徙 Vue3】