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 } = Vuelet 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 => onBeforeMountmounted => onMountedbeforeUpdate => onBeforeUpdateupdated => onUpdatedbeforeDestroy => onBeforeUnmountdestroyed => onUnmountederrorCaptured => 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.xVue.prototype.$http = () => {}// Vue3const 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"中,绑定值为 2oldValue:指令绑定的前一个值,仅在 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 办法被同一个插件屡次调用,插件将只会被装置一次。setupsetup 函数是一个新的组件选项。作为在组件内应用 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
reactivedesc: 接管一个一般对象而后返回该一般对象的响应式代理【等同于 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 对象领有一个指向外部值的繁多属性 .valueconst 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)])// 这里须要 .valueconsole.log(arr[0].value)const map = reactive(new Map([['foo', ref(0)]]))// 这里须要 .valueconsole.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 创立的另一个代理包裹了一层,那么同样也会返回 trueimport { reactive, isReactive, readonly } from 'vue'const state = reactive({ name: 'John' })// 用readonly创立一个只读响应式对象plainconst plain = readonly({ name: 'Mary'})//readonly创立的,所以isReactive为falseconsole.log(isReactive(plain)) // -> false // reactive创立的响应式代理对象包裹一层readonly,isReactive也是true,isReadonly也是trueconst stateCopy = readonly(state)console.log(isReactive(stateCopy)) // -> true
isReadonly查看一个对象是否是由 readonly 创立的只读代理reactive高级响应式零碎APItoRaw返回由 reactive 或 readonly 办法转换成响应式代理的一般对象。这是一个还原办法,可用于长期读取,拜访不会被代理/跟踪,写入时也不会触发更改。不倡议始终持有原始对象的援用【**不倡议赋值给任何变量**】。请审慎应用被toRaw之后的对象是没有被代理/跟踪的的一般对象const foo = {}const reactiveFoo = reactive(foo)console.log(toRaw(reactiveFoo) === foo) // trueconsole.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没有被标记为(永远不会转为响应式代理),导致最初的值一个reactiveshallowReactive只为某个对象的公有(第一层)属性创立浅层的响应式代理,不会对“属性的属性”做深层次、递归地响应式代理,而只是保留原样【第一层是响应式代理,深层次只保留原样(不具备响应式代理)】const state = shallowReactive({ foo: 1, nested: { bar: 2, },})// 变更 state 的自有属性是响应式的【第一档次响应式】state.foo++// ...但不会深层代理【深层次不是响应式】(渲染性能)isReactive(state.nested) // falsestate.nested.bar++ // 非响应式
shallowReadonly相似于shallowReactive,区别是:第一层将会是响应式代理【第一层批改属性会失败】,属性为响应式深层次的对象属性能够批改,属性不是响应式const state = shallowReadonly({ foo: 1, nested: { bar: 2, },})// 变更 state 的自有属性会失败state.foo++// ...然而嵌套的对象是能够变更的isReadonly(state.nested) // falsestate.nested.bar++ // 嵌套属性仍然可批改
ref 响应式零碎工具集unref unref是val = isRef(val) ? val.value : val 的语法糖unref(ref(0))===unref(0)===0 返回numberfunction useFoo(x: number | Ref<number>) { const unwrapped = unref(x) // unwrapped 肯定是 number 类型}
toReftoRef 能够用来为一个 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) // 2state.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) // 2stateAsRefs.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 高级响应式零碎APIcustomRef用于自定义一个 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是shallowshallow.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 = 1console.log(count.value) // 0
watchEffect
立刻执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时从新运行该函数computed与watchEffect区别:computed计算属性可通过setup return,再模板中应用,watchEffect不能;computed能够应用多个,并且对多个属性进行不同的响应计算,watchEffect会存在副作用const count = ref(0)watchEffect(() => console.log(count.value))// -> 打印出 0setTimeout(() => { 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:// 侦听一个 getterconst state = reactive({ count: 0 })watch( () => state.count, (count, prevCount) => { /* ... */ })// 间接侦听一个 refconst 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 版本生命周期绝对应的组合式 APIbeforeCreate -> 应用 setup()created -> 应用 setup()beforeMount -> onBeforeMountmounted -> onMountedbeforeUpdate -> onBeforeUpdateupdated -> onUpdatedbeforeDestroy -> onBeforeUnmountdestroyed -> onUnmountederrorCaptured -> onErrorCapturedimport { onMounted, onUpdated, onUnmounted } from 'vue'setup() { onMounted(() => { console.log('mounted!') }) onUpdated(() => { console.log('updated!') }) onUnmounted(() => { console.log('unmounted!') }) }
这些生命周期钩子注册函数只能在 setup() 期间同步应用, 因为它们依赖于外部的全局状态来定位以后组件实例(正在调用 setup() 的组件实例), 不在以后组件下调用这些函数会抛出一个谬误。组件实例上下文也是在生命周期钩子同步执行期间设置的,因而,在卸载组件时,在生命周期钩子外部同步创立的侦听器和计算状态也将主动删除。新增的钩子函数除了和 2.x 生命周期等效项之外,组合式 API 还提供了以下调试钩子函数:onRenderTrackedonRenderTriggered两个钩子函数都接管一个 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-classleave-class----->leave-from-class事件before-appeartransition-group 与 Vue2.x 统一slot 与 Vue2.x 统一teleport 【新增组件】Propsto - 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 / Injectprovide 和 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 providersetup() { 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 consumersetup() { 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 名转换为 camelCasev-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>
全局APIcreateApp返回一个应用程序实例,提供了一个应用程序上下文。应用程序实例挂载的整个组件树共享雷同的上下文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 functionsrender() { 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-modelv-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名称应该是onClickrender() { 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 watcherunwatch()
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【统一】实例 propertyvm.$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调用的其余参数。 例如,如果调用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都是函数
createAppconst app = createApp(App)app.use(store)app.use(router)app.mount('#app')
传了两个属性
v-model:selectKeys = "selectKeys"
import {reactive,toRef } from 'vueexport 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 'vueimport {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.jsimport {createStore} from 'vuexexport 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.jsimport {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.jsexport const SET_PLAN_LIST = 'SET_PLAN_LIST'export const ADD_PLAN = 'ADD_PLAN'export const DELETE_PLAN = 'DELETE_PLAN'//store.jsimport {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.vitelet { render} = Vueconst 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】