行列
行列
前端承接的需求及逻辑复杂度越来越高,同时异步场景随处可见。这对于前端工程师来说,要实现一个良好体验的应用需要花费更多的精力和时间,这篇主要讨论界面的立即响应细节 用户在使用系统时,都希望有交互后,比如按钮点击,系统能够立即响应,最好能直接给出要想的结果,讨厌等待。 在目前这种前后端交互的技术架构上,即使使用`socket`进行通讯,也无法做到数据处理及结果立即响应。针对这种场景,前端应该在合适的地方展示系统正在处理的内部过程,比如用户点击按钮,按钮变成`正在处理`的文字提示,或在界面某个地方,有一个处理进度条,实时告诉用户目前系统的处理进度。 但凡有异步的地方,均需要考虑提示用户当前系统的内部状态,切不可认为网络速度快就不处理。否则在网络不好的情况下,小白用户只会认为系统当前卡死了,无响应,事实上这个`卡死了,无响应`并不是开发者理解的`卡死`,而只是用户参与交互后,界面没有给出相应的提示。 再比如我们用`弹出框`做一个详情展示,用户点击`查看详情`时,弹出框应立即展示,弹出框的内容可以加一个正在加载的动画,表示正在获取详情,在获取到详情数据后,再把弹出框内容替换为真正的详情内容。 那么在弹框时,这个弹框切不可异步加载,我们知道对于一些组件、框架,有能力在需要时再临时加载相应的组件,但这个对话框不要这样做,否则用户点击`查看详情`时,会有一段时间用于异步加载弹出框组件,然后才能看到内容,这对于用户来说,会觉得这个系统`不灵敏`,从而造成反复点击 只要是用户参与交互的地方,均需要给出相应的提示和反馈,一定做到立即响应
### 为什么默认不支持 简言之,`class`写法比`magix`提供的`extend`写法要输入更多字符,没有明显的开发上的优势,而且开发者写`class`或对象字面量并无本质区别,再加上`class`诸多语法现阶段并不稳定,故默认支持更稳定的对象字面量的写法 ### 如何让magix支持class写法 通过`decorator`来间接支持 `magix`需要提供一个`abstract class`以支持`vscode`进行提示,如 ```ts abstract class View{ id:string render():void | Promise } ``` 提供 `magixView` 的`decorator`方法,如 ```ts let magixView = (BaseView, tmpl = null) =>...
当我们在做一些重要的数据收集(表单)时,希望能帮助用户减少因刷新、切换页面等带来的数据丢失,做一些提升使用体验的事情。 ### 草稿 我们可以把用户录入的数据实时的存本地如`localStorage`中,也可以使用`http`或`websocket`存储在远程服务器,通常草稿场景用在非常重要的数据录入上,如果把系统中每个场景都这样做,代价有点高。当然,草稿的体验是非常好的,能保留数据不会丢失。 > 草稿只是提一下,不在本文的讨论范围内 ### 离开提醒 如果用户录入的数据未得到保存,我们可以在用户刷新、切换页面时进行提示,这样即不用像草稿实现的那么代价高,也能保证一定的体验,当然,用户强行离开则会丢失数据 ### 离开场景 常见的离开一个录入数据的表单分为以下几种:用户刷新页面,用户切换到其他页面,表单在一个自定义的对话框中,关闭对话框,以及其它切换时,需要销毁表单组件 ### 技术实现 #### 刷新页面 我们可以监听`window.onbeforeunload`进行刷新或真正离开页面的提示,这个事件并非所有浏览器都支持,可以查阅这里:https://caniuse.com/?search=onbeforeunload 如果浏览器不支持,那么也无法实现提醒,这个是无解的。当然,浏览器支持的话,则会进行相应的提示 #### 路由改变 在`magix`中,路由实现分为`hash`和`popstate`,无论使用哪种,`magix`抽象了`change`(开始改变)和`changed`(改变完成)2个事件。我们通过监听`change`事件,在路由刚发生改变时进行处理:如果有数据未保存,则停留在当前页面状态上,不要继续渲染后续的路由变更导致的页面变更。 #### 区块改变 前述的刷新页面或路由改变,都是用户可直接参与操控的,而大多数场景下,我们需要处理用户即没刷新页面,也没改变路由的提示:比如一个自定义对话框,用户关闭时,如果对话框内是一个数据表单且未保存,则我们需要进行提示 `magix`在`vframe`的层级上提供了`exit`软退出的方法,开发者只需要调用软退出的方法即可完成提示处理 #### 代码使用 首先是如果某个`view`是表单收集数据,则可以通过`observeExit`方法进行监听退出行为,提供相应的提示语和状态检测方法,如下 ```ts import...
假设有`2`个`div`,`A`和`B`,它们的`clientHeight`相同,`scrollHeight`也相同,如何在滚动`A`的时候,`B`也同步滚动?即`A`和`B`的滚动条位置相同? 这个问题看上去很简单,我们只要监听`A`的`scroll`事件,在这个事件里面设置`B`的`scrollLeft`或`scrollTop`和`A`相同即可 ```ts A.addEventListener('scroll',() => { B.scrollTop = A.scrollTop; },{ passive:true }); ``` 事实上这个方案虽然简单,但在遇到复杂的页面时:有动画、滚动内容很多等情况下,浏览器已经开始掉帧,那么`scroll`事件的触发就会被延迟,表现为`A`已经滚走了一部分内容,而`B`还在原来的位置上,`A`和`B`的滚动并不同步。 这个问题在移动端表现更为明显,因为`scroll`事件是在滚动结束才触发,许多依赖`scroll`事件做的虚拟列表都会有该问题,比如这个:https://stackoverflow.com/questions/62489980/virtual-scroll-shows-white-spaces-on-mobile-devices-with-fast-kinetic-scrolling 那么我们该如何解决这个问题,让多个滚动容器同步的显示相应的区域呢?即使卡顿、掉帧的情况下也依然是同步的? 我从`vscode`中找到了方案 `vscode`的输入区域和滚动条完全是自己做的,这样何时滚动、滚动多少完全是自己控制的,在这种情况下,同步多个滚动容器且保持一致是完全可行的。 比如这里:https://github.com/microsoft/vscode/blob/main/src/vs/base/browser/mouseEvent.ts 进行了鼠标事件的封装,尤其是`wheel`事件的处理 在这里 https://github.com/microsoft/vscode/blob/main/src/vs/base/browser/ui/scrollbar/scrollableElement.ts#L360 进行了滚动处理。 只不过`vscode`对滚动区域和滚动条都是自己绘画控制的,难道我们也自己去做滚动条吗? 事实上我们只需要响应`wheel`事件,阻止默认行为即可,而移动端则需要响应`touchstart`等事件,同样的阻止默认行为,这样我们带有滚动条的容器就不会滚动了,除非拖动滚动条,而我的测试来看,如果是拖动滚动条滚动的话,是没出现不同步的情况的。 `vscode`处理`touch`事件可参考这里:https://github.com/microsoft/vscode/blob/main/src/vs/base/browser/touch.ts 所以我们只需要阻止默认的滚动行为,转而变成我们自己控制滚动即可。 ```ts A.addEventListener('scroll', ()...
```ts import Magix from 'magix5'; let { Event, mix, isPrimitive } = Magix; /** * 数据中价 * @param data 初始化数据 */ function DataMediator(data) { this['@:{data}'] = data; this['@:{async.count}'] = 0;...
```ts import React from 'react'; import "./styles.css"; export default class extends React.Component { constructor(props) { super(props); this.state = {isToggleOn: true,list:[]}; for(let i=0;i ({ isToggleOn: !prevState.isToggleOn })); } handleDelete(){ this.setState({ list:[]...
https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events 使用`pointer`事件进行尽可能的设备兼容。 移动端可配合使用样式`touch-action:none`禁止某个区域响应手指滑动,进而使用`pointermove`事件实现自己的行为 `pc`端可直接使用`pointer`替换`mouse`事件,目前看没什么问题
>2015~2018 magix3目标是项目统一,2018以后,magix5目标是性能及所有开发细节的完善 ### magix5核心思路 大到应用小到组件,均由数据描述驱动,由开发者同步或异步操作这份描述数据,再由`magix5`异步的更新界面。 这种方案尽可能的把浏览器资源优先用于处理重要的数据,而非界面更新。 比如上万条的列表,不用任何优化手段,全部渲染到界面上来,同时要进行全选,反选等选中操作。 首先用数据把这个列表表示出来,然后交与`magix5`渲染界面。如果进行全选、反选,也是操作数据进行相应的选中表示,再由`magix5`更新界面,如果要获取哪些选中行,也是通过数据进行查找获取。 即:一个功能纯靠数据进行实现,`ui`仅仅是把描述数据进行可视化。浏览器操作数据是快速的,这一步或同步,或异步进行操作。后续`magix5`则是通过异步、确保浏览器不卡的情况下更新界面,在`magix5`更新的过程中,用户依然可以继续操作这份数据,`magix5`最终会把界面渲染到与这份数据一致。 弊端:因为更新是异步的,如果界面更新量大,用户在后续操作时,数据变化不能及时反馈到界面上。当数据量大时,依然需要其它手段进行优化,不能单靠`magix5`。 ### 全局通用升级 #### at规则升级,由原来的`@`升级为`@:`。样式、模板及代码中所有使用`@`的地方均需要改写为`@:` > 升级原因是现在`npm`包支持如`@ali/sub-module`这样的形式,之前的`@`很容易把这样的字符串识别为路径,故升级规则 #### magix-combine升级为magix-composer > 因为和`magix`有关,`magix5`重新实现了模板、`view`嵌套渲染等机制,为了抛弃历史编译工具的包袱,直接建立新的编译工具来打包编译。 ### 样式升级 #### 删除`global@path/to/style` > 全局样式需要使用新的规则来书写,同时为了减轻编译工作量,全局样式不再由编译工具处理。可阅读`magix-composer`升级章节来获取进一步的信息 如果需要,可使用如 ```js let styleString=`style@:./path/to/style`;...
> 性能优化是一个很大的话题,这里只谈个人经历、实施过的优化方式。 在上一篇[极致的性能](https://github.com/xinglie/xinglie.github.io/issues/42)里所聊的更多是解决问题方案的优化,这里我们讨论一些其它在项目中常见的写法以及改进方案 #### 减少对象创建 假如我们有这样的代码 ```ts import Magix from 'magix'; export default Magix.View.extend({ init() { window.addEventListener('scroll', e => { }, { passive: true }); } }); ``` 我们应该把`AddEventListenerOptions`对象提升到外部,防止每个`view`对象在创建时,反复创建`AddEventListenerOptions`对象,如下 ```ts...
#### 插件实现 ```ts export default { ctor() { this.on('dompatch', () => { this.$refs = {}; }); this.on('domready', () => { let refs = this.root.querySelectorAll(`[mx5-host="${this.id}"][mx5-ref]`); for (let i = refs.length; i--;)...