You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
然后我们从 A 跳转到 B,因为 B 被初始化了,它会依次触发第一步中的一系列创建事件,但是此时 A 并没有被销毁,它只触发了两个事件:page:beforeout、page:afterout,且还留在 DOM 中,以便从 B 返回时能有滑动效果
然后我们从 B 跳转到 C,这时 A 才会被销毁,依次触发一系列销毁事件:page:beforeremove、beforeDestroy、destroyed;而 B 仅仅触发了 page:beforeout、page:afterout;C 会触发第一步中一系列的创建事件
现在我们从 C 返回到 B,此时 C 会触发一系列销毁事件,B 仅仅只会触发 page:reinit、page:before-in、page:afterin,接下来注意,虽然还没有返回到 A,但 A 在此时被提前创建了,触发了第一步中的一系列创建事件
最后我们从 B 返回到 A,这时 B 才会触发一系列销毁事件,A 仅仅只触发了 page:reinit、page:beforein、page:afterin
从上面这个步骤可以大致摸清 F7 Router 的特点:
页面组件默认会保持两个,所以当路由深入时,上一个页面组件不会销毁,除非进入到了第三个页面
当前页面组件在返回上一页时会立刻被销毁,并提前创建前面一个页面
这样会导致一些问题,例如我一般会在 A 的 created 钩子里请求数据,并显示一个 loading 层,现在由于从 C 返回 B 时会提前创建 A,导致本应该在 A 显示的 loading 层出现在了 B 页面,所以我还得区分这次请求数据时,A 是第一次进入还是由子页面提前创建的,避免显示不必要的 loading 层……
三年前我刚入职的时候接手了一个移动端的项目,当时代码已经很难维护了,构建工具用的是 Browserify,不是当时正火热的 Webpack,而且大部分依赖的版本也很老旧了,所以接手这个项目之后,我做的第一件事就是重写了这个项目。
那时,Framework7 还是 v1 版本,还没有对 Vue 做支持,所以我写了一个 Vue 组件版本的 Framework7(见 lmk123/vue-framework7);另外,当时在使用 Vuejs 官方的 Webpack 模版时遇到不少问题,提了 issue 给官方但迟迟没有修复,所以我又自己整理了一套 Webpack + Vue 的项目模版(见 lmk123/webpack-boilerplate)。
这两个项目一直用到了现在,但在这三年的时间里,Webpack 已经更新到 v4 了,Vue CLI v3 提供了能通过 npm 更新的项目模版,Framework7 也从 v1 更新到了 v3,并官方支持了 Vue(见 Framework7 Vue),与此同时,维护 webpack-boilerplate 与 vu-framework7 的成本越来越高,所以趁着临近春节比较得空,我决定给这个项目做一次升级。
这次升级大概用了 7 个工作日,升级的步骤是:
升级过程中踩到了不少坑,于是我决定写篇文章记录一下,接下来我会按照顺序依次写下踩到的坑和解决办法。
Babel 的 loose 模式导致的错误
项目在替换成 Vue CLI 之后,运行的时候控制台报了个错,最后发现跟 Babel 的 loose 模式有关。
举个例子,代码
a.push(...b)
中,当b
是undefined
的时候,按照 ES6 的规范,这里是应该报错的。默认情况下,Babel 会把这段代码转换成:toConsumableArray()
方法会确保当b
是undefined
的时候抛个错出来,但是如果开启了 loose 模式,代码会转换成:这导致
undefined
可能会被 push 到数组中,产生不可预测的 bug。升级之前,为了减小代码体积,我给 Babel 开启了 loose 模式,升级之后,Vue CLI 默认没有开启,所以这个问题暴露出来了。现在看来,开启 loose 模式是有问题的,所以我建议慎重启用。
Framework7 的源码里用到了 ES6 的幂运算符(**)导致不兼容低版本的设备
项目上线之后,立刻就有一个同事反馈打开项目白屏,且这个同事的 iOS 版本很低,查了下线上的代码,发现代码里出现了幂运算符,但我完全不记得自己在项目里用过,看了下上下文,才发现是 Framework7 里用到了,而
node_modules
目录下的代码默认是不经过 Babel 的。这个问题也很好解决,让 Framework7 经过 Babel 处理就可以了,在 Vue CLI 3 中,需要在 vue.config.js 添加下面的设置:
Framework7 Vue 不支持 vue-loader 的 Hot Reload
这大概是目前为止最棘手且没有解决办法的问题了。每次更改代码之后,hot reload 都会失败,并且会在浏览器的控制台抛一个错误,而且 Vue CLI v3 还没有提供配置项关闭 hot reload 改为自动刷新,我也尝试过直接改 Webpack 的配置,但是 Vue CLI 对 devServer 配置做了特别处理,改了不生效,最后只能作罢。
所以,目前我只能手动刷新浏览器,期待有人能提供更好的办法。
应该优先使用 Framework7 Vue 组件的
text
属性在使用组件时,我习惯把内容放在标签内,例如:
一开始我很好奇,明明可以直接把文字写在标签内,为什么 f7-link 还要提供
text
属性,后来我发现,如果我们用了 icon,这个组件会根据text
属性来判断这个链接是否有文字,以此来决定要不要给最终生成的<a>
标签加上.icon-only
的 CSS 类。举例来说,下面的代码:
会被渲染成:
如果有文字内容的链接加上了
.icon-only
这个类,样式上就会有问题——文本和图标会重叠。有同样情况的还有 f7-button。除此之外,大部分组件都提供了
text
或title
这种可以控制组件文本内容的属性,我的建议是为了保险起见,优先使用属性。Framework7 Vue 的表单组件不提供 v-model
举个例子,f7-input 的 checked 属性只是定义了 input 元素初始的勾选状态,如果用户点击了 input,这个 checked 属性完全不会变化,而这个组件又不提供 v-model,作者对此的回复是需要我们自行实现 v-model 机制,所以升级之后项目里有很多这样的代码:
不够优雅,但是也没有办法。
f7-searchbar 的位置
当 f7-searchbar 是 f7-page 的直接子节点时,它的 DOM 会自动跑到
.page-content
下面去:会渲染为
为了让它保持在
.page
下,需要用一个 div 包裹它:Framework7 的 Router 与 vue-router
剩下的问题全都是跟 Router 相关的,我先吐槽一句:Framework7 的 Router 很强大,但这也导致它很难用。
用习惯了 vue-router 之后,在 Framework7 的 router 里肯定会撞好几次墙。vue-router 的页面生命周期简单明了,keep alive 用起来也很方便,刚开始用 Framework7 的 Router 的时候,你会发现大部分 API 都是一样的,等碰了几次壁之后,就会发现它们之间有很多差别。vue-router 相信大家都挺熟了,接下来我简单介绍一下 F7 的生命周期。
F7 的生命周期
在 Vue 中,有两种生命周期周期:
created
、mounted
等beforeRouteEnter
、beforeRouteLeave
但 F7 中的「页面」是特殊的组件,它的顶级根元素只能是 f7-page,且生命周期有三种:
created
、mounted
等beforeRouteEnter
、beforeRouteLeave
既可以直接写在组件上,也可以写在路由配置里,但 F7 的beforeRoute
、afterRoute
只能写在 Router 配置里page:init
、page:beforein
等事件要理清这么多事件不简单,但它设计的这么复杂也是有原因的,因为默认情况下,页面之间的切换是有滑动效果的,所以它分别设计了三套事件,全面满足用户的各种需求。
文档上分别介绍了这三套事件,但它们组合起来就是另一回事了。我通过观察,大致了解了它们的触发顺序与时机,这里我简单介绍一下。
beforeCreate
、created
、beforeMount
、mounted
、page:init
、page:beforein
、page:afterin
page:beforeout
、page:afterout
,且还留在 DOM 中,以便从 B 返回时能有滑动效果page:beforeremove
、beforeDestroy
、destroyed
;而 B 仅仅触发了page:beforeout
、page:afterout
;C 会触发第一步中一系列的创建事件page:reinit
、page:before-in
、page:afterin
,接下来注意,虽然还没有返回到 A,但 A 在此时被提前创建了,触发了第一步中的一系列创建事件page:reinit
、page:beforein
、page:afterin
从上面这个步骤可以大致摸清 F7 Router 的特点:
这样会导致一些问题,例如我一般会在 A 的
created
钩子里请求数据,并显示一个 loading 层,现在由于从 C 返回 B 时会提前创建 A,导致本应该在 A 显示的 loading 层出现在了 B 页面,所以我还得区分这次请求数据时,A 是第一次进入还是由子页面提前创建的,避免显示不必要的 loading 层……page:beforeout 事件可能不会触发
上面提到过,F7 Router 的
beforeEnter
、afterEnter
不能直接写在组件里,很不方便,所以我一般用page:beforein
、page:beforeout
代替这两个事件,但后来我发现在用router.navigate(url, options)
方法或者用 f7-link 组件时,如果options
里设置了reloadCurrent
、reloadAll
和reloadPrevious
的其中一个为true
,会导致page:beforeout
不被触发,page:beforein
倒是不受影响。其它不同之处
除了生命周期要比 vue-router 复杂的多之外,F7 Router 还有这些坑要注意一下:
$f7router
只能在页面组件上获取到,如果页面组件的子组件里需要用到$f7router
上的属性,需要用$f7.views.main.router
访问。而我在开发的过程中发现,子组件的this
是能直接读取到$f7router
的,可用了之后才发现读到的不是实时的路由状态,最后老老实实从页面组件往下传了。href
是不带#!
的,所以如果你的项目用的是前端 hash 路由,没有配置 History 模式,那么用户选择「在新标签页中打开链接」时会得到一个 404 页面。移动端的项目一般很少有用户会这么做,这算是我吹毛求疵了,但我觉得还是值得提一下。a, button, label, span, .actions-button
元素临时加上.active-state
样式,所以 click 事件里对className
的判断(例如$event.target.className === 'my-class-name'
)会失效,本地开发过程中根本不会发现这个问题,所以要确保用classList
之类的 API 来判断类名。$router.meta.xxx
要改成$f7route.route.meta.xxx
page:afterin
事件触发之后再改变 DOM全文完。
The text was updated successfully, but these errors were encountered: