From 471d0adaec9619d922a05076d2f3ae4ccff0049b Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 26 Dec 2023 16:20:35 +0800 Subject: [PATCH 01/13] feat: production error codes --- .vitepress/config.ts | 3 ++- src/api/application.md | 6 ++++- src/api/composition-api-lifecycle.md | 4 ++++ src/api/options-lifecycle.md | 4 ++++ src/error-reference/ErrorsTable.vue | 34 ++++++++++++++++++++++++++++ src/error-reference/errors.data.ts | 17 ++++++++++++++ src/error-reference/index.md | 30 ++++++++++++++++++++++++ 7 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/error-reference/ErrorsTable.vue create mode 100644 src/error-reference/errors.data.ts create mode 100644 src/error-reference/index.md diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 3d72e387bb..6ad3eb2cd4 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -17,6 +17,7 @@ const nav: ThemeConfig['nav'] = [ { text: 'Quick Start', link: '/guide/quick-start' }, // { text: 'Style Guide', link: '/style-guide/' }, { text: 'Glossary', link: '/glossary/' }, + { text: 'Error Reference', link: '/error-reference/' }, { text: 'Vue 2 Docs', link: 'https://v2.vuejs.org' @@ -701,7 +702,7 @@ export default defineConfigWithTheme({ markdown: { config(md) { md.use(headerPlugin) - // .use(textAdPlugin) + // .use(textAdPlugin) } }, diff --git a/src/api/application.md b/src/api/application.md index 5f355123f8..b3c790e6df 100644 --- a/src/api/application.md +++ b/src/api/application.md @@ -285,7 +285,7 @@ Execute a callback with the current app as injection context. - **Details** - Expects a callback function and runs the callback immediately. During the synchronous call of the callback, `inject()` calls are able to look up injections from the values provided by the current app, even when there is no current active component instance. The return value of the callback will also be returned. + Expects a callback function and runs the callback immediately. During the synchronous call of the callback, `inject()` calls are able to look up injections from the values provided by the current app, even when there is no current active component instance. The return value of the callback will also be returned. - **Example** @@ -374,6 +374,10 @@ Assign a global handler for uncaught errors propagating from within the applicat - Custom directive hooks - Transition hooks + :::tip + In production, the 3rd argument (`info`) will be a shortened code instead of the full information string. You can find the code to string mapping in the [Production Error Code Reference](/error-reference/#runtime-errors). + ::: + - **Example** ```js diff --git a/src/api/composition-api-lifecycle.md b/src/api/composition-api-lifecycle.md index 637c4faa9d..ad76b6485b 100644 --- a/src/api/composition-api-lifecycle.md +++ b/src/api/composition-api-lifecycle.md @@ -206,6 +206,10 @@ Registers a hook to be called when an error propagating from a descendant compon The hook receives three arguments: the error, the component instance that triggered the error, and an information string specifying the error source type. + :::tip + In production, the 3rd argument (`info`) will be a shortened code instead of the full information string. You can find the code to string mapping in the [Production Error Code Reference](/error-reference/#runtime-errors). + ::: + You can modify component state in `errorCaptured()` to display an error state to the user. However, it is important that the error state should not render the original content that caused the error; otherwise the component will be thrown into an infinite render loop. The hook can return `false` to stop the error from propagating further. See error propagation details below. diff --git a/src/api/options-lifecycle.md b/src/api/options-lifecycle.md index d4f18648ff..5ce9cd5e6a 100644 --- a/src/api/options-lifecycle.md +++ b/src/api/options-lifecycle.md @@ -195,6 +195,10 @@ Called when an error propagating from a descendant component has been captured. The hook receives three arguments: the error, the component instance that triggered the error, and an information string specifying the error source type. + :::tip + In production, the 3rd argument (`info`) will be a shortened code instead of the full information string. You can find the code to string mapping in the [Production Error Code Reference](/error-reference/#runtime-errors). + ::: + You can modify component state in `errorCaptured()` to display an error state to the user. However, it is important that the error state should not render the original content that caused the error; otherwise the component will be thrown into an infinite render loop. The hook can return `false` to stop the error from propagating further. See error propagation details below. diff --git a/src/error-reference/ErrorsTable.vue b/src/error-reference/ErrorsTable.vue new file mode 100644 index 0000000000..b278c1a9fe --- /dev/null +++ b/src/error-reference/ErrorsTable.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/error-reference/errors.data.ts b/src/error-reference/errors.data.ts new file mode 100644 index 0000000000..d8044adf01 --- /dev/null +++ b/src/error-reference/errors.data.ts @@ -0,0 +1,17 @@ +import { defineLoader } from 'vitepress' +import { errorMessages } from 'vue/compiler-sfc' +// @ts-expect-error internal api +import { ErrorTypeStrings } from 'vue' + +function filterEmptyMsg(data: Record) { + return Object.fromEntries(Object.entries(data).filter(([_, msg]) => msg)) +} + +export default defineLoader({ + load() { + return { + compiler: filterEmptyMsg(errorMessages), + runtime: filterEmptyMsg(ErrorTypeStrings) + } + } +}) diff --git a/src/error-reference/index.md b/src/error-reference/index.md new file mode 100644 index 0000000000..c9d4e8e86c --- /dev/null +++ b/src/error-reference/index.md @@ -0,0 +1,30 @@ + + +# Production Error Code Reference {#error-reference} + +## Runtime Errors {#runtime-errors} + +In production builds, the 3rd argument passed to the following error handler APIs will be a short code instead of the full information string: + +- [`app.config.errorHandler`](/api/application.html#app-config-errorhandler) +- [`onErrorCaptured`](/api/composition-api-lifecycle.html#onerrorcaptured) (Composition API) +- [`errorCaptured`](/api/options-lifecycle.html#errorcaptured) (Options API) + +The following table maps the codes to their original full information strings. + + + +## Compiler Errors {#compiler-errors} + +The following table provides a mapping of the production compiler error codes to their original messages. + + From 9ea436abd33ef44053ff71c7dd0467d3efc93bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Tue, 14 Nov 2023 05:02:51 +0800 Subject: [PATCH 02/13] feat: add defineModel in sfc-script-setup.md --- src/api/sfc-script-setup.md | 52 +++++++++++++++++++++++++++++++++--- src/error-reference/index.md | 6 ++--- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/api/sfc-script-setup.md b/src/api/sfc-script-setup.md index e14e4b62b8..4b9003b3d1 100644 --- a/src/api/sfc-script-setup.md +++ b/src/api/sfc-script-setup.md @@ -227,6 +227,52 @@ const props = withDefaults(defineProps(), { This will be compiled to equivalent runtime props `default` options. In addition, the `withDefaults` helper provides type checks for the default values, and ensures the returned `props` type has the optional flags removed for properties that do have default values declared. +## defineModel() {#definemodel} + +This macro can be used to declare a two-way binding prop that can be consumed via `v-model` from the parent component, and it can be declared and mutated like a ref. This will declare a prop with the same name and a corresponding `update:propName` event. + +If the first argument is a literial string, it will be used as the prop name; Otherwise the prop name will default to `"modelValue"`. In both cases, you can also pass an additional object which will be used as the prop's options. + +```vue + + + +``` + +### Local mode + +The options object can also specify an additional option, `local`. When set to `true`, the ref can be locally mutated even if the parent did not pass the matching `v-model`, essentially making the model optional. + +```ts +// local mutable model, can be mutated locally +// even if the parent did not pass the matching `v-model`. +const count = defineModel('count', { local: true, default: 0 }) +``` + +### Provide value type {#provide-value-type} + +Like `defineProps` and `defineEmits`, `defineModel` can also receive a type argument to specify the type of the model value: + +```ts +const modelValue = defineModel() +// ^? Ref + +// default model with options, required removes possible undefined values +const modelValue = defineModel({ required: true }) +// ^? Ref +``` + ## defineExpose() {#defineexpose} Components using ` - - ``` -### Local mode +### Modifiers and Transformers -The options object can also specify an additional option, `local`. When set to `true`, the ref can be locally mutated even if the parent did not pass the matching `v-model`, essentially making the model optional. +To access modifiers used with the `v-model` directive, we can destructure the return value of `defineModel()` like this: -```ts -// local mutable model, can be mutated locally -// even if the parent did not pass the matching `v-model`. -const count = defineModel('count', { local: true, default: 0 }) +```js +const [modelValue, modelModifiers] = defineModel() + +// corresponds to v-model.trim +if (modelModifiers.trim) { + // ... +} +``` + +Usually, we need to conditionally transform the value read from or synced back to the parent when a modifier is present. We can achieve this via the `get` and `set` transformer options: + +```js +const [modelValue, modelModifiers] = defineModel({ + // get() omitted as it is not needed here + set(value) { + if (modelModifiers.trim) { + return value.trim() + } + return value + } +}) ``` -### Provide value type {#provide-value-type} +### Usage with TypeScript {#usage-with-typescript} -Like `defineProps` and `defineEmits`, `defineModel` can also receive a type argument to specify the type of the model value: +Like `defineProps` and `defineEmits`, `defineModel` can also receive type arguments to specify the types of the model value and the modifiers: ```ts const modelValue = defineModel() @@ -271,6 +291,9 @@ const modelValue = defineModel() // default model with options, required removes possible undefined values const modelValue = defineModel({ required: true }) // ^? Ref + +const [modelValue, modifiers] = defineModel() +// ^? Record<'trim' | 'uppercase', true | undefined> ``` ## defineExpose() {#defineexpose} diff --git a/src/guide/components/v-model.md b/src/guide/components/v-model.md index 91a648c426..9a28773a7b 100644 --- a/src/guide/components/v-model.md +++ b/src/guide/components/v-model.md @@ -1,7 +1,93 @@ # Component v-model {#component-v-model} +## Basic Usage + `v-model` can be used on a component to implement a two-way binding. +
+ +Starting in Vue 3.4, the recommended approach to achieve this is using the [`defineModel()`](/api/sfc-script-setup#definemodel) macro: + +```vue + + + + +``` + +The parent can then bind a value with `v-model`: + +```vue-html + + +``` + +The value returned by `defineModel()` is a ref. It can be accessed and mutated like any other ref, except that it acts as a two-way binding between a parent value and a local one: + +- Its `.value` is synced with the value bound by the parent `v-model`; +- When it is mutated by the child, it causes the parent bound value to be updated as well. + +This means you can also bind this ref to a native input element with `v-model`, making it straightforward to wrap native input elements while providing the same `v-model` usage: + +```vue + + + +``` + +[Playground Example](https://play.vuejs.org/#eNqFUtFKwzAU/ZWYl06YLbK30Q10DFSYigq+5KW0t11mmoQknZPSf/cm3eqEsT0l555zuefmpKV3WsfbBuiUpjY3XDtiwTV6ziSvtTKOLNZcFKQ0qiZRnATkG6JB0BIDJen2kp5iMlfSOlLbisw8P4oeQAhFPpURxVV0zWSa9PNwEgIHtRaZA0SEpOvbeduG5q5LE0Sh2jvZ3tSqADFjFHlGSYJkmhz10zF1FseXvIo3VklcrfX9jOaq1lyAedGOoz1GpyQwnsvQ3fdTqDnTwPhQz9eQf52ob+zO1xh9NWDBbIHRgXOZqcD19PL9GXZ4H0h03whUnyHfwCrReI+97L6RBdo+0gW3j+H9uaw+7HLnQNrDUt6oV3ZBzyhmsjiz+p/dSTwJfUx2+IpD1ic+xz5enwQGXEDJJaw8Gl2I1upMzlc/hEvdOBR6SNKAjqP1J6P/o6XdL11L5h4=) + +### Under the Hood + +`defineModel` is a convenience macro. The compiler expands it to the following: + +- A prop named `modelValue`, which the local ref's value is synced with; +- An event named `update:modelValue`, which is emitted when the local ref's value is mutated. + +This is how you would implement the same child component shown above prior to 3.4: + +```vue + + + +``` + +As you can see, it is quite a bit more verbose. However, it is helpful to understand what is happening under the hood. + +Because `defineModel` declares a prop, you can therefore declare the underlying prop's options by passing it to `defineModel`: + +```js +// making the v-model required +const model = defineModel({ required: true }) + +// providing a default value +const model = defineModel({ default: 0 }) +``` + +
+ +
+ First let's revisit how `v-model` is used on a native element: ```vue-html @@ -33,8 +119,6 @@ For this to actually work though, the `` component must do two thin Here's that in action: -
- ```vue - - -``` - -
- Now `v-model` should work perfectly with this component: ```vue-html ``` -
- [Try it in the Playground](https://play.vuejs.org/#eNqFkctqwzAQRX9lEAEn4Np744aWrvoD3URdiHiSGvRCHpmC8b93JDfGKYGCkJjXvTrSJF69r8aIohHtcA69p6O0vfEuELzFgZx5tz4SXIIzUFT1JpfGCmmlxe/c3uFFRU0wSQtwdqxh0dLQwHSnNJep3ilS+8PSCxCQYrC3CMDgMKgrNlB8odaOXVJ2TgdvvNp6vSwHhMZrRcgRQLs1G5+M61A/S/ErKQXUR5immwXMWW1VEKX4g3j3Mo9QfXCeKU9FtvpQmp/lM0Oi6RP/qYieebHZNvyL0acLLODNmGYSxCogxVJ6yW1c2iWz/QOnEnY48kdUpMIVGSllD8t8zVZb+PkHqPG4iw==) -
-
- -[Try it in the Playground](https://play.vuejs.org/#eNp9j81qwzAQhF9lEQE7kNp344SW0kNvPfVS9WDidSrQH9LKF+N37yoOxoSQm7QzO9/sJN68r8aEohFtPAflCSJS8idplfEuEEwQcIAZhuAMFGwtVuk9RXLm0/pEN7mqN7Ocy2YAac/ORgKDMXYXhGOOLIs/1NoVe2nbekEzlD+ExuuOkH8A7ZYxvhjXoz5KcUuSAuoTTNOaPM85bU0QB3HX58GdPQ7K4ldwPpY/xZXw3Wmu/svVFvHDKMpi8j3HNneeZ/VVBucXQDPmjVx+XZdikV6vNpZ2yKTyAecAOxzRUkVduCCfkqf7Zb9m1Pbo+R9ZkqZn) - -
- Another way of implementing `v-model` within this component is to use a writable `computed` property with both a getter and a setter. The `get` method should return the `modelValue` property and the `set` method should emit the corresponding event: -
- ```vue ``` -
- -## `v-model` arguments {#v-model-arguments} +[Try it in the Playground](https://play.vuejs.org/#eNqFkl9PwjAUxb9K05dhglsMb2SQqOFBE9Soj31Zxh0Uu7bpHxxZ9t29LWOiQXzaes7p2a+9a+mt1unOA53S3JaGa0csOK/nTPJaK+NISwxUpCOVUTVJMJoM1nJ/r/BNgnS9nWYnWujFMCFMlkpaRxx3AsgsFI6S3XWtViBIYda+Dg3QFLUWkFwxmWcHFqTAhQPUCwe4IiTf3Mzbtq/qujzDddRPYfruaUzNGI1PRkmG0Twb+uiY/sI9cw0/0VdQcQnL0D5KovgfL5fa4/69jiDQOOTo+S6SOYtfrvg63VolkauNN0lLxOUCzLN2HMkYnZLoBK8QQn0+Rs0ZD+OjXm6g/Dijb20TNEZfDFgwOwQZPIdzAWQN9uLtKXIPJtL7gH3BfAWrhA+Mh9idlyvEPslF2of4J3G5freLxoG0x0MF0JDsYp5RHE6Y1F9H/8adpJO4j8mOdl/Hw/nf) -By default, `v-model` on a component uses `modelValue` as the prop and `update:modelValue` as the event. We can modify these names passing an argument to `v-model`: +If prop options are also needed, they should be passed after the model name: -```vue-html - +```js +const title = defineModel('title', { required: true }) ``` -In this case, the child component should expect a `title` prop and emit an `update:title` event to update the parent value: - -
+
+Pre 3.4 Usage ```vue @@ -175,9 +224,12 @@ defineEmits(['update:title']) [Try it in the Playground](https://play.vuejs.org/#eNp9kE1rwzAMhv+KMIW00DXsGtKyMXYc7D7vEBplM8QfOHJoCfnvk+1QsjJ2svVKevRKk3h27jAGFJWoh7NXjmBACu4kjdLOeoIJPHYwQ+ethoJLi1vq7fpi+WfQ0JI+lCstcrkYQJqzNQMBKeoRjhG4LcYHbVvsofFfQUcCXhrteix20tRl9sIuOCBkvSHkCKD+fjxN04Ka57rkOOlrMwu7SlVHKdIrBZRcWpc3ntiLO7t/nKHFThl899YN248ikYpP9pj1V60o6sG1TMwDU/q/FZRxgeIPgK4uGcQLSZGlamz6sHKd1afUxOoGeeT298A9bHCMKxBfE3mTSNjl1vud5x8qNa76) +
+In this case, instead of the default `modelValue` prop and `update:modelValue` event, the child component should expect a `title` prop and emit an `update:title` event to update the parent value: + ```vue + + +``` + +[Try it in the Playground](https://play.vuejs.org/#eNqFkstuwjAQRX/F8iZUAqKKHQpIfbAoUmnVx86bKEzANLEt26FUkf+9Y4MDSAg2UWbu9fjckVv6oNRw2wAd08wUmitLDNhGTZngtZLakpZoKIkjpZY1SdCadNK3Ab3IazhowzQ2/ES0MVFIYSwpucbvxA/qJXO5FsldlKr8qDxL8EKW7kEQAQsLtapyC1gRkq3vp217mOccwf8wwLksRSlYIoMvCNkOarmEahyODAT2J4yGgtFzhx8UDf5/r6c4NEs7CNqnpxkvbO0kcVjNhCyh5AJe/SW9pBPOV3DJGvu3dsKFaiyxf8qTW9gheQwVs4Z90BDm5oF47cF/Ht4aZC75argxUmD61g9ktJC14hXoN2U5ZmJ0TILitbyq5O889KxuoB/7xRqKnwv9jdn5HqPvGnDVWwTpNJvrFSCul2efi4DeiRigqdB9RfwAI6vGM+5tj41YIvaJL9C+hOfNxerLzHYWhImhPKh3uuBnFJ/A05XoR9zRcBTOMeGo+wcs+yse) + +
+Pre 3.4 Usage + ```vue ``` -
-
+To conditionally adjust how the value should be read / written based on modifiers, we can pass `get` and `set` options to `defineModel()`. These two options receive the value on get / set of the model ref and should return a transformed value. This is how we can use the `set` option to implement the `capitalize` modifier: -```vue{11} - ``` -
- -Notice the component's `modelModifiers` prop contains `capitalize` and its value is `true` - due to it being set on the `v-model` binding `v-model.capitalize="myText"`. - -Now that we have our prop set up, we can check the `modelModifiers` object keys and write a handler to change the emitted value. In the code below we will capitalize the string whenever the `` element fires an `input` event. +[Try it in the Playground](https://play.vuejs.org/#eNp9UsFu2zAM/RVClzhY5mzoLUgHdEUPG9Bt2LLTtIPh0Ik6WxIkyosb5N9LybFrFG1OkvgeyccnHsWNtXkbUKzE2pdOWQKPFOwnqVVjjSM4gsMKTlA508CMqbMRuu9uDd80ajrD+XISi3WZDCB1abQnaLoNHgiuY8VsNptLvV72TbkdPwgbWxeE/ALY7JUHpW0gKAurqKjVI3rAFl1He6V30JkA3AbdKvLXUzXt+8Zssc6fM6+l6NtLAUtusF6O3cRCvFB9yY2SiYFw+8KSYcY/qfEC+FCVQuf/8rxbrJTG+4hkxyiWq2ZtUQecQ3oDqAqyMWeieyQAu0bBaUh5ebkv3A1lH+Y5md/WorstPGZzeHfGfa1KzD6yxzH11B/TCjHC4dPlX1j3P0CdjQ5S79/Z3WhpPF91lDz7Uald/uCNZj/TFFJE91SN7rslxX5JsRrmk6Koa/P/a4qRC7gY4uUey3+vxB/8Icak+OHQo2tRihGjwu2QtUb47te3pHsEWXWomX0B/Ine1CFq7Gmfg96y7Akvqf2StoKXcePvDoTaD0NFocnhxJeClyRu2FujP8u9yq+GnxGnJxSEO+M=) -
+
+Pre 3.4 Usage ```vue{11-13} + + +``` + +Notice the component's `modelModifiers` prop contains `capitalize` and its value is `true` - due to it being set on the `v-model` binding `v-model.capitalize="myText"`. + +Now that we have our prop set up, we can check the `modelModifiers` object keys and write a handler to change the emitted value. In the code below we will capitalize the string whenever the `` element fires an `input` event. + ```vue{13-15} +``` + +
+Pre 3.4 Usage + ```vue{5,6,10,11} ``` +
From 4d45fa4a2fd9fd95548e6409ac97fb3815f09cc8 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 27 Dec 2023 10:38:39 +0800 Subject: [PATCH 04/13] small tweaks to defineModel --- src/api/sfc-script-setup.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/api/sfc-script-setup.md b/src/api/sfc-script-setup.md index 34b924a768..9cacce0472 100644 --- a/src/api/sfc-script-setup.md +++ b/src/api/sfc-script-setup.md @@ -235,12 +235,12 @@ Under the hood, this macro declares a model prop and a corresponding value updat ```js // declares "modelValue" prop, consumed by parent via v-model -const modelValue = defineModel() +const model = defineModel() // OR: declares "modelValue" prop with options -const modelValue = defineModel({ type: String }) +const model = defineModel({ type: String }) // emits "update:modelValue" when mutated -modelValue.value = 'hello' +model.value = 'hello' // declares "count" prop, consumed by parent via v-model:count const count = defineModel('count') @@ -266,15 +266,17 @@ if (modelModifiers.trim) { } ``` -Usually, we need to conditionally transform the value read from or synced back to the parent when a modifier is present. We can achieve this via the `get` and `set` transformer options: +When a modifier is present, we likely need to transform the value when reading or syncing it back to the parent. We can achieve this by using the `get` and `set` transformer options: ```js const [modelValue, modelModifiers] = defineModel({ // get() omitted as it is not needed here set(value) { + // if the .trim modifier is used, return trimmed value if (modelModifiers.trim) { return value.trim() } + // otherwise, return the value as-is return value } }) From 2ac7d0159bf7f616024f89dced9f962d10e0736a Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 27 Dec 2023 10:55:40 +0800 Subject: [PATCH 05/13] v-bind same-name shorthand --- src/api/built-in-directives.md | 7 ++++++- src/guide/essentials/template-syntax.md | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/api/built-in-directives.md b/src/api/built-in-directives.md index 84991cbe64..f7ad1443d9 100644 --- a/src/api/built-in-directives.md +++ b/src/api/built-in-directives.md @@ -257,7 +257,9 @@ Attach an event listener to the element. Dynamically bind one or more attributes, or a component prop to an expression. -- **Shorthand:** `:` or `.` (when using `.prop` modifier) +- **Shorthand:** + - `:` or `.` (when using `.prop` modifier) + - Omitting value (when attribute and bound value has the same name) 3.4+ - **Expects:** `any (with argument) | Object (without argument)` @@ -291,6 +293,9 @@ Dynamically bind one or more attributes, or a component prop to an expression. + + + diff --git a/src/guide/essentials/template-syntax.md b/src/guide/essentials/template-syntax.md index eb2eb799d7..99b0a31b7d 100644 --- a/src/guide/essentials/template-syntax.md +++ b/src/guide/essentials/template-syntax.md @@ -64,6 +64,20 @@ Attributes that start with `:` may look a bit different from normal HTML, but it > For the rest of the guide, we will be using the shorthand syntax in code examples, as that's the most common usage for Vue developers. +### Same-name Shorthand {#same-name-shorthand} + +If the attribute has the same name with the JavaScript value being bound, the syntax can be further shortened to omit the attribute value: + +```vue-html + +
+ + +
+``` + +This is similar to the property shorthand syntax when declaring objects in JavaScript. Note this is a feature that is only available in Vue 3.4 and above. + ### Boolean Attributes {#boolean-attributes} [Boolean attributes](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes) are attributes that can indicate true / false values by their presence on an element. For example, [`disabled`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled) is one of the most commonly used boolean attributes. From 040bc989f2d3759f92aea6ed59b5dc4623da9d90 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 27 Dec 2023 11:55:24 +0800 Subject: [PATCH 06/13] computed stability optimization --- src/api/reactivity-core.md | 5 +-- src/guide/best-practices/performance.md | 47 +++++++++++++++++++++++++ src/guide/essentials/computed.md | 2 +- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/api/reactivity-core.md b/src/api/reactivity-core.md index dc0bf947c8..64a3475976 100644 --- a/src/api/reactivity-core.md +++ b/src/api/reactivity-core.md @@ -52,7 +52,7 @@ Takes a getter function and returns a readonly reactive [ref](#ref) object for t ```ts // read-only function computed( - getter: () => T, + getter: (oldValue: T | undefined) => T, // see "Computed Debugging" link below debuggerOptions?: DebuggerOptions ): Readonly>> @@ -60,7 +60,7 @@ Takes a getter function and returns a readonly reactive [ref](#ref) object for t // writable function computed( options: { - get: () => T + get: (oldValue: T | undefined) => T set: (value: T) => void }, debuggerOptions?: DebuggerOptions @@ -112,6 +112,7 @@ Takes a getter function and returns a readonly reactive [ref](#ref) object for t - [Guide - Computed Properties](/guide/essentials/computed) - [Guide - Computed Debugging](/guide/extras/reactivity-in-depth#computed-debugging) - [Guide - Typing `computed()`](/guide/typescript/composition-api#typing-computed) + - [Guide - Performance - Computed Stability](/guide/best-practices/performance#computed-stability) ## reactive() {#reactive} diff --git a/src/guide/best-practices/performance.md b/src/guide/best-practices/performance.md index 10774932e4..b78f03c746 100644 --- a/src/guide/best-practices/performance.md +++ b/src/guide/best-practices/performance.md @@ -124,6 +124,53 @@ Now, for most components the `active` prop will remain the same when `activeId` `v-memo` is a built-in directive that can be used to conditionally skip the update of large sub-trees or `v-for` lists. Consult its [API reference](/api/built-in-directives#v-memo) for more details. +### Computed Stability {#computed-stability} + +Starting in 3.4, a computed property will only trigger effects when its computed value has changed from the previous one. For example, the following `isEven` computed only triggers effects if the returned value has changed from `true` to `false`, or vice-versa: + +```js +const count = ref(0) +const isEven = computed(() => { + return count.value % 2 === 0 ? true : false +}) + +watchEffect(() => console.log(isEven.value)) // true + +// will not trigger new logs because the computed value stays `true` +count.value = 2 +count.value = 4 +``` + +This reduces unnecessary effect triggers, but unfortunately doesn't work if the computed creates a new object on each compute: + +```js +const computedObj = computed(() => { + return { + isEven: count.value % 2 === 0 ? true : false + } +}) +``` + +Because a new object is created each time, the new value is technically always different from the old value. Even if the `isEven` property remains the same, Vue won't be able to know unless it performs a deep comparison of the old value and the new value. Such comparison could be expensive and likely not worth it. + +Instead, we can optimize this by manually comparing the new value with the old value, and conditionally returning the old value if we know nothing has changed: + +```js +const computedObj = computed(oldValue => { + const newValue = { + isEven: count.value % 2 === 0 ? true : false + } + if (oldValue && oldValue.isEven === newValue.isEven) { + return oldValue + } + return newValue +}) +``` + +[Playground Example](https://play.vuejs.org/#eNqlVMlu2zAQ/ZUBgSZ24UpuczMkd4MPLdAFadEeyh4UibKVSKRADm2jhv+9Q1KyDTRRXOQggJrlzeO8Ge7Y27aN1lawGUtMrqsWwQi07ZzLqmmVRtiBFuUEctW0FkUxgU2G+WpRliJH2EOpVQOXhHDJJZe5kgYp1kqE1CWOpuOjXfikayvNzwpXyuKXFqum+pNhpWQX/+s3JfQwoeT9wb13pOriR1ZbAekcdlwCwaDVMpwBKrNYCzkLpKK1j3wGryBNU5jCa0BNhhmUWW2Ey9hzuX+Q8/mEz2YbUqXYdOYn8KakEo4VLi6gP0cBzSf3pTrbuC/Yta1POWB29j6tb8/JGIxG48N1BjUO14haa1ajAXW7sI7ffxU8p9rjpUorcy+cbYsMBVXzpeLYLUc33qggA53JguZfuN5K29wIfSIpafkpw1VU1krpkT+GeMJ7Di+nbjVc8FFfEgde0Ec6ExUukzjsJG0j/aBo2pro0B/Abtfx2HuRkhuLSITf5HWV36WcBeaczcNhmHQSh3SPnLjldwPhegqbIA+o03mm4sO73JGKA9S/iI/APYiVxCdNYBOGhnpdVsvo1ihJb5iXiTOndlUL7XBIC85m/ZBzltW12nz0NrdCk96er0R+d4/91mydjbOvWhih19TTgw8zvRQY3Itvn8WWzgdnowpbU/SA81oYVVvHMYS9s7Ig2idxnu0H/xJXcvndLLYopOkv5Yj6PfXxnNEz/H7g6ke6V9FV/9ax/V8w4Rl9) + +Note that you should always perform the full computation before comparing and returning the old value, so that the same dependencies can be collected on every run. + ## General Optimizations {#general-optimizations} > The following tips affect both page load and update performance. diff --git a/src/guide/essentials/computed.md b/src/guide/essentials/computed.md index 2fdab83e4b..ec97209a6f 100644 --- a/src/guide/essentials/computed.md +++ b/src/guide/essentials/computed.md @@ -263,7 +263,7 @@ Now when you run `fullName.value = 'John Doe'`, the setter will be invoked and ` ### Getters should be side-effect free {#getters-should-be-side-effect-free} -It is important to remember that computed getter functions should only perform pure computation and be free of side effects. For example, **don't make async requests or mutate the DOM inside a computed getter!** Think of a computed property as declaratively describing how to derive a value based on other values - its only responsibility should be computing and returning that value. Later in the guide we will discuss how we can perform side effects in reaction to state changes with [watchers](./watchers). +It is important to remember that computed getter functions should only perform pure computation and be free of side effects. For example, **don't mutate other state, make async requests, or mutate the DOM inside a computed getter!** Think of a computed property as declaratively describing how to derive a value based on other values - its only responsibility should be computing and returning that value. Later in the guide we will discuss how we can perform side effects in reaction to state changes with [watchers](./watchers). ### Avoid mutating computed value {#avoid-mutating-computed-value} From 6408ea5eff8d7c7353f6faf49d859ffb616c9559 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 27 Dec 2023 11:59:16 +0800 Subject: [PATCH 07/13] watch() once option --- src/api/reactivity-core.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api/reactivity-core.md b/src/api/reactivity-core.md index 64a3475976..49c2cececb 100644 --- a/src/api/reactivity-core.md +++ b/src/api/reactivity-core.md @@ -361,6 +361,7 @@ Watches one or more reactive data sources and invokes a callback function when t flush?: 'pre' | 'post' | 'sync' // default: 'pre' onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void + once?: boolean // default: false (3.4+) } ``` @@ -387,6 +388,7 @@ Watches one or more reactive data sources and invokes a callback function when t - **`deep`**: force deep traversal of the source if it is an object, so that the callback fires on deep mutations. See [Deep Watchers](/guide/essentials/watchers#deep-watchers). - **`flush`**: adjust the callback's flush timing. See [Callback Flush Timing](/guide/essentials/watchers#callback-flush-timing) and [`watchEffect()`](/api/reactivity-core#watcheffect). - **`onTrack / onTrigger`**: debug the watcher's dependencies. See [Watcher Debugging](/guide/extras/reactivity-in-depth#watcher-debugging). + - **`once`**: run the callback only once. The watcher is automatically stopped after the first callback run. Compared to [`watchEffect()`](#watcheffect), `watch()` allows us to: From 375537896ea348326ba6211bf32a4808d973dbfd Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 27 Dec 2023 12:02:30 +0800 Subject: [PATCH 08/13] prop validator receives full props as 2nd argument --- src/api/options-state.md | 2 +- src/guide/components/props.md | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/api/options-state.md b/src/api/options-state.md index f91e744f6d..b975a50f54 100644 --- a/src/api/options-state.md +++ b/src/api/options-state.md @@ -68,7 +68,7 @@ Declare the props of a component. type?: PropType required?: boolean default?: T | ((rawProps: object) => T) - validator?: (value: unknown) => boolean + validator?: (value: unknown, rawProps: object) => boolean } type PropType = { new (): T } | { new (): T }[] diff --git a/src/guide/components/props.md b/src/guide/components/props.md index 9cb983b417..e29a4e6a0c 100644 --- a/src/guide/components/props.md +++ b/src/guide/components/props.md @@ -405,8 +405,9 @@ defineProps({ } }, // Custom validator function + // full props passed as 2nd argument in 3.4+ propF: { - validator(value) { + validator(value, props) { // The value must match one of these strings return ['success', 'warning', 'danger'].includes(value) } @@ -459,8 +460,9 @@ export default { } }, // Custom validator function + // full props passed as 2nd argument in 3.4+ propF: { - validator(value) { + validator(value, props) { // The value must match one of these strings return ['success', 'warning', 'danger'].includes(value) } From d0df4cfff83a89401baef2cf8a79ccdf30a3f7f5 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 27 Dec 2023 12:12:31 +0800 Subject: [PATCH 09/13] jsxImportSource --- src/guide/extras/render-function.md | 18 ++++++++++++++---- src/guide/typescript/overview.md | 2 ++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/guide/extras/render-function.md b/src/guide/extras/render-function.md index 4fe7fa2076..158da1178d 100644 --- a/src/guide/extras/render-function.md +++ b/src/guide/extras/render-function.md @@ -241,13 +241,23 @@ Vue's type definition also provides type inference for TSX usage. When using TSX ### JSX Type Inference {#jsx-type-inference} -Similar to the transform, Vue's JSX also needs different type definitions. Currently, Vue's types automatically registers Vue's JSX types globally. This means TSX will work out of the box when Vue's type is available. +Similar to the transform, Vue's JSX also needs different type definitions. -The global JSX types may cause conflict with used together with other libraries that also needs JSX type inference, in particular React. Starting in 3.3, Vue supports specifying JSX namespace via TypeScript's [jsxImportSource](https://www.typescriptlang.org/tsconfig#jsxImportSource) option. We plan to remove the default global JSX namespace registration in 3.4. +Starting in Vue 3.4, Vue no longer implicitly registers the global `JSX` namespace. To instruct TypeScript to use Vue's JSX type definitions, make sure to include the following in your `tsconfig.json`: -For TSX users, it is suggested to set [jsxImportSource](https://www.typescriptlang.org/tsconfig#jsxImportSource) to `'vue'` in `tsconfig.json` after upgrading to 3.3, or opt-in per file with `/* @jsxImportSource vue */`. This will allow you to opt-in to the new behavior now and upgrade seamlessly when 3.4 releases. +```json +{ + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "vue" + // ... + } +} +``` + +You can also opt-in per file by adding a `/* @jsxImportSource vue */` comment at the top of the file. -If there is code that depends on the presence of the global `JSX` namespace, you can retain the exact pre-3.4 global behavior by explicitly referencing `vue/jsx`, which registers the global `JSX` namespace. +If there is code that depends on the presence of the global `JSX` namespace, you can retain the exact pre-3.4 global behavior by explicitly importing or referencing `vue/jsx` in your project, which registers the global `JSX` namespace. ## Render Function Recipes {#render-function-recipes} diff --git a/src/guide/typescript/overview.md b/src/guide/typescript/overview.md index 1a7b4b5c86..8edac4827f 100644 --- a/src/guide/typescript/overview.md +++ b/src/guide/typescript/overview.md @@ -48,6 +48,8 @@ When configuring `tsconfig.json` manually, some notable options include: - If you have configured resolver aliases in your build tool, for example the `@/*` alias configured by default in a `create-vue` project, you need to also configure it for TypeScript via [`compilerOptions.paths`](https://www.typescriptlang.org/tsconfig#paths). +- If you intend to use TSX with Vue, set [`compilerOptions.jsx`](https://www.typescriptlang.org/tsconfig#jsx) to `"preserve"`, and set [`compilerOptions.jsxImportSource`](https://www.typescriptlang.org/tsconfig#jsxImportSource) to `"vue"`. + See also: - [Official TypeScript compiler options docs](https://www.typescriptlang.org/docs/handbook/compiler-options.html) From f3eeece8fb9bbeb5acbe5d4b869a194bb014b375 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 27 Dec 2023 12:14:50 +0800 Subject: [PATCH 10/13] mark reactivity transform removed --- src/guide/extras/reactivity-transform.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/guide/extras/reactivity-transform.md b/src/guide/extras/reactivity-transform.md index e9317b10b3..09643d3085 100644 --- a/src/guide/extras/reactivity-transform.md +++ b/src/guide/extras/reactivity-transform.md @@ -1,12 +1,9 @@ # Reactivity Transform {#reactivity-transform} -:::danger Deprecated Experimental Feature -Reactivity Transform was an experimental feature, and has been deprecated. Please read about [the reasoning here](https://github.com/vuejs/rfcs/discussions/369#discussioncomment-5059028). +:::danger Removed Experimental Feature +Reactivity Transform was an experimental feature, and has been removed in the latest 3.4 release. Please read about [the reasoning here](https://github.com/vuejs/rfcs/discussions/369#discussioncomment-5059028). -It will eventually be removed from Vue core in a future minor release. - -- To migrate away from it, check out this [command line tool](https://github.com/edison1105/drop-reactivity-transform) that can automate the process. -- If you still intend to use it, it is now available via the [Vue Macros](https://vue-macros.sxzz.moe/features/reactivity-transform.html) plugin. +If you still intend to use it, it is now available via the [Vue Macros](https://vue-macros.sxzz.moe/features/reactivity-transform.html) plugin. ::: :::tip Composition-API-specific @@ -286,8 +283,8 @@ When explicitly importing the macros from `vue/macros`, the type will work witho ## Explicit Opt-in {#explicit-opt-in} -:::warning -The following only applies up to Vue version 3.3 and below. Core support will be removed in 3.4 and above. If you intend to continue using the transform, please migrate to [Vue Macros](https://vue-macros.sxzz.moe/features/reactivity-transform.html) instead. +:::danger No longer supported in core +The following only applies up to Vue version 3.3 and below. Support has been removed in Vue core 3.4 and above, and `@vitejs/plugin-vue` 5.0 and above. If you intend to continue using the transform, please migrate to [Vue Macros](https://vue-macros.sxzz.moe/features/reactivity-transform.html) instead. ::: ### Vite {#vite} From c1be6170f64590e04ef03a006787b06a49d758c4 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 27 Dec 2023 13:03:29 +0800 Subject: [PATCH 11/13] add compile-time flags reference --- .vitepress/config.ts | 3 +- src/api/compile-time-flags.md | 104 ++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/api/compile-time-flags.md diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 6ad3eb2cd4..b42735ae06 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -419,7 +419,8 @@ export const sidebar: ThemeConfig['sidebar'] = { { text: 'Render Function', link: '/api/render-function' }, { text: 'Server-Side Rendering', link: '/api/ssr' }, { text: 'TypeScript Utility Types', link: '/api/utility-types' }, - { text: 'Custom Renderer', link: '/api/custom-renderer' } + { text: 'Custom Renderer', link: '/api/custom-renderer' }, + { text: 'Compile-Time Flags', link: '/api/compile-time-flags' } ] } ], diff --git a/src/api/compile-time-flags.md b/src/api/compile-time-flags.md new file mode 100644 index 0000000000..4b7705399c --- /dev/null +++ b/src/api/compile-time-flags.md @@ -0,0 +1,104 @@ +--- +outline: deep +--- + +# Compile-Time Flags {#compile-time-flags} + +When using Vue with a build step, it is possible to configure a number of compile-time flags to enable / disable certain features. The benefit of using compile-time flags is that features disabled this way can be removed from the final bundle via tree-shaking. + +Vue will work even if these flags are not explicitly configured. However, it is recommended to always configure them so that the relevant features can be properly removed when possible. + +See [Configuration Guides](#configuration-guides) on how to configure them depending on your build tool. + +## `__VUE_OPTIONS_API__` + +- **Default:** `true` + + Enable / disable Options API support. Disabling this will result in smaller bundles, but may affect compatibility with 3rd party libraries if they rely on Options API. + +## `__VUE_PROD_DEVTOOLS__` + +- **Default:** `false` + + Enable / disable devtools support in production builds. This will result in more code included in the bundle, so it is recommended to only enable this for debugging purposes. + +## `__VUE_PROD_HYDRATION_MISMATCH_DETAILS__` + +- **Default:** `false` + + Enable/disable detailed warnings for hydration mismatches in production builds. This will result in more code included in the bundle, so it is recommended to only enable this for debugging purposes. + +## Configuration Guides + +### Vite + +`@vitejs/plugin-vue` automatically provides default values for these flags. To change the default values, use Vite's [`define` config option](https://vitejs.dev/config/shared-options.html#define): + +```js +// vite.config.js +import { defineConfig } from 'vite' + +export default defineConfig({ + define: { + // enable hydration mismatch details in production build + __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'true' + } +}) +``` + +### vue-cli + +`@vue/cli-service` automatically provides default values for some of these flags. To configure /change the values: + +```js +// vue.config.js +module.exports = { + chainWebpack: (config) => { + config.plugin('define').tap((definitions) => { + Object.assign(definitions[0], { + __VUE_OPTIONS_API__: 'true', + __VUE_PROD_DEVTOOLS__: 'false', + __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false' + }) + return definitions + }) + } +} +``` + +### webpack + +Flags should be defined using webpack's [DefinePlugin](https://webpack.js.org/plugins/define-plugin/): + +```js +// webpack.config.js +module.exports = { + // ... + plugins: [ + new webpack.DefinePlugin({ + __VUE_OPTIONS_API__: 'true', + __VUE_PROD_DEVTOOLS__: 'false', + __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false' + }) + ] +} +``` + +### Rollup + +Flags should be defined using [@rollup/plugin-replace](https://github.com/rollup/plugins/tree/master/packages/replace): + +```js +// rollup.config.js +import replace from '@rollup/plugin-replace' + +export default { + plugins: [ + replace({ + __VUE_OPTIONS_API__: 'true', + __VUE_PROD_DEVTOOLS__: 'false', + __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false' + }) + ] +} +``` From 300d985a95beca398da67c08ccd42269a70c1cc0 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 27 Dec 2023 13:06:00 +0800 Subject: [PATCH 12/13] add a note --- src/api/compile-time-flags.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/compile-time-flags.md b/src/api/compile-time-flags.md index 4b7705399c..d240e9db41 100644 --- a/src/api/compile-time-flags.md +++ b/src/api/compile-time-flags.md @@ -4,6 +4,10 @@ outline: deep # Compile-Time Flags {#compile-time-flags} +:::tip +Compile-time flags only apply when using the `esm-bundler` build of Vue (i.e. `vue/dist/vue.esm-bundler.js`). +::: + When using Vue with a build step, it is possible to configure a number of compile-time flags to enable / disable certain features. The benefit of using compile-time flags is that features disabled this way can be removed from the final bundle via tree-shaking. Vue will work even if these flags are not explicitly configured. However, it is recommended to always configure them so that the relevant features can be properly removed when possible. From 03d17cc56dbb518e30cb09d4281ce0ee25ac3586 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 28 Dec 2023 15:41:03 +0800 Subject: [PATCH 13/13] remove unnecessary booleans --- src/guide/best-practices/performance.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/guide/best-practices/performance.md b/src/guide/best-practices/performance.md index b78f03c746..83035add0a 100644 --- a/src/guide/best-practices/performance.md +++ b/src/guide/best-practices/performance.md @@ -130,9 +130,7 @@ Starting in 3.4, a computed property will only trigger effects when its computed ```js const count = ref(0) -const isEven = computed(() => { - return count.value % 2 === 0 ? true : false -}) +const isEven = computed(() => count.value % 2 === 0) watchEffect(() => console.log(isEven.value)) // true @@ -146,7 +144,7 @@ This reduces unnecessary effect triggers, but unfortunately doesn't work if the ```js const computedObj = computed(() => { return { - isEven: count.value % 2 === 0 ? true : false + isEven: count.value % 2 === 0 } }) ``` @@ -156,9 +154,9 @@ Because a new object is created each time, the new value is technically always d Instead, we can optimize this by manually comparing the new value with the old value, and conditionally returning the old value if we know nothing has changed: ```js -const computedObj = computed(oldValue => { +const computedObj = computed((oldValue) => { const newValue = { - isEven: count.value % 2 === 0 ? true : false + isEven: count.value % 2 === 0 } if (oldValue && oldValue.isEven === newValue.isEven) { return oldValue @@ -167,7 +165,7 @@ const computedObj = computed(oldValue => { }) ``` -[Playground Example](https://play.vuejs.org/#eNqlVMlu2zAQ/ZUBgSZ24UpuczMkd4MPLdAFadEeyh4UibKVSKRADm2jhv+9Q1KyDTRRXOQggJrlzeO8Ge7Y27aN1lawGUtMrqsWwQi07ZzLqmmVRtiBFuUEctW0FkUxgU2G+WpRliJH2EOpVQOXhHDJJZe5kgYp1kqE1CWOpuOjXfikayvNzwpXyuKXFqum+pNhpWQX/+s3JfQwoeT9wb13pOriR1ZbAekcdlwCwaDVMpwBKrNYCzkLpKK1j3wGryBNU5jCa0BNhhmUWW2Ey9hzuX+Q8/mEz2YbUqXYdOYn8KakEo4VLi6gP0cBzSf3pTrbuC/Yta1POWB29j6tb8/JGIxG48N1BjUO14haa1ajAXW7sI7ffxU8p9rjpUorcy+cbYsMBVXzpeLYLUc33qggA53JguZfuN5K29wIfSIpafkpw1VU1krpkT+GeMJ7Di+nbjVc8FFfEgde0Ec6ExUukzjsJG0j/aBo2pro0B/Abtfx2HuRkhuLSITf5HWV36WcBeaczcNhmHQSh3SPnLjldwPhegqbIA+o03mm4sO73JGKA9S/iI/APYiVxCdNYBOGhnpdVsvo1ihJb5iXiTOndlUL7XBIC85m/ZBzltW12nz0NrdCk96er0R+d4/91mydjbOvWhih19TTgw8zvRQY3Itvn8WWzgdnowpbU/SA81oYVVvHMYS9s7Ig2idxnu0H/xJXcvndLLYopOkv5Yj6PfXxnNEz/H7g6ke6V9FV/9ax/V8w4Rl9) +[Playground Example](https://play.vuejs.org/#eNqVVMtu2zAQ/JUFgSZK4UpuczMkow/40AJ9IC3aQ9mDIlG2EokUyKVt1PC/d0lKtoEminMQQC1nZ4c7S+7Yu66L11awGUtNoesOwQi03ZzLuu2URtiBFtUECtV2FkU5gU2OxWpRVaJA2EOlVQuXxHDJJZeFkgYJayVC5hKj6dUxLnzSjZXmV40rZfFrh3Vb/82xVrLH//5DCQNNKPkweNiNVFP+zBsrIJvDjksgGrRahjVAbRZrIWdBVLz2yBfwBrIsg6mD7LncPyryfIVnywupUmz68HOEEqqCI+XFBQzrOKR79MDdx66GCn1jhpQDZx8f0oZ+nBgdRVcH/aMuBt1xZ80qGvGvh/X6nlXwnGpPl6qsLLxTtitzFFTNl0oSN/79AKOCHHQuS5pw4XorbXsr9ImHZN7nHFdx1SilI78MeOJ7Ca+nbvgd+GgomQOv6CNjSQqXaRJuHd03+kHRdg3JoT+A3a7XsfcmpbcWkQS/LZq6uM84C8o5m4fFuOg0CemeOXXX2w2E6ylsgj2gTgeYio/f1l5UEqj+Z3yC7lGuNDlpApswNNTrql7Gd0ZJeqW8TZw5t+tGaMdDXnA2G4acs7xp1OaTj6G2YjLEi5Uo7h+I35mti3H2TQsj9Jp6etjDXC8Fhu3F9y9iS+vDZqtK2xB6ZPNGGNVYpzHA3ltZkuwTnFf70b+1tVz+MIstCmmGQzmh/p56PGf00H4YOfpR7nV8PTxubP8P2GAP9Q==) Note that you should always perform the full computation before comparing and returning the old value, so that the same dependencies can be collected on every run.