# vue3_api **Repository Path**: 120901/vue3_api ## Basic Information - **Project Name**: vue3_api - **Description**: No description available - **Primary Language**: JavaScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-08-20 - **Last Updated**: 2021-11-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Vue3.0 ## setup `setup 函数是一个新的组件选项。作为在组件内使用Composition API的入口点` * 调用时机 - 创建组件实例,然后初始化props, 紧接着就调用setup函数。从生命周期钩子的视角来看,它会在beforeCreate钩子之前被调用 * 模板中使用 - 如果setup返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文 * 渲染函数 / JSX中使用 - setup也可以返回一个函数,函数中也能使用当前setup函数作用域中的响应式数据 * 参数 - 该函数接收props作为其第一个参数(不能使用解构的方法使用) - 第二个参数是context,提供了一个上下文对象,选择性的暴露了一些property attrs/slots/emit(可以解构使用) ``` export default { props: { name: String }, setup (props) { console.log(props.name) } } ``` **注意props对象是响应式的,watchEffer或watch会观察和响应props的更新** ``` export default { props: { name: String }, setup (props) { watchEffect(() => { console.log(`name is:` + props.name) }) } } // 解构的写法会散失响应性 setup({ name }) // xxxx ``` * this的用法 - this在setup()中不可用 * 类型定义 ``` interface Data { [key: string]: unknown } interface SetupContext { attr: Data slots: Slots emit: (event: string, ...args: unknown[]) => void } function setup(props: Data, context: SetupContext): Data ``` ## 响应式系统API ### reactive - 接收一个普通对象然后返回该普通对象的响应式代理。 等同于Vue.observable() ### 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 ``` ``` * 作为响应式对象的属性访问 - 当ref作为reactive对象的property被访问或修改时,也将自动解套value值,其行为类似普通属性 ``` const count = ref(0) const state = reactive({ count }) console.log(state.count) // 0 state.count = 1 console.log(count.value) // 1 ``` - 如果将一个新的ref分配给现有的ref,将替换旧的ref ``` const otherCount = ref(2) state.count = otherCount console.log(state.count) // 2 console.log(count.value) // 1 ``` **注意当嵌套在reactive中时,ref才会解套。从Arrya或者Map等原生集合类中访问ref时,不会自动解套,需要使用.value** * 类型定义 ``` interface Ref { value: T } function ref(value: T): Ref // 有时我们可能需要为ref做一个较为复杂的类型标注。我们可以通过在调用ref时传递泛型参数来覆盖默认推导 const foo = ref('foo') // foo的类型为: Ref foo.value = 123 // 能够通过 ``` ### computed * 传入一个getter函数,返回一个默认不可手动修改的ref对象 ``` const count = ref(1) const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2 plusOne.value++ // error ``` * 传入一个拥有get和set函数的对象,创建一个可手动修改的计算状态 ``` const count = ref(1) const plusOne = couputed({ get: () => count.value + 1, set: (val) => { count.value = val -1 } }) plusOne.value = 1 console.log(count.value) // 0 ``` * 类型定义 ``` // 只读的 function computed(getter: ()=> T):Readonly>> // 可更改的 function computed(options: { get: () => T set: (value: T) => void }) ``` ### readonly * 传入一个对象(响应式或普通)或ref, 返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。 ``` const original = reactive({ count: 0 }) const copy = readonly(original) watchEffect(() => { // 依赖追踪 console.log(copy.count) }) // original上的修改会触发copy上的侦听 original.count++ // 无法修改copy并会被警告 copy.count++ // warning ``` ### watchEffecct * 立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数 ``` const count = ref(0) watchEffect(() => console.log(count.value)) setTimeout(() => { count.value++ // -> 打印出 1 }, 100) ``` * 停止侦听: 当watchEffect在组件的setup()函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。 ``` // 在一些情况下,也可以显式调用返回值以停止侦听 const stop = watch(() => { /* ... */ }) // 之后 stop() ``` * 清除副作用: 有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除(即完成之前状态已经改变了)。所以侦听副作用传入的函数可以接收一个onInvalidate函数作入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发: - 副作用即将重新执行时 - 侦听器被停止(如果在setup()或生命周期钩子函数中使用了watchEffect,则在卸载组件时) ``` watchEffect((onInvalidate) => { const token = performAsyncOperation(id.value) onInvalidate(() => { // id改变时 或 停止侦听时 // 取消之前的异步操作 token.cacel() }) }) ``` **在执行数据请求时,副作用函数往往是一个异步函数** ``` const data = ref(null) watchEffect(async () => { data.value = await fetchData(props.id) }) ``` * 副作用刷新时机 - Vue的响应式系统会存在副作用函数,并异步地刷新他们,这样可以避免同一个tick中多个状态改变导致的不必要的重复调用。在核心的具体实现中,组件的更新函数也是一个被侦听的副作用。当一个用户定义的副作用函数进入队列时,会在所有的组件更新后执行: ``` // 同步运行 watchEffect( () => {}, {flush: 'sync'} ) // 组件更新前执行 watchEffect( () => {}, {flush: 'pre'} ) ``` ### watch * 完全等效于2.x this.$watch。watch需要侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调 * 对比watchEffect,watch允许我们: - 懒执行函数 - 更明确哪些状态的改变会触发侦听器重新运行执行函数 - 访问侦听状态变化前后的值 * 侦听单个数据源 - 侦听器的数据源可以是一个拥有返回值的getter函数,也可以是ref ``` // getter const state = reacitve({ count: 0 }) watch( () => state.count, (count, prevCount) => { /* ... */ } ) // ref const count = ref(0) watch(count, (count, prevCount) => { /*...*/ }) ``` * 侦听多个数据源 - 数组的形式 ``` watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar])=> {...}) ``` * 与watchEffect的共同点 - watch 和 watchEffect 在停止侦听, 清除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入),副作用刷新时机 和 侦听器调试 等方面行为一致. ## 生命周期钩子函数 * 与2.x版本生命周期相对应的组合式API - ~~beforeCreate~~ ==> setup() - ~~created~~ ==> setup() - ~~beforeMount~~ ==> onBeforeMount - ~~mounted~~ ==> onMounted - ~~beforeUpdate~~ ==> onBeforeUpdate - ~~updated~~ ==> onUpdated - ~~beforeDestroy~~ ==> onBeforeUnmount - ~~destroyed~~ ==> onUnmounted - ~~errorCaptured~~ ==> onErrorCaptured * 新增的钩子函数 - onRenderTracked - onRenderTriggered - 都接收一个DebuggerEvent ``` export default { onRenderTracked (e) { debugger } // 检查哪个依赖性导致组件重新渲染 } ``` * hooks使用方式: 只能在setup内调用 ``` import { onMounted, onUpdated, onUnmounted } from 'vue' const MyComponent = { setup () { onMounted(() => { console.log('mounted!') }) onUpdated(() => { console.log('updated!') }) onUnmounted(() => { console.log('unmounted!') }) } } ``` ## 依赖注入 * provide和inject提供依赖注入,功能类似2.x的provide/inject,都只能在当前活动组件实例的setup中调用 ``` import { provide, inject } from 'vue' cont ThemeSymbol = Symbol() const Ancestor = { setup () { provide(ThemeSymbol, 'dark') } } const Descendent = { setup () { const theme = inject(ThemeSymbol, 'light' /* optional default value */ ) } } ``` **inject接受一个可选的默认值作为第二个参数。如果未提供默认值,并且在provide上下文中未找到该属性,则inject返回undefined** * 注入的响应式 - 可以使用ref来保证provide和inject之间值得响应 ``` // 提供者 const themeRef = ref('dark) provide(ThemeSymbol, themeRef) // 使用者 const theme = inject(ThemeSymbol, ref('light')) watchEffect(() => { console.log(`theme set to: ${theme.value}`) }) ``` **如果注入一个响应式对象,则它得状态变化也可以被侦听** ## 模板refs ``` ``` **模板 ref 仅在渲染初始化后才能访问** * 在v-for中使用 ```
``` ## 响应式系统工具集 * unref: 如果参数是一个ref则返回它得value,否则返回参数本身。它是val = isRef(val) ? val.value : val的语法糖 ``` function useFoo(x: number | Ref) { const unwrapped = unref(x) // unwrapped 一定是 number 类型 } ``` * toRef: 可以用来为一个reactive对象的属性创建一个ref。这个ref可以被传递并且能够保持响应性 ``` const state = reactive({ foo: 1, bar: 2 }) const fooRef = toRef(state, 'foo') fooRef.value++ console.log(state.foo) // 2 state.foo++ console.log(fooRef.value) // 3 // 将一个prop中的属性作为ref传给组合逻辑函数 setup(props) { useSomeFeature(toRef(props, 'foo')) } ``` * toRefs: 把一个响应式对象转换成普通对象,该普通对象的每个property都是一个ref,和响应式对象property一一对应 ``` const state = reactive({ foo: 1, bar: 2 }) const stateAsRefs = toRefs(state) /* stateAsRefs的类型如下: { foo: Ref, bar: Ref } */ // ref对象与原属性的引用是"链接"上的 state.foo++ console.log(stateAsRefs.foo.value) // 2 stateAsRefs.foo.value++ console.log(state.foo) // 3 ``` **让组件可以解构/扩展(...操作符)使用** ``` function useFeatureX() { const state = reactive({ foo: 1, bar: 2 }) } return toRefs(state) export default { setup() { // 可以解构,不会丢失响应性 const { foo, bar } = useFeatureX() return { foo, bar } } } ``` * isRef: 检查一个值是否为一个ref对象 * isProxy: 检查一个对象是否由reactive或者readyonly方法创建的代理 * isReactive: 检查一个对象是否是由reactive创建的响应式代理 * isReadonly: 检查一个对象是否是由readonly创建的只读代理 ## 高级响应式系统API ......