首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

被称为“三大框架”替代方案,Svelte 如何简化 Web 开发工作(下)

Web 框架层出不穷,作为主流 Web 框架之一的 Svelte,有着独特的优势。它不仅可以构建完整的 Web 应用程序,还可以创建自定义元素,并在其他框架实现的已有 Web 应用程序中使用。本文将对 Svelte 进行详细的介绍,并带领读者了解使用 Svelte 从头开始构建 Web 应用程序所需的基础知识。您可点击这里回顾该文章上篇。

存储

存储(Stores)在所有组件外部保持应用程序状态。它们是使用props或上下文来使数据在组件中可用的替代方法。

对于应该对所有组件可用的存储,请在src/stores.js之类的文件中定义并导出它们,并在需要时从该文件导入它们。

对于应该仅对给定组件的后代可用的存储,请在这个组件中定义它们,然后使用props或上下文将它们传递给后代。

Svelte提供三种存储。

  1. 可写存储——这是唯一可以由组件修改的存储。
  2. 可读存储——这些存储处理它们自己的数据。
  3. 派生存储——这些存储从其他存储的当前值派生数据。

这些存储都有一个subscribe方法,该方法返回一个可调用的函数来unsubscribe。

也可以创建自定义存储。它们唯一的限制是成为具有正确实现的subscribe方法的对象。示例见

可写存储

要创建可写存储,请调用svelte/store包中定义的writable函数。然后传递初始值,还可以传递一个带有set函数的函数。如果传入了后者,它可以异步确定存储的值。例如,它可以调用REST服务,并将返回的值传递给set。在第一个组件订阅存储之前不会调用此函数。

除了subscribe方法外,可写存储还有以下方法:

  • set(newValue)

这将为商店设置一个新值。

  • update(fn)

这将基于当前值更新存储值。fn是一个传递当前值并返回新值的函数。

下面是仅使用初始值定义可写存储的示例。

代码语言:javascript
复制
// 在stores.js内
import {writable} from 'svelte/store';
 
// 初始值是空数组。
export const dogStore = writable([]);

这是一个使用函数确定值来定义可写存储的示例。

代码语言:javascript
复制
// 在stores.js内
import {writable} from 'svelte/store';
 
export const dogStore = writable(initialValue, async set => {
  // 订阅计数由0到1时调用。
  // 计算初始值并传递给set函数。
  const res = await fetch('/dogs');
  const dogs = await res.json();
  set(dogs);
 
  return () => {
    // 订阅计数归零时调用。
  };
});

可以将表单元素的值绑定到可写存储。当用户更改表单元素值时将更新存储。

代码语言:javascript
复制
<input bind:value={$someStore} />

存储名称上的$前缀接下来会解释。

可读存储

要创建可读存储,请调用svelte/store包中定义的readable函数。

与可写存储一样,这里要为它传递一个初始值,还可以传递一个带有set函数的函数。

例如:

代码语言:javascript
复制
import {readable} from 'svelte/store';
 
export const dogStore = readable(
  [], // 初始值。
  set => {
    const res = await fetch('/dogs');
    const dogs = await res.json();
    set(dogs);
    // 这里可以返回一个清理函数。
  }
);

set函数可以使用setInterval来连续更改值。

使用存储

要开始使用存储,请选择下面一种方式来访问​​它:

  1. 作为prop接受它。
  2. 从上下文中获取它。
  3. 从.js文件导入(适用于全局范围)。

有两种方法从存储中获取值:

  1. 在其上调用subscribe方法(有些冗长)。
  2. 使用自动订阅的捷径(通常是首选)。

下面是使用subscribe方法的示例。

代码语言:javascript
复制
<script>
  import {onDestroy} from 'svelte';
  import {dogStore} from './stores.js';
  let dogs;
  const unsubscribe = dogStore.subscribe(value => (dogs = value));
  onDestroy(unsubscribe);
</script>
 
<!-- 在HTML中使用dogs。 -->

下面是使用自动订阅的示例。

名称以$开头的所有变量都必须放入存储。通过这种方法,组件在首次使用时会自动订阅存储,而被销毁时会自动取消订阅。

代码语言:javascript
复制
<script>
  import {dogStore} from './stores.js';
</script>
 
<!-- 在HTML中使用 $dogStore。 -->

下面是更改可写存储的示例。

订阅存储的组件将看到更改。

代码语言:javascript
复制
<script>
  import {dogStore} from './stores.js';
  import Child from './Child.svelte';
 
  const dog = $dogStore;
 
  function changeDog() {
    // 方法 #1 - 创建新对象
    //dogStore.set({age: 2, breed: 'GSP', name: 'Oscar'});
 
    // 方法 #2 - 调整并复用对象
    dog.age = 2;
    dog.breed = 'GSP';
    dog.name = 'Oscar';
    dogStore.set(dog);
  }
</script>
 
<h1>Store Demo</h1>
<Child />
<button on:click={changeDog}>Change Dog</button>

下面是一个使用HTML中的$引用从存储中获取更改的示例。

代码语言:javascript
复制
<script>
  import {dogStore} from './stores.js';
</script>
 
<div>
  {$dogStore.name} is a {$dogStore.breed} that is {$dogStore.age} years old.
</div>

下面的代码效果同上,但是使用JavaScript代码从存储中获取数据。

代码语言:javascript
复制
<script>
  import {dogStore} from './stores.js';
 
  // 这里需要Parens才能知道开放的大括号不是块的开头。
  $: ({age, breed, name} = $dogStore);
</script>
<div>{name} is a {breed} that is {age} years old.</div>

模块上下文(module context)

想要只在组件源文件中运行一次JavaScript代码,而不是为创建的每个组件实例都运行一次代码,请将代码包含在指定模块上下文的script标记中。

代码语言:javascript
复制
<script context="module">
  ...
</script>

如果script标记未指定其上下文,则它为实例上下文

两种script标记(实例和模块上下文)都可以出现在组件源文件中。

两种上下文中都可以导出值。无法指定默认导出,因为组件本身会自动成为默认导出。

模块上下文可以声明变量并定义函数。这些可以在组件所有实例的实例上下文中访问,但它们不是响应式的。组件更改时不会重新渲染。这样就能在所有实例之间共享数据。

实例上下文变量和函数在模块上下文中不可访问。

请注意,不需要将不访问组件状态的函数移至模块上下文,因为(根据Svelte API文档)“ Svelte将从组件定义中提升所有不依赖本地状态的函数。” 但将函数放在模块上下文中的一个目的是从外部导出和调用它们。

批处理DOM更新

可以更改顶级组件变量的值来使组件状态无效。

根据Svelte文档,“当你使Svelte中的组件状态无效时,它不会立即更新DOM。相反,它会等到下一个微任务才查看是否需要应用其他任何更改(包括在其他组件中)。这样做避免了不必要的工作,并使浏览器可以更有效地对事物进行批处理。”

tick函数“返回一个promise,该promise将在任何未决状态更改应用于DOM时立即解析(如果没有未决状态更改,则立即解析)。”

应用DOM更新后,可以使用此方法进行其他状态更改。

代码语言:javascript
复制
<script>
  import {tick} from 'svelte';
  ...
  // 做一些状态更改。
 
  // 下面的内容预防tick调用后的批量更新。
 await tick();
 
  // DOM更新后做更多状态更改。
  ...
</script>

调用await tick()在测试中也很有用,可以在测试效果之前等待更改被处理。

动画

Svelte提供了许多功能用来轻松将动画添加到元素。以下是它提供的一些函数和过渡效果值。

  • svelte/animate包提供了flip函数。
  • svelte/motion包提供spring和tweened函数。
  • svelte/transition包提供了crossfade函数及过渡值draw(用于SVG元素)、fade、fly、scale和slide。
  • 另请参见svelte/easing包,这个包提供了控制动画随时间变化的速率的缓动函数。

下面是一个基本的动画示例,其中有一个列表项在装载时淡入并在销毁时淡出。

代码语言:javascript
复制
<script>
  import {fade} from 'svelte/transition';
</script>
 
<li transition:fade>
  <!-- 一些内容 -->
</li>

可以创建自定义动画。示例见

组件可以侦听事件,以了解过渡何时开始和结束。需要侦听的事件有:

  • introstart和introend
  • outrostart和outroend

特殊元素

Svelte支持几种特殊元素,其形式为<svelte:name props>。总结如下。

代码语言:javascript
复制
<svelte:component this = {expression} optionalProps>

它将渲染expression指定的组件。如果expression是虚值则不渲染任何内容。可选的props会被传递到要渲染的组件。

代码语言:javascript
复制
<svelte:self props>

它允许组件渲染其自身的实例。它支持递归组件,这是必需的,因为组件无法导入自身。

代码语言:javascript
复制
<svelte:window on:eventName={handler}>

它将注册一个由DOM window对象调度给定事件时要调用的函数。resize事件就是一个例子。

代码语言:javascript
复制
<svelte:window bind:propertyName={variable}>

它会将变量绑定到window属性。一个例子是innerWidth。

代码语言:javascript
复制
<svelte:body on:eventName={handler}>

当DOM body元素调度给定事件时,此方法注册一个要调用的函数。例子包括mouseEnter和mouseLeave。

代码语言:javascript
复制
<svelte:head>elements</svelte:head>

它会将元素插入DOM文档的head元素中。例子包括插入link和script标记。

代码语言:javascript
复制
<svelte:options option={value} />

它位于.svelte文件的顶部,而不是script标记内部。它指定了编译器选项,包括:

代码语言:javascript
复制
immutable

它意味着props将被视为不可变的,从而提供了优化。

默认值为false。不可变意味着父组件将为对象props创建新对象,而不是修改现有对象的属性。这使Svelte可以通过对比对象引用(而不是对象属性)来确定prop是否已更改。

当此选项设置为true时,如果父组件修改了子组件的对象属性,则子组件将不会检测到更改并且不会重新渲染。

代码语言:javascript
复制
accessors

它为组件props添加了getter和setter方法。默认为false。将Svelte组件编译为非Svelte应用程序中使用的自定义元素时,这个方法很有用。

代码语言:javascript
复制
namespace="value"

它指定了组件的命名空间。一种用途是为SVG组件指定命名空间svg。

代码语言:javascript
复制
tag="value"

它指定将Svelte组件编译为自定义元素时要使用的名称。它允许Svelte组件用作非Svelte应用程序中的自定义元素。

调试

当给定变量更改时,使用@debug中断,并在devtools控制台中输出它们的值。

将其放置在HTML部分的顶部,不要放在script标记内部。

例如:

代码语言:javascript
复制
{@debug var1, var2, var3}

被监视的变量可以具有任何类型的值,包括对象。

要在所有状态更改时中断,请省略变量名称。

代码语言:javascript
复制
{@debug}

ESLINT

ESLint称自己为“针对JavaScript和JSX的可插入式linting实用程序”。它可以报告许多语法错误和潜在的运行时错误。它还可以报告与指定编码准则的差异。

要在Svelte项目中安装ESLint所需的全部内容,请输入npm install -D name,其中name为:

  • eslint
  • eslint-plugin-svelte3

创建具有以下内容的.eslintrc.json文件:

代码语言:javascript
复制
{
  "env": {
    "browser": true,
    "es6": true,
    "jest": true
  },
  "extends": ["eslint:recommended", "plugin:import/recommended"],
  "globals": {
    "cy": "readonly"
  },
  "overrides": [
    {
      "files": ["**/*.svelte"],
      "processor": ["svelte3/svelte3"]
    }
  ],
  "parserOptions": {
    "ecmaVersion": 2019,
    "sourceType": "module"
  },
  "plugins": ["svelte3"],
  "rules": {
    "no-console": "off"
   }
}

将以下npm脚本添加到package.json:

代码语言:javascript
复制
"lint": "eslint --fix --quiet src --ext .js,.svelte",

要运行ESLint,请输入npm run lint。

有关针对Svelte的ESLint选项的更多信息,请参见文档

Prettier

Prettier称自己为“经过优化的JavaScript格式化程序”。它支持多种语言和语言功能,包括ES2017、TypeScript、JSON、HTML、CSS、LESS、SCSS、JSX、Vue和Markdown。

要在Svelte项目中安装Prettier所需的全部内容,请输入npm install -D name,其中name为:

  • prettier
  • prettier-plugin-svelte

Svelte ESLint插件强制按scriptstyle和HTML的顺序执行。

将以下npm脚本添加到package.json:

代码语言:javascript
复制
"format": "prettier --write '{public,src}/**/*.{css,html,js,svelte}'",

要运行Prettier,请输入npm run format。

ToDo应用

下面来看一个简单的Todo应用程序的实现,正好过一遍最重要的那些Svelte概念。代码链接在GitHub上。

要添加新的待办事项时,需要在输入框中输入待办事项的文本,然后按“添加”按钮或Enter键。

要将待办事项在已完成和未完成的状态之间切换,需要单击其左侧的复选框。请注意,顶部附近的“remaining”文本显示当前未检查的待办事项数和待办事项总数。

要删除待办事项,需要单击其右侧的“Delete”按钮。

要存档所有已检查的待办事项,需要单击“Archive Completed”按钮。但这个版本的应用并不会真的存储它们,其实它们都被删掉了。

下面是文件src/main.js,它在文档主体中渲染TodoList组件来启动应用程序。

代码语言:javascript
复制
import TodoList from './TodoList.svelte';
 
const app = new TodoList({target: document.body});
 
export default app;

下面是文件src/Todo.svelte中Todo组件的代码。

它是一个包含以下内容的列表项:

  • 待办事项文本
  • 一个复选框
  • 一个“Delete”按钮

它需要一个名为“todo”的prop来保存待办事项的文本。

切换复选框后,它将调度一个“toggleDone”事件。

按下“Delete”按钮时,它将调度一个“删除”事件。

代码语言:javascript
复制
<script>
  import {createEventDispatcher} from 'svelte';
  const dispatch = createEventDispatcher();
  export let todo; // 唯一的prop
</script>
 
<style>
  /* 在todo的文本上画一条线标记其完成状态。 */
  .done-true {
    color: gray;
    text-decoration: line-through;
  }
  li {
    margin-top: 5px;
  }
</style>
 
<li>
  <input
    type="checkbox"
    checked={todo.done}
    on:change={() => dispatch('toggleDone')}
  />
  <span class={'done-' + todo.done}>{todo.text}</span>
  <button on:click={() => dispatch('delete')}>Delete</button>
</li>

下面是文件src/TodoList.svelte中TodoList组件的代码。

看到这里,关于Svelte你已经很熟悉了,所以这些代码应该很容易看懂。

代码语言:javascript
复制
<script>
  import Todo from './Todo.svelte';
 
  let lastId = 0;
 
  // 创建一个todo对象。
  const createTodo = (text, done = false) => ({id: ++lastId, text, done});
 
  let todoText = '';
 
  // 应用程序初始有两个todo项目。
  let todos = [
    createTodo('learn Svelte', true),
    createTodo('build a Svelte app')
  ];
 
  let uncompletedCount = 0;
 
  // 这是"响应式声明"。
  // 它保证未完成的代码在todo数组修改时被更新。
  $: uncompletedCount = todos.filter(t => !t.done).length;
 
  // 这是另一个"响应式声明"。
  // 保证当uncompletedCount或todo数组更改时状态随时更新
  $: status = `${uncompletedCount} of ${todos.length} remaining`;
 
  // 创建并添加一个新的todo.
  function addTodo() {
    // 回想这里为何必须使用concat代替push。
    todos = todos.concat(createTodo(todoText));
    todoText = ''; // 清空input
  }
 
  // 删除全部标记为完成的todo。
  const archiveCompleted = () => (todos = todos.filter(t => !t.done));
 
  // 删除特定todo。
  const deleteTodo = todoId => (todos = todos.filter(t => t.id !== todoId));
 
  // 改变给定todo的状态。
  function toggleDone(todo) {
    const {id} = todo;
    todos = todos.map(t => (t.id === id ? {...t, done: !t.done} : t));
  }
</script>
 
<style>
  button {
    margin-left: 10px;
  }
 
  /* 从加点(·)列表中移除点。 */
  ul.unstyled {
    list-style: none;
    margin-left: 0;
    padding-left: 0;
  }
</style>
 
<div>
  <h2>To Do List</h2>
  <div>
    {status}
    <button on:click={archiveCompleted}>Archive Completed</button>
  </div>
  <br />
  <!-- 我们不想真的提交表单。
       使用表单使得按下enter键时触发"Add"按钮。 -->
  <form on:submit|preventDefault>
    <input
      type="text"
      size="30"
      autofocus
      placeholder="enter new todo here"
      bind:value={todoText}
    />
    <button disabled={!todoText} on:click={addTodo}>
      Add
    </button>
  </form>
  <ul class="unstyled">
    {#each todos as todo}
      <Todo
        todo={todo}
        on:delete={() => deleteTodo(todo.id)}
        on:toggleDone={() => toggleDone(todo)}
      />
    {/each}
  </ul>
</div>

单元测试

Svelte组件的单元测试可以使用Jest实现。另外建议使用“Svelte测试库”。它与Jest协作,可以简化Svelte组件的单元测试编写。

本文不会深入探究这些测试工具的细节,但下面提供了测试代码示例。要了解这些工具的更多信息,请访问https://jestjs.io/https://testing-library.com/

要安装所需的全部内容,请输入npm install -D name,其中name为:

  • @babel/core
  • @babel/preset-env
  • @testing-library/svelte
  • babel-jest
  • jest
  • jest-transform-svelte

使用以下内容创建文件babel.config.js:

代码语言:javascript
复制
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          node: 'current'
        }
      }
    ]
  ]
};

如果未按上面所示设置targets.node,则在运行测试时将显示错误消息“regenerator-runtime not found”。

使用以下内容创建文件jest.config.js:

代码语言:javascript
复制
module.exports = {
  transform: {
    '^.+  .js$': 'babel-jest',
    '^.+  .svelte$': 'jest-transform-svelte'
  },
  moduleFileExtensions: ['js', 'svelte'],
  bail: false,
  verbose: true
};

将bail设置为false意味着Jest在某个测试失败时不应退出测试套件。

将verbose设置为true会使Jest显示每个测试的结果,而不只是各个测试套件的结果摘要。

将以下npm脚本添加到package.json:

代码语言:javascript
复制
"test": "jest --watch src",

要运行单元测试,请输入npm test。

以下是用于测试文件src/Todo.spec.js中Todo组件的代码:

代码语言:javascript
复制
import {cleanup, render} from '@testing-library/svelte';
 
import Todo from './Todo.svelte';
 
describe('Todo', () => {
  const text = 'buy milk';
  const todo = {text};
 
  // 卸载之前测试中挂载的所有组件。
  afterEach(cleanup);
 
  test('should render', () => {
    const {getByText} = render(Todo, {props: {todo}});
    const checkbox = document.querySelector('input[type="checkbox"]');
    expect(checkbox).not.toBeNull(); // 找到复选框
    expect(getByText(text)); // 找到todo文本
    expect(getByText('Delete')); // 找到Delete按钮
  });
 
  // 测试事件在checkbox状态更改或"Delete"按钮按下时是否fired没有捷径。  
  // 它们由TodoList.spec.js的测试覆盖。
});

以下是用于测试文件src/TodoList.spec.js中TodoList组件的代码:

代码语言:javascript
复制
import {tick} from 'svelte';
import {cleanup, fireEvent, render, wait} from '@testing-library/svelte';
 
import TodoList from './TodoList.svelte';
 
describe('TodoList', () => {
  const PREDEFINED_TODOS = 2;
 
  afterEach(cleanup);
 
  // 它被下面的很多测试函数使用。
  function expectTodoCount(count) {
    return wait(() => {
      // 每个todo有一个<li>根元素。
      const lis = document.querySelectorAll('li');
      expect(lis.length).toBe(count);
    });
  }
 
  test('should render', async () => {
    const {getByText} = render(TodoList);
    expect(getByText('To Do List'));
    expect(getByText('1 of 2 remaining'));
    expect(getByText('Archive Completed')); // 按钮
    await expectTodoCount(PREDEFINED_TODOS);
  });
 
  test('should add a todo', async () => {
    const {getByTestId, getByText} = render(TodoList);
 
    const input = getByTestId('todo-input');
    const value = 'buy milk';
    fireEvent.input(input, {target: {value}});
    fireEvent.click(getByText('Add'));
 
    await expectTodoCount(PREDEFINED_TODOS + 1);
    expect(getByText(value));
  });
 
  test('should archive completed', async () => {
    const {getByText} = render(TodoList);
    fireEvent.click(getByText('Archive Completed'));
    await expectTodoCount(PREDEFINED_TODOS - 1);
    expect(getByText('1 of 1 remaining'));
  });
 
  test('should delete a todo', async () => {
    const {getAllByText, getByText} = render(TodoList);
    const text = 'learn Svelte'; // 第一个todo
    expect(getByText(text));
 
    const deleteBtns = getAllByText('Delete');
    fireEvent.click(deleteBtns[0]); // 删除第一个todo
    await expectTodoCount(PREDEFINED_TODOS - 1);
  });
 
  test('should toggle a todo', async () => {
    const {container, getByText} = render(TodoList);
    const checkboxes = container.querySelectorAll('input[type="checkbox"]');
 
    fireEvent.click(checkboxes[1]); // 第二个todo
    await tick();
    expect(getByText('0 of 2 remaining'));
 
    fireEvent.click(checkboxes[0]); // 第一个todo
    await tick();
    expect(getByText('1 of 2 remaining'));
  });
});

端到端测试

可以使用Cypress来实现Svelte应用程序的端到端测试。本文不会深入探究Cypress的细节,但是下面提供了测试代码示例。

要了解有关Cypress的更多信息,请访问https://www.cypress.io/

要安装Cypress,请输入npm install -D cypress。

将以下npm脚本添加到package.json:

代码语言:javascript
复制
"cy:open": "cypress open",
"cy:run": "cypress run",

要以交互方式启动Cypress测试工具,请输入npm run cy:open。如果尚不存在cypress目录,它还会创建一个带有以下子目录的目录:

代码语言:javascript
复制
fixtures

这个目录可以保存测试使用的数据。数据通常在导入到测试的.json文件中。

代码语言:javascript
复制
integration

你的测试文件在此目录的顶部或子目录中。

代码语言:javascript
复制
plugins

此目录扩展了Cypress的功能。示例请参见https://github.com/bahmutov/cypress-svelte-unit-test。在运行每个规范文件之前,Cypress会自动在该目录的index.js文件中运行代码。

代码语言:javascript
复制
screenshots

这个目录存放屏幕截图,截图通过调用cy.screenshot()生成。这在调试测试时很有用。

代码语言:javascript
复制
support

此处的文件会添加自定义的Cypress命令,使它们在测试中可用。在运行每个规范文件之前,Cypress会自动在该目录的index.js文件中运行代码。

这些目录中装有示例文件,所有示例文件都可以删除。

在cypress/integration目录下创建带有.spec.js扩展名的测试文件。

要运行端到端测试:

  1. 使用npm run dev启动应用程序服务器
  2. 输入npm run cy:open
  3. 按下Cypress工具右上角的“Run all specs”按钮

这将打开一个浏览器窗口,在其中运行所有测试。

完成测试后,关闭此浏览器窗口和Cypress工具。

以下是文件cypress/integration/TodoList.spec.js中Todo应用程序的端到端测试代码。

代码语言:javascript
复制
const baseUrl = 'http://localhost:5000/';
 
describe('Todo app', () => {
  it('should add todo', () => {
    cy.visit(baseUrl);
    cy.contains('1 of 2 remaining');
    // "Add"按钮应被禁用,直到文本输入后才解除。
    cy.contains('Add')
      .as('addBtn')
      .should('be.disabled');
 
    // 输入todo文本。
    const todoText = 'buy milk';
    cy.get('[data-testid=todo-input]')
      .as('todoInput')
      .type(todoText);
 
    cy.get('@addBtn').should('not.be.disabled');
    cy.get('@addBtn').click();
 
    cy.get('@todoInput').should('have.value', '');
    cy.get('@addBtn').should('be.disabled');
    cy.contains(todoText);
    cy.contains('2 of 3 remaining');
  });
 
  it('should toggle done', () => {
    cy.visit(baseUrl);
    cy.contains('1 of 2 remaining');
 
    // 找到第一个checkbox并打勾。
    cy.get('input[type=checkbox]')
      .first()
      .as('cb1')
      .click();
    cy.contains('2 of 2 remaining');
 
    // 转换同一个checkbox。
    cy.get('@cb1').check();
    cy.contains('1 of 2 remaining');
  });
 
  it('should delete todo', () => {
    cy.visit(baseUrl);
    cy.contains('1 of 2 remaining');
 
    const todoText = 'learn Svelte'; // 第一个todo
    cy.contains('ul', todoText);
 
    // 点击第一个"Delete"按钮。
    cy.contains('Delete').click();
    cy.contains('ul', todoText).should('not.exist');
    cy.contains('1 of 1 remaining');
  });
 
  it('should archive completed', () => {
    cy.visit(baseUrl);
 
    const todoText = 'learn Svelte'; // 第一个todo
    cy.contains('ul', todoText);
 
    // 点击"Archive Completed"按钮。
    cy.contains('Archive Completed').click();
    cy.contains('ul', todoText).should('not.exist');
    cy.contains('1 of 1 remaining');
  });
});

要重新运行测试,请单击浏览器窗口顶部附近的圆形箭头按钮。

为了更好地调试,请在应用程序代码中添加console.log调用,然后在运行测试的浏览器窗口中打开devtools控制台。

将更改保存到应用程序源文件或测试文件时,测试将自动重新运行。

要以命令行模式启动Cypress测试工具,请输入npm run cy:run。这将在终端窗口中输出测试结果,记录测试运行的视频,并输出视频的文件路径。双击视频文件即可观看。

相关工具

以下是推荐读者研究的Svelte相关工具。

这是“由Svelte支持的应用程序框架”。Sapper是一名士兵,负责诸如修建和维修道路和桥梁,铺设和清除地雷等任务。Sapper类似Next和Gatsby。它提供路由、服务端渲染和代码拆分功能。

这是一个社区推动的项目,支持使用Svelte实现原生移动应用程序。它基于nativescript-vue。

这是Three.js 3D图形库的Svelte版本,正在开发中。

总结

到这里就结束了!Svelte是当前流行的React、Vue和Angular框架的很好的替代品。它有许多好处,包括较小的包体积、简单的组件定义、方便的状态管理以及无需虚拟DOM的反应性。

非常感谢Charles Sharp和Kristin Kroeger审阅本文!

原文链接https://objectcomputing.com/resources/publications/sett/july-2019-web-dev-simplified-with-svelte

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/YCKvxXX6DzpvuP3FQ06k
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券