avatar

vue3的rfcs 更新东西 (下)

0026-async-component-api(异步组件api)

  • Start Date: 2020-03-25
  • Target Major Version: 3.x

总结

引入一个用于定义异步组件的专用API

基础例子

1
2
3
4
5
6
7
8
9
10
11
12
13
import { defineAsyncComponent } from "vue"

// simple usage
const AsyncFoo = defineAsyncComponent(() => import("./Foo.vue"))

// with options
const AsyncFooWithOptions = defineAsyncComponent({
loader: () => import("./Foo.vue"),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})

动机

根据[RFC-0008: Render Function API Change]中引入的更改,Vue 3中的普通函数现在被视为功能组件。异步组件现在必须通过API方法显式定义。

详细设计

简单的使用

1
2
3
4
import { defineAsyncComponent } from "vue"

// simple usage
const AsyncFoo = defineAsyncComponent(() => import("./Foo.vue"))

defineAsyncComponent可以接受一个加载器函数,该函数返回一个解析到实际组件的承诺。

  • 如果解析的值是ES模块,模块的default 导出将自动用作组件。

  • 与2.x 的区别 :注意,loader函数不再像2.x中那样接收resolvereject 参数 -承诺必须总是被返回。

    对于依赖于loader函数中的自定义resolvereject 的代码,转换很简单:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // before
    const Foo = (resolve, reject) => {
    /* ... */
    }

    // after
    const Foo = defineAsyncComponent(() => new Promise((resolve, reject) => {
    /* ... */
    }))

选择使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { defineAsyncComponent } from "vue"

const AsyncFooWithOptions = defineAsyncComponent({
loader: () => import("./Foo.vue"),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 100, // default: 200
timeout: 3000, // default: Infinity
suspensible: false, // default: true
onError(error, retry, fail, attempts) {
if (error.message.match(/fetch/) && attempts <= 3) {
retry()
} else {
fail()
}
}
})
  • delaytimeout 选项的工作方式与2.x完全相同。

与 2.x 不同:

  • The component option is replaced by the new loader option, which accepts the same loader function as in the simple usage.

  • component 选项被新的 loader 选项替换,它接受与简单用法相同的loader函数。

在2.x,一个带有选项的异步组件被定义为

1
2
3
4
() => ({
component: Promise<Component>
// ...other options
})

而在3.x现在是:

1
2
3
4
defineAsyncComponent({
loader: () => Promise<Component>
// ...other options
})
  • 2.x loadingerror 选项被分别重命名为 loadingComponenterrorComponent ,以便更显式地显示。

重试控制

新的 onError 选项提供了一个钩子,用于在加载器错误时执行定制的重试行为:

1
2
3
4
5
6
7
8
9
10
11
const Foo = defineAsyncComponent({
// ...
onError(error, retry, fail, attempts) {
if (error.message.match(/fetch/) && attempts <= 3) {
// retry on fetch errors, 3 max attempts
retry()
} else {
fail()
}
}
})

请注意,retry/fail 类似于promise的 resolve/reject :必须调用其中一个来继续错误处理。

使用 Suspense

异步组件在3.x 默认情况下是suspensible。这意味着如果它在父链中有一个<Suspense> ,它将被视为<Suspense> 的异步依赖。在这种情况下,加载状态将由<Suspense> 控制,组件自己的loading, error, delaytimeout 选项将被忽略。

async组件可以选择退出悬置控制(Suspense control),并通过在选项中指定 suspensible: false 来始终控制自己的加载状态。

0027-custom-elements-interop(自定义-元素交互操作)

  • Start Date: 2020-03-25
  • Target Major Version: 3.x

总结

  • Breaking: 自定义元素白名单现在在模板编译期间执行,并且应该通过编译器选项而不是运行时配置来配置。

  • Breaking: 只在保留的 <component> 标签上使用特殊的 is 属性。

  • 引入新的 v-is 指令来支持 2.x 个用例,在原生元素上使用 is 来绕过原生HTML解析限制。

动机

  • 以更高性能的方式提供本机自定义元素支持
  • 改进对[自定义内置元素]的支持

详细设计

自主定制元素

在Vue 2.x,通过 Vue.config.ignoredElements 将标签白名单作为自定义元素。缺点是,当使用这个配置选项时,需要对每个vnode创建调用执行检查。

在Vue 3.0中,这个检查是在模板编译期间执行的 例如,给定下面的模板:

1
<plastic-button></plastic-button>

默认生成的渲染函数代码是(伪代码):

1
2
3
4
function render() {
const component_plastic_button = resolveComponent('plastic-button')
return createVNode(component_plastic_button)
}

如果没有找到名为 plastic-button 的组件,它将发出警告。

如果用户希望使用名为 plastic-button 的本地自定义元素,所需生成的代码应该是:

1
2
3
function render() {
return createVNode('plastic-button') // render as native element
}

指示编译器将<plastic-button>视为自定义元素:

  • 如果使用构建步骤:将 isCustomElement 选项传递给Vue模板编译器。如果使用vue-loader,这应该通过vue-loadercompilerOptions选项传递:
1
2
3
4
5
6
7
8
9
10
11
12
13
// in webpack config
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
options: {
compilerOptions: {
isCustomElement: tag => tag === 'plastic-button'
}
}
}
// ...
]
  • 如果使用动态模板编译,请通过app.config传递:
1
2
3
const app = Vue.createApp(/* ... */)

app.config.isCustomElement = tag => tag === 'plastic-button'

注意,运行时配置只影响运行时模板编译——它不会影响预编译模板。

定制的内置的元素

自定义元素规范提供了一种将自定义元素作为[自定义内置元素]使用的方法,方法是在内置元素中添加 is 属性:

1
<button is="plastic-button">Click Me!</button>

Vue对is特殊属性的使用模拟了原生属性在浏览器中普遍可用之前的功能。然而,在2.x它被解释为呈现名为plastic-button的Vue组件。这将阻止上面提到的定制内置元素的本机使用。

在3.0中,我们将Vue对 is的特殊处理限制为 <component> 标签。

  • 当在保留的 <component> 标签上使用时,它的行为和2.x中的完全一样。

当在普通组件上使用时,它的行为将像一个普通的属性:

1
<foo is="bar" />
  • 2.x 行为: 渲染bar组件.
  • 3.x 行为: 渲染foo组件并传递is属性。
  • 当在普通元素上使用时,它将作为is选项传递给createElement调用,并作为原生属性呈现。这支持使用定制的内置元素。

    1
    <button is="plastic-button">Click Me!</button>
    • 2.x 行为: 渲染plastic-button组件.

    • 3.x 行为: 通过调用来呈现原生按钮

      1
      document.createElement('button', { is: 'plastic-button' })

‘ v-is ‘用于解决In-DOM模板解析问题

注意:本节只影响直接在页面的HTML中编写Vue模板的情况。

当使用in-DOM模板时,模板遵循原生HTML解析规则。一些HTML元素,如<ul>, <ol>, <table><select>,都对其内部可以出现的元素有限制,而一些元素,如<li>, <tr>, 和 <option>,只能出现在某些其他元素内部。

在2.x我们建议通过在本地标签上使用is来绕过这些限制:

1
2
3
<table>
<tr is="blog-post-row"></tr>
</table>

鉴于上述is的行为改变,我们需要引入新的 v-is 指令来处理这些情况:

1
2
3
<table>
<tr v-is="'blog-post-row'"></tr>
</table>

注意v-is函数就像动态的2.x :is 绑定——所以要通过注册名来渲染组件,它的值应该是一个JavaScript字符串字面值。

0028-router-active-link (路由器主动链接)

  • Start Date: 2020-02-28
  • Target Major Version: Router v4

总结

改变router-link-active的应用方式,使其更符合路由器的概念。现在,如果生成的url是当前位置的部分版本,则该链接是_active_。这产生了一些问题:

  • 别名(不一定)匹配

  • 在带有首斜杠的子节点上,指向父节点的链接不会显示为活动的,因为url是不同的

  • 查询用于激活链路

嵌套的路由

值得注意的是,嵌套路由只有在与渲染路由相关的参数相同的情况下才会匹配。

例如,给定这些路线:

1
2
3
4
5
6
7
8
9
10
11
12
const routes = [
{
path: '/parent/:id',
children: [
// empty child
{ path: '' },
// child with id
{ path: 'child/:id' },
{ path: 'child-second/:id' }
]
}
]

如果当前路由是 /parent/1/child/2 ,这些链接将是活跃的:

url active exact active
/parent/1/child/2
/parent/1/child/3
/parent/1/child-second/2
/parent/1
/parent/2
/parent/2/child/2
/parent/2/child-second/2

不相关但相似的路线

从记录的视图来看,不相关但共享一个公共路径的路由不再是活动的。

例如,给定这些路线:

1
2
3
4
5
const routes = [
{ path: '/movies' },
{ path: '/movies/new' },
{ path: '/movies/search' }
]

如果当前路径是/movies/new,这些链接将是激活的:

url active exact active
/movies
/movies/new
/movies/search

注意:这种行为与实际行为不同

值得注意的是,嵌套它们仍然可以从_active_链接中获益:

1
2
3
4
5
6
7
8
9
10
11
12
const routes = [
{
path: '/movies',
// we need this to render the children (see note below)
component: { render: () => h('RouterView') },
children: [
{ path: 'new' },
// different child
{ path: 'search' }
]
}
]

如果当前路径是/movies/new,这些链接将是激活的:

url active exact active
/movies
/movies/new
/movies/search

注意:为了便于使用,我们可以让component不存在,在内部表现得就像有一个component选项来渲染RouterView组件一样

别名

假定一个别名只是一个不同的path,而保持在一个记录上的其他一切,这是有意义的别名是活动的path时,他们被混叠匹配,反之亦然。

例如,给定这些路线:

1
const routes = [{ path: '/movies', alias: ['/films'] }]

如果当前路径是/movies or /films,两个链接都会激活:

url active exact active
/movies
/films

嵌套的别名

处理别名路由的嵌套子路由时,其行为与此类似。

例如,给定这些路线:

1
2
3
4
5
6
7
8
9
10
11
12
const routes = [
{
path: '/parent/:id',
alias: '/p/:id',
children: [
// empty child
{ path: '' },
// child with id
{ path: 'child/:id', alias: 'c/:id' }
]
}
]

如果当前路由是/parent/1/child/2, /p/1/child/2, /p/1/c/2, 或, /parent/1/c/2,这些链接将会激活:

url active exact active
/parent/1/child/2
/parent/1/c/2
/p/1/child/2
/p/1/c/2
/p/1/child/3
/parent/1/child/3
/parent/1
/p/1
/parent/2
/p/2

绝对的嵌套的别名

嵌套的子节点可以通过使其以/,开头来拥有一个绝对的 path ,在这个场景中也适用相同的规则。鉴于这些路线:

例如,给定这些路线:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const routes = [
{
path: '/parent/:id',
alias: '/p/:id',
name: 'parent',
children: [
// empty child
{ path: '', alias: ['alias', '/p_:id'], name: 'child' },
// child with absolute path. we need to add an `id` because the parent needs it
{ path: '/p_:id/absolute-a', alias: 'as-absolute-a' },
// same as above but the alias is absolute
{ path: 'as-absolute-b', alias: '/p_:id/absolute-b' }
]
}
]

如果当前路由是 /p_1/absolute-a, /p/1/as-absolute-a, 或, /parent/1/as-absolute-a,这些链接将被激活:

url active exact active
/p/1/as-absolute-a
/p_1/absolute-a
/parent/1/absolute-a
/parent/2/absolute-a
/parent/1/absolute-b
/p/1/absolute-b
/p_1/absolute-b
/parent/1
/p/1
/parent/1/alias
/p/1/alias
/p_1
/parent/2
/p/2

请注意,空的 path记录与另一个子记录/p/1/absolute-b是_active_,但不是_exact active_。它的所有别名也都是_active_,因为它们是空 path的别名。如果是相反的情况: path 不是空的,但其中一个别名是空的 path ,那么没有它们中的任何一个将是活动的,因为原始的 path 优先于别名。

命名为嵌套的路线

如果url是通过父路由的name(在本例中是 parent )解析的,它将不包含空路径子路由。这一点很重要,因为它们都解析到相同的url,但当在router-links to prop中使用时,当涉及到_active_和/或_exact active_时,它们会产生不同的结果。这与他们呈现的不同和其他主动行为是一致的。

例如,给定的路线上一个示例中,如果当前位置/parent/1,parent 与 child 的视图都是渲染,我们实际上是在{ name: 'child' },而不是在{ name: 'parent' },这是类似的表之前的还包括to:

to‘s value resolved url active exact active
{ name: 'parent', params: { id: '1' } } /parent/1 (parent)
'/parent/1' /parent/1 (child)
{ name: 'child', params: { id: '1' } } /parent/1 (child)
'/p_1' /p_1 (child)
'/parent/1/alias' /parent/1/alias (child)

But if the current location is { name: 'parent' }, it will still yield the same url, /parent/1, but a different table:

to‘s value resolved url active exact active
{ name: 'parent', params: { id: '1' } } /parent/1 (parent)
'/parent/1' /parent/1 (child)
{ name: 'child', params: { id: '1' } } /parent/1 (child)
'/p_1' /p_1 (child)
'/parent/1/alias' /parent/1/alias (child)

重复的参数

使用重复的参数,比如

  • /articles/:id+
  • /articles/:id*

0029-router-dynamic-routing(路由器动态路由)

  • Start Date: 2020-01-27
  • Target Major Version: Vue 3 Router 4

总结

引入允许在路由器工作时添加和删除路由记录的API

  • router.addRoute(route: RouteRecord) 新增一个新路由
  • router.removeRoute(name: string | symbol) 移除一个现有的路由
  • router.hasRoute(name: string | symbol): boolean 检查这个路由是否存在
  • router.getRoutes(): RouteRecord[] 获取当前的路由列表

基础例子

给定一个正在运行的应用程序中的路由器实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
router.addRoute({
path: '/new-route',
name: 'NewRoute',
component: NewRoute
})

// add to the children of an existing route
router.addRoute('ParentRoute', {
path: 'new-route',
name: 'NewRoute',
component: NewRoute
})

router.removeRoute('NewRoute')

// normalized version of the records added
const routeRecords = router.getRoutes()

0030-emits-option(派发 选项)

  • Start Date: 2019-02-27
  • Target Major Version: 2.x & 3.x

总结

让组件显式地发出哪些事件。

基础例子

1
2
3
4
5
6
7
8
9
10
11
12
13
const Comp = {
emits: {
submit: payload => {
// validate payload by returning a boolean
}
},

created() {
this.$emit('submit', {
/* payload */
})
}
}

详细设计

数组的语法

1
2
3
4
5
6
{
emits: [
'eventA',
'eventB'
}
}

对象的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
emits: {
// no validation
click: null,

// with validation验证器函数应该返回一个布尔值来指示事件参数是否有效
//
submit: payload => {
if (payload.email && payload.password) {
return true
} else {
console.warn(`Invalid submit event payload!`)
return false
}
}
}
}

类型推断

对象验证器语法是在考虑TypeScript类型推断时选择的。验证器类型签名可以用于键入$emit调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Foo = defineComponent({
emits: {
submit: (payload: { email: string; password: string }) => {
// perform runtime validation
}
},

methods: {
onSubmit() {
this.$emit('submit', {
email: 'foo@bar.com',
password: 123 // Type error!
})

this.$emit('non-declared-event') // Type error!
}
}
})

0031-attr-fallthrough(失效的属性)

  • Start Date: 2019-11-05

  • Target Major Version: 3.x

  • 在组件上使用的v-on监听器会失效,并在子组件根上注册为本地监听器。不再需要.native修饰符。

  • inheritAttrs: false 现在影响 classstyle

  • this.$attrs 现在包含传递给组件的所有内容,但不包含那些显式声明为 props 的内容,包括 classstylev-on 监听器。this.$listeners 是被移除了

  • 功能组件属性落差行为调整:

    • 使用显式的props声明:完全失败,就像有状态组件。
    • 没有props声明:只有 class, style and v-on 监听器。

0032-vtu-improve-async-workflow(vtu改进异步工作流程)

  • Start Date: 2020.02.01
  • Target Major Version: VTU beta-0.3x/1.x

总结

允许等待触发重新呈现的Vue测试Utils api。这使得在重新呈现后断言更改更容易。

基础例子

1
2
3
const wrapper = mount(Component)
await wrapper.find('.element').trigger('click')
expect(wrapper.emitted('event')).toBeTruthy()

0033-router-navigation-failures(路由器导航失败)

  • Start Date: 2020-03-26
  • Target Major Version: Vue Router v4

总结

  • 明确定义什么是导航失败,以及我们如何和在哪里可以捕捉到它们。
  • 更改基于Promise 的router.push(和router.replace by extension)resolves and rejects。
  • 使router.pushrouter.afterEachrouter.afterEach 一致。

基础例子

router.push

如果有未处理的错误或错误被传递给next:

1
2
3
4
5
6
7
8
// any other navigation guard
router.beforeEach((to, from, next) => {
next(new Error())
// or
throw new Error()
// or
return Promise.reject(new Error())
})

然后由 router.push rejects 返回 promise :

1
2
3
router.push('/url').catch((err) => {
// ...
})

所有其他情况下,promise被resolved。我们可以通过检查解析后的值来知道导航是否失败:

1
2
3
4
5
6
router.push('/dashboard').then((failure) => {
if (failure) {
failure instanceof Error // true
failure.type // NavigationFailure.canceled
}
})

router.afterEach

相当于全局中的 router.push().then()

router.onError

相当于全局中的 router.push().catch()

0034-router-view-keep-alive-transitions(路由过渡)

  • Start Date: 2020-04-20
  • Target Major Version: Vue Router 4.x

总结

考虑到Vue 3中功能组件的变化,通过简单地将KeepAlive and TransitionRouterView 组合起来使用,将不再可能使用RouterViewKeepAlive/Transition。相反,我们需要一种方法来直接把由RouterView渲染的组件提供给这些组件:

1
2
3
4
5
<router-view v-slot="{ Component }">
<transition :name="transitionName" mode="out-in">
<component :is="Component"></component>
</transition>
</router-view>

0035-router-scroll-position (路由滚动位置)

  • Start Date: 2020-05-31
  • Target Major Version: Vue Router v3 and v4

总结

弃用selector, x, y and offset ,引入 el, top, left (and behavior),它们与原生api对齐,更灵活。

基础例子

1
2
3
4
5
6
7
8
9
10
11
12
const router = new Router({
scrollBehavior(to, from, savedPosition) {

// scroll to id `can~contain-special>characters` + 200px
return {
el: '#can~contain-special>characters'
// top relative offset
top: 200
// instead of `offset: { y: 200 }`
}
}
})

scrollBehavior返回的其他可能值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// scroll smoothly (when supported by the browser) to 400px from top and 20px from left
{
top: 400,
left: 20,
behavior: 'smooth'
}

// scroll smoothly to selector .container
{
el: '.container'
behavior: 'smooth'
}

// directly pass an Element to `el`
{
// use the fragment(to.hash) on the url but scroll to a child of it with a class `container`
el: document.getElementById(to.hash.slice(1)).querySelector('.container')
}

0036-router-view-route-prop

  • Start Date: 2020-04-01
  • Target Major Version: Vue Router v3 and v4

总结

添加一个route prop,允许自定义在router-view组件中显示什么。这允许用户显示不是连接到URL的视图。它支持像modals这样的模式

基础例子

假设有一个backgroundView变量,它允许我们知道是否正在显示不同的路径。然后我们可以计算一个路由位置,它会解析到一个不同于url上显示的位置:

1
2
3
4
5
6
7
8
9
10
11
const App = {
computed: {
routeWithModal() {
if (backgroundView) {
return this.$router.resolve(backgroundView)
} else {
return this.$route
}
}
}
}

我们可以把这个变量传递给router-view:

1
<router-view :route="routeWithModal"></router-view>

在大多数情况下,这会显示与当前url (this.$route)相关联的组件,而在其他情况下,会显示一些不同的内容,同时仍然暴露解析后的位置在this.$route

0037-router-return-guards(路由器返回警卫)

  • Start Date: 2020-06-08
  • Target Major Version: Vue Router 3 and 4

总结

允许导航守卫返回值或 a Promise,而不是调用next:

1
2
3
4
5
6
7
8
// change
router.beforeEach((to, from, next) => {
if (!isAuthenticated) next(false)
else next()
})

// into
router.beforeEach(() => isAuthenticated)

因为我们可以检查导航守卫的数量或参数,所以我们可以自动知道是否应该查看返回值。这不是一个突破性的改变

文章作者: 张复明
文章链接: https://hexo.zhangaming.com/2021/01/28/vue3-active-rfcs-bottom/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 阿明的博客
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论