React vs Svelte

379次阅读  |  发布于2年以前

JavaScript 前端开发框架中,Svelte 算是一个新来的搅局者,在网上我们已经听到很多关于 Svelte 的哔哔。因此我决定试试这个家伙,顺便跟 React 做个简单的比较。

本文将展示 SvelteReact 在构建一个基础应用的差异,其中涉及到的内容包括:

还有很多其他方面的内容需要讨论,例如 按需渲染 和 生命周期 等其他炫酷的概念。限于篇幅,这篇文章还是聚焦在基础使用上吧。

「准备工作」

在继续往下阅读之前,你应该准备好如下环境:

「Svelte 与 React」

Svelte 和 React.js 两者都是基于组件的 JavaScript 框架,主要用于 Web 应用的开发。最主要的区别是 Svelte 没有使用虚拟 DOM。Svelte 在构建的时候就将代码编译成 Vanilla JS 代码,而 React 在运行时解释代码。

Svelte 文档写道:

‎Svelte 是一种全新的构建 Web 应用的方法。诸如 React 和 Vue 这类传统的框架,它们的大部分工作都在浏览器上执行,而 Svelte 在构建应用的过程做就了大量的工作。

‎Svelte 没有使用虚拟 DOM 技术,而是当应用状态发生变化时,通过代码如手术般的更新 DOM。‎

酷!但是这些底层的细节对我来说并不重要。我只想从开发人员的角度看看,在使用 Svelte 和 React 开发应用程序时,感觉好吗?有趣吗?直观吗?

开工!

「创建应用脚手架」

在这篇文章中,我们将创建一个很小的 Web 应用,产品经理给这个应用确定了如下需求:

首先使用如下命令在你电脑上创建一个新的目录,暂且命名为 svelte-react:

mkdir svelte-react
cd svelte-react

接着分别创建 Svelte 和 React 的应用模板并运行。这里 Svelte 的初始步骤比 React 多了一步,此外 Svelte 默认端口是 5000 ,而 React 是 3000 。

「Svelte」

打开终端窗口,运行如下命令:

npx degit sveltejs/template svelte-test
cd svelte-test
npm install
npm run dev

「React」

打开第二个终端窗口,进入刚建好的 svelte-react 目录,运行命令:

npx create-react-app react-test
cd svelte-react
npm start

你会发现 Svelte 的命令运行快得多,因为你不是真正在运行一个工具,而是克隆一个项目模板。

「构建应用组件」

运行完上述命令后,你会注意到 Svelte 和 React 各自生成很多很多的文件,感兴趣的话,可以随便浏览看看这些生成的文件。

不管是 Svelte 和 React ,都是把组件源码放到 src 文件夹下,Svelte 项目主要是一些扩展名为 svelte 的文件,而 React 项目则是一些 .js 的文件。

两个项目都有一个 App 组件,分别是 App.svelte 和 App.js 。用你喜好的编辑器分别打开这两个文件,清空它们,我们从头开始。

「组件结构」

「Svelte」

和 React 组件不同的是,Svelte 的代码更像是以前我们在写 HTML、CSS 和 JavaScript 一样。

所有的 JavaScript 代码都位于 Svelte 文件顶部的 <script></script> 标签当中。

然后是 HTML 代码,你还可以在 <style></style> 标签中编写样式代码。有趣的是,组件中的样式代码只对当前组件有效。这意味着在组件中为

标签编写的样式不会影响到其他组件中的

元素。

接下来我们开始编写 App.svelte,首先删空文件内容,然后添加一个空的 <script> 标签:

<script>
</script>

我们将在这个标签中编写大部分组件代码。

「React」

在 React 项目中,打开 App.js 文件,清空所有内容,然后添加如下代码:

function App() {

}

export default App;

这几行代码创建并输出了一个最基础的函数式组件,名为 App() 。注意到这里还有另外一个不同之处就是 —— Svelte 无需输出组件。

「Imports」

前面我们介绍过这个应用包含三个组件:App, Heading和Button。不管是 Svelte 还是 React ,Heading 和 Button 组件都被引入到 App 中,这样就可以被当成 App 的子组件使用。我们将在后面继续编写这三个组件的代码,但现在你只需要知道,构建 App 组件时需要引入其他两个组件。

「Svelte」

Svelte 需要在 <script> 使用 import 语句进行组件引入,编辑 App.svelte 文件添加两个 import 语句:

<script>
  import Button from './Button.svelte';
  import Heading from './Heading.svelte';
</script>

「React」

React 的 import 语句位于文件的顶部,置于所有的函数或者类定义之前。在 App.js 最顶部,App() 函数之前,添加如下代码:

import Heading from './Heading.js';
import Button from './Button.js';
import { useState } from 'react';

在这里,React 同时引入了 userState 钩子,因为 App 是一个有状态的组件。而 Svelte 不需要这个东西。

「状态初始化」

App 是一个有状态的组件,它有两个状态值分别是 color 和 count。

color 表示按钮的颜色,这个值作为一个属性传递给 Button 组件,并且它在每次点击按钮的时候改变。其初始值是 #000000,即为黑色。

count 代表按钮点击的次数,其初始值为 0。

「Svelte」

在 Svelte 中,状态等同于变量赋值,在 import 语句下方,<script> 标签之前添加如下状态定义:

let count = 0;
let color = '#000000';

Svelte 同时提供了名为”反应式声明“ 的概念,用来重新计算状态值,你不一定必须用这个,但如果状态值依赖于其他可能更改的状态,这时候就很方便。

需要注意的是在 Svelte 中是通过状态变量的赋值来实现 DOM 更新的。如果状态包含数组或者对象,当对数组使用类似 .push() 方法并不会触发 DOM 更新。Svelte 提供了一个详细文档来介绍这个问题。

「React」

现在已经引入了 useState 钩子,所以只需要让它工作起来即可。

在 App.js 的 App() 函数中添加如下状态声明:

const [count, setCount] = useState(0);
const [color, setColor] = useState('#000000');

上述代码创建一个名为 count 的状态变量,其初始值为 0,以及一个用来更新值的函数名为 setCount()。同样的,React 创建了另一个状态变量 color 初始值为 #000000 以及名为 setColor() 的更新函数。从这点来看,Svelte 的状态初始化方法要简单易懂得多。

「组件渲染和属性传递」

两个项目我们都是要创建一个由 <main> 元素构建的用户界面,该元素包含两个嵌套的组件 Heading 和 Button。

App 组件传递属性给两个子组件。Heading 组件接收 count 状态值,Button 组件接收 color 状态值,此外还有一个名为 handleClick() 的事件处理函数。

「Svelte」

Svelte 使用它自己的模板语言来创建用户界面,而 React 使用 JSX 。Svelte 模板语言跟写 HTML 没什么两样。接下来只需在 <script> 标签结束后开始编写。

拷贝如下

部分代码到 App.svelte 文件中,形如:

<script>
  ...
</script>

<main>
  <Heading count={count} />
  <Button color={color} handleClick={handleClick} />
</main>

「React」

回到 App.js, 将如下代码拷贝到你的 App() 函数中状态声明部分的下方:

return (
  <main>
    <Heading count={count} />     
    <Button color={color} handleClick={handleClick} />
  </main>
)

该代码从 App() 函数中返回 UI 界面的 JSX。

这里 Svelte 和 React 的做法都很类似,属性的传递也几乎相同。而 Svelte 的模板看起来跟 React 的 JSX 很像。

如果你是一个对 Svelte 充满好奇的 React 开发人员,在属性传递上 Svelte 没有什么新奇之处。而在接收属性时 Svelte 有点点不一样,后面将进行介绍。

「状态向上传递」

为了让这个应用正常工作,每次点击按钮时,必须让 App 组件的 count 状态值增1。因此需要一个机制来将数据从子组件传递给父组件。

前面已经通过将 handleClick() 函数作为属性传递给 Button 组件。

接下来马上要开始编写的这个属于 App 组件的函数。当把它作为属性传递给 Button 子组件,Button 组件就能在每次被点击时调用这个函数。这就是 App 组件能响应其子组件状态变更的原因。

handleClick() 这个函数负责用来更新 App 组件的 count 和 color 状态值。

「Svelte」

在 App.svelte 中编写 handleClick 函数代码如下:

const colors = ['#00ff00', '#ff0000', '#0000ff'];

let handleClick = () => {
  count++;
  color = colors[Math.floor(Math.random() * 3)];
}

「React」

在 App.js 中编写 handleClick 函数代码如下:

const colors = ['#00ff00', '#ff0000', '#0000ff'];

let handleClick = () => {
  setCount(count+1);
  setColor(colors[Math.floor(Math.random() * 3)]);
}

在 React 需要使用早先声明的 setCount() 和 setColor() 方法来更新状态值,而 Svelte 则可以直接更新。

现在我们可以开始编写 Heading 组件了。

「编写 Heading 组件」

Heading 组件显示这个应用的标题以及点击计数器。这不是一个有状态的组件,其接收状态值 count 来显示按钮点击次数。

在 Svelte 项目的 src 文件夹中创建一个名为 Heading.svelte 的文件。

同样的在 React 项目的 src 文件夹中创建新文件 Heading.js.

「接收属性」

「Svelte」

拷贝如下代码到 Heading.svelte 文件:

<script>
  export let count;
</script>

<h1>Hello, I am a Svelte App!</h1>
<h2>The following button has been clicked {count} times.</h2>

请注意看上述代码中 <script> 里的代码。这行代码告诉 Svelte 说,该组件将接收一个名为 count 的属性。

这样就可以在 Heading 组件的 HTML 模板中直接显示 count 这个属性。

这个写法稍微有点点奇怪,但在文件顶部直接声明属性的方式看起来不错,而且可以直接使用这个属性。

「React」

切换到 Heading.js 文件,拷贝如下内容到该文件:

// ConardLi
function Heading({ count }) {
  return (
    <div>
      <h1>Hello, I am a React App!</h1>
      <h2>The following button has been clicked {count} times.</h2>
    </div>
  )
}

export default Heading;

这段代码创建一个新的名为 Heading 函数式组件,该组件有一个参数 { count }, 这是从传递给组件的 props 对象中提取出来的。

「编写 Button 组件」

Button 组件在界面上显示一个按钮,同时接收两个属性,分别是用来定义颜色的 color 和在点击时触发的 handleClick() 函数。

在 Svelte 项目的 src 文件夹中创建新文件 Button.svelte.

在 React 项目的 src 文件夹中创建新文件 Button.js.

「事件侦听」

类似点击和其他鼠标事件等交互式事件的侦听上,Svelte 和 React 的做法有一些不同。

「Svelte」

拷贝如下代码到 Button.svelte:

// ConardLi
<script>
  export let handleClick;
  export let color;
</script>

<button style="--color: {color}" on:click={handleClick}>
  Click me!
</button>

上述代码中两个属性都是在顶部的 <script>标签中定义的。

然后它创建了一个按钮。请注意第 6 行代码的语法,忽略掉下一节要介绍的样式部分,直接看按钮点击的事件侦听器,它跟以往使用的习惯不同。

Svelte 使用一个 on: 指令来给 DOM 元素添加事件侦听器。Svelte 使用非常简洁方法进行事件修改,甚至可以只在按钮首次点击时触发。更详细的关键事件的触发请阅读 dispatch your own component events 这篇文档。

「React」

拷贝如下代码到 Button.js:

function Button({ color, handleClick }) {
  return (
    <button style={styles}  onClick={handleClick}>
      Click me!
    </button>
  )
}

export default Button;

如果服务依然运行中,将会看到这里有报错信息,别担心,下面我们将通过添加 styles 对象来可以解决这个问题。

上述代码创建一个名为 Button() 的函数式组件,同时接收一个参数 props, 参数包含两个属性 color 和 handleClick。handleClick() 函数在 handleClick 属性上定义,可以在 JSX 上使用一个标准的 onClick 事件来触发。

「动态样式」

在这个应用中 Button 组件介绍一个颜色值作为属性,该颜色值就是按钮的背景色。

「Svelte」

Svelte 的动态样式没有我期望的那么直接。

很不幸,不能直接在 <style> 标签中使用属性值。不过可以使用组件的 HTML 作为在 JavaScript 和 CSS 之间通讯的方法。

在 Button 组件 Button.svelte 的 HTML 代码下方增加如下代码:

<style>
  button {
    color: white;
    background-color: var(--color);
  }
</style>

background-color 样式属性不能直接引用 color 属性的值,它引用的是一个名为 color的样式变量,这个样式变量在前面的 HTML 代码中通过 style="--color: {color}" 进行定义。

这个做法有一点点笨拙,但考虑到这个样式仅在组件内有效,我们也是可以接受的。当然了,也可以定义全局样式,具体请阅读 global CSS 这篇文档:https://svelte.dev/docs#style。

「React」

在 React 中可以有很多种方法给组件添加样式。直接在元素上编写样式是最常用的方法。

要在 JSX 中使用内嵌样式,可以使用样式创建一个对象,然后赋值给元素的 style 属性,剩下的部分前面已经实现过了。

在 Button() 函数中的 return 语句前面添加如下代码来创建 styles 对象:

const styles = {
  backgroundColor: color,
  color: '#ffffff'
}

「测试应用」

保存所有文件,如果应用还没有启动,那现在就各自启动服务 ( Svelte : npm run dev, React : npm start)。然后打开浏览器的两个 Tab 分别访问 localhost:5000 和 localhost:3000 。

依次点击两个页面的按钮,看看效果

「Svelte」

图片

「React」

图片

从运行效果来看,Svelte 和 React 似乎在样式上有点不同,但是功能已经完成了。你对这两个框架的感觉怎样呢?

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8