以太坊官网

以太坊官网

以太坊(Ethereum)简介

以太坊(Ethereum)是一个去中心化、开源并且具备智能合约功能的公共区块链平台,以太币(ETH)是以太坊的原生加密货币————摘自wikipedia 以太坊

以太坊的特点

相较于较大多数其他加密货币或区块链技术,以太坊的特点包括以下几点:

  • 智能合约:存储在区块链上的程序,由各节点执行,需要执行程序的人支付手续费给节点的矿工或权益人。
  • 分布式应用程序:以太坊上的分布式应用程序不会停机,也不能被关掉。
  • 代币(tokens):智能合约可以创造代币供分布式应用程序使用。分布式应用程序的代币化让用户、投资者以及管理者的利益一致。代币也可以用来进行首次代币发行。
  • 权益证明:相较于工作量证明更有效率,可节省大量在挖矿时浪费的电脑资源,并避免特殊应用集成电路造成网络中心化。2022年9月15日与主链合并。
  • 燃料(gas):由交易手续费的概念扩展,在执行各种运算时需计算燃料消耗量,并缴交燃料费,包括发送以太币或者其他代币也被视为一种运算动作。
  • 原丹克分片(Proto-Danksharding):在部分节点上暂存资料,以提升效率(尚未实现)。
  • 叔块:此功能在转为权益证明后已停用。原本功能是使用有向无环图的相关技术,将因速度较慢而未及时被收入母链的较短区块链并入,用以提升交易量。

以太坊是可编程的?how

经常有问题会问道:以太坊与比特币有什么不同? 二者都是虚拟货币?
以太坊立足比特币创新之上,于 2015 年启动,两者之间有一些显著不同。

两者都允许你使用数字货币,而无需支付服务提供商或银行。但是以太坊是可编程的,所以你还可以在以太坊网络上构建和部署去中心化应用程序。

账户模型

这不得不提到账户模型。比特币有两种类型的账户:用户账户和合约账户。

以太坊的全局“共享状态”是有很多小对象(账户)来组成的,这些账户可以通过消息传递来与对方进行交互。每个账户都有一个与之关联的状态(state)和一个20字节的地址(address)。在以太坊中一个地址是160位的标识符,用来识别账户。

两种不同类型的账户:

  • 外部拥有的账户,被私钥控制且没有任何代码与之关联
  • 合约账户,被它们的合约代码控制且有代码与之关联

外部拥有账户与合约账户的比较

理解外部拥有账户和合约账户的基本区别是很重要的。一个外部拥有账户可以通过创建和用自己的私钥来对交易进行签名,来发送消息给另一个外部拥有账户或合约账户。在两个外部拥有账户之间传送的消息只是一个简单的价值转移。但是从外部拥有账户到合约账户的消息会激活合约账户的代码,允许它执行各种动作。(比如转移代币,写入内部存储,挖出一个新代币,执行一些运算,创建一个新的合约等等)。

eth account

不像外部拥有账户,合约账户不可以自己发起一个交易。相反,合约账户只有在接收到一个交易之后(从一个外部拥有账户或另一个合约账户处),为了响应此交易而触发一个交易。

因此,在以太坊上任何的动作,总是被外部拥有账户触发的交易所发动的。

这也是为什么以太坊被称为一个“智能合约”平台,叫做可编程的原因。因为合约账户的代码可以被外部拥有账户调用,这样就可以执行一些操作。

账户状态

账户状态有四个组成部分,不论账户类型是什么,都存在这四个组成部分:

  • nonce:如果账户是一个外部拥有账户,nonce代表从此账户地址发送的交易序号。如果账户是一个合约账户,nonce代表此账户创建的合约序号
  • balance: 此地址拥有Wei的数量。1Ether=10^18Wei
  • storageRoot: Merkle Patricia树的根节点Hash值(我们后面在解释Merkle树)。Merkle树会将此账户存储内容的Hash值进行编码,默认是空值
  • codeHash:此账户EVM(以太坊虚拟机,后面细说)代码的hash值。对于合约账户,就是被Hash的代码并作为codeHash保存。对于外部拥有账户,codeHash域是一个空字符串的Hash值

Web3 DAO的迷思

第一次接触到Web3 DAO还是在X上面,看到了以往的程序员郭宇对此的理解和实践 CodeforDAO 的诞生与自组织的互联网未来

现在的Web3 DAO的市场还处于摸索阶段,可以看到Github上面存在着一些智能合约的雏形,但是还没有一个完整的DAO的生态。

使用 DAO 的方法论来改善创业公司的架构,甚至,能否再进一步,允许任何人轻易地创建一个自组织的链上公司,链上组织。

其实,你可以很轻易地在学校社团、公司、家庭、朋友圈等等,找到一些自组织的例子。这些自组织的例子,往往是由一群志同道合的人,通过一些共同的目标、规则、价值观等等,自发地组织在一起,共同实现一些目标。这与 DAO 不谋而合。

DAO以及Web3治理的挑战

Web3 之所以会出现,是因为中心化机构在管理金融和社会基础设施时无法保障安全性、公平性和透明性。Web3基于区块链和预言机等信任最小化的分布式网络建立,利用密码学、共识协议和机制设计来管理数字化基础设施,无需信任人类第三方,而是通过技术来实现保障,这就是所谓的“加密事实”。

DAO是什么?

DAO的全名是“decentralized autonomous organization”,中文是“去中心化的自治组织”。DAO的主要目的是超越传统组织形式,以分布式、透明和信任最小化的方式来进行集体决策。简而言之,DAO是一种新型的组织架构。人们可以独立验证组织的运行方式,并基于这样的共识朝着共同的目标努力。

更详细的解释,可以在这里看到:一文读懂DAO以及Web3治理的挑战

值得一提的是,虽然DAO的缩写中包含autonomous(自治)一词,但DAO并非完全自治。DAO是由人组成的,因此需要用户手动操作才能运行,比如用户需要进行投票、部署代码并讨论提案。之所以会使用“自治”这个词,是因为DAO的一些具体功能被写在了智能合约代码中,无法被篡改。然而,人们仍需要与智能合约(即:代码)交互(即:提供输入),以执行具体的任务(即:输出结果)。

DAO的种类

虽然DAO目前仍处于发展初期,但大致可以分为以下六种:

协议DAO——这类DAO负责开发和管理去中心化应用(dApp)或dApp的基础架构。协议DAO的主要任务是开发开源技术,这点类似于企业或基金会。

  • Tezos是一条区块链,采用类似DAO的链上治理架构,通过代表制投票体系来触发协议升级,需要绝大多数人投票达成共识才能通过提案。
  • MakerDAO是一个组织,负责管理去中心化的stablecoin DAI。DAO成员负责为协议制定参数,比如调整利率、添加/删除抵押资产以及核心部门团队入职/离职。

投资DAO——这类DAO负责控制DAO金库中的资金,并使用这些资金来发起并管理投资。投资DAO的主要目的是为成员创造利润,这点与私募基金或对冲基金类似。

  • BitDAO是一个DAO,BIT通证持有者通过投票决定各种投资策略,并为DAO金库实现收益。BitDAO宣称对Web3项目的投资额超过38亿美元。
    MetaCartel Ventures (风投DAO)是一家营利性DAO,专门投资于早期dApp。其主要目的是以社区为中心建立会员制,参与机制比传统的风投基金更加灵活。

事业型DAO——这类DAO负责针对某一事业管理资金和计划。事业型DAO聚焦在某一领域达成共识,比如慈善、政治以及公用事业,这点与慈善组织、游说团体和奖金激励计划等传统组织类似。

  • Gitcoin是一个DAO,用户在平台上可以通过二次方投票机制,共同为以太坊上的公用事业以及其他开源区块链项目募款。
  • Big Green是一个DAO,为学校、社区和家庭提供慈善奖金,帮助他们学习如何种植粮食。

社交DAO——这类DAO负责管理一个共享社交空间,共同拥有具有艺术价值的资产,为成员营造文化并组织活动。社交DAO围绕娱乐、艺术、游戏和其他社交领域将社区组织起来,这点类似于现代社交俱乐部。

  • Bored Ape Yacht Club(BAYC)是限量版的NFT藏品,NFT既代表会员身份,又可以领取特殊福利。
  • Krause House是一个由篮球迷组成的社交DAO,目标是有朝一日买下一支NBA球队。Krause House DAO目前已经买下了Big3篮球联盟的球队Ball Hogs。

数据DAO——这类DAO负责开发和管理DAO控制的数据。数据DAO旨在将用户数据聚集在一起,或开发独特的数据产品卖给第三方用户,具体应用场景包括开发AI算法或展开市场调研。

  • dClimate是一个买卖天气数据、天气预报和预测模型的市场,用户可以在其中销售创新的数据集,机构也可以在其中购买数据。这个DAO会评估发布的数据,以保障数据质量并发放适当的网络激励。
  • Delphia是机器人投资顾问,会支付原生通证购买用户个人数据。Delphia会将用户个人数据聚集在一起,并基于数据制定投资策略,用户可以使用原生通证查看这些投资策略。

DAO的治理架构

对于任何DAO来说,达成共识都是最重要也最具挑战的任务之一,因为达成共识意味着以去中心化的方式做出决策。下文列举了目前用于达成共识的几种治理机制,其中结合了上文提到的一些工具。

直接的链上民主——指DAO成员直接在链上对提案进行投票,提案要通过必须满足上文提到的条件。大多数采用该模式的DAO都会使用通证加权投票机制,用户持有的通证数量决定了他的投票权重(通常1个通证=1张选票)。这是DAO最常见也是最简单的共识达成方式,因为这种方式的复杂性和成本最低,而且可以抵御女巫攻击。

直接的链下民主——指DAO使用快照在链下进行投票,投票要通过必须满足一定条件。多数采用这种模式的DAO也会使用通证加权投票机制,但需要可信实体通过多重签名的方式严格按照提案来执行链上变更。因此,链下民主需要一定信任假设,就是多重签名者会如实按照DAO投票结果的快照来投票。

代表制民主——指DAO委托代表在链上投票,以通过DAO的提案。代表通常由DAO选出,并可能会参考链下快照来了解社区民意。DAO可能还会设置某种机制,当代表的投票结果严重偏离社区民意时对结果进行否决或变更。

二次方民主——指基于二次方投票的治理架构,公式如下:投票者的成本=(投票数量)^2。比如,对某一提案投一票需要花费一个治理通证,但如果要投五票就需要25个治理通证。二次方投票可以防止DAO的投票结果被少数巨鲸控制。多数成员的共同投票结果将拥有同等或甚至更大的效力。然而,要真正执行二次方投票,需要建立抗女巫攻击机制,以防止欺诈行为或将通证分散在不同钱包里。

Web3 DAO的未来与现状

互联网上已经有相当多的 DAO,但大部分 DAO 似乎并不依赖于他们的基础合约而运行,换句话说,许多 DAO 采用广泛的人治,而有限地依赖合约治理。这些治理的目的都相当宏观,譬如,是否允许使用 DAO 金库的一些钱来完成一笔投资,或者是制定一个复杂的 Roadmap,由于目的过于宏观,这些治理大部分使用 snapshot 的链下投票来完成,它有点像我们熟悉的代议制民主:可以参与投票,但没有人能保证提案目的是否会得到完整执行。

“网络国家是一个网络社会,在道德上创新,拥有国家意识和公认的创始人,有能力开展集体行动,人与人之间和谐相处,发行cryptocurrency,使用社会智能合约来约束基于共识的政府,通过众筹的方式买下实体领土并形成群岛,建立虚拟首都,在链上进行人口普查,证明国家人口、收入以及房地产,并因此获得外交地位。”

设计和技术目标

早期版本的 HTTP 协议的设计初衷是为了简化实现:HTTP/0.9 是用来引导万维网的一行协议;HTTP/1.0 以信息性标准记录了 HTTP/0.9 的热门扩展;HTTP/1.1 则引入了官方 IETF 标准;请参阅 HTTP 简史 。因此,HTTP/0.9-1.x 可以完全实现其预期用途:HTTP 是互联网上最广泛采用的应用协议之一。

遗憾的是,实现简单是以牺牲应用性能为代价的:HTTP/1.x 客户端需要使用多个连接来实现并发并缩短延迟时间;HTTP/1.x 不会压缩请求和响应标头,从而导致不必要的网络流量;HTTP/1.x 不允许有效的资源优先级排序,从而导致底层 TCP 连接被滥用;等等。

这些限制并不是致命的,但随着 Web 应用的范围、复杂性和在我们日常生活中的重要性不断增长,它们给 Web 开发者和用户造成了越来越大的负担,而这正是 HTTP/2 旨在解决的问题:

HTTP/2 通过引入标头字段压缩并允许在同一连接上交错进行多个并发交换,可以更高效地利用网络资源并减少对延迟的感知。具体而言,它允许在同一连接上交错请求和响应消息,并对 HTTP 标头字段使用高效的编码。它还允许对请求进行优先级排序,让更重要的请求更快速地完成,从而进一步提升性能。

生成的协议对网络更友好,因为与 HTTP/1.x 相比,可以使用的 TCP 连接更少。这意味着与其他流的竞争更少,并且连接的持续时间更长,进而可以更好地利用可用网络容量。最后,HTTP/2 还可以通过使用二进制消息分帧来更高效地处理消息。(超文本传输协议版本 2,草稿 17)

请务必注意,HTTP/2 是对以前的 HTTP 标准的扩展,而不是取代。HTTP 的应用语义相同,提供的功能或核心概念(例如 HTTP 方法、状态代码、URI 和标头字段)没有发生任何变化。这些更改显然不在 HTTP/2 工作的范围之内。也就是说,虽然高层级 API 保持不变,但了解低层级更改如何解决了先前协议的性能限制仍非常重要。我们来简单了解一下二进制分帧层及其功能。

二进制分帧层

HTTP/2 所有性能增强的核心是新的二进制分帧层,它规定了 HTTP 消息的封装方式,并在客户端和服务器之间传输。

HTTP/2 Frame

“层”是指一种设计选择,用于在套接字接口与提供给应用的更高级别 HTTP API 之间引入一种经过优化的新编码机制:HTTP 语义(例如动词、方法和标头)不受影响,但其在传输过程中的编码方式有所不同。与以换行符分隔的 HTTP/1.x 协议明文形式不同,所有 HTTP/2 通信都会拆分为较小的消息和帧,其中的每个消息和帧都采用二进制格式进行编码。

因此,客户端和服务器都必须使用新的二进制编码机制来相互理解:HTTP/1.x 客户端无法理解仅支持 HTTP/2 的服务器,反之亦然。幸运的是,我们的应用仍可轻松了解所有这些变化,因为客户端和服务器会代表我们执行所有必要的取景工作。

Binary protocol versus text protocol isn’t really about how binary blobs are encoded. The difference is really whether the protocol is oriented around data structures or around text strings. Let me give an example: HTTP. HTTP is a text protocol, even though when it sends a jpeg image, it just sends the raw bytes, not a text encoding of them.

信息流、消息和帧

HTTP/2 Frame

新的二进制分帧机制改变了客户端与服务器之间的数据交换方式。为了描述此过程,我们需要先熟悉 HTTP/2 术语:

  • 数据流:已建立的连接内的双向字节流,可承载一条或多条消息。
  • 消息:映射到逻辑请求或响应消息的完整帧序列。
  • 帧:HTTP/2 中的最小通信单元,每个单元包含一个帧标头,至少标识该帧所属的数据流。

这些术语之间的关系可总结如下:

  • 所有通信都在一个 TCP 连接上完成,该连接可以承载任意数量的双向数据流。
  • 每个数据流都有一个唯一标识符和可选的优先级信息,用于承载双向消息。
  • 每条消息都是一条逻辑 HTTP 消息(例如请求或响应),包含一个或多个帧。
  • 帧是最小的通信单位,承载着特定类型的数据,例如,HTTP 标头、邮件载荷等。来自不同数据流的帧可以交错,然后通过每个帧标头中的嵌入式数据流标识符重新组装。

也就是说,HTTP/2 的每个消息都由一个或多个帧组成,这些帧可以交错发送,然后再在接收端重新组装。帧的顺序并不重要,因为每个帧都包含其相对位置的信息。

请求和响应多路复用

使用 HTTP/1.x 时,如果客户端想要发出多个并行请求以提高性能,则必须使用多个 TCP 连接(请参阅使用多个 TCP 连接)。

注意这里是并行!!
客户端往往会为每个请求创建一个新的连接,以便并行发送请求,达到性能最大化。然而,这种方法会导致一些问题。

这是 HTTP/1.x 传送模型的直接结果,该行为可确保每个连接一次只传送一个响应(响应排队)。更糟糕的是,这也会导致队首阻塞,以及底层 TCP 连接的效率低下。

HTTP/2 中新的二进制分帧层消除了这些限制并实现了完整的请求和响应多路复用,方法是允许客户端和服务器将 HTTP 消息分解为独立的帧,交错发送,然后在另一端重新组装这些帧。

该快照捕捉了同一连接内传输的多个数据流。客户端正在向服务器传输 DATA 帧(流 5),而服务器正在将交错的帧序列发送到客户端,以便流 1 和流 3。因此,正在传输三个并行流。

能够将 HTTP 消息分解为独立的帧,交错这些帧,然后在另一端重新组装这些帧,是 HTTP/2 最重要的增强功能。事实上,它在所有 Web 技术的整个堆栈中带来了众多性能优势的连锁效应,使我们能够:

  • 并行交错地发送多个请求,请求之间互不影响。
  • 并行交错地发送多个响应,响应之间互不影响。
  • 使用一个连接并行发送多个请求和响应。
  • 移除了不必要的 HTTP/1.x 权宜解决方法(请参阅针对 HTTP/1.x 进行优化,例如串联文件、image sprites 和网域分片)。
  • 消除不必要的延迟并提高可用网络容量的利用率,从而缩短网页加载时间。
  • 等等…

HTTP/2 中新的二进制分帧层解决了 HTTP/1.x 中存在的队首阻塞问题,并且消除了并行处理及传送请求和响应所需的多个连接。因此,我们的应用部署速度更快、操作更简单、部署成本更低。

数据流优先级

一旦 HTTP 消息可以拆分为多个单独的帧,并且我们允许对来自多个数据流的帧进行多路复用,那么客户端和服务器交错和传送这些帧的顺序就成为关键的性能考虑因素。为了方便起见,HTTP/2 标准允许每个数据流具有关联的权重和依赖关系:

  • 可以为每个数据流分配一个介于 1 到 256 之间的整数。
  • 每个数据流都可以显式依赖于另一个数据流。

通过结合使用数据流依赖关系和权重,客户端可以构建和传递“优先级树”,表明其希望如何接收响应。反过来,服务器可以使用这些信息通过控制 CPU、内存和其他资源的分配来确定流处理的优先级,并在响应数据可用后分配带宽,以确保将高优先级响应以最优方式传递给客户端。

HTTP/2 Frame

每个源一个连接

有了新的二进制分帧机制,HTTP/2 不再需要多个 TCP 连接来并行复用数据流;每个数据流都会拆分为许多帧,这些帧可以交错并设定优先级。因此,所有 HTTP/2 连接都是永久性的,并且每个来源只需要一个连接,从而提供大量性能优势。

SPDY 和 HTTP/2 的杀手级功能是,在单个拥塞受控的通道上任意进行多路复用。它的重要性和良好运作机制让我惊叹不已我喜欢的一个重要指标是创建的连接所占的比例,这些连接仅承载单个 HTTP 事务(因此该事务承担所有开销)。对于 HTTP/1,我们 74% 的活动连接只承载一个事务 - 持久性连接没有我们所有人想要的那样帮助。但在 HTTP/2 中,这一比例下降至 25%。 这在减少开销方面是一项巨大的成功。(HTTP/2 已在 Firefox 中推出,Patrick McManus)

大多数 HTTP 传输都是短暂的且具有突发性的,而 TCP 则针对长期的批量数据传输进行了优化。通过重复使用同一连接,HTTP/2 既可以更有效地利用每个 TCP 连接,也可以显著降低整体协议开销。此外,使用较少的连接可以减少整个连接路径(即客户端、中间服务器和源服务器)上的内存和处理占用空间。这样可以降低整体运营费用,并提高网络利用率和容量。因此,改用 HTTP/2 不仅可以减少网络延迟,还有助于提高吞吐量并降低运营费用。

CodeWave

CodeWave: 汇集开发者线下技术会议的导航,链接你与技术界的最新动态和机遇。

技术会议主页

国内

  • GIAC全球互联网架构大会 - 集中探讨互联网架构的发展趋势,邀请业内顶尖的架构师分享他们的经验和见解。

  • QCon全球软件开发大会 - 聚焦软件开发的最新趋势和技术,旨在为软件开发者提供一个学习和交流的平台。

  • D2 终端技术大会 - 由阿里巴巴主办,专注于前端及移动开发领域的技术创新和实践交流。

  • 稀土开发者大会 - 面向广大开发者,分享最前沿的技术动态和开发经验。

  • GMTC全球大前端技术大会 - 聚焦大前端领域的最新技术和应用实践,旨在推动前端技术的创新和发展。

  • ArchSummit全球架构师峰会 - 针对软件架构领域,邀请知名架构师分享架构设计的最佳实践和经验。

  • 前端早早聊大会 - 一个面向前端社区的分享和交流平台,旨在促进前端技术和经验的分享。

  • VueConf - 面向Vue.js社区的开发者和爱好者,分享Vue.js的最新进展和应用案例。

  • CssConf - 集中探讨CSS相关的技术、工具和最佳实践,旨在推动CSS技术的发展。

  • Web前沿技术论坛 - 由W3C主办,探讨Web技术的最新标准和发展趋势。

  • 中国软件研发管理行业技术峰会 - 聚焦软件研发管理领域,分享管理实践和创新方法。

  • 中国DevOps社区, 近期活动 - 专注于DevOps文化和实践的推广,组织相关的活动和交流。

  • Elastic Meetup - Elastic 官方中文社区定期举办的线下交流活动 - 面向Elasticsearch用户和开发者的交流活动,分享使用经验和最佳实践。

  • JS World - 世界上最大的 JavaScript 会议,涵盖 JavaScript 的各个方面,邀请业界知名公司和组织的演讲嘉宾,提供技术更新和案例研究。最近一次举办于 2024.2.28。

  • VueJS Amsterdam - 规模最大的 Vue 会议,汇集全球参与者,与 Vue 创作者和维护者互动,建立联系并获得终生难忘的体验。最近一次举办于 2024.2.28。

  • VueConf US - Vue.js 社区组织的年度会议,由 Vue.js 创建者和核心团队主持,聚集全球 Vue.js 开发者和爱好者,分享最新技术和经验。最近一次举办于 2023.5.24。

  • React Summit, React Summit US - 全球最大的 React 会议,汇集 React 开发者、工程师和专家,分享 React 相关的技术和行业动态。最近一次举办于 2023.5.24 和 2023.11.13。

  • React Live - 与 500 名 React 开发人员一起参加一天的 React 庆典,深入探讨 React 相关的最新主题。最近一次举办于 2023.9.29。

  • DEV WORLD - 全球排名第一的开发者大会,提供与 7500 名开发人员面对面交流的机会,涵盖多个技术领域。最近一次举办于 2024.2.29。

  • Vite Conf - 沉浸式互动在线活动,面向前端团队,探讨使用 Vite 构建下一代网络。最近一次举办于 2023.10.5。

  • CSS Day - 高级 CSS 会议,提供轻松氛围和深入的讨论。最近一次举办于 2024.6.8。

  • FEday

  • ReactNext - 以色列最大的 React 框架会议,汇集本地和国际演讲者,分享高级主题。最近一次举办于 2024.6.24。

  • JSHeroes - 非营利性社区组织的会议,汇集全球 JS 和 Web/前端开发爱好者,提供演讲和网络活动。最近一次举办于 2024.5.23。

  • iJS - 国际 JavaScript 大会,关注 JavaScript 的最新趋势和变革。最近一次举办于 2023.10.23。

  • ng-conf - Angular 社区最大规模的年度技术峰会,邀请知名演讲嘉宾分享 Angular 相关经验和最佳实践。最近一次举办于 2023.6.14。

  • GOPS 2023全球运维大会 - 全球运维领域的年度大会,聚焦于最新的运维技术和实践。

  • DTCC 中国数据库技术大会 - 专注于数据库技术的交流和分享,汇集众多数据库领域的专家和技术爱好者。

  • Gdevops 全球敏捷运维峰会 - 探讨敏捷运维的最佳实践和前沿技术,旨在提高运维效率和质量。

  • ArchSummit 全球架构师峰会 - 集结全球顶尖架构师,分享架构设计的最新理念和技术。

  • KubeCon + CloudNativeCon - Kubernetes和云原生社区的年度聚会,分享云原生技术的最新进展。

  • 2019产品经理大会 - 针对产品经理的专业大会,探讨产品管理的理念、工具和最佳实践。

  • WOT 全球人工智能技术峰会 - 聚焦人工智能技术的前沿进展,汇集AI领域的研究者和实践者。

  • NSC中国网络安全大会 - 专注于网络安全领域的交流和分享,旨在提升网络安全意识和技能。

  • DOIS 2019运维开发国际峰会 - 探讨运维与开发的融合,促进DevOps文化和实践的发展。

  • GTLC全球技术领导力大会 - 聚焦技术领导力的培养和发展,为技术领导者提供交流和学习的平台。

活动发布平台

组织

相关资源推荐

产品用户体验优化

自己做了独立开发,最多的问题就是:怎么推广,怎么获客?」但我遇到的绝大多数的提出这个问题的人,产品都发布了,都没有过太多思考。

问题之所以发生,是因为之前的问题没有解决。站在问题链末端,很多问题是无法解决的。这就像鲁伯特之泪,拿锤子拼命砸它,不如用钳子剪断末梢。

往往解决问题的方式并不是回答问题本身,而是从上游出发解决核心矛盾。

如果你的独立产品期望快速找到100个狂热用户,通过他们快速增长,那请牢牢记住这四件事,并围绕这四件事不断向自己提问,找到解决方案:

1、定位目标用户,勾勒画像,了解当下用户遇到的问题
2、提供新解决方案,确定核心功能解决核心痛点
3、包装,写广告语,做落地页
4、冷启动,找到100个狂热用户

选择赛道,定义目标用户:

  1. 你的用户是谁?具体画像是什么样?他们兜里有钱吗?
  2. 现在这个时间点做这个事情,是最佳时间吗?有没有什么时刻,比现在更容易做?
  3. 有什么政策支持或政策风险吗?
  4. 这个产品,只能你在其中挣钱吗?怎么能让别人,也通过你的产品挣钱?
  5. 这件事只能你来做吗?你做这件事,有什么额外的优势吗?
  6. 在你产品上线后,有可以合作进行销售的现成渠道资源吗?
  7. 你觉得你的产品,是具有很直接价值感的产品吗?还是需要教育用户才能意识到产品价值的?
  8. 和你做相同事情的竞品,多吗?他们做的如何?
  9. 用户对竞品的差评里,在抱怨什么?

定义产品核心功能

  1. 你的产品帮助他做了什么?这个过程,他现在是怎么完成的?
  2. 现在在持续完成这个过程的人,在你的用户画像中,占比有多高?
  3. 他如果不用你,想达到相同效果,还有什么其他替代方案?效率上和用你的产品相比,能相差多少?
  4. 如果你只有一个核心功能或链路,用户为其付费,这个功能是什么?
  5. 有什么现成工具,可以帮助你快速构建这个链路吗?(或者完成初步测试)

确定营销包装

  1. 你的用户,究竟为你产品的哪个点付费?该如何凸显,才能让用户感觉这个点对他们有用?
  2. 请用一句话打动客户为你的产品付费,这句话是什么?
  3. 竞品包装的打动用户的点是什么?和你有差异化吗?
  4. 如果没有差异,竞品的劣势是什么?是否可以将它的劣势转化为你的优势?进行精准打击?用户在意这个点吗?

冷启动与扩量

  1. 你的目标用户在哪里聚集?
  2. 你初期如何找到他们?
  3. 你能依靠哪些资源或人,来变相触及到他们?
  4. 当他们想要找你这类产品时,最可能从哪些渠道发现?搜索的关键词是?
  5. 竞品都在哪里做广告?
  6. 最有可能帮你去卖你产品的人,是谁?

天使投资选择:关注技术变革和应用场景

如果去做天使投资,我现在会看什么类型的项目?

天使投资是最难做的,很多基金DPI都小于1,投不好基本就融不到钱了。所以如果是我,我会怎么选择?

我请教了一个朋友,朋友讲了一个技术变革周期的故事。

从上一轮苹果开始,先是科研人员有了一些成果,促使底层技术有了革命性进展。然后出现了iphone时刻。之后伴随着一路网络基础设施建设,以及一些底层开发生态的建设,把原来web时代的场景,迁移到了移动终端上。当越来越多的场景迁移完成,开始卷不动了,人们就开始去做新场景的创新,出现了抖音,拼多多这种巨头。应用侧没的卷了,就开始卷内容,无数多的人挤进去做内容创新。

这个路线,放到AI上也一样可以借鉴的。新技术变革就像GPT3.5出现时一样,前期也是在卷模型,RAG,infra,所以这种做底层基建的,是会考虑投入的。因为长期来看,生态需要它。在每个赛道总是能跑出1,2个头部并持续发展下去的。

然而目前我们还没进入旧场景迁移的时候,目测现在的应用层还远远达不到真正实用的要求,所以暂时也不会考虑投入。很可能技术的势头一到,重新做旧场景迁移,会比现在快的多。那现在创业者做的事情,往往就会成为技术债务,船大难掉头。所以目前不会投这块的项目。

我感觉大家思路都差不多,我考量的是AI应用不成熟,所以会选择做AI流量直接变现,或者toB去做服务解决方案,而不是自己做个东西。

但其实AI应用里也会拆分,往往也有底层的应用范畴。比如和infra层对接比较紧密的,抽象能力足够强以至于可以应对变化的这种项目,也是值得看的。

这个思路其实也适用于visionOS带来的空间计算变革,只是感觉visionOS才刚刚走到新技术变革—>底层基础建设 这块。

前言

24年的年度目标其中就有一个是进入开源社区,而万事开头难,一开始选了VueCore和ElementUI作为我的开始,也向VueCore交了一个PR,但是没发现这个issue上一周就被修复了,所以就没了下文。

但是ElementUI的issue挺有意思的,下面我们来细说下:

问题

issue长这样:牙齿尖尖的

这边也贴一个SFC的链接吧:SFC

刚看到这个issue还挺有意思的:这个bug是在菜单组件过多时,elementUI会将过长的menu进行收纳进一个展开栏中:

elementUI

也就是上图中的省略号那个区域

当我们hover或者click那个区域时,会弹出一个展开栏,里面包含了所有的菜单

但是这个issue的问题是,当我们点击展开栏,当菜单栏的长度过多,会导致展开栏的长度过长,页面会往下拓展,导致页面的滚动条出现,后面页面的视宽内容会被挤压。

挤压不要紧,这个应该是符合预期的,因为当菜单栏过多时,我们的展开栏的长度也会变长,他就需要有地方进行拓展。

但是问题是,这个组件有一个节流函数:

const getIndexPath = (index: string) => subMenus.value[index].indexPath

// Common computer monitor FPS is 60Hz, which means 60 redraws per second. Calculation formula: 1000ms/60 ≈ 16.67ms, In order to avoid certain chance of repeated triggering when `resize`, set wait to 16.67 * 2 = 33.34
const debounce = (fn: () => void, wait = 33.34) => {
   let timmer: ReturnType<typeof setTimeout> | null
  return () => {
    timmer && clearTimeout(timmer)
    timmer = setTimeout(() => {
      fn()
    }, wait)
  }
}

let isFirstTimeRender = true
const handleResize = () => {
  if (sliceIndex.value === calcSliceIndex()) return
   const callback = () => {
    sliceIndex.value = -1
    nextTick(() => {
       sliceIndex.value = calcSliceIndex()
     })
   }    // execute callback directly when first time resize to avoid shaking
   isFirstTimeRender ? callback() : debounce(callback)()
   isFirstTimeRender = false
 }

他是这样的:当我们页面的视宽发生变化时,会触发resize事件,然后会执行handleResize函数,这个函数会计算当前的菜单栏的长度,然后会执行一个节流函数,这个节流函数会在一定的时间内,只执行一次回调函数,这个回调函数就是计算当前的菜单栏的长度,然后将这个长度赋值给sliceIndex,然后页面就会根据这个长度来进行展开栏的展开。

搞笑的就来了:刚刚我们触发的滚动条,使得这个节流函数的触发,页面就开始抽搐了 ,因为这个节流函数的节流时间是33.34ms,而我们的滚动条的触发频率是16.67ms,所以这个节流函数就会被频繁的触发,导致页面的抽搐。

解决

那么怎么solve这个问题呢:

我一开始想到的是改这个scss的样式,但是这个样式是根据菜单栏的长度来进行展开的,所以改样式是不行的。

后面我发现这个节流函数的触发应该是来解决拖动视窗时组件的销毁和重建,那么我们有必要这么频繁的触发这个节流函数吗?我们是否可以将当他改变组件数量的时候再进行触发呢?

所以在原来的代码中加了一行:

if (sliceIndex.value === calcSliceIndex()) return

这个问题整体来说还算简单,但是我觉得这个问题还是挺有意思的,所以就记录下来了。

提了这个PR也让我复习了一下git的操作,以及体验到了开源项目的PR流程和规范,还是挺有意思的。

下一个阶段的小目标:向Vue和elementUI提PR,看能不能有机会进core team!

前言

做项目需要用到富文本编辑器,但是看了很多的富文本编辑器都很少支持公式编辑,但项目有要用到,怎么办呢?先写一个显然并不现实。于是使用easy-formula-editorwangeditor写了一个功能小插件,是 基于Vue3和MathJax渲染的Latex富文本公式编辑器,支持零基础即可编辑公式,支持自定义编辑器配置和风格,支持二次编辑公式,支持作为插件和富文本编辑器一起使用。

  • 零基础即可编辑公式
  • 支持自定义编辑器配置和风格
  • 支持二次编辑公式
  • 支持作为插件和富文本编辑器一起使用

MathJax

安装和使用

NPM

npm i easy-formula-editor

import formulaEditor from "easy-formula-editor";
const editor = new formulaEditor();
editor.create('#test');

CDN

<script type="text/javascript" src="../dist/formula-editor.min.js"></script>
<script type="text/javascript">
  const editor = new formulaEditor();
  editor.create('#test');
</script>

导出

// latex 公式
editor.latex.text()

// html 公式
editor.$textSvgElem.html()

富文本编辑器菜单栏扩展

注册菜单

【注意】 推荐使用全局模式来注册菜单。 如果有多个编辑器,每个编辑器的自定义菜单不一样,则使用实例的方式来注册菜单

全局模式

// 菜单 key ,各个菜单不能重复
const menuKey = 'alertMenuKey' 

// 注册菜单
E.registerMenu(menuKey, AlertMenu)

const editor = new E('#div1')
editor.create()

const editor2 = new E('#div2')
editor2.create()

实例模式

// 菜单 key ,各个菜单不能重复
const menuKey = 'alertMenuKey' 
const menuKey2 = 'alertMenuKey2'

const editor = new E('#div1')
// 注册菜单
editor.menus.extend(menuKey, AlertMenu)

// 将菜单加入到 editor.config.menus 中    const menuKey = 'alertMenuKey' 
// 也可以通过配置 menus 调整菜单的顺序,参考【配置菜单】部分的文档    editor.config.menus.push(menuKey)
editor.config.menus = editor.config.menus.concat(menuKey)

// 注册完菜单,再创建编辑器,顺序很重要!!
editor.create()

const editor2 = new E('#div2')
editor2.menus.extend(menuKey2, AlertMenu)
editor2.config.menus.push(menuKey2)
editor2.create()

实际项目结合示例

import E from "wangeditor";
import formulaEditor from "easy-formula-editor";
import createPanelConf from "./create-panel-conf";

const { PanelMenu, Panel } = E;

class AlertMenu extends PanelMenu {
  constructor(editor) {
    // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
    const $elem = E.$(
      `<div class="w-e-menu" data-title="数学公式">
        <span>公式</span>
      </div>`
    );
    super($elem, editor);
  }

  /**
   * 菜单点击事件
   */
  clickHandler() {
    const formula = new formulaEditor();
    const conf = createPanelConf(this.editor, formula);
    const panel = new Panel(this, conf);
    panel.create();

    formula.create("#edit-content");
  }

  tryChangeActive() {}
}

const menuKey = "alertMenuKey";

// 注册菜单
E.registerMenu(menuKey, AlertMenu);

export default E;
//create-panel-conf.ts
export default function (wangEditor, formulaEditor) {
    const btnOkId = 'btn-ok'
  
    /**
     * 插入公式
     */
    function insertFomule() {
      const formula = formulaEditor.latex.text()
      // 注意插入wangEditor时左右两边的空格不能去掉,不然会导致无法获取焦点
      wangEditor.txt.append('<p>'+formula+'</p>')
      return true
    }
  
    // tabs配置
    const tabsConf = [
      {
        // tab 的标题
        title: "插入数学公式",
        // 模板
        tpl: `<div>
                <div id="edit-content"></div>
                <div class="w-e-button-container">
                  <button type="button" id="${btnOkId}" class="right">插入</button>
                </div>
              </div>`,
        // 事件绑定
        events: [
          // 插入视频
          {
            selector: '#' + btnOkId,
            type: 'click',
            fn: insertFomule,
            bindEnter: true,
          },
        ],
      }, // tab end
    ]
  
    return {
        width: 660,
        height: 0,
        // panel 中可包含多个 tab
        tabs: tabsConf, // tabs end
      }
}

使用上面的代码,就可以在富文本编辑器中添加一个公式编辑器的菜单了:

<template>
  <div class="formula-container">
    <v-card elevation="0" class="formula-card" title="输出区域" subtitle="Output">
      <div id="formula" class="formula-content">
        {{ renderedFormula ? `$${renderedFormula}$` : '' }}
      </div>
    </v-card>
    <div class="editor-area">
      <div id="wang-editor" class="editor"></div>
    </div>
  </div>
</template>


<script setup>
import E from "../utils/formula-menu-conf";
import { ref, onMounted, nextTick, defineProps, watchEffect } from "vue";

// 定义props
const props = defineProps({
  initMessage: {
    type: String,
    default: "",
  }
});

watchEffect(() => {
  props.initMessage;
});

const editor = ref(null);
const renderedFormula = ref("");

function convert() {
  MathJax.texReset();
  MathJax.typesetClear();
  MathJax.typesetPromise();
}

function updateFormula() {
  renderedFormula.value = editor.value.txt.text();
  nextTick(convert);
}

onMounted(() => {
  editor.value = new E("#wang-editor");
  editor.value.config.height = 360;
  editor.value.config.menus = ['head', 'bold', 'underline', 'strikeThrough','emoticon', 'undo', 'redo'];
  editor.value.config.onchange = updateFormula;
  editor.value.config.onchangeTimeout = 500;
  editor.value.create();
  editor.value.txt.html(props.initMessage);
});

</script>

可以看到,我们的公式编辑器和富文本编辑器完美结合了,效果如下:

MathJax

贴一个wangeditor的官方文档:www.wangeditor.com/v4

前言

本篇主要对React的状态进行一个介绍。

声明式 UI 与命令式 UI 的比较

当你设计 UI 交互时,可能会去思考 UI 如何根据用户的操作而响应变化。想象一个允许用户提交一个答案的表单:

  • 当你向表单输入数据时,“提交”按钮会随之变成可用状态

  • 当你点击“提交”后,表单和提交按钮都会随之变成不可用状态,并且会加载动画会随之出现

  • 如果网络请求成功,表单会随之隐藏,同时“提交成功”的信息会随之出现

  • 如果网络请求失败,错误信息会随之出现,同时表单又变为可用状态

在 命令式编程 中,以上的过程直接告诉你如何去实现交互。你必须去根据要发生的事情写一些明确的命令去操作 UI。

而在Vuejs渐进式框架和React库里面都是采用的声明式编程。

声明式地考虑 UI

你已经从上面的例子看到如何去实现一个表单了,为了更好地理解如何在 React 中思考,一般情况下要用 React 重新实现这个 UI就要经过这几个一般步骤:

  1. 定位你的组件中不同的视图状态

  2. 确定是什么触发了这些 state 的改变

  3. 表示内存中的 state(需要使用 useState)

  4. 删除任何不必要的 state 变量

  5. 连接事件处理函数去设置 state

React的状态管理

在React中,状态管理是一个非常重要的概念,React的状态管理是通过state来实现的。

state

state是React组件中的一个内置对象,用于存储组件内部的状态。state是一个普通的JavaScript对象,我们可以通过this.state来访问它。

React 会为 UI 中的组件结构构建 渲染树。

当向一个组件添加状态时,那么可能会认为状态“存在”在组件内。但实际上,状态是由 React 保存的。React 通过组件在渲染树中的位置将它保存的每个状态与正确的组件关联起来。

这边与Vuejs的区别是,Vue的响应式系统是与组件进行隔离开来的,而React的状态是与组件进行绑定的。

我们将state嵌入进React的组件里面,作为组件vnode的一部分信息,当React的state更新时,也意味着组件的信息更新,会触发组件的重新渲染。

如果理解了这一点,那么我们就可以很轻易的理解React的响应式是怎么工作的了,React的状态是与组件绑定的,当状态更新时,组件也会更新,这也是React的响应式系统。

我们来具体给一个例子:

import { useState } from 'react';

export default function App() {
  const counter = <Counter />;
  return (
    <div>
      {counter}
      {counter}
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        加一
      </button>
    </div>
  );
}

下面是它们的树形结构的样子:

vue-react

这是两个独立的 counter,因为它们在树中被渲染在了各自的位置。 一般情况下你不用去考虑这些位置来使用 React,但知道它们是如何工作会很有用。

而他们有各自的状态,因为每个组件实例都有自己的 state。 你可以在一个组件中使用多个 state 变量。当我更新了state之后,会触发组件的重新渲染,这边我们可以看到,两个counter的状态是独立的,互不影响。

相同位置的相同组件会使得 state 被保留下来

import { useState } from 'react';

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  return (
    <div>
      {isFancy ? (
        <Counter isFancy={true} /> 
      ) : (
        <Counter isFancy={false} /> 
      )}
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        使用好看的样式
      </label>
    </div>
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }
  if (isFancy) {
    className += ' fancy';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        加一
      </button>
    </div>
  );
}

vue-react

那么相同位置的不同组件会使 state 重置,这一点应该大家也能够知道为什么了。

再举一个例子:将一个<Counter> 替换为一个 <p>

vue-react

当 Counter 变为 p 时,Counter 会被移除,同时 p 被添加。

vue-react

当切换回来时,p 会被删除,而 Counter 会被添加

参考的React文档在这里:状态与渲染树中的位置相关

提取状态逻辑到 reducer 中

对于那些需要更新多个状态的组件来说,过于分散的事件处理程序可能会令人不知所措。对于这种情况,你可以在组件外部将所有状态更新逻辑合并到一个称为 “reducer” 的函数中。这样,事件处理程序就会变得简洁,因为它们只需要指定用户的 “actions”。在文件的底部,reducer 函数指定状态应该如何更新以响应每个 action

useReducer 是 React 中的一个 Hook,用于管理组件的状态逻辑。它提供了一种更复杂状态逻辑的组织方式,适用于那些包含多个子值或需要根据先前的状态来更新的状态。 useReducer 的使用方式类似于 Redux 中的 reducer 概念。

下面是 useReducer 的基本用法:

import React, { useReducer } from 'react';

// reducer函数,接受当前state和action,返回新的state
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

const initialState = { count: 0 };

const Counter = () => {
  // 使用useReducer,传入reducer和初始state
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
    </div>
  );
};

export default Counter;

主要的步骤包括:

  1. 定义 reducer 函数: 这个函数接收当前状态 state 和一个描述动作的 action,然后根据 action 的类型执行相应的逻辑,并返回新的状态。

  2. 初始化状态: 通过调用 useReducer(reducer, initialState) 来创建状态和 dispatch 函数。

  3. 在组件中使用状态和 dispatch: 通过 state 访问当前状态值,通过 dispatch 触发 reducer 执行。

使用 Context 进行深层数据传递

通常,你会通过 props 将信息从父组件传递给子组件。 但是,如果要在组件树中深入传递一些 prop,或者树里的许多组件需要使用相同的 prop,那么传递 prop 可能会变得很麻烦。Context 允许父组件将一些信息提供给它下层的任何组件,不管该组件多深层也无需通过 props 逐层透传。

import React, { createContext, useContext, useState } from 'react';

// 创建一个Context
const MyContext = createContext();

// 创建一个包含Provider和Consumer的组件
const MyProvider = ({ children }) => {
  const [data, setData] = useState('Hello from Context');

  return (
    <MyContext.Provider value={{ data, setData }}>
      {children}
    </MyContext.Provider>
  );
};

// 使用 useContext 获取Context中的值
const ChildComponent = () => {
  const { data, setData } = useContext(MyContext);

  return (
    <div>
      <p>{data}</p>
      <button onClick={() => setData('Updated Context')}>Update Context</button>
    </div>
  );
};

// 在应用中使用Provider包裹需要共享数据的组件
const App = () => {
  return (
    <MyProvider>
      <ChildComponent />
    </MyProvider>
  );
};

export default App;

使用 Reducer 和 Context 进行状态扩展

Reducer 帮助你合并组件的状态更新逻辑同时Context 帮助你将信息深入传递给其他组件。

你可以将 reducers 和 context 组合在一起使用,以管理复杂应用的状态。

基于这种想法,使用 reducer 来管理一个具有复杂状态的父组件。组件树中任何深度的其他组件都可以通过 context 读取其状态。还可以 dispatch 一些 action 来更新状态。

合理的使用 Reducer 和 Context 可以帮助你更好的管理状态,同时也可以让你的代码更加简洁。

相比于Vuejs基于Proxy的响应式系统,使用代理的方式,以及provide和inject的方式,Vuejs的状态管理是在一个单独的WeekMap里面进行管理的,当变量的状态进行改变,可以激活effect副作用函数,并进行值的更新。而React的状态管理是与组件绑定的,当状态更新时,组件也会更新,但是当我们要进行一个全局状态的管理时,就得进行在父组件里面定义。

能基于具体的情况进行React状态的定义,才能更好的进行状态的管理。

前言

这一篇主要来聊一聊React的JSX如何被编译成VNode的。

Vue的render函数

在此之前,我们都知道,Vue是使用Vue模板方法的方式进行组件的声明。我们先来回顾一下Vue是怎么处理的。

Vue模板经过Vue的编译器编译成render函数,最后render函数返回VNode,然后再通过patch函数将VNode渲染成真实DOM。

具体的renderer细节在以前的章节里面有提到过,这边就不再赘述了。

React的JSX

react通过将组件编写的JSX映射到屏幕,以及组件中的状态发生了变化之后 React会将这些「变化」更新到屏幕上

JSX通过babel最终转化成React.createElement这种形式,至于babel是具体怎么将JSX转化成React.createElement的,我们可以通过babel的在线编译器来查看。

<div>
  < img src="avatar.png" className="profile" />
  <Hello />
</div>

会被bebel转化成如下:

React.createElement(
  "div",
  null,
  React.createElement("img", {
    src: "avatar.png",
    className: "profile"
  }),
  React.createElement(Hello, null)
);

Babel提供一个REPL在线编译器,可以在线将ES6代码转为ES5代码。转换后的代码,可以直接作为ES5代码插入网页运行,这边贴一个链接:https://babeljs.io/

vue-react

在转化过程中,babel在编译时会判断 JSX 中组件的首字母:

当首字母为小写时,其被认定为原生 DOM 标签,createElement 的第一个变量被编译为字符串

当首字母为大写时,其被认定为自定义组件,createElement 的第一个变量被编译为对象

最终都会通过RenderDOM.render(...)方法进行挂载,如下:

ReactDOM.render(<App />,  document.getElementById("root"));

React的JSX转换具体是怎么做的呢

在react中,节点大致可以分成四个类别:

  • 原生标签节点
  • 文本节点
  • 函数组件
  • 类组件

如下所示:

class ClassComponent extends Component {
  static defaultProps = {
    color: "pink"
  };
  render() {
    return (
      <div className="border">
        <h3>ClassComponent</h3>
        <p className={this.props.color}>{this.props.name}</p >
      </div>
    );
  }
}

function FunctionComponent(props) {
  return (
    <div className="border">
      FunctionComponent
      <p>{props.name}</p >
    </div>
  );
}

const jsx = (
  <div className="border">
    <p>xx</p >
    < a href=" ">xxx</ a>
    <FunctionComponent name="函数组件" />
    <ClassComponent name="类组件" color="red" />
  </div>
);

这些类别最终都会被转化成React.createElement这种形式

vue-react

React.createElement其被调用时会传⼊标签类型type,标签属性props及若干子元素children,作用是生成一个虚拟Dom对象,如下所示:

function createElement(type, config, ...children) {
    if (config) {
        delete config.__self;
        delete config.__source;
    }
    // ! 源码中做了详细处理,⽐如过滤掉key、ref等
    const props = {
        ...config,
        children: children.map(child =>
   typeof child === "object" ? child : createTextNode(child)
  )
    };
    return {
        type,
        props
    };
}
function createTextNode(text) {
    return {
        type: TEXT,
        props: {
            children: [],
            nodeValue: text
        }
    };
}
export default {
    createElement
};

createElement会根据传入的节点信息进行一个判断:

  • 如果是原生标签节点, type 是字符串,如div、span
  • 如果是文本节点, type就没有,这里是 TEXT
  • 如果是函数组件,type 是函数名
  • 如果是类组件,type 是类名
  • 虚拟DOM会通过ReactDOM.render进行渲染成真实DOM,使用方法如下:
ReactDOM.render(element, container[, callback])

当首次调用时,容器节点里的所有 DOM 元素都会被替换,后续的调用则会使用 React 的 diff算法进行高效的更新

如果提供了可选的回调函数callback,该回调将在组件被渲染或更新之后被执行
render大致实现方法如下:

function render(vnode, container) {
    console.log("vnode", vnode); // 虚拟DOM对象
    // vnode _> node
    const node = createNode(vnode, container);
    container.appendChild(node);
}

// 创建真实DOM节点
function createNode(vnode, parentNode) {
    let node = null;
    const {type, props} = vnode;
    if (type === TEXT) {
        node = document.createTextNode("");
    } else if (typeof type === "string") {
        node = document.createElement(type);
    } else if (typeof type === "function") {
        node = type.isReactComponent
            ? updateClassComponent(vnode, parentNode)
        : updateFunctionComponent(vnode, parentNode);
    } else {
        node = document.createDocumentFragment();
    }
    reconcileChildren(props.children, node);
    updateNode(node, props);
    return node;
}

// 遍历下子vnode,然后把子vnode->真实DOM节点,再插入父node中
function reconcileChildren(children, node) {
    for (let i = 0; i < children.length; i++) {
        let child = children[i];
        if (Array.isArray(child)) {
            for (let j = 0; j < child.length; j++) {
                render(child[j], node);
            }
        } else {
            render(child, node);
        }
    }
}
function updateNode(node, nextVal) {
    Object.keys(nextVal)
        .filter(k => k !== "children")
        .forEach(k => {
        if (k.slice(0, 2) === "on") {
            let eventName = k.slice(2).toLocaleLowerCase();
            node.addEventListener(eventName, nextVal[k]);
        } else {
            node[k] = nextVal[k];
        }
    });
}

// 返回真实dom节点
// 执行函数
function updateFunctionComponent(vnode, parentNode) {
    const {type, props} = vnode;
    let vvnode = type(props);
    const node = createNode(vvnode, parentNode);
    return node;
}

// 返回真实dom节点
// 先实例化,再执行render函数
function updateClassComponent(vnode, parentNode) {
    const {type, props} = vnode;
    let cmp = new type(props);
    const vvnode = cmp.render();
    const node = createNode(vvnode, parentNode);
    return node;
}
export default {
    render
};

在react源码中,虚拟Dom转化成真实Dom整体流程如下图所示:

vue-react

其渲染流程如下所示:

  • 使用React.createElementJSX编写React组件,实际上所有的 JSX 代码最后都会转换成React.createElement(...) Babel帮助我们完成了这个转换的过程。

  • createElement函数对key和ref等特殊的props进行处理,并获取defaultProps对默认props进行赋值,并且对传入的孩子节点进行处理,最终构造成一个虚拟DOM对象

  • ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM

前言

最近面了得物春季青训营,项目要求掌握React。之前写过一段时间Next.js,对jsx的理解还算是有些片面,作为Vuejs转React的一个开发者,打算先将Vuejs和React的功能特性做一个对比,然后再根据项目入手React。

Vuejs和React的相同点

vue-react

Vue和React相同点非常多:

  • 都使用Virtural DOM
  • 都使用组件化思想,流程基本一致
  • 都是响应式,推崇单向数据流(Vue的v-model指令允许在表单元素上进行双向数据绑定。)
  • 都有成熟的社区,都支持服务端渲染

Vue和React实现原理和流程基本一致,都是使用Virtual DOM + Diff算法。

不管是Vue的template模板 + options api写法(即使用SFC体系),还是React的Class或者Function(js 的class写法也是function函数的一种)的jsx写法,底层最终都是为了生成render函数。

render函数执行返回VNode(虚拟DOM树)。 当每一次UI更新时,总会根据render重新生成最新的VNode,然后跟以前缓存起来老的VNode进行比对,再使用Diff算法(框架核心)去真正更新真实DOM(虚拟DOM是JS对象结构,同样在JS引擎中,而真实DOM在浏览器渲染引擎中,所以操作虚拟DOM比操作真实DOM开销要小的多)。

Vue和React通用流程:

vue template/react jsx -> render函数 -> 生成VNode -> 当有变化时,新老VNode diff -> diff算法对比,并真正去更新真实DOM。

核心还是Virtual DOM,VNode的好处不用多说:

  • 减少直接操作DOM。框架给我们提供了屏蔽底层dom书写的方式,减少频繁的整更新dom,同时也使得数据驱动视图

  • 为函数式UI编程提供可能(React核心思想)

  • 可以跨平台,渲染到DOM(web)之外的平台。比如ReactNative,Weex

vue-react

Vue说自己是框架,是因为官方提供了从声明式渲染到构建工具一整套东西。

React说自己是库,是因为官方只提供了React.js这个核心库,路由、状态管理这些都是社区第三方提供了,最终整合成了一套方案。

Vue和React的区别

组件实现不同

Vuejs实现

Vue源码实现是把options挂载到Vue核心类上,然后再new Vue({options})拿到实例(vue组件的script导出的是一个挂满options的纯对象而已)。

所以options api中的this指向内部Vue实例,对用户是不透明的,所以需要文档去说明this.$slot、this.$xxx这些api。另外Vue插件都是基于Vue原型类基础之上建立的,这也是Vue插件使用Vue.install的原因,因为要确保第三方库的Vue和当前应用的Vue对象是同一个。

React实现

React内部实现比较简单,直接定义render函数以生成VNode ,而React内部使用了四大组件类包装VNode,不同类型的VNode使用相应的组件类处理,职责划分清晰明了(后面的Diff算法也非常清晰)。React类组件都是继承自React.Component类,其this指向用户自定义的类,对用户来说是透明的。

响应式原理不同

Vuejs实现

  • Vue依赖收集,自动优化,数据可变。
  • Vue递归监听data的所有属性,直接修改。
  • 当数据改变时,自动找到引用组件重新渲染。

React实现

  • React基于状态机,手动优化,数据不可变,需要setState驱动新的State替换老的State。
  • 当数据改变时,以组件为根目录,默认全部重新渲染

diff算法不同

Vue基于snabbdom库,它有较好的速度以及模块机制。Vue Diff使用双向链表,边对比,边更新DOM。

React主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。

事件机制不同

Vuejs实现

  • Vue原生事件使用标准Web事件
  • Vue组件自定义事件机制,是父子组件通信基础
  • Vue合理利用了snabbdom库的模块插件

React实现

  • React原生事件被包装,所有事件都冒泡到顶层document监听,然后在这里合成事件下发。基于这套,可以跨端使用事件机制,而不是和Web DOM强绑定。
  • React组件上无事件,父子组件通信使用props