react 架构方案_3种轻量级React替代方案:Preact,VirtualDom和Deku

react 架构方案

本文由Craig BilnerBruno Mota进行同行评审。 感谢所有SitePoint的同行评审员使SitePoint内容达到最佳状态!

React的声明性组件和虚拟DOM渲染已席卷了前端开发领域,但这并不是唯一基于这些思想的库。 今天,我们将探讨在其他三个类似React的替代方案中构建应用程序的感觉。

我们假设您已经熟悉React及其在其生态系统中使用的术语。 如果您需要抓紧时间或只是重新整理,请查阅我们之前的文章之一

总览

让我们开始对我们将要比较的库进行高级概述。

德库(2.0.0-rc15)

NPM上的Deku

Deku的目标是成为React的更多功能替代品。 它防止组件具有局部状态,这允许将所有组件编写为与诸如Redux之类的外部状态管理解决方案进行通信的纯函数。

预先(4.1.1)

在npm上执行

Preact是尝试使用尽可能少的代码来模仿React的核心功能。 假设您将使用ES2015,Preact将采取一些捷径并精简React的原始功能集,以产生一个只有3KB的微型库。

虚拟DOM(2.1.1)

npm上的virtual-dom

在React,Deku和Preact为您提供虚拟DOM之上的组件抽象的地方,virtual-dom包为您提供了您自己创建,比较和呈现虚拟DOM节点树所需的底层工具。 ( 这与构建React和Preact的虚拟DOM不同!

像Virtual-DOM这样的低级库似乎可以替代React,但是如果您有兴趣编写高效的移动Web体验,那么观看Pocket大小的JS是一个不错的起点。 实际上,这是我们将Virtual-DOM包含在比较中的原因。

我们将使用这些库中的每一个来构建组件,构建数据流并最终查看每个应用程序的大小和性能。

组件

这是一个React组件,它将使用标记的库来呈现一些Markdown。

import React from 'react';
import marked from 'marked';

const Markdown = React.createClass({
  propTypes: {
    text: React.PropTypes.string
  },
  getDefaultProps() {
    return { text: '' };
  },
  render() {
    return (
      <div
        dangerouslySetInnerHTML={{
          __html: marked(this.props.text)
        }}>
      </div>
    );
  }
});

我们正在使用道具验证来让组件在收到错误类型的道具时警告我们。 它还实现了getDefaultProps()方法,该方法允许我们在没有传入任何值的情况下为组件提供默认值。最后,我们实现了render方法,该方法返回此组件的用户界面。

为了防止React在渲染时逃避Markdown,我们需要将其传递给危险的SetInnerHTML属性。

德库

接下来,我们将使用Deku实现相同的组件。

/** @jsx element */
import { element } from 'deku';
import marked from 'marked';

const Markdown = {
  render({ props: { text='' } }) {
    return <div innerHTML={marked(text)}></div>;
  }
};

第一行是编译器编译指示,告诉编译器将像<h1>Hello</h1>这样的JSX转换为element('h1', null, 'Hello')而不是React.createElement('h1', null, 'Hello') ,这使我们可以将JSX与Deku结合使用,而不是React。 也可以使用.babelrc文件配置此选项。

与React相比,我们的Deku组件绝对简单。 Deku组件没有可以引用的实例, this意味着该组件可能需要的所有数据将作为称为model的对象传递到方法中。 该对象包含组件的props ,我们可以使用解构语法提取text道具。

Deku没有prop验证,但是我们至少可以通过在这些解构分配中提供默认值来模拟getDefaultProps()

事前

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

接下来是Preact。

/** @jsx h */
import { h, Component } from 'preact';
import marked from 'marked';

class Markdown extends Component {
  render() {
    const { text='' } = this.props;
    return (
      <div
        dangerouslySetInnerHTML={{
          __html: marked(text)
        }}>
      </div>
    );
  }
}

同样,我们需要告诉编译器将JSX转换为Preact可以理解的东西。 Preact组件与React的ES2015类组件非常相似,并且我们能够复制之前的大多数渲染代码。 像Deku一样,Preact不支持道具验证或默认属性,但是我们可以再次使用解构分配模拟默认道具。

虚拟DOM

最后,我们将看一下Virtual-DOM。

/** @jsx h */
import { h } from 'virtual-dom-util';
import marked from 'marked';

function Markdown({ text='' }) {
  return <div innerHTML={marked(text)}></div>;
}

我们没有提供用于构造组件的任何工具,因此您在这里看不到诸如thispropsstate构造。 实际上,这些“组件”只是返回虚拟DOM节点树的函数。

创建虚拟DOM节点的本机方式与JSX不兼容,因此我们使用virtual-dom-util包为我们提供了与JSX兼容的替代方案。 在渲染组件之前,我们实际上不需要导入virtual-dom软件包。

渲染组件

接下来,我们将研究如何将组件呈现到DOM中。 所有这些库都渲染到目标节点中,因此我们将在HTML文件中创建一个。

<div id="app"></div>

React

import { render } from 'react-dom'

render(
  <Markdown text='Hello __world__' />,
  document.getElementById('app')
);

要渲染React组件,我们需要使用react-dom包,该包提供了一个render功能,该功能可以理解如何将React组件树转变为DOM节点树。

为了使用它,我们传递了一个React组件的实例和一个对DOM节点的引用。 ReactDOM处理其余的事情。

德库

/** @jsx element */
import { createApp, element } from 'deku';

const render = createApp(
  document.getElementById('app')
);

render(
  <Markdown text='Hello __world__' />
);

Deku呈现组件的方式略有不同。 因为Deku组件不是有状态的,所以它们不会自动重新渲染。 取而代之的是,我们使用createApp()围绕DOM节点构建渲染函数,每次外部状态更改时,我们就可以调用该函数。

现在我们可以传递Deku组件的实例以在该节点中渲染它们。

事前

/** @jsx h */
import { h, render } from 'preact';

render(
  <Markdown text='Hello __world__' />,
  document.getElementById('app')
);

Preact为我们提供了一个类似的接口,用于将组件渲染到DOM节点中,但是与ReactDOM不同,它位于核心Preact包中。 像Preact API一样,没有什么新鲜的东西可以学习,而且React的概念也很容易移植。

虚拟DOM

/** @jsx h */
import { create } from 'virtual-dom';
import { h } from 'virtual-dom-util';

const tree = <Markdown text='Hello __world__' />;
const root = create(tree);

document
  .getElementById('app')
  .appendChild(root);

虚拟DOM使我们在创建和使用组件方面具有更大的灵活性。 首先,我们创建一个虚拟树的实例,并使用create函数将其实现为DOM节点。 最后,我们可以随意使用任意方式将此子级添加到DOM中。

数据流

在我们正在考虑的三个库中,有两种不同的方法来管理应用程序状态。

与React一样,Preact也允许组件管理自己的状态。

组件管理自己的状态

每个组件都跟踪对不变状态对象的引用,该引用可以通过称为setState的特殊组件方法进行更新。 调用此函数时,组件将假定已进行了某些更改并尝试重新渲染。 从状态已更新的组件接收支持的任何组件也将重新呈现。

Preact还为我们提供了一种机制,该机制可以使用shouldComponentUpdate形式的细粒度控件来覆盖默认行为。

Deku做出了将状态管理移到组件之外的深思熟虑的决定,而Virtual-DOM级别太低,无法与状态之类的抽象相关。 这意味着,如果要使用它构建应用程序,则需要将状态保留在其他位置。

组件外的状态管理

在这种情况下,我们的状态移到外部容器中,根组件使用该外部容器为应用程序的其余部分提供数据。 每次状态容器更新时,我们都需要重新渲染整个应用程序。

要更新状态,组件必须与状态容器通信更改。 在类似Flux的系统中,这种交流通常以动作的形式出现。

重要的是要记住,尽管React和Preact支持组件的本地状态,但它们也可以与外部状态管理解决方案一起使用。

应用结构

本节将研究如何将有关状态,数据流和重新渲染的这些想法实现为实际代码。 在此过程中,我们将把Markdown组件构建到实时Markdown编辑器中。 您可以在下一部分中看到完成组件的演示。

德库

Deku应用程序通常由两个主要部分组成: 组件树store

订阅调度模型

我们将Redux用作商店,因为它可以与Deku很好地配合使用。 树中的组件将分派动作,我们的Redux缩减程序将使用它们来更改状态,并且只要状态发生变化,我们将使用订阅机制重新呈现组件树。

首先,我们将建立一个简单的Redux存储。

import { createStore } from 'redux';

const initState = { text: '' };
const store = createStore((state=initState, action) => {
  switch(action.type) {
    case 'UPDATE_TEXT':
      return { text: action.payload };
    default:
      return state;
  }
});

无需赘述,Redux存储由一个reducer函数构建,该函数将当前状态和一个动作作为参数。 函数应基于操作中的数据返回新状态。

现在,我们将重新访问渲染代码,以使Deku知道我们的Redux商店。

const render = createApp(
  document.getElementById('app'),
  store.dispatch
);

因为Deku希望您使用外部状态管理解决方案,所以它的createApp函数接受调度函数作为第二个参数。 反过来,Deku将为其所有组件提供此分发功能,以便它们可以与Redux商店对话。

我们还将把商店的当前状态传递给render函数。 Deku会将此值作为context提供给每个组件,从而允许从存储中读取树中的任何组件。

render(
  <MarkdownEditor />,
  store.getState()
);

我们可以使用store.subscribe()方法来侦听状态更改,以便我们可以重新渲染组件树。

store.subscribe(() => {
  render(
    <MarkdownEditor />,
    store.getState()
  );
});

要更新状态,组件应将动作传递给它们的调度功能。 但是,在组件内部创建动作很容易导致组件代码膨胀,因此,我们将创建中间人函数来为我们调度参数化的动作。 这些功能通常被称为“动作创建者”。

const actions = {
  updateText: dispatch => text => {
    dispatch({
      type: 'UPDATE_TEXT',
      payload: text
    });
  }
};

动作创建者采用一个调度功能和一个参数,然后使用它们来创建和调度适当的动作对象。 为了方便起见,我们正在设计动作,使其符合Flux标准动作

为了完全结合起来,我们的组件将从context读取状态,并使用新的动作创建者来分派动作。

const MarkdownEditor = {
  render({ context, dispatch }) {
    return (
      <main>
        <section>
          <label>Markdown</label>
          <hr />
          <Editor onEdit={actions.updateText(dispatch)} />
        </section>
        <section>
          <label>Preview</label>
          <hr />
          <Markdown text={context.text} />
        </section>
      </main>
    );
  }
};

事前

渲染Preact组件后,它将通过侦听对其内部状态的更改来管理自己的重新渲染。

import { Component } from 'preact';
import { bind } from 'decko';

class MarkdownEditor extends Component {
  constructor() {
    super()
    this.state = { text: '' };
  }
  @bind
  onEdit(text) {
    this.setState({ text });
  }
  render() {
    return (
      <main>
        <section>
          <label>Markdown</label>
          <hr />
          <Editor onEdit={this.onEdit} />
        </section>
        <section>
          <label>Preview</label>
          <hr />
          <Markdown text={this.state.text} />
        </section>
      </main>
    );
  }
}

我们使用构造函数来初始化此组件的状态。 然后,我们创建一个onEdit方法,用于基于参数更新状态。 您可能还会注意到,我们在这里使用了@bind装饰器。

这个装饰器来自一个叫做Decko (不是Deku!)的库,我们正在使用它来确保onEdit方法具有正确的this值,即使从组件外部调用它也是如此。

最后,我们将this.state.text作为道具传递给我们的<Markdown />组件。 每次调用onEdit回调时,我们都会更新状态,组件将重新呈现。

虚拟DOM

与React,Deku和Preact不同,Virtual-DOM不假设您如何管理状态或虚拟节点在何处接收其数据。 这意味着我们将不得不做一些额外的工作来进行设置。

值得庆幸的是,Redux足够不受限制,因此我们也可以在这里使用它。 实际上,我们可以从Deku示例中借用创建商店的代码。

import { createStore } from 'redux';

const store = createStore((state = initState, action) => {
  switch (action.type) {
    case 'UPDATE_TEXT':
      return {
        text: action.payload
      };
    default:
      return state;
  }
});

与其将商店的调度功能传递给我们的组件,不如直接从动作创建者那里引用它。

const actions = {
  updateText(text) {
    store.dispatch({
      type: 'UPDATE_TEXT',
      payload: text
    });
  }
}

这可能比我们其他动作创建者更简单,但由于它们对Redux商店有着不可理解的依赖性,因此使他们更加难以隔离和测试。

我们将初始状态传递给组件进行第一次渲染。

let tree = <MarkdownEditor state={store.getState()} />;
let root = create(tree);

document
  .getElementById('app')
  .appendChild(root);

然后,我们将使用订阅机制来监听状态更改。

import { diff, patch } from 'virtual-dom';

store.subscribe(function() {
  let newTree = <MarkdownEditor state={store.getState()} />;
  let patches = diff(tree, newTree);
  root = patch(root, patches);
  tree = newTree;
});

我们不是手动渲染新树,而是手动执行diff,然后使用返回的补丁集应用必要的最小数量的更改,以使渲染的DOM节点反映newTree的虚拟DOM节点。

最后,我们覆盖我们的旧树,为下一个渲染做好准备。

演示版

我们将这些组件放在一起,并为每个框架创建了一个简单的分屏实时Markdown编辑器。 您可以在Codepen上查看代码并与完成的编辑器一起玩。

尺寸

当我们开发旨在用于台式机和移动设备的轻型应用程序时,选择视图层时,必须从服务器传输的数据量是一个重要因素。

在每种情况下,我们都将创建一个包含应用程序代码和依赖项的缩小包,以进行比较。

4.React

  • 代码行数 :61
  • 依赖关系reactreact-dommarked
  • 捆绑包大小 :154.1kb
  • 压缩后 :45.3kb

根据React团队的建议,我们使用的是React的预构建生产版本,而不是自己最小化。 独立版本的Marked缩小版本约为17kb。 最小版本的React和ReactDOM的总时钟频率约为136kb。

3.德库

  • 代码行数 :80
  • 依赖项dekureduxmarked
  • 捆绑包大小 :51.2kb
  • 压缩后 :15.3kb

我们的Deku捆绑软件已经比React轻100kb,并且我们还包括了Redux形式的功能完善的状态管理器。 Redux和Marked的总重约为30kb。 将我们的应用程序代码和对Deku的依赖保留为〜21kb。

2.虚拟DOM

  • 代码行数 :85
  • 依赖项virtual-domvirtual-dom-utilredux ,已marked
  • 捆绑包大小 :50.5kb
  • 压缩后 :15.2kb

尽管具有最低限度的低级性质,但我们的Virtual-DOM捆绑包的重量约为50kb(与Deku大致相同)。 同样,Redux和Marked负责该大小的〜30kb。 虚拟域软件包和负责约20kb的应用程序代码一起。

1.预言

  • 代码行数 :62
  • 依赖关系preactdeckomarked
  • 捆绑包大小 :30.6kb
  • 压缩后 :10.5kb

忠实于其目的,我们的Preact捆绑包令人印象深刻,达到30.6kb。 Decko和Marked共同负责其中的〜19kb,而Preact和我们的应用程序代码仅占11kb。

性能

对于移动网络,我们应该同样意识到并非所有的移动设备处理器都是相同的。 我们将看一下我们的应用程序将其第一帧显示到屏幕上的速度。

4.React

React时间线

浏览器在30毫秒左右开始评估JavaScript。 然后,重新计算的风格,回流和更新,以层树后,我们在173.6ms得到油漆事件,那么层复合,最后在183ms浏览器中的第一帧土地。 因此,我们正在考虑大约150ms的周转时间。

3.德库

德库时间轴

浏览器大约在55毫秒左右开始评估JavaScript。 然后,我们看到相同的样式重新计算,重排和图层树更新,然后在111ms处看到绘画事件,图层被合成并且第一帧在118ms着陆 。 Deku将React的周转时间减少了一半以上,将其缩短到大约70ms。

2.预言

预先时间表

我们看到浏览器开始在大约50毫秒处评估脚本,并且绘制事件出现在86.2毫秒处,并且第一帧降落在102毫秒处,周转时间为50毫秒。

1.虚拟DOM

虚拟DOM时间轴

浏览器在32ms时开始评估,绘制事件在80.3ms时着陆(有趣的是,与其他框架相比,浏览器花费多于10倍的时间来合成图层),然后框架在89.9ms着陆 。 周转时间接近60毫秒。 因此,尽管Virtual-DOM具有最快的成帧时间,但它的呈现过程似乎比Preact慢。

当然,我们在这里看到的是微观性能,总体而言,所有这些库都非常快(对于此应用程序而言)。 他们都在200毫秒内在屏幕上显示了第一帧。

这些测试结果也是在Chromebook而非移动设备上捕获的,因此它们仅用于比较这些库之间的相对性能。

您可以在GitHub上找到这些测试的代码。

结论

React改变了我们开发应用程序的思维方式。 没有React,我们将不会有任何出色的替代方案,并且在生态系统,工具和社区方面,它仍然是无可争议的。

npm上已经有数百个(甚至数千个)React软件包可用,并且一个ReactJS社区组织围绕20多个高质量的开源项目创建,以确保它们得到长期的支持和维护。

React满足了我们在其他库中看到的大多数编程风格。 如果您想将状态转移到Redux之类的商店中并使用无状态组件,React将允许您这样做。 同样,React还支持功能性无状态组件

该库本身已经过实战测试, 大量先进技术公司 (包括Facebook)在生产中使用React,npm软件包每周获得数十万次下载。

但是我们在这里考虑使用React的替代方法。 因此,让我们看看您可能想考虑使用其他库的位置,时间,地点和原因。

德库

如果Redux是您工作流程的重要组成部分,那么您可能想尝试Deku。 它的重量更轻,并且(在我们的案例中)运行速度比React快一点,它采用了一种固执己见的方法,可以削减很多原始功能集。

Deku非常适合希望React实施更多功能风格的程序员。

虚拟DOM

虚拟DOM非常适合构建您自己的抽象。 它提供的开箱即用的工具不足以构造完整的应用程序,并且默认情况下它不支持JSX,但遗憾的是,这些特性使其成为不适合高级抽象的目标的理想选择。React自己。

对于希望使用基于声明的,基于组件的模型而又不用担心被DOM操作弄伤手的语言开发人员来说,虚拟DOM将继续成为他们的理想目标。 例如,它目前作为Elm的一部分使用效果非常好。

事前

精确是这里的惊喜。 它不仅捆绑在最小的应用程序中,而且周转率极低,无法将帧显示在屏幕上。

它是轻量级的,它具有一个很小但正在增长的生态系统,并且越来越多的React软件包可以与Preact一起批量使用。 无论是构建高性能应用程序,还是需要通过低速网络连接传递页面的页面,Preact都是一个值得关注的好项目。

翻译自: https://www.sitepoint.com/react-alternatives-preact-virtualdom-deku/

react 架构方案

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值