272021.8

我写了一个vue全局状态管理器,被喷的五体投地

在知乎上看到一个问题,问“vue3下vuex是不是就没意义了”,便随手回答了一下,调侃自己对vuex本身的设计就表示不认同,主要的理由是vue本身支持直接修改属性完成响应式,而vuex却要学redux那一套定义mutation、action之类的,过于复杂,真正简单的方法,应该像vue本身一样,直接修改属性。于是写下了自己的想法:

const store = new Store({ a: 1 })
app.use(store)

然后在组件里面修改:

this.$state.a = 2

这样看上去多舒服,思路上和vue一致,定义全局状态时,也不需要复杂的mutation, action,只需要一个对象。这才是最符合直觉的vuex使用方式吧。

简单写完上面的想法之后,我就没管了,没想到过了两天,这问答下面都积累了50条评论,点赞也蹭蹭蹭的在增加,感觉要火呀

但是,当我看完这些评论之后,却高兴不起来,因为在这么纯粹的想法面前,各种抖机灵的评论都出来了。其中一个重要的讨论点在于,基于dispatch('SOME_NAME'的方式有利于调试,找到状态被修改的位置。我对这种辩解嗤之以鼻,大部分人根本没搞懂redux里面dispatch的设计思想,就在这里各种吹捧,他们无非把dispatch当一个带字符串标记的update看,而人家真正的设计是send(message)的理念,躺在两条河里还说洗的一个澡,我也是有点服。

不就是想追踪状态被修改的位置吗?我想,既然都已经提出来了,不妨实现吧,毕竟我造轮功力还是挺强的。简单翻阅了vue的文档,花了2个多小时,写了一个stupid-vue库。

https://github.com/tangshuang/stupid-vuex

使用方式如下:

不得不说,vue3的api设计比vue2合理了非常多,在vue2时代,我做相关工具的时候,非常吃力,但是到了vue3,所有api都是顶级暴露的,想要什么能力都可以组装出来,很厉害。

一通操作下来,这个包就这样发布了。

等一下,状态修改追踪呢?我利用了Error的特性,抓取它的stack,直接可以得到状态修改时,是由哪一行代码造成的。

看上图,通过点击浏览器里面的调试代码,就可以直接定位到源码的位置,这比所谓vue devtool给的一大串跳来跳去,只能看数据流,最后你还是不知道具体哪个位置带来的变化来的更直接吧。

多好。

故事就结束了

....并没有。“你这不符合单向数据流的理念”,WTF??在vue里面你跟我讲单向数据流的理念,入魔了吧。

我大部分时间都是用react来写项目,没曾想vue发展这么快,vue3真的很好用,唯一的不足在于它强依赖构建工具,如果能做到vue2一样直接浏览器引一个库,在运行时编译,那就很方便了。不过随着前端发展,这种依赖工具的框架慢慢成主流,也是正常的。

网络上会喷的人很多,但是,看一个人行不行还是要看他的行动力,只会喷不会造,这种人不必理会,提升自己的内功才是王道。

08:46:25 已有1条回复
  1. 确实直接new一个Vue拿来用其实就挺好
    #1085 1188 2021-08-27 15:39 回复
252021.8

certbot依赖版本低导致https自动续费失败

今天登陆博客时,直接被浏览器拦截,ssl证书过期了。很奇怪,因为我已经有快一年多没有理会了,certbot renew --dry-run 会自动帮忙续费证书,为啥现在突然无法访问了呢?然后准备登陆服务器,额。。。太久没有登,用户名密码全部忘的干干净净,哎。折腾了很久,终于登进去了,然后各种折腾,忘记了certbot的命令,幸好自己有写博客的习惯,当时开https的时候记了一笔,顺藤摸瓜,找到certbot的官网,才找出命令。跑完renew之后还是报错,这就让我很尴尬了。再经过一番搜索和折腾之后,原来是之前的版本不再支持,所以证书到期之后,自动续费机制失效了,但是这个我并不知道。搜了一大圈,最后在国外的一个网站上找到一句话,yum update certbot,启发了我,想是不是要升级certbot,于是折腾升级完毕,再renew,果真可以了。世界上真的没有一劳永逸的东西。

23:09:23 已有0条回复
062021.8

全真互联网、ipfs、元宇宙

2020年我第一次接触了“元宇宙”的概念,那时我觉得它好像是一个哲学意义上的概念。今天,我联系了前两年年会上pony提出的“全真互联网”的概念,隐隐觉得,这一概念越来越清晰。简单讲,“元宇宙”和“全真互联网”是在描述一件事情,即线上是真实自我。这件事确实挺有意思的,最早的QQ用户给自己使用一个卡通头像,给自己取了一个独特的网名,实际上就开启了一个新的时代,即在线上拥有了一个新身份。这原本是一个小众市场,但随着互联网的发展,各种不同的人都挤进来,以至于最终这种独特的身份被堕落为谁都可以拥有的身份。慢慢的,这种独特身份不仅不独特,反而很多坏人利用网络来散播谣言、控制舆论等等。

元宇宙的概念会比全真互联网更大一些。我们先看看全真互联网吧。它的核心理念在于,你可以在线上做任意的事情。听上去,好像没有什么,因为我们觉得我们现在确实可以在线上做任意的事情了。但是,我问你,你可以在线上自己改装一个车吗?实际上,你在线上没有一个车,或者只有一个不属于你的车。目前的线上,没有一个可以让你改装的车,所以,你不可能改装这个车。但是,现在有一款游戏,叫做“我的世界”,你可以在里面造自己的房子,你可以邀请你的朋友过来参观,给房子做一些点缀,甚至开party。是的,这就是全真互联网,目前还比较低级。笼统的讲,就是你在网络中可以做现实中的事情。以程序员的角度,你可以在这个世界里,去收集芯片,制造自己的电脑,并且为电脑编程,写一个操作系统,这个过程全部在网络的世界中完成,而非现实世界。怎么样,有没有一种科幻片的即视感?你怎么知道现在的你,是不是别人在网络世界中制造出来的一段代码而已?

好了,我们现在为这个世界再增加一点难度。你不仅制作了这台电脑,而且,你现在可以卖掉它。虽然你无法收到现实世界的法币,但是,你在网络中将获得一般等价物,并用它们来购买别人制造的其他有意思的东西。你看,这个世界是不是突然丰富起来了?

今年突然火起来一个东西叫NFT,它是基于区块链的资产转移证明。简单讲就是,你有一个数字内容,比如用PS做的一幅画,你打算把它卖掉,但是,买它的人希望获得它的独家版权,或者拥有它的完整的所有权,此时,通过NFT交易,你把这幅数字画的所有权转移给了买它的人,而交易的过程,可以使用法币也可以使用数字货币偿付,当然,更加推荐数字货币,并且将数字货币的转账交易和NFT凭证绑定在一起。

讲NFT是为了结合前面所讲的,当你在这个网络的世界中和别人进行交易之后,你怎么能确保你的交易是和现实世界的交易一样具有信用。

你可能想到了电影《头号玩家》,是的,元宇宙的概念因为这部电影更加的清晰。但是,元宇宙和头号玩家中描述的游戏“绿洲”还是有所不同。先看相同的地方,在“绿洲”中,每个人拥有一个自己的身份和形象,并且你可以在里面完成现实世界中的很多很多事情,比如赚钱、购买、社交、战争等等。但是,元宇宙和“绿洲”最大的不同在于,元宇宙一定是去中心化的,它不能由某一个公司创造或维护,它是像比特币一样一旦上线之后,就有共识机制维系的系统。

这也就让我想到ipfs,这个期望称为下一代网络而取代中心化的互联网的家伙。现在的区块链项目,10个有10个都是与具体的币挂钩,而ipfs它解耦了去中心化项目的各个部分,其核心是ipfs协议,当然,基于这一协议,你可以搭建起类似区块链或比特币这样的应用,但是单纯ipfs而言,它是比区块链更底层的技术。(你可以把区块链当作数据库,而ipfs当作http。)

只有基于ipfs这样的协议建立起来的元宇宙,才是真正的元宇宙,它有自己的共识机制,满足人们的网络身份和数字资产交易的兴趣。

23:53:48 已有1条回复
  1. 现在元宇宙的概念很火啊,我们老板前天也给我们讲了讲这个东西。
    #1082 羡鱼 2021-08-18 16:57 回复
242021.6

通用底层DOM/BOM平台

最近这两天一直在想一种实现方式。我们现在的前端应用,实现上基于DOM是最好的最流行的,不过,大部分前端都构建了自己的路有系统,这又依赖BOM。也就是说,目前大部分前端应用,都是基于DOM/BOM实现的。那么,我是否可以创建一个通用的DOM/BOM平台,让这些应用跑在这个平台上。

让前端应用跑在一个DOM/BOM平台上,听上去有点莫名其妙,为啥不跑在浏览器里面?没错,我就是想脱离浏览器跑这些应用。比如在nodejs里面跑(目前有JSDOM可以支持这个效果),虽然没有界面可以看,但是可以用来做一些单元测试的工作。比如在webworker中跑,众所周知,webworker是没有DOM API的,假如有了这个平台,那么就可以在worker中跑一个vue应用(虽然没有界面)。同样的道理,能否更远一点,跑在非js的环境中,比如flutter(目前阿里开源的Kraken支持这个效果)甚至C++写的原生应用中。想一想,在一个树莓派中跑一个vue应用。于是,我想出了这样一个架构:

其中,VBOM这一层和Driver framework这一层是实现的核心。VBOM是用纯js实现的DOM+BOM环境,只要把已有的vue代码,包在一个VBOM实例的闭包里面(用babel把全局变量的引用切换到window上),那么vue代码所做的任何关于DOM/BOM的操作,都是在这个VBOM的实例内部完成。Driver framework则是监听/操作这个VBOM实例,对接不同的平台,比如小程序,由于小程序是js写的,所以,引用js-driver,然后在driver的各个生命周期钩子函数上写小程序要做的事情,driver会把VBOM中有关DOM/BOM的变化告诉给小程序,也会把来自小程序的消息发送给VBOM产生新的副作用。而flutter上,我们可以使用dart-driver,原生应用可以使用cpp-driver,python-driver, rust-driver等等,通过driver之后,终端代码里面只写终端语言代码,不需要写任何js相关的东西。而回过来,应用这端,开发者只需要写vue或react,不需要考虑自己的应用会在哪个环境里面去跑。

再用一个基于webworker的微前端方案的例子说明:

微前端要解决的是一个沙箱问题,比如一个vue应用,要在宿主应用中加载运行,那么,这个vue应用里面可能操作DOM, location, history等等,这些东西会导致vue这个子应用和宿主应用去抢location, history进行操作。我在mfy中的做法是创建一个iframe,然后把里面的window, location, history借过来给子应用去操作,通过监听,同步子应用和父应用的相关信息,这样就不会出现抢的局面。但是,由于浏览器环境中的特殊原因,目前所有的微前端方案,实现沙箱都需要借助Function来实现,同时还需要用with这个性能极差的语法(而且这个语法已经被抛弃了)来解决全局变量修改问题(虽然也可以用babel进行编译处理来去掉with语法)。这些方案都无法避免父子应用的某些冲突,特别是子应用代码运行过程中发现当前提供的资源不符合自己代码实现的逻辑,报错。

既然浏览器环境下沙箱问题这么多,性能这么差,那么我能不能把沙箱迁移到一个webworker中,在worker环境下,根本不存在抢资源问题,也不需要用Function来包裹vue代码。但是,worker中没有DOM啊,也没有location, history,所以就想到了,我们需要自己造一套DOM/BOM的环境,然后把这套环境放在webworker中,这样可以让vue应用在worker中跑起来,vue操作的,是VBOM,虽然不会报错,但是没有任何界面效果。

所以,接下来,就需要driver出场,driver监听VBOM中的变化,把这些变化交给controller(controller是微前端框架基于js-driver实现的一个运行在webworker中的控制器),controller通过postMessage发送给主线程,由主线程的renderer收到message之后决定怎么修改DOM。当用户点击子应用区域内的某个按钮之后,renderer将该点击事件postMessage给controller,由controller分析并把对应的DOM事件发送给driver,driver知道这个事件发生在哪个DOM节点上,于是在VBOM中对应的节点上触发该事件,VBOM实际上是vue的运行环境,vue组件上的事件被触发,回调函数被执行,引发新的DOM更新,再次走一遍前面的过程。

看上去好绕啊!!!但是,你要知道,其中,对于框架开发者而言,他们维护的是微前端框架,对于宿主开发者而言,他们维护的是微前端框架的配置,对于子应用开发者而言,他们维护的是vue这个应用。真正需要经常调整、修改的,是vue应用这一层,其他两者基本上是以逸待劳,一劳永逸。所以,一旦这一套跑起来之后,不管是vue也好,react也好,开发者只需要了解自己熟悉的基于DOM/BOM的web开发即可,不需要关心自己是在什么环境下运行。这对小程序的开发者而言,肯定深有感触,小程序开发因为有线程约束,导致开发者有的时候非常痛苦,翻边开发文档都找不到问题的根源。而如果有了上面这一套方案,开发者只需要考虑在自己熟悉的web平台上开发即可,而不需要考虑其他。

补充一点,如果需要调用原生的能力,需要框架开发者在driver那一层,向VBOM提供某些接口的能力,比如提供调用摄像头的能力,比如提供app是否被切换到后台运行的事件等等(很多hybird的操作手法)。对于应用的开发者而言,无非是增加了一些特殊接口和事件,仍然还是DOM/BOM那一套。

11:27:10 已有1条回复
  1. 不知为何,想象不出来虚拟bom是什么样子
    #1065 1188 2021-06-24 18:39 回复
092021.6

今天心血来潮,想着写完jqvm有没可能比vue的性能还好,因为没有走virtual dom,而是真实dom patch,会不会有性能优势,于是找到benchmark的仓库,试一下,一试不得了,jqvm直接1000行的table渲染卡成狗,而vue是秒出,这种性能差距,我已经崩溃了,不知天高地厚,幸好自己摔了一鼻子灰。我去观察了一下,为啥1000行的table,vue能够秒出呢?猜测vue的首次渲染绝对不是一次性全部渲染,肯定是分批次渲染的,但是我没有证据……而且不是说vue没有做事件切片吗?那它怎么做到无感的分批次渲染?看来还是要去看源码才能理解。

22:17:03 已有2条回复
  1. vue这么猛吗, 是哪个版本的, table里头有动态的变量吗
    #1057 1188 2021-06-10 10:04 回复
  2. 因为nextTick队列?
    #1058 清尘 2021-06-11 09:11 回复
082021.6

今天给老板汇报,上周末花了半天梳理,结果不到5分钟就草草过了。自己反思一下,主要是没有抓住老板对这件事情的预期。汇报之前一定要想好,老板对这个事情是怎么看到的,他/她预期是什么,想要听到的核心点是什么。给老板准备汇报要抓住老板最需要的信息,首先关键的关键,应该是用一句话阐述,我做这件事的核心价值体现在哪个点上。要一上来就把这东西摆在台面上,而不是陈述一堆后再递进式的提出来,因为在这个陈述过程中,老板就可能已经打断你的陈述,然后往你不可预期的方向发展。你在做一件事情,这个事情是老板所有事情,也就是全局里面的一环,很可能是非常小的一环,但对你来说却是你工作的非常大一部分,所以,不能站在自己的角度,阐述这个事情本身的重要性或意义,而是站在老板的全局去看,这个事情对全局的意义。如果对全局没啥意义,那老板肯定提不起兴趣,你更要一针见血的在一分钟内把这个意义搞出来。只要已开始,让老板认可了这个意义,那么,后面的汇报随便老板怎么打断,都是在闲聊,毕竟汇报的过程也就是那样,老板见太多了,后面听多长时间,完全是他/她对你最前面表达出来的关键价值的认可,你可以认为后面全程他/她都在发呆,如果一开始你给出的价值得到他/她认可,那么他/她可以把呆发到你讲完,但是如果他/她一开始没有get到你想要表达的价值点,那么后面他/她连呆都不想发,所以就会找机会打断你,然后表达一下自己在这件事上的观点,然后结束这次汇报。

17:18:54 已有0条回复
212021.5

不用Parcel打包了

有一个项目,为了方便使用了Parcel打包,结果来事,构建结果中来这么一出:

老哥,这个变量根本就没有被赋值过啊,当然是undefined!坑的一笔!

17:33:26 已有0条回复
122021.5

浅探 Web Worker 与 JavaScript 沙箱

https://segmentfault.com/a/1190000039795656

这篇文章提供了一种基于web worker的js沙箱的想法,没有具体的实现,但是对于微前端实现沙箱有非常重要的一种新型的意义,和我最近在思考的东西非常接近。最近我在思考,无论是微前端,还是跨端开发,都存在一个问题,就是我们无法直接操作DOM。在这一点上,react选择创建vdom来做,但是,随着时间的推移,vdom明显已经显示出局限性。而小程序的架构,也就是应用分为渲染线程和执行线程这种架构,对于需要将js封装起来运行提供了非常大的借鉴。我的设想是,所有的编程(基于DOM的编程)都丢到执行线程中编写,开发者可以无感的使用全部web技术来做,而框架要做的是,在执行线程提供全套DOM甚至BOM的api,同时,框架要自己解决渲染线程和执行线程通信,以真正更新界面的效果。这个通信包含的内容非常多,比如假的DOM和真实的DOM(真实的不一定是DOM,可能是flutter等渲染管线)的diff与patch,再比如用户在界面上的点、触等事件要回到执行线程中。基于这种架构,无论是微前端,还是跨端开发,都可以很轻松的抽象和分层。

082021.5

使跨层级的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的宽度来适配最长的那一个。

12:34:30 已有0条回复
072021.5

基于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 的期数特别少,因为手上的工作比较多,同时,我也逐渐发现,要完成一些具有挑战的项目逻辑是比较困难的,不是有了想法和行动,就能做到尽善尽美,特殊的场景实在太多了,想要做一个完完全全通用化的功能模块,几乎是不可能的,这也意味着,未来还有很长的路要走,而且是不可替代的。

00:09:30 已有3条回复
  1. nautil看着确实有意思, 有能跑起来的demo吗
    #1036 1188 2021-05-07 13:35 回复
  2. 打算写一个 beautiful-admin 的 demo,有什么建议么?
    #1037 回复给#1036 否子戈 2021-05-07 13:49 回复
  3. 希望能尽量简单点, 方便理解其中的各种概念.
    #1040 回复给#1037 1188 2021-05-08 13:51 回复