极速体验vue3.0新特性
vue3.0环境搭建
- 安装nodejs 推荐 node 12.14.1稳定版
- 获取vue3.0代码 https://github.com/vuejs/vue-next-webpack-preview
- 开发工具:vscode 或 webStrom
快速开始
npm install 或 yarn install
npm run dev
demo效果
访问http://localhost:8080/出现以下效果,恭喜你顺利搭建好3.0开发环境。
目录说明:
基本上和vue2.0保持一致,没有什么变化
API 参考
引入API
import { ref, reactive } from 'vue'
setup
setup
是新的组件选项。它充当在组件内部使用入口点。
-
调用时间
setup
创建组件实例时,在初始组件解析后立即调用。在生命周期方面,它在beforeCreate
之后,created
之前被调用(2.x)。 -
模板使用
如果
setup
返回一个对象,则该对象的属式将合并到组件模板的渲染上下文中:<template> <div>{{ count }} {{ object.foo }}</div> </template> <script> import { ref, reactive } from 'vue' export default { setup() { const count = ref(0) const object = reactive({ foo: 'bar' }) // expose to template return { count, object } } } </script>
请注意,
setup
在模板中访问时,从ref返回的引用将自动解包,因此模板中使用不需要.value
。在setup
中访问必须需要.value
-
Render/ JSX用法
``setup` 还可以返回一个render函数:
import { h, ref, reactive } from 'vue' export default { setup() { const count = ref(0) const object = reactive({ foo: 'bar' }) return () => h('div', [ count.value, object.foo ]) } }
-
接收 props 数据
第一个接收的是props数据:
export default { props: { name: String }, setup(props) { console.log(props.name) } }
props
数据可以用watch
方法来监听:export default { props: { name: String }, setup(props) { watch(() => { console.log(`name is: ` + props.name) }) } }
在开发过程中,
props
对象不可更改(如果用户代码尝试对其进行更改,则会发出警告).第二个参数提供了一个上下文对象,该对象公开了先前在2.x API中使用
this
公开的属式:// 2.0 中 this.$emit() const MyComponent = { setup(props, context) { console.log(context) context.attrs context.slots context.emit context.ref } }
context中的对象使用方式和2.0中的保持一致:
attrs
并且slots
是内部组件实例上对应值的代理。这样可以确保即使在更新后它们也始终显示最新值,以便我们可以对它们进行结构解析而不必担心访问陈旧的引用:
const MyComponent = {
setup(props, { attrs }) {
// a function that may get called at a later stage
function onClick() {
console.log(attrs.foo) // guaranteed to be the latest reference
}
}
}
-
this
用法this
里面没有setup()
。由于setup()
是在解析2.x选项之前调用的,因此this
内部setup()
(如果可用)的行为将与this
其他2.x选项完全不同。避免this
进入的另一个原因setup()
是对于初学者来说非常常见的陷阱:setup() { function onClick() { this // not the `this` you'd expect! } }
reactive
取得一个对象并返回原始对象的响应式代理。这等效于2.x的Vue.observable()
。
const obj = reactive({ count: 0 })
响应式转换是“深度”的:它影响所有嵌套的属式。在基于ES2015代理的实现中,返回的代理不等于原始对象。建议仅与响应式代理一起使用,并避免依赖原始对象。
ref
接受一个值并返回一个响应式且可变的ref对象。ref对象具有.value
指向内部值的单个属式。
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
如果将一个对象分配为ref的值,则该reactive
方法会使该对象具有高度的响应式。
-
模板访问
当ref作为渲染上下文(从中返回的对象
setup()
)的属式返回并在模板中进行访问时,它会自动展开为内部值。无需.value
在模板中附加:<template> <div>{{ count }}</div> </template> <script> export default { setup() { return { count: ref(0) } } } </script>
-
响应式对象中的访问
当ref被访问或作为响应对象的属式进行更改时,它会自动展开为内部值,因此其行为类似于普通属式:
const count = ref(0) const state = reactive({ count }) console.log(state.count) // 0 state.count = 1 console.log(count.value) // 1
请注意,
ref
内部值可以被覆盖/替换:const otherCount = ref(2) state.count = otherCount console.log(state.count) // 2 console.log(count.value) // 1
isRef
检查值是否是ref
的引用对象。
const unwrapped = isRef(foo) ? foo.value : foo
toRefs
将响应对象转换为普通对象,其中结果对象上的每个属式都是指向原始对象中相应属式的ref;常用于reactive
解构/扩展时使用。
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
Type of stateAsRefs:
{
foo: Ref<number>,
bar: Ref<number>
}
*/
// The ref and the original property is "linked"
state.foo++
console.log(stateAsRefs.foo) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
toRefs
从组合函数返回反应对象时,此函数很有用,以便使用组件可以对返回的对象进行解构/扩展而不会失去响应式:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
// logic operating on state
// convert to refs when returning
return toRefs(state)
}
export default {
setup() {
// can destructure without losing reactivity
const { foo, bar } = useFeatureX()
return {
foo,
bar
}
}
}
computed
使用getter函数,并为getter返回的值返回一个不变的响应式ref对象。
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // error
或者,它可以使用具有get
和set
功能的对象来创建可读写的ref对象。
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: val => { count.value = val - 1 }
})
plusOne.value = 1
console.log(count.value) // 0
readonly
接受一个对象(响应式或普通)或ref并返回对原始对象的只读(和响应式)代理。可以理解为代理。
const original = reactive({ count: 0 })
const copy = readonly(original)
watch(() => {
// works for reactivity tracking
console.log(copy.count)
})
// mutating original will trigger watchers relying on the copy
original.count++
// mutating the copy will fail and result in a warning
copy.count++ // warning!
watch
-
基本用法
监听数据变化,当监听的对象发生变化时运行。可监听
reactive
或ref
const count = ref(0) watch(() => console.log(count.value)) // -> logs 0 setTimeout(() => { count.value++ // -> logs 1 }, 100)
-
监听指定数据源
在某些情况下,我们可能还想:
- 查看初始值和改变后的值。
watch
接受两个箭头函数,第一个:监听的数据,第二个:返回的新旧值
- 查看初始值和改变后的值。
// watching a getter const state = reactive({ count: 0 }) watch( () => state.count, (count, prevCount) => { /* … */ } )
// directly watching a ref
const count = ref(0)
watch(
count,
(count, prevCount) => { /* ... */ }
)
```
-
监听多个数据源
可以使用数组同时监视多个源:
watch( [fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { /* ... */ console.log(foo) // 新的foo console.log(bar) // 新的bar console.log(prevFoo) // 旧的foo console.log(prevBar) // 旧的bar } )
-
停止监听
当
watch
一个组件的过程中调用setup()
函数,watch
继承该组件的生命周期,当组件卸载将自动停止。在其他情况下,
watch
返回停止句柄id,可以调用该句柄以停止监听程序:const stop = watch(() => { /* ... */ }) // later stop()
-
Side Effect Cleanup
Sometimes the watcher callback will perform async side effects that need to be invalidated when the watched value changes. The watcher callback receives a cleanup registrator function that can be used to register a cleanup callback. The cleanup callback is called when:
- the watcher is about to re-run
- the watcher is stopped (i.e. when the component is unmounted if
watch
is used insidesetup()
)
// cleanup passed as 1st argument to simple usage watch(onCleanup => { const token = performAsyncOperation(id.value) onCleanup(() => { // id has changed or watcher is stopped. // invalidate previously pending async operation token.cancel() }) }) // cleanup passed as 3rd argument in with-source usage watch(idRef, (id, oldId, onCleanup) => { const token = performAsyncOperation(id) onCleanup(() => { // id has changed or watcher is stopped. // invalidate previously pending async operation token.cancel() }) })
We are registering cleanup via a passed-in function instead of returning it from the callback (like React
useEffect
) because the return value is important for async error handling. It is very common for the watcher callback to be an async function when performing data fetching:const data = ref(null) watch(getId, async (id) => { data.value = await fetchData(id) })
An async function implicitly returns a Promise, but the cleanup function needs to be registered immediately before the Promise resolves. In addition, Vue relies on the returned Promise to automatically handle potential errors in the Promise chain.
-
Callback Flush Timing
Vue’s reactivity system buffers watcher callbacks and flush them asynchronously to avoid unnecessary duplicate invocation when there are many state mutations happening in the same “tick”. Internally, a component’s update function is also a watcher callback. When a user watcher callback is queued, it is always invoked after all component render functions:
<template> <div>{{ count }}</div> </template> <script> export default { setup() { const count = ref(0) watch(() => { console.log(count.value) }) return { count } } } </script>
In this example:
- The count will not be logged synchronously.
- The callback will first be called after the component has mounted.
- When
count
is mutated, the callback will be called after the component has updated.
Rule of thumb: when a watcher callback is invoked, the component state and DOM state are already in sync.
In cases where a watcher callbacks needs to be invoked synchronously or before component updates, we can pass an additional options object with the
flush
option (default is'post'
):// fire synchronously watch(() => { /* ... */ }, { flush: 'sync' }) // fire before component updates watch(() => { /* ... */ }, { flush: 'pre' })
-
延迟调用(只在数据改变之后载调用)
在2.x中,
this.$watch
andwatch
选项的默认行为是惰性的:它将急切地执行getter,但仅在第一次更改后才触发回调。这导致在监视程序回调和生命周期挂钩(例如mounted
)中都需要重复逻辑。watch
此处提出的API通过急于调用回调来避免这种重复。如果您希望使用2.x行为,则可以使用以下lazy
选项:watch( () => state.foo, foo => console.log('foo is ' + foo), { lazy: true } )
Note the
lazy
option only works when using the getter + callback format, since it does not make sense for the single callback usage. -
Debugging
The
onTrack
andonTrigger
options can be used to debug a watcher’s behavior.onTrack
will be called when a reactive property or ref is tracked as a dependencyonTrigger
will be called when the watcher callback is triggered by the mutation of a dependency
Both callbacks will receive a debugger event which contains information on the dependency in question. It is recommended to place a
debugger
statement in these callbacks to interactively inspect the dependency:watch(() => { /* ... */ }, { onTrigger(e) { debugger } })
onTrack
andonTrigger
only works in development mode.
生命周期钩子
可以使用直接导入的onXXX
功能注册生命周期钩子:
import { onMounted, onUpdated, onUnmounted } from 'vue'
const MyComponent = {
setup() {
onMounted(() => {
console.log('mounted!')
})
onUpdated(() => {
console.log('updated!')
})
onUnmounted(() => {
console.log('unmounted!')
})
}
}
这些生命周期钩子注册功能只能在期间同步使用setup()
(只能在setup()
中调用),因为它们依赖于内部全局状态来定位当前活动实例(当前setup()
正在被调用的组件实例)。在没有当前活动实例的情况下调用它们将导致错误。
-
2.x生命周期选项和3.0Composition API之间的映射
-> 使用beforeCreate
setup()
-> 使用created
setup()
beforeMount
->onBeforeMount
mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeDestroy
->onBeforeUnmount
destroyed
->onUnmounted
errorCaptured
->onErrorCaptured
-
新钩子
除了2.x生命周期等效项之外,3.0Composition API还提供了以下调试挂钩:
onRenderTracked
onRenderTriggered
两个钩子都收到
DebuggerEvent
类似于onTrack
和onTrigger
观察者的选项:export default { onRenderTriggered(e) { debugger // inspect which dependency is causing the component to re-render } }
provide
& inject
provide
并inject
启用类似于2.x provide/inject
选项的依赖项注入。两者都只能在setup()
当前活动实例期间调用。
import { provide, inject } from 'vue'
const ThemeSymbol = Symbol()
const Ancestor = {
setup() {
provide(ThemeSymbol, 'dark')
}
}
const Descendent = {
setup() {
const theme = inject(ThemeSymbol, 'light' /* optional default value */)
return {
theme
}
}
}
inject
接受可选的默认值作为第二个参数。如果未提供默认值,并且在Provide上下文中找不到该属性,则inject
返回undefined
。
-
使用响应式
为了保持提供的值和注入的值之间的响应式,可以使用ref:
// in provider const themeRef = ref('dark') provide(ThemeSymbol, themeRef) // in consumer const theme = inject(ThemeSymbol, ref('light')) watch(() => { console.log(`theme set to: ${theme.value}`) })
模板引用
使用Composition API时,响应式引用和模板引用的概念是统一的。为了获得对模板中元素或组件实例的引用,我们可以像往常一样声明ref并从中返回setup()
:
<template>
<div ref="root"></div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
console.log(root.value) // <div/>
})
return {
root
}
}
}
</script>
用作模板ref的ref的行为与其他任何ref一样:它们是响应式的,可以传递到组合函数中(或从中返回)。
-
Render/ JSX的用法
export default { setup() { const root = ref(null) return () => h('div', { ref: root }) // with JSX return () => <div ref={root}/> } }
-
内部用法
v-for
Composition API template refs do not have special handling when used inside
v-for
. Instead, use function refs (new feature in 3.0) to perform custom handling:<template> <div v-for="(item, i) in list" :ref="el => { divs[i] = el }"> {{ item }} </div> </template> <script> import { ref, reactive, onBeforeUpdate } from 'vue' export default { setup() { const list = reactive([1, 2, 3]) const divs = ref([]) // make sure to reset the refs before each update onBeforeUpdate(() => { divs.value = [] }) return { list, divs } } } </script>
defineComponent
该功能仅用于类型推断。为了让TypeScript知道应该将对象视为组件定义,以便可以推断传递给的数据的类型,这是必需的setup()
。这是无操作行为的明智选择。它需要一个组件定义并按原样返回参数。
import { defineComponent } from 'vue'
export default defineComponent({
props: {
foo: String
},
setup(props) {
props.foo // <- type: string
}
})
Alternatively if your component does not use any option other than setup
itself, you can pass the function directly:
import { defineComponent } from 'vue'
// provide props typing via argument annotation.
export default defineComponent((props: { foo: string }) => {
props.foo
})
When using SFCs with VSCode + Vetur, the export will be implicitly wrapped so there is no need for the users to do this themselves.