-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Composition API reference #97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a lot of information in this PR, I feel I might have missed something so I would do another pass on it. (this week sometime)
src/api/composition-api.md
Outdated
} | ||
``` | ||
|
||
`attrs` and `slots` are proxies to the corresponding values on the internal component instance. This ensures they always expose the latest values even after updates so that we can destructure them without worrying accessing a stale reference: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's little unclear. We should unambiguously state that context
can be destructured but attrs
and slots
cannot be. Destructuring e.g. setup(props, { attrs: { name } })
would break attrs
reactivity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I've split this statement onto 2 parts, so hopefully, it's clearer now:
Unlike props
, context
argument can be destructured safely so attrs
and slots
would always expose the latest values even after updates:
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
}
}
}
However, attrs
and slots
themselves cannot be destructured without losing reactivity:
const MyComponent = {
setup(props, { attrs: { foo } }) {
function onClick() {
console.log(foo) // won't be the latest reference as we lost `attrs` reactivity with destructuring
}
}
}
src/api/composition-api.md
Outdated
|
||
- **Usage of `this`** | ||
|
||
**`this` is not available inside `setup()`.** Since `setup()` is called before other component options are resolved, `this` inside `setup()` (if made available) will behave quite differently from `this` in other options. Making it available will likely cause confusions when using `setup()` along other 2.x options. Another reason for avoiding `this` in `setup()` is a very common pitfall for beginners: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel we can drop the if made available as this hypothetical situation is not providing any value. And the previous statement seems enough reasoning for the unavailability of this
in setup()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds pretty weird to me that we're saying "this
is not available" at first and right after that "[this] will behave quite differently," which implies that it's still available. AFAIK, this
is always available. Whether it's the Vue instance or the parent context (global/window) is another story.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I rephrased the explanation so now we rather say this
won't be a reference to Vue instance:
-
Usage of
this
Inside
setup()
,this
won't be a reference to Vue instance Sincesetup()
is called before other component options are resolved,this
insidesetup()
will behave quite differently fromthis
in other options. This might cause confusions when usingsetup()
along other Options API. Another reason for avoidingthis
insetup()
is a very common pitfall for beginners:setup() { const that = this function onClick() { console.log(this !== that) // not the `this` you'd expect! } }
src/api/composition-api.md
Outdated
|
||
- **Access in Templates** | ||
|
||
When a ref is returned as a property on the render context (the object returned from `setup()`) and accessed in the template, it automatically unwraps to the inner value. There is no need to append `.value` in the template: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This stands correct for inline event handlers, which can be confusing, given that the same event handler in setup()
would have to use .value
. I feel we should add an example for this scenario.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added this explanation:
However, if we decide to change the inline event handler on button click to the component method declared in setup
, we need to remember that ref
is not unwrapped there:
<template>
<div>
<span>{{ count }}</span>
<button @click="increment">Increment count</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
count.value++ // ref is not unwrapped outside the template, so we need to use .value here
}
return {
count,
increment
}
}
}
</script>
|
||
```html | ||
<template> | ||
<div ref="root"></div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of context but :ref="root"
behaves differently and it's an easy pitfall to use v-bind
with ref
as it feels so natural. I feel we should be explicit about it and convey that ref="root"
is equivalent to :ref="el => root = el"
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a warning there:
:::warning
Please note that ref
will behave differently when used with v-bind
directive. ref="root"
would be equivalent to :ref="el => root = el"
:::
However, I don't feel good about it as it adds an unnecessary cognitive load (at least for me it does) 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oof yeah that is confusing... 😕
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that's template ref
🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind :D 🤦
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, I don't feel good about it as it adds an unnecessary cognitive load (at least for me it does) 😅
True! Should we have separate caveats or faq or common pitfalls section?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I feel this should be handled in runtime. This confusion is an unnecessary burden on users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@znck I probably should remove it for now
src/api/composition-api.md
Outdated
|
||
Check if an object is a reactive proxy created by `reactive`. | ||
|
||
It also returns `true` if the proxy is created by `readonly`, but is wrapping another proxy created by `reactive`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This deserves an example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added an extended example here:
isReactive
Check if an object is a reactive proxy created by reactive
.
import { reactive, isReactive } from 'vue'
export default {
setup() {
const state = reactive({
name: 'John'
})
console.log(isReactive(state)) // -> true
}
}
It also returns true
if the proxy is created by readonly
, but is wrapping another proxy created by reactive
.
import { reactive, isReactive, readonly } from 'vue'
export default {
setup() {
const state = reactive({
name: 'John'
})
// readonly proxy created from plain object
const plain = readonly({
name: 'Mary'
})
console.log(isReactive(plain)) // -> false
// readonly proxy created from reactive proxy
const stateCopy = readonly(state)
console.log(isReactive(stateCopy)) // -> true
}
}
Co-authored-by: Rahul Kadyan <hi@znck.me>
Co-authored-by: Rahul Kadyan <hi@znck.me>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏👏👏
src/api/composition-api.md
Outdated
|
||
- **Usage of `this`** | ||
|
||
**`this` is not available inside `setup()`.** Since `setup()` is called before other component options are resolved, `this` inside `setup()` (if made available) will behave quite differently from `this` in other options. Making it available will likely cause confusions when using `setup()` along other 2.x options. Another reason for avoiding `this` in `setup()` is a very common pitfall for beginners: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds pretty weird to me that we're saying "this
is not available" at first and right after that "[this] will behave quite differently," which implies that it's still available. AFAIK, this
is always available. Whether it's the Vue instance or the parent context (global/window) is another story.
src/api/composition-api.md
Outdated
import { ref, reactive } from 'vue' | ||
|
||
export default { | ||
setup() { | ||
const count = ref(0) | ||
const object = reactive({ foo: 'bar' }) | ||
|
||
// expose to template | ||
return { | ||
count, | ||
object | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm quite sure we don't indent the first level of code inside <script>
and <style>
😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @NataliaTepluhina I'm thinking moving Reactivity-related APIs (Reactivity APIs + Reactivity Utilities + Advanced Reactivity APIs) as a independent page, before Composition API page. Because it's not only useful to Composition API, but also to Options API, even out of Vue Components.
For example:
export default {
data() {
return {
somethingReadonly: readonly(foo),
somethingWontChange: toRaw(bar)
}
}
}
or:
// shared.js
export const someGlobalData = reactive(baz)
<script>
import { someGlobalData } from './shared'
export default {
setup() {
return {
x: someGlobalData
}
}
}
</script>
For others like provide
, inject
and lifecycle hooks, they must be used inside setup
. So I think is make sense to leave them here.
WDYT 😅
Thanks.
src/api/composition-api.md
Outdated
`setup` can also return a render function, which can directly make use of reactive state declared in the same scope: | ||
|
||
```js | ||
import { h, ref, reactive } from 'vue' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we introduce h
somewhere in our API Reference? It seems not appeared so far.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Jinjiang the suggestion about Reactivity APIs makes perfect sense to me! Will separate it from the Composition API
As for h
, I was planning on smth like Global Vue API
to have h
, defineComponent
etc there. Not sure about naming though 🤔
…t into composition-api-reference
9215661
to
a4b6494
Compare
@pikax sorry, I failed to add you to reviewers list on the 1st review 🤦♀️ Would be happy if you could take a look! |
@NataliaTepluhina for Reactivity APIs I have some further ideas. When I'm trying to find the usage of these APIs for a special purpose like read-only data, I need to know I think for guidelines it's better to separate things into basic and advanced parts. But for references, maybe it's better to separate them into groups for different cases or purposes, like:
Does that make more sense? ... or just don't group them and assemble them into one page together? Thanks 😄 |
@Jinjiang What if we place them on the same page ( |
I personally think that’s great 🙂 @NataliaTepluhina |
@Jinjiang I still ended up dividing it onto 3 pages just for the sake of the effective sidebar usage: if we place them to the same page, some sections will go to Also, a huge thanks for these suggestions! It makes lots of sense in terms of structuring the API properly 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job with all the edits and updates @NataliaTepluhina!
This is a very dense section and I have an instinct to come up with a similar approach to the Essentials of Vue.js so we can gradually build people up for a conceptually heavy topic, but it will take some time for me to come up with the proposal.
Nice work!
|
||
```html | ||
<template> | ||
<div ref="root"></div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oof yeah that is confusing... 😕
@NataliaTepluhina that looks much better to me :-) Just one more little struggled idea: how about change the title "Proxy-related APIs" to "Basic" directly? Because:
WDYT? :-) |
@Jinjiang done! 😅 |
|
||
> This section uses [single-file component](TODO: SFC) syntax for code examples | ||
|
||
## `setup` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this section the same as https://deploy-preview-97--vue-next.netlify.app/api/options-composition.html#setup ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes and I am still not sure where we should keep it (because it's a component option but it's also a special option
}) | ||
``` | ||
|
||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if I'm being blind, but I can't understand this paragraph with the example context, shouldn't we use onInvalidate
on the async effect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pikax would something like this work?
const data = ref(null)
watchEffect(async onInvalidate => {
data.value = await fetchData(props.id)
onInvalidate(() => {...})
})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that case you would only register onInvalidate
after the fetch is done, is useful if you do something like:
export default defineComponent({
props: {
id: Number,
},
setup(props) {
const data = ref(null);
watchEffect(async (onInvalidate) => {
let controller = new AbortController();
onInvalidate(() => {
if (controller) {
controller.abort();
console.log("aborted");
}
});
const response = await fetch(`https://swapi.dev/api/people/${props.id}`, {
signal: controller.signal,
mode: "cors",
});
data.value = await response.json();
// clear the controller
controller = null;
});
return {
data,
};
},
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewing again, and on my first pass I misunderstood what this example is all about, probably add a gotcha and be more explicit of using onInvalidate
after the promise is resolved might cause unexpected issues. Or add the correct way of doing it, ie register the onInvalidate
before the promise resolve.
NOTE: Sorry for my misunderstanding
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pikax no need to be sorry! It wasn't clear indeed.
I fixed an example and added a comment
@NataliaTepluhina looks great. no more from my side. :-) |
@pikax thank you for the detailed review! I've addressed your comments, would you mind taking a look again? 😅 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎊 👏 looks great!!
Thanks everyone for the review! I am merging this beast so if we have any fixes, let's handle them in the follow-up PRs |
This is mostly a copy of https://vue-composition-api-rfc.netlify.app/api.html with few subtle changes. I believe the reference there is sufficient enough (maybe sometimes too detailed). Would be happy to hear your thoughts!