Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Framework7 Vue 踩坑记录 #70

Open
lmk123 opened this issue Feb 11, 2019 · 0 comments
Open

Framework7 Vue 踩坑记录 #70

lmk123 opened this issue Feb 11, 2019 · 0 comments

Comments

@lmk123
Copy link
Owner

lmk123 commented Feb 11, 2019

三年前我刚入职的时候接手了一个移动端的项目,当时代码已经很难维护了,构建工具用的是 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 个工作日,升级的步骤是:

  1. 用 Vue CLI 替换 webpack-boilerplate
  2. 升级 Framework7 到 v3,并用 Framework7 Vue 替换 vue-framework7
  3. 升级项目的依赖到最新版本

升级过程中踩到了不少坑,于是我决定写篇文章记录一下,接下来我会按照顺序依次写下踩到的坑和解决办法。

Babel 的 loose 模式导致的错误

项目在替换成 Vue CLI 之后,运行的时候控制台报了个错,最后发现跟 Babel 的 loose 模式有关。

举个例子,代码 a.push(...b) 中,当 bundefined 的时候,按照 ES6 的规范,这里是应该报错的。默认情况下,Babel 会把这段代码转换成:

// 默认情况下
a.push.apply(a, babelRuntimeHelpers.toConsumableArray(b))

toConsumableArray() 方法会确保当 bundefined 的时候抛个错出来,但是如果开启了 loose 模式,代码会转换成:

// loose 模式下
a.push.apply(a, b)

这导致 undefined 可能会被 push 到数组中,产生不可预测的 bug。

升级之前,为了减小代码体积,我给 Babel 开启了 loose 模式,升级之后,Vue CLI 默认没有开启,所以这个问题暴露出来了。现在看来,开启 loose 模式是有问题的,所以我建议慎重启用。

Framework7 的源码里用到了 ES6 的幂运算符(**)导致不兼容低版本的设备

项目上线之后,立刻就有一个同事反馈打开项目白屏,且这个同事的 iOS 版本很低,查了下线上的代码,发现代码里出现了幂运算符,但我完全不记得自己在项目里用过,看了下上下文,才发现是 Framework7 里用到了,而 node_modules 目录下的代码默认是不经过 Babel 的。

这个问题也很好解决,让 Framework7 经过 Babel 处理就可以了,在 Vue CLI 3 中,需要在 vue.config.js 添加下面的设置:

module.exports = {
  transpileDependencies: ['framework7']
}

Framework7 Vue 不支持 vue-loader 的 Hot Reload

这大概是目前为止最棘手且没有解决办法的问题了。每次更改代码之后,hot reload 都会失败,并且会在浏览器的控制台抛一个错误,而且 Vue CLI v3 还没有提供配置项关闭 hot reload 改为自动刷新,我也尝试过直接改 Webpack 的配置,但是 Vue CLI 对 devServer 配置做了特别处理,改了不生效,最后只能作罢。

所以,目前我只能手动刷新浏览器,期待有人能提供更好的办法。

应该优先使用 Framework7 Vue 组件的 text 属性

在使用组件时,我习惯把内容放在标签内,例如:

<f7-link>文字内容</f7-link>

一开始我很好奇,明明可以直接把文字写在标签内,为什么 f7-link 还要提供 text 属性,后来我发现,如果我们用了 icon,这个组件会根据 text 属性来判断这个链接是否有文字,以此来决定要不要给最终生成的 <a> 标签加上 .icon-only 的 CSS 类。

举例来说,下面的代码:

<f7-link icon="my-icon">文字内容</f7-link>
<f7-link icon="my-icon" text="文字内容"></f7-link>

会被渲染成:

<a class="icon-only my-icon">文字内容</a>
<a class="my-icon">文字内容</a>

如果有文字内容的链接加上了 .icon-only 这个类,样式上就会有问题——文本和图标会重叠。

有同样情况的还有 f7-button。除此之外,大部分组件都提供了 texttitle 这种可以控制组件文本内容的属性,我的建议是为了保险起见,优先使用属性。

Framework7 Vue 的表单组件不提供 v-model

举个例子,f7-input 的 checked 属性只是定义了 input 元素初始的勾选状态,如果用户点击了 input,这个 checked 属性完全不会变化,而这个组件又不提供 v-model,作者对此的回复是需要我们自行实现 v-model 机制,所以升级之后项目里有很多这样的代码:

<f7-searchbar :value="search" @input="search = $event.target.value"></f7-searchbar>
<f7-list-item radio :checked="checked" @change="checked = $event.target.checked"></f7-list-item>

不够优雅,但是也没有办法。

f7-searchbar 的位置

当 f7-searchbar 是 f7-page 的直接子节点时,它的 DOM 会自动跑到 .page-content 下面去:

<f7-page>
  <f7-searchbar></f7-searchbar>

  这行文本会出现在 .page-content 下
</f7-page>

会渲染为

<div class="page">
  <div class="page-content">
    <div class="searchbar">
      ...
    </div>

    这行文本会出现在 .page-content 下
  </div>
</div>

为了让它保持在 .page 下,需要用一个 div 包裹它:

<f7-page>
  <div>
    <f7-searchbar></f7-searchbar>
  </div>

  这行文本会出现在 .page-content 下
</f7-page>

Framework7 的 Router 与 vue-router

剩下的问题全都是跟 Router 相关的,我先吐槽一句:Framework7 的 Router 很强大,但这也导致它很难用。

用习惯了 vue-router 之后,在 Framework7 的 router 里肯定会撞好几次墙。vue-router 的页面生命周期简单明了,keep alive 用起来也很方便,刚开始用 Framework7 的 Router 的时候,你会发现大部分 API 都是一样的,等碰了几次壁之后,就会发现它们之间有很多差别。vue-router 相信大家都挺熟了,接下来我简单介绍一下 F7 的生命周期。

F7 的生命周期

在 Vue 中,有两种生命周期周期:

  • 组件的生命周期,例如 createdmounted
  • router 的生命周期,例如 beforeRouteEnterbeforeRouteLeave

但 F7 中的「页面」是特殊的组件,它的顶级根元素只能是 f7-page,且生命周期有三种:

  • 类似于 Vue 组件的 createdmounted
  • Router 生命周期,有点不同于 vue-router 的是,vue-router 的 beforeRouteEnterbeforeRouteLeave 既可以直接写在组件上,也可以写在路由配置里,但 F7 的 beforeRouteafterRoute 只能写在 Router 配置里
  • f7-page 独家提供了 page:initpage:beforein 等事件

要理清这么多事件不简单,但它设计的这么复杂也是有原因的,因为默认情况下,页面之间的切换是有滑动效果的,所以它分别设计了三套事件,全面满足用户的各种需求。

文档上分别介绍了这三套事件,但它们组合起来就是另一回事了。我通过观察,大致了解了它们的触发顺序与时机,这里我简单介绍一下。

  1. 假设我们有三个页面 A、B、C,首页是 A,第一次进入时它会依次触发一系列创建事件:beforeCreatecreatedbeforeMountmountedpage:initpage:beforeinpage:afterin
  2. 然后我们从 A 跳转到 B,因为 B 被初始化了,它会依次触发第一步中的一系列创建事件,但是此时 A 并没有被销毁,它只触发了两个事件:page:beforeoutpage:afterout,且还留在 DOM 中,以便从 B 返回时能有滑动效果
  3. 然后我们从 B 跳转到 C,这时 A 才会被销毁,依次触发一系列销毁事件:page:beforeremovebeforeDestroydestroyed;而 B 仅仅触发了 page:beforeoutpage:afterout;C 会触发第一步中一系列的创建事件
  4. 现在我们从 C 返回到 B,此时 C 会触发一系列销毁事件,B 仅仅只会触发 page:reinitpage:before-inpage:afterin接下来注意,虽然还没有返回到 A,但 A 在此时被提前创建了,触发了第一步中的一系列创建事件
  5. 最后我们从 B 返回到 A,这时 B 才会触发一系列销毁事件,A 仅仅只触发了 page:reinitpage:beforeinpage:afterin

从上面这个步骤可以大致摸清 F7 Router 的特点:

  • 页面组件默认会保持两个,所以当路由深入时,上一个页面组件不会销毁,除非进入到了第三个页面
  • 当前页面组件在返回上一页时会立刻被销毁,并提前创建前面一个页面

这样会导致一些问题,例如我一般会在 A 的 created 钩子里请求数据,并显示一个 loading 层,现在由于从 C 返回 B 时会提前创建 A,导致本应该在 A 显示的 loading 层出现在了 B 页面,所以我还得区分这次请求数据时,A 是第一次进入还是由子页面提前创建的,避免显示不必要的 loading 层……

page:beforeout 事件可能不会触发

上面提到过,F7 Router 的 beforeEnterafterEnter 不能直接写在组件里,很不方便,所以我一般用 page:beforeinpage:beforeout 代替这两个事件,但后来我发现在用router.navigate(url, options) 方法或者用 f7-link 组件时,如果 options 里设置了 reloadCurrentreloadAllreloadPrevious 的其中一个为 true,会导致 page:beforeout 不被触发,page:beforein 倒是不受影响。

其它不同之处

除了生命周期要比 vue-router 复杂的多之外,F7 Router 还有这些坑要注意一下:

  1. 文档上专门提到过$f7router 只能在页面组件上获取到,如果页面组件的子组件里需要用到 $f7router 上的属性,需要用 $f7.views.main.router 访问。而我在开发的过程中发现,子组件的 this 是能直接读取到 $f7router 的,可用了之后才发现读到的不是实时的路由状态,最后老老实实从页面组件往下传了。
  2. f7-link 组件生成的 href 是不带 #! 的,所以如果你的项目用的是前端 hash 路由,没有配置 History 模式,那么用户选择「在新标签页中打开链接」时会得到一个 404 页面。移动端的项目一般很少有用户会这么做,这算是我吹毛求疵了,但我觉得还是值得提一下。
  3. F7 Router 路径里的中文 params 会被自动 encode 成乱码,所以如果你在路径里用到了中文 params,还需要 decode 一下;querystring 里的中文不会被 encode。
  4. F7 默认情况下会且仅会在移动端下,给被点击的 a, button, label, span, .actions-button 元素临时加上 .active-state 样式,所以 click 事件里对 className 的判断(例如 $event.target.className === 'my-class-name')会失效,本地开发过程中根本不会发现这个问题,所以要确保用 classList 之类的 API 来判断类名。
  5. vue-router 中的 meta 写法在 F7 router 中照旧,但读取 meta 的 $router.meta.xxx 要改成 $f7route.route.meta.xxx
  6. 在页面的滑动效果进行中的时候不要改变 DOM,这会导致滑动效果卡顿,可以在 page:afterin 事件触发之后再改变 DOM

全文完。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant