/composition-api

Composition API plugin for Vue 2

Primary LanguageTypeScriptMIT LicenseMIT

@vue/composition-api

Vue 2 plugin for Composition API

npm GitHub Workflow Status Minzipped size

English | 中文Composition API Docs

Installation

NPM

npm install @vue/composition-api
# or
yarn add @vue/composition-api

You must install @vue/composition-api as a plugin via Vue.use() before you can use the Composition API to compose your component.

import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'

Vue.use(VueCompositionAPI)
// use the APIs
import { ref, reactive } from '@vue/composition-api'

💡 When you migrate to Vue 3, just replacing @vue/composition-api to vue and your code should just work.

CDN

Include @vue/composition-api after Vue and it will install itself automatically.

<script src="https://cdn.jsdelivr.net/npm/vue@2.6"></script>
<script src="https://cdn.jsdelivr.net/npm/@vue/composition-api@1.0.0-beta.11"></script>

@vue/composition-api will be exposed to global variable window.VueCompositionAPI.

const { ref, reactive } = VueCompositionAPI

TypeScript Support

TypeScript version >3.5.1 is required

To let TypeScript properly infer types inside Vue component options, you need to define components with defineComponent

import { defineComponent } from '@vue/composition-api'

export default defineComponent({
  // type inference enabled
})

JSX/TSX

To make JSX/TSX work with @vue/composition-api, check out babel-preset-vca-jsx by @luwanquan.

SSR

Even if there is no definitive Vue 3 API for SSR yet, this plugin implements the onServerPrefetch lifecycle hook that allows you to use the serverPrefetch hook found in the classic API.

import { onServerPrefetch } from '@vue/composition-api'

export default {
  setup(props, { ssrContext }) {
    const result = ref()

    onServerPrefetch(async () => {
      result.value = await callApi(ssrContext.someId)
    })

    return {
      result,
    }
  }
}

Limitations

✅ Support     ❌ Not Supported

Ref Unwrap

Unwrap is not working with Array index.

Should NOT store ref as a direct child of Array
const state = reactive({
  list: [ref(0)],
})
// no unwrap, `.value` is required
state.list[0].value === 0 // true

state.list.push(ref(1))
// no unwrap, `.value` is required
state.list[1].value === 1 // true
Should NOT use ref in a plain object when working with Array
const a = {
  count: ref(0),
}
const b = reactive({
  list: [a], // `a.count` will not unwrap!!
})

// no unwrap for `count`, `.value` is required
b.list[0].count.value === 0 // true
const b = reactive({
  list: [
    {
      count: ref(0), // no unwrap!!
    },
  ],
})

// no unwrap for `count`, `.value` is required
b.list[0].count.value === 0 // true
Should always use ref in a reactive when working with Array
const a = reactive({
  list: [
    reactive({
      count: ref(0),
    }),
  ]
})
// unwrapped
a.list[0].count === 0 // true

a.list.push(
  reactive({
    count: ref(1),
  })
)
// unwrapped
a.list[1].count === 1 // true
⚠️ `set` workaround for adding new reactive properties

⚠️ Warning: set does NOT exist in Vue 3. We provide it as a workaround here, due to the limitation of Vue 2.x reactivity system. In Vue 2, you will need to call set to track new keys on an object(similar to Vue.set but for reactive objects created by the Composition API). In Vue 3, you can just assign them like normal objects.

import { reactive, set } from '@vue/composition-api'

const a = reactive({
  foo: 1
})

// add new reactive key
set(a, 'bar', 1)

Template Refs

✅ String ref && return it from setup()
<template>
  <div ref="root"></div>
</template>

<script>
  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>
✅ String ref && return it from setup() && Render Function / JSX
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,
    }
  },
  render() {
    // with JSX
    return () => <div ref="root" />
  },
}
❌ Function ref
<template>
  <div :ref="el => root = el"></div>
</template>

<script>
  export default {
    setup() {
      const root = ref(null)

      return {
        root,
      }
    },
  }
</script>
❌ Render Function / JSX in setup()
export default {
  setup() {
    const root = ref(null)

    return () =>
      h('div', {
        ref: root,
      })

    // with JSX
    return () => <div ref={root} />
  },
}
⚠️ $refs accessing workaround

⚠️ Warning: The SetupContext.refs won't exist in Vue 3.0. @vue/composition-api provide it as a workaround here.

If you really want to use template refs in this case, you can access vm.$refs via SetupContext.refs

export default {
  setup(initProps, setupContext) {
    const refs = setupContext.refs
    onMounted(() => {
      // the DOM element will be assigned to the ref after initial render
      console.log(refs.root) // <div/>
    })

    return () =>
      h('div', {
        ref: 'root',
      })

    // with JSX
    return () => <div ref="root" />
  },
}

You may also need to augment the SetupContext when working with TypeScript:

import Vue from 'vue'

declare module '@vue/composition-api' {
  interface SetupContext {
    readonly refs: { [key: string]: Vue | Element | Vue[] | Element[] }
  }
}

Reactive

⚠️ reactive() mutates the original object

reactive uses Vue.observable underneath which will mutate the original object.

💡 In Vue 3, it will return an new proxy object.

Watch

onTrack and onTrigger are not available in WatchOptions
watch(() => {
    /* ... */
}, {
  immediate: true,
  onTrack() {}, // not available
  onTrigger() {}, // not available
})

createApp

⚠️ createApp() is global

In Vue 3, createApp() is introduced to provide context(plugin, components, etc.) isolation between app instances. Due the the design of Vue 2, in this plugin, we provide createApp() as a forward compatible API which is just an alias of the global.

const app1 = createApp(RootComponent1)
app1.component('Foo', Foo) // equivalent to Vue.component('Foo', Foo)
app1.use(VueRouter) // equivalent to Vue.use(VueRouter)

const app2 = createApp(RootComponent2)
app2.component('Bar', Bar) // equivalent to Vue.use('Bar', Bar)

shallowReadonly

⚠️ shallowReadonly() will create a new object and with the same root properties, new properties added will not be readonly or reactive.

💡 In Vue 3, it will return an new proxy object.

props

⚠️ toRefs(props.foo.bar) will incorrectly warn when acessing nested levels of props. ⚠️ isReactive(props.foo.bar) will return false.
defineComponent({
  setup(props) {
    const { bar } = toRefs(props.foo) // it will `warn`
    
    // use this instead 
    const { foo } = toRefs(props)
    const a = foo.value.bar
  }
})

Missing APIs

The following APIs introduced in Vue 3 are not available in this plugin.

  • readonly
  • defineAsyncComponent
  • onRenderTracked
  • onRenderTriggered
  • isProxy

Reactive APIs in data()

❌ Passing ref, reactive or other reactive apis to data() would not work.
export default {
  data() {
    return {
      // will result { a: { value: 1 } } in template
      a: ref(1),
    }
  },
}

Performance Impact

Due the the limitation of Vue2's public API. @vue/composition-api inevitably introduced some extract costs. It shouldn't bother you unless in extreme environments.

You can check the benchmark results for more details.