极速体验vue3.0新特性 - 曾小晖
发布于 2个月前 作者 zengxiaohui2019 3441 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

极速体验vue3.0新特性

vue3.0环境搭建

快速开始

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

或者,它可以使用具有getset功能的对象来创建可读写的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

  • 基本用法

    监听数据变化,当监听的对象发生变化时运行。可监听reactiveref

    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 inside setup())
    // 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.$watchand watch选项的默认行为是惰性的:它将急切地执行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 and onTrigger options can be used to debug a watcher’s behavior.

    • onTrack will be called when a reactive property or ref is tracked as a dependency
    • onTrigger 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 and onTrigger 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类似于onTrackonTrigger观察者的选项:

    export default {
      onRenderTriggered(e) {
        debugger
        // inspect which dependency is causing the component to re-render
      }
    }
    

provide & inject

provideinject启用类似于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.

回到顶部