使跨层级的label保持相同宽度
在表单开发中,我们往往会因为label字数不同而无法保证不换行的情况下还要对齐。怎么解决呢?当然是上脚本来处理。
function adjustNodesWidthToBeSame(selector) { const getTextWidth = (text, font) => { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); context.font = font; const dism = context.measureText(text); return dism.width; }; const labels = document.querySelectorAll(selector); const items = Array.from(labels); let maxWidth = 0; items.forEach((item) => { const text = item.innerText; const { font } = window.getComputedStyle(item); const width = getTextWidth(text, font); maxWidth = Math.max(maxWidth, width); }); items.forEach((item) => { /* eslint-disable no-param-reassign */ item.style.width = `${maxWidth}px`; }); }
将这个函数在整个界面上的label发生变化的时候执行,它就会自动调整label的宽度来适配最长的那一个。
基于MVC的范式写React应用
从大约两年前开始写 nautil,从一开始主要目标是创造一个跨平台开发的框架,现在来看,这个目标开始有所转变了。以前觉得跨平台开发是趋势,是一项重要工作,是亮点,但随着时间的推移,我越来越觉得,跨平台是一种技巧,而不是一种技术。在我长期以来的开发中,我逐渐意识到,项目工程的可靠性是团队/个人开发的重中之重。如何让一个项目的代码组织更加合理呢?特别是以 react 为基础的应用中,怎么去更好的组织码流(代码顺序和引用)?
代码写的多了以后,追求一种抽象的范式。所谓范式,是在方法范畴层面更广的风格。我发现在前端领域,react 和 angular 是两个极端,react 是只关注视图更新,不断深入,寻找各种方式提升视图更新性能,但是在工程性上完全放飞,性能代码量全部由开发者自己去处理。而 angular 反过来,它根本不关注(或者说不是卖点)视图更新问题,angular 沿用了 angularjs 时代的脏检查思路,只是做了优化,做到了没有 digest,只需要一次检查;而在工程性方面,angular 想尽一切办法约束开发者,因此,angular 的语法多得多,所以学习曲线比较漫长,但是我个人认为收益会很大,其中,引入 typescript 以静态类型语言的风格编写应用程序,会让应用更加健壮(但编程的灵活性降低,同时增加了类型编程的麻烦/乐趣),引入 rxjs 让事件处理从原始的 single push 模式升级为 multiple push 模式,增加了事件处理的灵活性;angular 具有 module 和 service 的概念,module 可以认为是一个业务模块(和 react 中的 component 概念相似但不同),包含该模块的所有信息,并通过工具进行解析构建;service 为模块提供处理能力,可以通过 angular 特殊的依赖注入完成比较魔法的写法。
而在 react 的生态中,除了 nextjs 比较知名外,还没有比较成熟的工程框架方案。而且 nextjs 走 ssr 路线,更像是以前的 php 换 nodejs 来过一遍,而非纯前端(运行时)的方案。于是,我想写一个框架来融合各种优秀理念(大部分其实还是我自己的),提供一种可靠的工程性框架的可能性。市面上其实也有一些自称为 react 框架的方案,比如 dva,但是实际上大部分 react+redux+react-router 的项目,就可以称自己为框架,但是,这类框架仍然停留在视图层编程的层面,没有上升为工程性框架,没有提供真正的编程范式。而写 nautil 之初是为了实现跨端开发,但是,随着慢慢深入开发,我发现 nautil 有可能成为真正的对标 angular 的 react 工程性框架。
首先,前端应用需要区分数据、界面和事件,而类似 dva 之类的框架,你按照它的写法,永远只会写出把业务逻辑最终杂糅在视图组件中。没有区分数据和界面,是前端项目目前最大的问题。举一个简单的例子,在一段明显只描述业务逻辑的代码块中,你很容易找到一两句用来处理界面的代码,比如打开一个弹窗、颜色变化等。而 nautil 则首先要求开发者有分层的概念在里面,从代码层面看,就是有关业务的代码写在一个或多个文件中,有关界面的代码写在一个或多个文件中,这两者绝对不会出现直接的联系,这也就是 MVC 中的 M 和 V,同时,需要有一个东西将它们串联起来,也就是 C。在 nautil 中编程,很像传统的 MVC 编程,需要写 .model.js, .controller.js,看上去又回到了 MVC 的老路,但是有一点,这里我们避免了一些坑,我们不可能直接照搬后端的 MVC 概念,后端的 C 才是请求的入口,而在前端 V 才是入口。所以在前端 module 中必须存在视图组建,但可以不存在 controller 和 model。
其次,关于事件,我认为是大部分前端容易忽略的一个点,很多人认为事件就是事件,很简单,没必要整那么复杂。但是在实际开发中,事件几乎串联了所有业务和交互,可以说,没有事件处理就没有这个应用。但是大部分编程,都只有事件回调函数,在 react 中通过 on 开头的一些回调来获得 DOM 的事件回调。但是,回调函数的方式比较钝,当一个动作可能引发一连串非线性的反应的时候,回调函数是不可能解决的。举个例子,比如我们有一个场景,当用户点击 A 时弹出一个弹框,当点击 B 时需要先放大 B 元素然后再相当于点击 A。这里点击 B 就是一个会带来一连串非线性的反应的场景,在 jquery 时代,我们可以通过 trigger A 的 click 来达到这个目的,但是在 react 中,我们无法做到这个效果。而在 nautil 中,我引入了 rxjs 来解决事件处理。普通的回调函数是 single push 模式,只能处理单一线性的任务,而 rxjs 是 multiple push 模式,可以处理多向离散任务,同时,由于 rxjs 的 Subject 既是观察者也是被观察者,所以多个事件之间可以轻松串联起来。在 v0.30 之前,我在 Component 中使用 on/off/emit 作为事件的处理工具,但是这种命名仍然是传统的事件注册与回调的意味,因此,我改为了 subscribe/unsubscribe/dispatch,虽然只是名字的改变,但体现了事件流处理的理念的转变。
最后,Controller 的发明也是非常有意思。一个 controller 观察模型,保管一些用于完成事件到数据交互的组件,这些组件只完成交互(事件->数据),而不完成布局和样式,所以细小碎片化,但是却形成了另外一种模式:业务不仅包含数据描述,还包含交互描述。通过 Model+Controller,我们可以很好的还原需求文档中有关业务逻辑的描述(一般需求文档除了描述业务数据本身,还会描述用户的某一个交互所带来的数据变化,而在这个描述中,往往还没有给出具体的界面效果)。所以,在我看来,一个 Controller 其实是一个交互模型,这个交互模型的基础,就是拥有基于 rxjs 的事件流管理机制。基于这种思想,一个 module 除了视图组件和样式文件之外,其他的都是模型(包括 Service 在内,因为它为业务服务)。只有视图组件是为界面服务的,这就导致我们的开发方式与以往的 react 开发方式具有极大的不同,这个不同非常非常大!可以说,视图组件的编写,只占到整个应用的一小部分,而真正的大头应该是在 Model+Controller 上,这也符合我们对“业务系统的重点是这实现业务”的朴素认知。
2021 年已经 5 月了,今年我做 Robust 的期数特别少,因为手上的工作比较多,同时,我也逐渐发现,要完成一些具有挑战的项目逻辑是比较困难的,不是有了想法和行动,就能做到尽善尽美,特殊的场景实在太多了,想要做一个完完全全通用化的功能模块,几乎是不可能的,这也意味着,未来还有很长的路要走,而且是不可替代的。
-
nautil看着确实有意思, 有能跑起来的demo吗#1036 1188 2021-05-07 13:35
-
打算写一个 beautiful-admin 的 demo,有什么建议么?#1037 回复给#1036 否子戈 2021-05-07 13:49
-
希望能尽量简单点, 方便理解其中的各种概念.#1040 回复给#1037 1188 2021-05-08 13:51
在 webworker 中运行 react virtual dom
在一次内部分享会中,我表达了可以把 react 的 virtual dom 那一套全部放到 webworker 中,这样就可以避免在主线程进行计算量很大的 diff 操作,一方面节省主线程的计算消耗提升性能,另一方面也不需要再去构建 react 的 scheduler 做那么复杂的协程工程。大致意思就是,在主线程只做 DOM 操作,而 DOM 的操作,全部由 worker 线程发送消息到主线程完成。在 worker 中计算得到 patches,发送到主线程,由主线程的程序完成 patch,也就是在主线程中,只存在 DOM 操作,不存在计算过程。其中比较重要的一点,是事件响应问题。当用户在界面上做了一次点击后,需要发送一个 event 消息到 worker 中,然后由 worker 中构建的一套事件处理系统完成事件响应和接下来的 diff,将最终的 patch 发送回主线程。
之后,在同事的推荐和自己的寻找下,找到两个项目:
- web-perf/react-worker-dom: Experiments to see the advantages of using Web Workers to Render React Virtual DOM (github.com)
- dai-shi/react-worker-components: React Worker Components simplify using Web Workers (github.com)
- ampproject/worker-dom: The same DOM API and Frameworks you know, but in a Web Worker. (github.com)
前一个项目比较老了,感觉正好符合我的想法。后一个项目受最近 server components 的启发才出来,也是非常有意思。
在深入想想。既然我们可以把计算过程放到 worker 中,也可以放到 wasm 中去。我们可以用 rust 重写 virtual dom 的实现,并像上面 worker 中的处理方式,放到 wasm 中,通过接口传递数据,完成计算后再回调方法,最终实现这个效果。
而以上这套思路,还有点像小程序的思路,但最终还是不大相同。
有些东西坚持很久,东西本身可能无所谓,但坚持这件事很有意义
今天QQ音乐推荐了我的个人播客节目《Robust》。
虽然是由于腾讯内的同事在帮忙推,但是也是一件很开心的事情。做《Robust》是圆自己一个电台梦,可以有一方自己的王国,想说什么就说什么,其他人只能听。但是我是一个很淡然的人,我见到一些朋友做播客喜欢走极端路线,比如一些同性恋节目、离婚、一个事情是不是犯法等等,我觉得都太功利目的了,都想通过内容主题来吸引听众。我做的内容,完完全全不是要吸引听众,就跟我写博客一样,更多是一种表达的方式,把自己的知识或感悟积累成为可以用语言表达的有框架的知识体系。所以,有网友说我一个人讲太无聊了,要是两个人讲就轻松一些。因为我讲的东西偏技术,有的甚至讲技术细节,所以听起来肯定很枯燥,但是并不影响听众打发时间。枯燥是枯燥,但是我还是希望尽可能沉淀东西进去,很久以后回来听,也不会太过时。
我的计划是最少要做100期,我现在做了3年,做了25期,也就是说还要9年才能做满100期,那时我正好40岁,还真是有仪式感的一段岁月。
我写博客也写了马上10年,博客的内容有的时候也是很糙,但是,就像我标题说的,其实,内容本身可能无所谓,因为现在写的东西,过一两年就过时了,甚至是错误的,内容即使在当下很超前很硬,但也有过时的一天。而写博客这件事本身,却伴随了我整整10年,对我来说,是一件了不起的事情。一件事如果能坚持10年,就是了不起,又有谁太在意它的内容本身呢?
-
给你点个赞 (。>∀<。)
-
加油#1031 1188 2021-04-20 09:34
-
谢谢支持!#1032 回复给#1031 否子戈 2021-04-20 10:49
-
谢谢哦#1033 回复给#1030 否子戈 2021-04-20 10:49
腾讯3年可以申请更换设备,虽然大家都说Mac好,但是我还是申请更换了一台windows,因为已经有一台iMac,所以打算留一台windows以备不时之需。以前用windows最大的问题是开发环境不好搭,在morningstar的时候,自己装了个vmware跑Ubuntu,但是性能很不爽。而如今windows直接上WSL,在系统底层跑linux子系统,性能和直接用windows没差,简直飞起。而且由于linux子系统和windows主系统共享文件系统和网络,在linux里面跑的服务,直接可以在windows上访问,减少了各种配置工作。而且说实话,windows上的软件,虽然界面确实没有mac好看,但是从功能完整性讲,绝对比mac好很多,在加上winget,总之,现在我已经有点享受windows上开发的乐趣了。
-
WSL简直太棒了, 有时候在win上装node的package必须要linux环境,现在直接用vscode一键wsl搞定, 配合自家的vscode用起来简直不要太爽了,完全就是为开发者准备的.#1029 maoa mao 2021-04-17 02:29
JS 糟粕之全局变量和引用
JS的全局变量是排第一的糟粕。比如:
const a = 1
function get() {
retun a
}
函数内可以直接使用一个函数外的变量,而且可以层层往上引用。如果这个变量还是对象的话,还可以修改对象。例如:
const o = { count: 0 }
function set(count) {
o.count = count
}
这种写法,由于我们写JS写的久了,觉得天经地义,而且还是个与其他语言不同的特性(优点),殊不知,这种东西,害人害己。
全局变量的设计,会使得内存引用及其复杂,且导致内存溢出。虽然js是自动回收垃圾,但是实际上很多情况下开发者需要自己手动释放,而且由于全局变量这个特性,根本释放不了。
如果要改进这门语言,我觉得可以学习rust,去掉全局变量这种东西。
const a = 1
function get(&a) {
return a
}
const b = get(a) // b === a === 1
let b = a // a === null, b === 1
// ---------------
mut o = { count: 1 }
function set(mut &o, int count) {
o.count = count
}
function clone(o) {
return o
}
const n = clone(o)
虽然这使得这门语言麻烦很多,理解起来更复杂,但是可以避免很多问题。