diff --git a/.vitepress/config.js b/.vitepress/config.js index 5f8ca76..8e337bc 100644 --- a/.vitepress/config.js +++ b/.vitepress/config.js @@ -1,156 +1,20 @@ +import locales from "./locales" + +/** + * @type {import('vitepress').UserConfig} + */ export default { - title: 'Vue 3 Migration Guide', - description: 'Guide on migrating from Vue 2 to Vue 3', srcDir: 'src', + locales: locales.vitepressConfig, + themeConfig: { - nav: [{ text: 'Vue 3 Docs', link: 'https://vuejs.org' }], - - sidebar: [ - { - text: 'Guide', - items: [ - { text: 'Overview', link: '/' }, - { text: 'New Recommendations', link: '/recommendations' }, - { text: 'Migration Build', link: '/migration-build' }, - { - text: 'Breaking Changes', - link: '/breaking-changes/' - } - ] - }, - { - text: 'Global API', - items: [ - { - text: 'Global API Application Instance', - link: '/breaking-changes/global-api' - }, - { - text: 'Global API Treeshaking', - link: '/breaking-changes/global-api-treeshaking' - } - ] - }, - { - text: 'Template Directives', - items: [ - { text: 'v-model', link: '/breaking-changes/v-model' }, - { - text: 'key Usage Change', - link: '/breaking-changes/key-attribute' - }, - { - text: 'v-if vs. v-for Precedence', - link: '/breaking-changes/v-if-v-for' - }, - { text: 'v-bind Merge Behavior', link: '/breaking-changes/v-bind' }, - { - text: 'v-on.native modifier removed', - link: '/breaking-changes/v-on-native-modifier-removed' - } - ] - }, - { - text: 'Components', - items: [ - { - text: 'Functional Components', - link: '/breaking-changes/functional-components' - }, - { - text: 'Async Components', - link: '/breaking-changes/async-components' - }, - { text: 'emits Option', link: '/breaking-changes/emits-option' } - ] - }, - { - text: 'Render Function', - items: [ - { - text: 'Render Function API', - link: '/breaking-changes/render-function-api' - }, - { - text: 'Slots Unification', - link: '/breaking-changes/slots-unification' - }, - { - text: '$listeners merged into $attrs', - link: '/breaking-changes/listeners-removed' - }, - { - text: '$attrs includes class & style', - link: '/breaking-changes/attrs-includes-class-style' - } - ] - }, - { - text: 'Custom Elements', - items: [ - { - text: 'Custom Elements Interop Changes', - link: '/breaking-changes/custom-elements-interop' - } - ] - }, - { - text: 'Removed APIs', - items: [ - { - text: 'v-on keyCode Modifiers', - link: '/breaking-changes/keycode-modifiers' - }, - { text: 'Events API', link: '/breaking-changes/events-api' }, - { text: 'Filters', link: '/breaking-changes/filters' }, - { - text: 'inline-template', - link: '/breaking-changes/inline-template-attribute' - }, - { text: '$children', link: '/breaking-changes/children' }, - { text: 'propsData option', link: '/breaking-changes/props-data' } - ] - }, - { - text: 'Other Minor Changes', - items: [ - { - text: 'Attribute Coercion Behavior', - link: '/breaking-changes/attribute-coercion' - }, - { - text: 'Custom Directives', - link: '/breaking-changes/custom-directives' - }, - { text: 'Data Option', link: '/breaking-changes/data-option' }, - { - text: 'Mount API changes', - link: '/breaking-changes/mount-changes' - }, - { - text: 'Props Default Function this Access', - link: '/breaking-changes/props-default-this' - }, - { - text: 'Transition Class Change', - link: '/breaking-changes/transition' - }, - { - text: 'Transition as Root', - link: '/breaking-changes/transition-as-root' - }, - { - text: 'Transition Group Root Element', - link: '/breaking-changes/transition-group' - }, - { - text: 'VNode lifecycle events', - link: '/breaking-changes/vnode-lifecycle-events' - }, - { text: 'Watch on Arrays', link: '/breaking-changes/watch' } - ] - } - ] + localeLinks: { + items: [ + {text: 'English', link: '/'}, + {text: '中文简体', link: '/zh/'} + ] + }, + locales: locales.themeConfig } } diff --git a/.vitepress/locales/en.js b/.vitepress/locales/en.js new file mode 100644 index 0000000..9cab33e --- /dev/null +++ b/.vitepress/locales/en.js @@ -0,0 +1,159 @@ +export default { + vitepressConfig: { + title: 'Vue 3 Migration Guide', + description: 'Guide on migrating from Vue 2 to Vue 3', + lang: 'en-US' + }, + themeConfig: { + nav: [ + { text: 'Vue 3 Docs', link: 'https://vuejs.org' }, + ], + + sidebar: [ + { + text: 'Guide', + items: [ + { text: 'Overview', link: '/' }, + { text: 'New Recommendations', link: '/recommendations' }, + { text: 'Migration Build', link: '/migration-build' }, + { + text: 'Breaking Changes', + link: '/breaking-changes/' + } + ] + }, + { + text: 'Global API', + items: [ + { + text: 'Global API Application Instance', + link: '/breaking-changes/global-api' + }, + { + text: 'Global API Treeshaking', + link: '/breaking-changes/global-api-treeshaking' + } + ] + }, + { + text: 'Template Directives', + items: [ + { text: 'v-model', link: '/breaking-changes/v-model' }, + { + text: 'key Usage Change', + link: '/breaking-changes/key-attribute' + }, + { + text: 'v-if vs. v-for Precedence', + link: '/breaking-changes/v-if-v-for' + }, + { text: 'v-bind Merge Behavior', link: '/breaking-changes/v-bind' }, + { + text: 'v-on.native modifier removed', + link: '/breaking-changes/v-on-native-modifier-removed' + } + ] + }, + { + text: 'Components', + items: [ + { + text: 'Functional Components', + link: '/breaking-changes/functional-components' + }, + { + text: 'Async Components', + link: '/breaking-changes/async-components' + }, + { text: 'emits Option', link: '/breaking-changes/emits-option' } + ] + }, + { + text: 'Render Function', + items: [ + { + text: 'Render Function API', + link: '/breaking-changes/render-function-api' + }, + { + text: 'Slots Unification', + link: '/breaking-changes/slots-unification' + }, + { + text: '$listeners merged into $attrs', + link: '/breaking-changes/listeners-removed' + }, + { + text: '$attrs includes class & style', + link: '/breaking-changes/attrs-includes-class-style' + } + ] + }, + { + text: 'Custom Elements', + items: [ + { + text: 'Custom Elements Interop Changes', + link: '/breaking-changes/custom-elements-interop' + } + ] + }, + { + text: 'Removed APIs', + items: [ + { + text: 'v-on keyCode Modifiers', + link: '/breaking-changes/keycode-modifiers' + }, + { text: 'Events API', link: '/breaking-changes/events-api' }, + { text: 'Filters', link: '/breaking-changes/filters' }, + { + text: 'inline-template', + link: '/breaking-changes/inline-template-attribute' + }, + { text: '$children', link: '/breaking-changes/children' }, + { text: 'propsData option', link: '/breaking-changes/props-data' } + ] + }, + { + text: 'Other Minor Changes', + items: [ + { + text: 'Attribute Coercion Behavior', + link: '/breaking-changes/attribute-coercion' + }, + { + text: 'Custom Directives', + link: '/breaking-changes/custom-directives' + }, + { text: 'Data Option', link: '/breaking-changes/data-option' }, + { + text: 'Mount API changes', + link: '/breaking-changes/mount-changes' + }, + { + text: 'Props Default Function this Access', + link: '/breaking-changes/props-default-this' + }, + { + text: 'Transition Class Change', + link: '/breaking-changes/transition' + }, + { + text: 'Transition as Root', + link: '/breaking-changes/transition-as-root' + }, + { + text: 'Transition Group Root Element', + link: '/breaking-changes/transition-group' + }, + { + text: 'VNode lifecycle events', + link: '/breaking-changes/vnode-lifecycle-events' + }, + { text: 'Watch on Arrays', link: '/breaking-changes/watch' } + ] + } + ] + } +} \ No newline at end of file diff --git a/.vitepress/locales/index.js b/.vitepress/locales/index.js new file mode 100644 index 0000000..ff3ef3c --- /dev/null +++ b/.vitepress/locales/index.js @@ -0,0 +1,13 @@ +import en from './en' +import zh from './zh' + +export default { + vitepressConfig: { + '/': en.vitepressConfig, + '/zh/': zh.vitepressConfig + }, + themeConfig: { + '/': en.themeConfig, + '/zh/': zh.themeConfig + } +} \ No newline at end of file diff --git a/.vitepress/locales/zh.js b/.vitepress/locales/zh.js new file mode 100644 index 0000000..c69adf0 --- /dev/null +++ b/.vitepress/locales/zh.js @@ -0,0 +1,165 @@ + +export default { + vitepressConfig: { + title: 'Vue 3 迁移指南', + description: '从Vue 2迁移到Vue 3的指南', + lang: 'zh-CN', + base: '/zh/', + }, + themeConfig: { + docFooter: { + prev: '上一页', + next: '下一页', + }, + outlineTitle: '本页目录', + nav: [ + { text: 'Vue 3 文档', link: 'https://cn.vuejs.org' }, + ], + sidebar: [ + { + text: '指南', + items: [ + { text: '概览', link: '/zh/' }, + { text: '新的推荐', link: '/zh/recommendations' }, + { text: '迁移构建', link: '/zh/migration-build' }, + { + text: '非兼容性改变', + link: '/zh/breaking-changes/' + } + ] + }, + { + text: '全局 API', + items: [ + { + text: '全局 API 应用实例', + link: '/zh/breaking-changes/global-api' + }, + { + text: '全局 API Treeshaking', + link: '/zh/breaking-changes/global-api-treeshaking' + } + ] + }, + { + text: '模板指令', + items: [ + { text: 'v-model', link: '/zh/breaking-changes/v-model' }, + { + text: 'key 使用改变', + link: '/zh/breaking-changes/key-attribute' + }, + { + text: 'v-if 与 v-for 优先级', + link: '/zh/breaking-changes/v-if-v-for' + }, + { text: 'v-bind 合并行为', link: '/zh/breaking-changes/v-bind' }, + { + text: 'v-on.native 移除', + link: '/zh/breaking-changes/v-on-native-modifier-removed' + } + ] + }, + { + text: '组件', + items: [ + { + text: '函数式组件', + link: '/zh/breaking-changes/functional-components' + }, + { + text: '异步组件', + link: '/zh/breaking-changes/async-components' + }, + { text: 'emits 选项', link: '/zh/breaking-changes/emits-option' } + ] + }, + { + text: '渲染函数', + items: [ + { + text: '渲染函数 API', + link: '/zh/breaking-changes/render-function-api' + }, + { + text: '插槽统一', + link: '/zh/breaking-changes/slots-unification' + }, + { + text: '$listeners 合并到 $attrs', + link: '/zh/breaking-changes/listeners-removed' + }, + { + text: '$attrs 包含 class & style', + link: '/zh/breaking-changes/attrs-includes-class-style' + } + ] + }, + { + text: '自定义元素', + items: [ + { + text: '与自定义标签的互操作性', + link: '/zh/breaking-changes/custom-elements-interop' + } + ] + }, + { + text: '移除的 APIs', + items: [ + { + text: '按键修饰符', + link: '/zh/breaking-changes/keycode-modifiers' + }, + { text: '事件 API', link: '/zh/breaking-changes/events-api' }, + { text: '过滤器', link: '/zh/breaking-changes/filters' }, + { + text: '内联模板', + link: '/zh/breaking-changes/inline-template-attribute' + }, + { text: '$children', link: '/zh/breaking-changes/children' }, + { text: 'propsData 选项', link: '/zh/breaking-changes/props-data' } + ] + }, + { + text: '其他一些小的变化', + items: [ + { + text: 'Attribute 强制行为', + link: '/zh/breaking-changes/attribute-coercion' + }, + { + text: '自定义指令', + link: '/zh/breaking-changes/custom-directives' + }, + { text: 'Data 选项', link: '/zh/breaking-changes/data-option' }, + { + text: 'Mount API 的改变', + link: '/zh/breaking-changes/mount-changes' + }, + { + text: 'Props 的默认函数访问 this', + link: '/zh/breaking-changes/props-default-this' + }, + { + text: 'Transition class 名更改 ', + link: '/zh/breaking-changes/transition' + }, + { + text: 'Transition 作为根节点', + link: '/zh/breaking-changes/transition-as-root' + }, + { + text: 'TransitionGroup 根元素', + link: '/zh/breaking-changes/transition-group' + }, + { + text: 'VNode 生命周期事件', + link: '/zh/breaking-changes/vnode-lifecycle-events' + }, + { text: 'Watch 侦听数组', link: '/zh/breaking-changes/watch' } + ] + } + ] + } +} \ No newline at end of file diff --git a/.vitepress/theme/MigrationBadges.vue b/.vitepress/theme/MigrationBadges.vue index 2d5ce2a..46b1170 100644 --- a/.vitepress/theme/MigrationBadges.vue +++ b/.vitepress/theme/MigrationBadges.vue @@ -1,10 +1,20 @@ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ac84bd..caa3be4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: 5.4 +lockfileVersion: 5.3 specifiers: vitepress: ^1.0.0-alpha.8 diff --git a/src/zh/breaking-changes/array-refs.md b/src/zh/breaking-changes/array-refs.md new file mode 100644 index 0000000..43f26ff --- /dev/null +++ b/src/zh/breaking-changes/array-refs.md @@ -0,0 +1,79 @@ +--- +title: v-for 中的 Ref 数组 +badges: + - breaking +--- + +# {{ $frontmatter.title }} + +在 Vue 2 中,在 `v-for` 中使用的 `ref` attribute 会用 ref 数组填充相应的 `$refs` property。当存在嵌套的 `v-for` 时,这种行为会变得不明确且效率低下。 + +在 Vue 3 中,此类用法将不再自动创建 `$ref` 数组。要从单个绑定获取多个 ref,请将 `ref` 绑定到一个更灵活的函数上 (这是一个新特性): + +```html +
+``` + +结合选项式 API: + +```js +export default { + data() { + return { + itemRefs: [] + } + }, + methods: { + setItemRef(el) { + if (el) { + this.itemRefs.push(el) + } + } + }, + beforeUpdate() { + this.itemRefs = [] + }, + updated() { + console.log(this.itemRefs) + } +} +``` + +结合组合式 API: + +```js +import { onBeforeUpdate, onUpdated } from 'vue' + +export default { + setup() { + let itemRefs = [] + const setItemRef = el => { + if (el) { + itemRefs.push(el) + } + } + onBeforeUpdate(() => { + itemRefs = [] + }) + onUpdated(() => { + console.log(itemRefs) + }) + return { + setItemRef + } + } +} +``` + +注意: + +- `itemRefs` 不必是数组:它也可以是一个对象,其 ref 可以通过迭代的 key 被设置。 + +- 如有需要,`itemRefs` 也可以是响应式的,且可以被侦听。 + +## 迁移策略 + +[迁移构建开关:](migration-build.html#兼容性配置) + +- `V_FOR_REF` +- `COMPILER_V_FOR_REF` diff --git a/src/zh/breaking-changes/async-components.md b/src/zh/breaking-changes/async-components.md new file mode 100644 index 0000000..79bacfe --- /dev/null +++ b/src/zh/breaking-changes/async-components.md @@ -0,0 +1,98 @@ +--- +badges: + - new +--- + +# 异步组件 + +## 概览 + +以下是对变化的总体概述: + +- 新的 `defineAsyncComponent` 助手方法,用于显式地定义异步组件 +- `component` 选项被重命名为 `loader` +- Loader 函数本身不再接收 `resolve` 和 `reject` 参数,且必须返回一个 Promise + +如需更深入的解释,请继续阅读! + +## 介绍 + +以前,异步组件是通过将组件定义为返回 Promise 的函数来创建的,例如: + +```js +const asyncModal = () => import('./Modal.vue') +``` + +或者,对于带有选项的更高阶的组件语法: + +```js +const asyncModal = { + component: () => import('./Modal.vue'), + delay: 200, + timeout: 3000, + error: ErrorComponent, + loading: LoadingComponent +} +``` + +## 3.x 语法 + +现在,在 Vue 3 中,由于函数式组件被定义为纯函数,因此异步组件需要通过将其包裹在新的 `defineAsyncComponent` 助手方法中来显式地定义: + +```js +import { defineAsyncComponent } from 'vue' +import ErrorComponent from './components/ErrorComponent.vue' +import LoadingComponent from './components/LoadingComponent.vue' + +// 不带选项的异步组件 +const asyncModal = defineAsyncComponent(() => import('./Modal.vue')) + +// 带选项的异步组件 +const asyncModalWithOptions = defineAsyncComponent({ + loader: () => import('./Modal.vue'), + delay: 200, + timeout: 3000, + errorComponent: ErrorComponent, + loadingComponent: LoadingComponent +}) +``` + +::: tip 注意 +Vue Router 支持一个类似的机制来异步加载路由组件,也就是俗称的*懒加载*。尽管类似,但是这个功能和 Vue 所支持的异步组件是不同的。当用 Vue Router 配置路由组件时,你**不**应该使用 `defineAsyncComponent`。你可以在 Vue Router 文档的[懒加载路由](https://router.vuejs.org/zh/guide/advanced/lazy-loading.html)章节阅读更多相关内容。 +::: + +对 2.x 所做的另一个更改是,`component` 选项现在被重命名为 `loader`,以明确组件定义不能直接被提供。 + +```js{4} +import { defineAsyncComponent } from 'vue' + +const asyncModalWithOptions = defineAsyncComponent({ + loader: () => import('./Modal.vue'), + delay: 200, + timeout: 3000, + errorComponent: ErrorComponent, + loadingComponent: LoadingComponent +}) +``` + +此外,与 2.x 不同,loader 函数不再接收 `resolve` 和 `reject` 参数,且必须始终返回 Promise。 + +```js +// 2.x 版本 +const oldAsyncComponent = (resolve, reject) => { + /* ... */ +} + +// 3.x 版本 +const asyncComponent = defineAsyncComponent( + () => + new Promise((resolve, reject) => { + /* ... */ + }) +) +``` + +有关异步组件用法的详细信息,请参阅: + +- [指南:异步组件](https://cn.vuejs.org/guide/components/async.html) +- [迁移构建开关:`COMPONENT_ASYNC`](../migration-build.html#兼容性配置) diff --git a/src/zh/breaking-changes/attribute-coercion.md b/src/zh/breaking-changes/attribute-coercion.md new file mode 100644 index 0000000..687bbc0 --- /dev/null +++ b/src/zh/breaking-changes/attribute-coercion.md @@ -0,0 +1,146 @@ +--- +badges: + - breaking +--- + +# attribute 强制行为 + +:::info 信息 +这是一个底层的内部 API 更改,绝大多数开发人员不会受到影响。 +::: + +## 概览 + +以下是对变化的总体概述: + +- 移除枚举 attribute 的内部概念,并将这些 attribute 视为普通的非布尔 attribute +- **非兼容**:如果值为布尔值 `false`,则不再移除 attribute。取而代之的是,它将被设置为 attr="false"。若要移除 attribute,应该使用 `null` 或者 `undefined`。 + +如需更深入的解释,请继续阅读! + +## 2.x 语法 + +在 2.x,我们有以下策略来强制 `v-bind` 的值: + +- 对于某些 attribute/元素对,Vue 始终使用相应的 IDL attribute (property):[比如 ``,` + +``` + +## 3.x 行为 + +`$attrs` 包含了*所有的* attribute,这使得把它们全部应用到另一个元素上变得更加容易了。现在上面的示例将生成以下 HTML: + +```html + +``` + +## 迁移策略 + +在使用了 `inheritAttrs: false` 的组件中,请确保样式仍然符合预期。如果你之前依赖了 `class` 和 `style` 的特殊行为,那么一些视觉效果可能会遭到破坏,因为这些 attribute 现在可能被应用到了另一个元素中。 + +[迁移构建开关:`INSTANCE_ATTRS_CLASS_STYLE`](../migration-build.html#兼容性配置) + +## 参考 + +- [相关的 RFC](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0031-attr-fallthrough.md) +- [迁移指南 - 移除 `$listeners`](./listeners-removed.md) +- [迁移指南 - 新增 Emits 选项](./emits-option.md) +- [迁移指南 - 移除 `.native` 修饰符](./v-on-native-modifier-removed.md) +- [迁移指南 - 渲染函数 API 的更改](./render-function-api.md) diff --git a/src/zh/breaking-changes/children.md b/src/zh/breaking-changes/children.md new file mode 100644 index 0000000..34aa46a --- /dev/null +++ b/src/zh/breaking-changes/children.md @@ -0,0 +1,44 @@ +--- +badges: + - removed +--- + +# $children + +## 概览 + +`$children` 实例 property 已从 Vue 3.0 中移除,不再支持。 + +## 2.x 语法 + +在 2.x 中,开发者可以使用 `this.$children` 访问当前实例的直接子组件: + +```vue + + + +``` + +## 3.x 更新 + +在 3.x 中,`$children` property 已被移除,且不再支持。如果你需要访问子组件实例,我们建议使用 [模板引用](https://cn.vuejs.org/guide/essentials/template-refs.html#template-refs)。 + +## 迁移策略 + +[迁移构建开关:`INSTANCE_CHILDREN`](../migration-build.html#兼容性配置) diff --git a/src/zh/breaking-changes/custom-directives.md b/src/zh/breaking-changes/custom-directives.md new file mode 100644 index 0000000..ca26672 --- /dev/null +++ b/src/zh/breaking-changes/custom-directives.md @@ -0,0 +1,111 @@ +--- +badges: + - breaking +--- + +# 自定义指令 + +## 概览 + +指令的钩子函数已经被重命名,以更好地与组件的生命周期保持一致。 + +额外地,`expression` 字符串不再作为 `binding` 对象的一部分被传入。 + +## 2.x 语法 + +在 Vue 2 中,自定义指令通过使用下列钩子来创建,以对齐元素的生命周期,它们都是可选的: + +- **bind** - 指令绑定到元素后调用。只调用一次。 +- **inserted** - 元素插入父 DOM 后调用。 +- **update** - 当元素更新,但子元素尚未更新时,将调用此钩子。 +- **componentUpdated** - 一旦组件和子级被更新,就会调用这个钩子。 +- **unbind** - 一旦指令被移除,就会调用这个钩子。也只调用一次。 + +下面是一个例子: + +```html +

以亮黄色高亮显示此文本

+``` + +```js +Vue.directive('highlight', { + bind(el, binding, vnode) { + el.style.background = binding.value + } +}) +``` + +此处,在这个元素的初始设置中,通过给指令传递一个值来绑定样式,该值可以在应用中任意更改。 + +## 3.x 语法 + +然而,在 Vue 3 中,我们为自定义指令创建了一个更具凝聚力的 API。正如你所看到的,它们与我们的组件生命周期方法有很大的不同,即使钩子的目标事件十分相似。我们现在把它们统一起来了: + +- **created** - 新增!在元素的 attribute 或事件监听器被应用之前调用。 +- bind → **beforeMount** +- inserted → **mounted** +- **beforeUpdate**:新增!在元素本身被更新之前调用,与组件的生命周期钩子十分相似。 +- update → 移除!该钩子与 `updated` 有太多相似之处,因此它是多余的。请改用 `updated`。 +- componentUpdated → **updated** +- **beforeUnmount**:新增!与组件的生命周期钩子类似,它将在元素被卸载之前调用。 +- unbind -> **unmounted** + +最终的 API 如下: + +```js +const MyDirective = { + created(el, binding, vnode, prevVnode) {}, // 新增 + beforeMount() {}, + mounted() {}, + beforeUpdate() {}, // 新增 + updated() {}, + beforeUnmount() {}, // 新增 + unmounted() {} +} +``` + +因此,API 可以这样使用,与前面的示例相同: + +```html +

以亮黄色高亮显示此文本

+``` + +```js +const app = Vue.createApp({}) + +app.directive('highlight', { + beforeMount(el, binding, vnode) { + el.style.background = binding.value + } +}) +``` + +既然现在自定义指令的生命周期钩子与组件本身保持一致,那么它们就更容易被推理和记住了! + +### 边界情况:访问组件实例 + +通常来说,建议在组件实例中保持所使用的指令的独立性。从自定义指令中访问组件实例,通常意味着该指令本身应该是一个组件。然而,在某些情况下这种用法是有意义的。 + +在 Vue 2 中,必须通过 `vnode` 参数访问组件实例: + +```js +bind(el, binding, vnode) { + const vm = vnode.context +} +``` + +在 Vue 3 中,实例现在是 `binding` 参数的一部分: + +```js +mounted(el, binding, vnode) { + const vm = binding.instance +} +``` + +:::warning +有了[片段](../new/fragments.html#概览)的支持,组件可能会有多个根节点。当被应用于多根组件时,自定义指令将被忽略,并将抛出警告。 +::: + +## 迁移策略 + +[迁移构建开关:`CUSTOM_DIR`](../migration-build.html#兼容性配置) diff --git a/src/zh/breaking-changes/custom-elements-interop.md b/src/zh/breaking-changes/custom-elements-interop.md new file mode 100644 index 0000000..eb6a3c7 --- /dev/null +++ b/src/zh/breaking-changes/custom-elements-interop.md @@ -0,0 +1,134 @@ +--- +badges: + - breaking +--- + +# 与自定义元素的互操作性 + +## 概览 + +- **非兼容**:检测并确定哪些标签应该被视为自定义元素的过程,现在会在模板编译期间执行,且应该通过编译器选项而不是运行时配置来配置。 +- **非兼容**:特殊的 `is` attribute 的使用被严格限制在保留的 `` 标签中。 +- **新增**:为了支持 2.x 在原生元素上使用 `is` 的用例来处理原生 HTML 解析限制,我们用 `vue:` 前缀来解析一个 Vue 组件。 + +## 自主定制元素 + +如果我们想要在 Vue 外部定义添加自定义元素 (例如使用 Web Components API),则需要“指示”Vue 将其视为自定义元素。让我们以下面的模板为例。 + +```html + +``` + +### 2.x 语法 + +在 Vue 2.x 中,通过 `Vue.config.ignoredElements` 将标签配置为自定义元素: + +```js +// 这将使 Vue 忽略在其外部定义的自定义元素 +// (例如:使用 Web Components API) + +Vue.config.ignoredElements = ['plastic-button'] +``` + +### 3.x 语法 + +**在 Vue 3.0 中,此检查在模板编译期间执行**。要指示编译器将 `` 视为自定义元素: + +- 如果使用构建步骤:给 Vue 模板编译器传入 `isCustomElement` 选项。如果使用了 `vue-loader`,则应通过 `vue-loader` 的 `compilerOptions` 选项传递: + + ```js + // webpack 中的配置 + rules: [ + { + test: /\.vue$/, + use: 'vue-loader', + options: { + compilerOptions: { + isCustomElement: tag => tag === 'plastic-button' + } + } + } + // ... + ] + ``` + +- 如果使用动态模板编译,请通过 `app.config.compilerOptions.isCustomElement` 传递: + + ```js + const app = Vue.createApp({}) + app.config.compilerOptions.isCustomElement = tag => tag === 'plastic-button' + ``` + + 需要注意的是,运行时配置只会影响运行时的模板编译——它不会影响预编译的模板。 + +## 定制内置元素 + +自定义元素规范提供了一种将自定义元素作为[自定义内置元素](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-customized-builtin-example)的方法,方法是向内置元素添加 `is` attribute: + +```html + +``` + +在原生 attribute 于浏览器中普遍可用之前,Vue 对 `is` 这个特殊 attribute 的使用就已经在模拟其行为。但是,在 2.x 中,它将被解释为渲染一个名为 `plastic-button` 的 Vue 组件,这将阻碍上面所提到的自定义内置元素的原生用法。 + +在 3.0 中,我们将 Vue 对 `is` attribute 的特殊处理限制在了 `` 标签中。 + +- 在保留的 `` 标签上使用时,它的行为将与 2.x 中完全相同; +- 在普通组件上使用时,它的行为将类似于普通 attribute: + + ```html + + ``` + + - 2.x 的行为:渲染 `bar` 组件。 + - 3.x 的行为:渲染 `foo` 组件,并将 `is` attribute 传递给它。 + +- 在普通元素上使用时,它将作为 `is` attribute 传递给 `createElement` 调用,并作为原生 attribute 渲染。这支持了自定义内置元素的用法。 + + ```html + + ``` + + - 2.x 的行为:渲染 `plastic-button` 组件。 + - 3.x 的行为:通过调用以下函数渲染原生的 button + + ```js + document.createElement('button', { is: 'plastic-button' }) + ``` + +[迁移构建开关:`COMPILER_IS_ON_ELEMENT`](../migration-build.html#兼容性配置) + +## 使用 `vue:` 前缀来解决 DOM 内模板解析问题 + +> 提示:本节仅影响直接在页面的 HTML 中写入 Vue 模板的情况。 +> 在 DOM 模板中使用时,模板受原生 HTML 解析规则的约束。一些 HTML 元素,例如 `
    `、`
      `、`` 和 ``、和 `
      + +
      +``` + +### 3.x 语法 + +随着 `is` 的行为发生变化,现在将元素解析为 Vue 组件需要添加一个 `vue:` 前缀: + +```html + + +
      +``` + +## 迁移策略 + +- 将 `config.ignoredElements` 替换为 `vue-loader` 的 `compilerOptions` (使用构建步骤) 或 `app.config.compilerOptions.isCustomElement` (使用动态模板编译) + +- 将所有非针对 `` 标签的 `is` 用法更改为 `` (对于 SFC 模板) 或为其添加 `vue:` 前缀 (对于 DOM 内模板)。 + +## 参考 + +- [迁移指南 - Vue 与 Web Components](https://cn.vuejs.org/guide/extras/web-components.html) \ No newline at end of file diff --git a/src/zh/breaking-changes/data-option.md b/src/zh/breaking-changes/data-option.md new file mode 100644 index 0000000..bc212bf --- /dev/null +++ b/src/zh/breaking-changes/data-option.md @@ -0,0 +1,128 @@ +--- +title: Data 选项 +badges: + - breaking +--- + +# {{ $frontmatter.title}} + +## 概览 + +- **非兼容**:组件选项 `data` 的声明不再接收纯 JavaScript `object`,而是接收一个 `function`。 + +- **非兼容**:当合并来自 mixin 或 extend 的多个 `data` 返回值时,合并操作现在是浅层次的而非深层次的 (只合并根级属性)。 + +## 2.x 语法 + +在 2.x 中,开发者可以通过 `object` 或者是 `function` 定义 `data` 选项。 + +例如: + +```html + + + + + +``` + +虽然这种做法对于具有共享状态的根实例提供了一些便利,但是由于其只可能存在于根实例上,因此变得混乱。 + +## 3.x 更新 + +在 3.x 中,`data` 选项已标准化为只接受返回 `object` 的 `function`。 + +使用上面的示例,代码只可能有一种实现: + +```html + +``` + +## Mixin 合并行为变更 + +此外,当来自组件的 `data()` 及其 mixin 或 extends 基类被合并时,合并操作现在将被*浅层次*地执行: + +```js +const Mixin = { + data() { + return { + user: { + name: 'Jack', + id: 1 + } + } + } +} + +const CompA = { + mixins: [Mixin], + data() { + return { + user: { + id: 2 + } + } + } +} +``` + +在 Vue 2.x 中,生成的 `$data` 是: + +```json +{ + "user": { + "id": 2, + "name": "Jack" + } +} +``` + +在 3.0 中,其结果将会是: + +```json +{ + "user": { + "id": 2 + } +} +``` + +[迁移构建开关:`OPTIONS_DATA_FN`](../migration-build.html#兼容性配置) + +## 迁移策略 + +对于依赖对象式声明的用户,我们建议: + +- 将共享数据提取到外部对象并将其用作 `data` 中的一个 property +- 重写对共享数据的引用以指向新的共享对象 + +对于依赖 mixin 的深度合并行为的用户,我们建议重构代码以完全避免这种依赖,因为 mixin 的深度合并非常隐式,这让代码逻辑更难理解和调试。 + +[迁移构建开关:](../migration-build.html#兼容性配置) + +- `OPTIONS_DATA_FN` +- `OPTIONS_DATA_MERGE` diff --git a/src/zh/breaking-changes/emits-option.md b/src/zh/breaking-changes/emits-option.md new file mode 100644 index 0000000..077ac6b --- /dev/null +++ b/src/zh/breaking-changes/emits-option.md @@ -0,0 +1,97 @@ +--- +title: emits 选项 +badges: + - new +--- + +# `emits` 选项 + +## 概述 + +Vue 3 现在提供一个 `emits` 选项,和现有的 `props` 选项类似。这个选项可以用来定义一个组件可以向其父组件触发的事件。 + +## 2.x 的行为 + +在 Vue 2 中,你可以定义一个组件可接收的 prop,但是你无法声明它可以触发哪些事件: + +```vue + + +``` + +## 3.x 的行为 + +和 prop 类似,现在可以通过 `emits` 选项来定义组件可触发的事件: + +```vue + + +``` + +该选项也可以接收一个对象,该对象允许开发者定义传入事件参数的验证器,和 `props` 定义里的验证器类似。 + +更多信息请参阅[这部分特性的 API 文档](https://cn.vuejs.org/api/options-state.html#emits)。 + +## 迁移策略 + +强烈建议使用 `emits` 记录每个组件所触发的所有事件。 + +这尤为重要,因为我们[移除了 `.native` 修饰符](./v-on-native-modifier-removed.md)。任何未在 `emits` 中声明的事件监听器都会被算入组件的 `$attrs`,并将默认绑定到组件的根节点上。 + +### 示例 + +对于向其父组件透传原生事件的组件来说,这会导致有两个事件被触发: + +```vue + + +``` + +当一个父级组件拥有 `click` 事件的监听器时: + +```html + +``` + +该事件现在会被触发*两次*: + +- 一次来自 `$emit()`。 +- 另一次来自应用在根元素上的原生事件监听器。 + +现在你有两个选项: + +1. 正确地声明 `click` 事件。当你真的在 `` 的事件处理器上加入了一些逻辑时,这会很有用。 +2. 移除透传的事件,因为现在父组件可以很容易地监听原生事件,而不需要添加 `.native`。适用于你只想透传这个事件。 + +## 参考 + +- [相关 RFC](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0030-emits-option.md) +- [迁移指南 - 移除 `.native` 修饰符](./v-on-native-modifier-removed.md) +- [迁移指南 - 移除 `$listeners`](./listeners-removed.md) +- [迁移指南 - `$attrs` 包含 `class` & `style`](./attrs-includes-class-style.md) +- [迁移指南 - 渲染函数 API 的改动](./render-function-api.md) diff --git a/src/zh/breaking-changes/events-api.md b/src/zh/breaking-changes/events-api.md new file mode 100644 index 0000000..5f1cf39 --- /dev/null +++ b/src/zh/breaking-changes/events-api.md @@ -0,0 +1,104 @@ +--- +badges: + - breaking +--- + +# 事件 API + +## 概览 + +`$on`,`$off` 和 `$once` 实例方法已被移除,组件实例不再实现事件触发接口。 + +## 2.x 语法 + +在 2.x 中,Vue 实例可用于触发由事件触发器 API 通过指令式方式添加的处理函数 (`$on`,`$off` 和 `$once`)。这可以用于创建一个*事件总线*,以创建在整个应用中可用的全局事件监听器: + +```js +// eventBus.js + +const eventBus = new Vue() + +export default eventBus +``` + +```js +// ChildComponent.vue +import eventBus from './eventBus' + +export default { + mounted() { + // 添加 eventBus 监听器 + eventBus.$on('custom-event', () => { + console.log('Custom event triggered!') + }) + }, + beforeDestroy() { + // 移除 eventBus 监听器 + eventBus.$off('custom-event') + } +} +``` + +```js +// ParentComponent.vue +import eventBus from './eventBus' + +export default { + methods: { + callGlobalCustomEvent() { + eventBus.$emit('custom-event') // 当 ChildComponent 已被挂载时,控制台中将显示一条消息 + } + } +} +``` + +## 3.x 更新 + +我们从实例中完全移除了 `$on`、`$off` 和 `$once` 方法。`$emit` 仍然包含于现有的 API 中,因为它用于触发由父组件声明式添加的事件处理函数。 + +## 迁移策略 + +[迁移构建开关:`INSTANCE_EVENT_EMITTER`](../migration-build.html#兼容性配置) + +在 Vue 3 中,借助这些 API 从一个组件内部监听其自身触发的事件已经不再可能了。该用例没有办法迁移。 + +### 根组件事件 + +静态的事件监听器可以通过 prop 的形式传递给 `createApp` 以添加到根组件中。 + +```js +createApp(App, { + // 监听 'expand' 事件 + onExpand() { + console.log('expand') + } +}) +``` + +### 事件总线 + +事件总线模式可以被替换为使用外部的、实现了事件触发器接口的库,例如 [mitt](https://github.com/developit/mitt) 或 [tiny-emitter](https://github.com/scottcorgan/tiny-emitter)。 + +示例: + +```js +// eventBus.js +import emitter from 'tiny-emitter/instance' + +export default { + $on: (...args) => emitter.on(...args), + $once: (...args) => emitter.once(...args), + $off: (...args) => emitter.off(...args), + $emit: (...args) => emitter.emit(...args), +} +``` + +它提供了与 Vue 2 相同的事件触发器 API。 + +在绝大多数情况下,不鼓励使用全局的事件总线在组件之间进行通信。虽然在短期内往往是最简单的解决方案,但从长期来看,它维护起来总是令人头疼。根据具体情况来看,有多种事件总线的替代方案: + +* Props 和 事件 应该是父子组件之间沟通的首选。兄弟节点可以通过它们的父节点通信。 +* `provide` / `inject` 允许一个组件与它的插槽内容进行通信。这对于总是一起使用的紧密耦合的组件非常有用。 +* `provide` / `inject` 也能够用于组件之间的远距离通信。它可以帮助避免“prop 逐级透传”,即 prop 需要通过许多层级的组件传递下去,但这些组件本身可能并不需要那些 prop。 +* Prop 逐级透传也可以通过重构以使用插槽来避免。如果一个中间组件不需要某些 prop,那么表明它可能存在关注点分离的问题。在该类组件中使用 slot 可以允许父节点直接为它创建内容,因此 prop 可以被直接传递而不需要中间组件的参与。 +* [全局状态管理](https://cn.vuejs.org/guide/scaling-up/state-management.html),比如 [Pinia](https://pinia.vuejs.org/zh/index.html)。 diff --git a/src/zh/breaking-changes/filters.md b/src/zh/breaking-changes/filters.md new file mode 100644 index 0000000..cbc97d8 --- /dev/null +++ b/src/zh/breaking-changes/filters.md @@ -0,0 +1,107 @@ +--- +badges: + - removed +--- + +# 过滤器 + +## 概览 + +从 Vue 3.0 开始,过滤器已移除,且不再支持。 + +## 2.x 语法 + +在 2.x 中,开发者可以使用过滤器来处理通用文本格式。 + +例如: + +```html + + + +``` + +虽然这看起来很方便,但它需要一个自定义语法,打破了大括号内的表达式“只是 JavaScript”的假设,这不仅有学习成本,而且有实现成本。 + +## 3.x 更新 + +在 3.x 中,过滤器已移除,且不再支持。取而代之的是,我们建议用方法调用或计算属性来替换它们。 + +以上面的案例为例,以下是一种实现方式。 + +```html + + + +``` + +## 迁移策略 + +我们建议用计算属性或方法代替过滤器,而不是使用过滤器。 + +[迁移构建开关:](../migration-build.html#兼容性配置) + +- `FILTERS` +- `COMPILER_FILTERS` + +### 全局过滤器 + +如果在应用中全局注册了过滤器,那么在每个组件中用计算属性或方法调用来替换它可能就没那么方便了。 + +取而代之的是,你可以通过[全局属性](https://cn.vuejs.org/api/application.html#app-config-globalproperties)以让它能够被所有组件使用到: + +```js +// main.js +const app = createApp(App) + +app.config.globalProperties.$filters = { + currencyUSD(value) { + return '$' + value + } +} +``` + +然后,可以通过这个 `$filters` 对象修正所有的模板,就像这样: + +```html + +``` + +注意,这种方式只适用于方法,而不适用于计算属性,因为后者只有在单个组件的上下文中定义时才有意义。 diff --git a/src/zh/breaking-changes/functional-components.md b/src/zh/breaking-changes/functional-components.md new file mode 100644 index 0000000..a96f889 --- /dev/null +++ b/src/zh/breaking-changes/functional-components.md @@ -0,0 +1,120 @@ +--- +badges: + - breaking +--- + +# 函数式组件 + +## 概览 + +对变化的总体概述: + +- 在 3.x 中,2.x 带来的函数式组件的性能提升可以忽略不计,因此我们建议只使用有状态的组件 +- 函数式组件只能由接收 `props` 和 `context` (即:`slots`、`attrs`、`emit`) 的普通函数创建 +- **非兼容**:`functional` attribute 已从单文件组件 (SFC) 的 `