如何安装svelte_如何使用Svelte + Typescript构建Github的文件搜索功能

如何安装svelte

A few days ago, I came upon this tutorial that explains how to build the GitHub file search functionality with React. If you don’t know what that is, try going to any github repo and press ‘t’ to start a file search.

几天前,我遇到了本教程该教程说明了如何使用React构建GitHub文件搜索功能。 如果您不知道这是什么,请尝试转到任何github存储库 ,然后按“ t”开始文件搜索。

This is a nice component for training, not too simple, not too complicated. Here I’ll show you how to build it with Svelte + Typescript instead of React.

这是一个很好的培训组件,不太简单,也不太复杂。 在这里,我将向您展示如何使用Svelte + Typescript而非React构建它。

Here is a live version of the app we’re going to build. Press ‘t’ to try out the file search.

这是我们将要构建的应用程序实时版本 。 按“ t”尝试搜索文件。

项目设置 (Project Setup)

Two options :

两种选择:

If you’d like to learn how to do it manually, follow my Svelte + Typescript + TailwindCSS + SCSS how-to article.

如果您想学习手动操作方法,请阅读我的Svelte + Typescript + TailwindCSS + SCSS操作方法文章

If you just want to get building the component right away, follow the instructions below

如果您只是想立即构建组件,请按照以下说明进行操作

npx degit https://github.com/skflowne/svelte-ts-scss-tailwind-template github-file-search-svelte
cd github-file-search-svelte
yarn

I’m using yarn as package manager but you can use npm if you like

我正在使用yarn作为包管理器,但是如果您愿意,可以使用npm

You can now run yarn dev to run the development server

现在您可以运行yarn dev来运行开发服务器

API数据 (API data)

We need some data to render in our component, I’m using the same data as the original React tutorial.

我们需要一些数据来渲染我们的组件,我正在使用与原始React教程相同的数据。

Download the API data file from here

从此处下载API数据文件

Create a new api folder within your app’s src folder and copy the file’s content into index.js

在应用程序的src文件夹中创建一个新的api文件夹,并将文件内容复制到index.js

Note that everytime I tell you to create a new folder in the rest of the article, it’s going to be within thesrc folder

请注意,每当我告诉您在本文的其余部分中创建一个新文件夹时,该文件夹都将位于src文件夹中

This is how our data looks like :

这就是我们的数据的样子:

We can see our files have an id , a type which can be either file or folder , a name , a commit comment and the file’s last modified_time

我们可以看到我们的文件具有id ,可以是filefoldername ,提交comment以及文件的最后modified_time

Let’s create an interface named File to respresent our data

让我们创建一个名为Fileinterface来表示我们的数据

In your src folder, create an interfaces folder and a new file file.interface.ts

src文件夹中,创建一个interfaces文件夹和一个新文件file.interface.ts

We now have a custom Typescript type to represent our files.

现在,我们有一个自定义的Typescript类型来表示我们的文件。

You might be thinking, why don’t we use a string type for our type property ?

您可能在想,为什么我们不将string类型用作type属性?

This is because we only want to accept either file or folder as values for this property and this will allow Typescript to warn us if we use an invalid value.

这是因为我们只希望将filefolder作为该属性的值,并且如果我们使用无效的值,这将使Typescript发出警告。

Typescript will also provide autocompletion with the valid values which is another benefit of using Typescript.

Typescript还将提供具有有效值的自动补全功能,这是使用Typescript的另一个好处。

This provides an easy way to know what our File data structure should look like directly in our IDE. It will help other people on our project or our future self, who might not remember as much as our present self.

这提供了一种简单的方法,可以直接在IDE中了解我们的File数据结构。 它将帮助我们的项目或未来的自我的其他人,他们可能不记得我们现在的自我。

通过Svelte商店访问我们的文件 (Accessing our files through a Svelte store)

Now that we have our data in api/index.js and our custom type in interfaces/file.interface.ts , let’s create a Svelte store to use in our application.

现在,我们将数据存储在api/index.js并将自定义类型存储在interfaces/file.interface.ts ,让我们创建一个Svelte存储以在我们的应用程序中使用。

Since this is supposed to be data coming from an API, we would never write to it, therefore we will create a readonly store with readable from svelte/store

由于这应该是来自API的数据,因此我们永远不会对其进行写操作,因此我们将创建一个只读存储,并从svelte/store readable

Create a new folder store and create a new files.ts file inside.

创建一个新的文件夹store并在其中创建一个新的files.ts文件。

Notice that readable is of the form readable<T> which allows us to specify the type of our store.

请注意, readable格式为readable<T> ,它使我们可以指定商店的类型。

We don’t set the type on the files variable because files is not a File[] , it’s a store containing a File[] , if you hover over it in VSCode you’ll see its actual type is Readable<File[]> , a readable store containing a File array.

我们没有在files变量上设置类型,因为files不是File[] ,它是一个包含File[]的商店,如果将鼠标悬停在VSCode上,您会看到其实际类型为Readable<File[]> ,一个包含File数组的可读存储。

readable takes two arguments: start and a setter function (set)=> void

readable有两个参数: start和setter函数(set)=> void

Here we provide an empty array as start and provide the setter function which then simply sets our store’s value to the data contained in our /api/index.js file

在这里,我们提供了一个空数组作为start并提供了setter函数,该函数随后将商店的值简单地设置为/api/index.js文件中包含的数据

While this store isn’t very useful since we’re using static data, keep in mind you could use the setter function to make an API call or setup a subscription to a Firebase collection for example.

尽管由于我们使用的是静态数据,所以此存储不是很有用,但是请记住,您可以使用setter函数进行API调用或设置Firebase集合的订阅。

It also helps separate our concerns by having all of our data logic in the store, we don’t have to clutter App.svelte with an import to our File type for example.

通过将所有数据逻辑存储在商店中,它也有助于分离我们的关注点,例如,我们不必将App.svelte杂乱地导入到File类型。

We will do more useful things with stores when we implement the actual file search.

当我们实现实际的文件搜索时,我们将对商店做更多有用的事情。

I’ll be using stores in a “liberal” way to demonstrate how they can make our life easier. However, when building such a component, which should be able to stand alone, you should avoid having dependencies to stores in the subcomponents.

我将以“自由”的方式使用商店来展示它们如何使我们的生活更轻松。 但是,在构建这样的组件(该组件应能够独立运行)时,应避免依赖于子组件中的存储。

Ideally, everything that is in file-search should make up one component to which you can pass whatever data it might need through props, so that if you later decide to use it in another project it wouldn’t complain that the stores don’t exist.

理想情况下, file-search所有内容都应该组成一个组件,您可以通过prop传递它可能需要的任何数据,这样,如果您以后决定在其他项目中使用它,就不会抱怨商店没有存在。

Another option might be to make the stores a part of that component, so that you could still enjoy the benefits without having unmet dependencies.

另一个选择可能是使商店成为该组件的一部分,以便您仍然可以享受好处而不会遇到未满足的依赖关系。

But this is a discussion for another time, I won’t be bothering with it here, just keep in mind that the organisation shown here is not best for reusability.

但这是另一次讨论,在这里我不会打扰,只是要记住,这里显示的组织并不是最佳的可重用性。

构建我们的FileItem组件 (Building our FileItem component)

Let’s import our files store in our root App.svelte component and start building our first component FileItem.svelte

让我们将files存储导入到我们的根App.svelte组件中,然后开始构建我们的第一个组件FileItem.svelte

First, you can clean up App.svelte by removing the style tag entirely as we won’t need it thanks to TailwindCSS, and you can also remove the HTML inside our <main> tag.

首先,您可以通过完全删除样式标签来清理App.svelte ,因为TailwindCSS不需要了它,您也可以删除它,并且还可以删除<main>标签内HTML。

Create a new components folder, inside it create a file-search folder to hold all of our file search related components.

创建一个新的components文件夹,在其中创建一个file-search文件夹,以保存我们所有与文件搜索相关的组件。

In there, create our first component FileItem.svelte

在其中创建我们的第一个组件FileItem.svelte

Notice we’re doing import type to import our File interface because you’ll get an error if you just do import , this is required because the Svelte + Typescript default configuration is using importsNotUsedAsValues is set to “error”, you can read more about this syntax here.

请注意,我们正在执行import type以导入我们的File接口,因为如果您只执行import ,则会出现错误,这是必需的,因为Svelte + Typescript默认配置使用的是importsNotUsedAsValues设置为“ error”,您可以阅读有关的更多信息这种语法在这里

I’m not sure why the configuration is setup like this, you can override it in your tsconfig.json by adding a new compilerOptions object and setting it’s importsNotUsedAsValues to either preserve or remove . Both seem to work fine. If anyone knows why this is setup this way, let me know in the comments please. My best guess is that a type only import will be completely removed and therefore might avoid unnecessary imports. But I would also guess that setting the configuration to remove would have the same effect while still allowing you to use the normal import syntax.

我不知道为什么配置设置这样,你可以覆盖在你的tsconfig.json通过添加新compilerOptions对象和它的设置importsNotUsedAsValues要么preserveremove 。 两者似乎都工作正常。 如果有人知道为什么这样设置,请在评论中告诉我。 我最好的猜测是只导入type将被完全删除,因此可以避免不必要的导入。 但是我也猜想,将配置设置为remove将具有相同的效果,同时仍然允许您使用常规的import语法。

However, since I don’t know why this is setup this way, I’ll leave the configuration as it is.

但是,由于我不知道为什么要这样设置,因此我将保持原样。

Also notice thelang="ts" attribute set on our script tag, this is how we tell the compiler to treat our code as Typescript, you’ll get an error on any Typescript specific syntax if you forget it.

还要注意在script标记上设置的lang="ts"属性,这是我们告诉编译器将代码视为Typescript的方式,如果您忘记了任何特定于Typescript的语法,就会出错。

We’re also defining a component prop of type File with the export keyword. This is how you tell Svelte to expect an external prop.

我们还使用export关键字定义File类型的组件prop。 这是您告诉Svelte期望使用外部道具的方式。

export let myProp: Type

Update our App.svelte to display the first file

更新我们的App.svelte以显示第一个文件

We import our FileItem.svelte component and our files store

我们导入FileItem.svelte组件和files存储

Then we render a FileItem to which we pass our first file by using $files to access the value contained in the store.

然后,我们呈现一个FileItem ,使用$files访问存储中包含的值,将第一个文件传递给该FileItem

Adding the special $ in front of a store tells Svelte that we want to subscribe to the store and access the value inside.

在商店前面添加特殊的$告诉Svelte我们要订阅商店并访问其中的值。

Without the $ in front, you’re accessing the store itself, with the $ you’re telling Svelte to setup an automatic subscription to that store and get back the actual value instead.

如果没有$放在前面,您将访问商店本身,而$告诉Svelte设置对该商店的自动订阅并取回实际值。

This is equivalent to doing

这等效于

Keep in mind that the code above is a simplification, normally you should also manually unsubscribe when your component unmounts.

请记住,上面的代码只是一种简化,通常在卸载组件时也应手动取消订阅。

Let’s display the rest of our file’s informations, display either a file icon or a folder icon depending on our File ‘s type and style our component with TailwindCSS.

让我们显示文件的其余信息,根据Filetype显示文件图标或文件夹图标,并使用TailwindCSS设置组件样式。

We’ll be using svelte-awesome to display fontawesome icons and date-fns to display our modified_time as a distance such as “a month ago”.

我们将使用svelte-awesome ,以显示fontawesome图标和date-fns显示我们modified_time的距离,如“一个月前”。

In the original tutorial, they use moment , I’m using date-fns because it is a much lighter package and it has similar features.

在原始教程中,他们使用moment ,而我使用的是date-fns因为它是一个轻巧得多的软件包,并且具有类似的功能。

At the end, we’ll do a quick comparison of bundle size to the React + Moment.js version.

最后,我们将对捆绑包大小与React + Moment.js版本进行快速比较。

yarn add -D svelte-awesome @fortawesome/free-regular-svg-icons date-fns

Notice we use -D because Svelte is a compiler, it will take care of including the code it needs from the packages in our bundle and we don’t need them as dependencies

注意,我们使用-D是因为Svelte是编译器,它将负责从捆绑包中包含所需的代码,而我们不需要将它们作为dependencies

Update our component to look like this

更新我们的组件,使其看起来像这样

We use TailwindCSS’s classes to style everything without the need to write any CSS. If you’d like to know what those classes do head over to TailwindCSS’s docs.

我们使用TailwindCSS的类来设置所有样式的样式,而无需编写任何CSS。 如果您想知道这些类的作用,请访问TailwindCSS的文档。

We import Icon from svelte-awesome which will allow us to display fontawesome icons provided by the @fortawesome/free-regular-svg-icons by passing them into the data attribute.

我们从svelte-awesome导入Icon ,这将允许我们将@fortawesome/free-regular-svg-icons提供的@fortawesome/free-regular-svg-icons传递到data属性中。

You’ll get an error in IDE on the data attribute because svelte-awesome doesn’t have Typescript types definitions but it will still work.

您会在IDE的data属性上遇到错误,因为svelte-awesome没有Typescript类型定义,但仍然可以使用。

const distance = formatDistanceToNow(parseJSON(file.modified_time))

Finally, we calculate the distance of time between now and our modified_time by first converting our JSON date with parseJSON and passing the resulting Date to formatDistanceFromNow , both methods provided by date-fns

最后,我们计算现在和我们之间的时间距离modified_time首先将我们的JSON日期parseJSON以及将得到的Date ,以formatDistanceFromNow ,提供两种方法date-fns

创建FileList组件 (Creating the FileList component)

Create a new file called FileList.svelte inside the our components/file-search

在我们的components/file-search内创建一个名为FileList.svelte的新文件

We definie afiles prop of type File[]

我们定义File[]类型的files道具

When this array is not empty, we loop over the files with an {#each as file (file.id)} block which allows us to render a FileItem component to which we pass our file as a prop. {file} is a shorthand for file={file} .

当此数组不为空时,我们使用{#each as file (file.id)}{#each as file (file.id)} ,这使我们可以呈现FileItem组件,并将文件作为道具传递给该组件。 {file}file={file}的简写。

(file.id) takes care of providing a unique key for our component, in the same way that you would pass key={file.id} if you were using React or key="file.id" with Vue.js

(file.id)负责为我们的组件提供唯一的密钥,就像在key={file.id}中使用React或key="file.id"时,您将传递key={file.id}

When our array is empty we display a “No matching files” message instead.

如果数组为空,则会显示“无匹配文件”消息。

Modifiy App.svelte to display the FileList component

修改App.svelte以显示FileList组件

使用Svelte商店实现文件搜索 (Implementing the file search using Svelte stores)

We’ll use a store to keep track of what our user enters in the search input and derive the search results from both the user’s input and our files data.

我们将使用商店来跟踪用户在搜索输入中输入的内容,并从用户输入和文件数据中得出搜索结果。

Create a fileSearch.ts file in our store folder

在我们的store文件夹中创建一个fileSearch.ts文件

We simply create a writable store, specify it will hold a string , which will be our user’s input and initialize it to an empty string.

我们只需创建一个可写存储,指定它将包含一个string ,这将是我们用户的输入,并将其初始化为一个空字符串。

Create another file namedfileSearchResults.ts in our store folder for our search results.

store文件夹中为搜索结果创建另一个名为fileSearchResults.ts文件。

We use a derived store to recalculate our search results every time either our fileSearch store or our files store updates.

每当我们的fileSearch存储或files存储更新时,我们都使用派生存储重新计算搜索结果。

derived takes in a store or an array of stores and a callback function.

derived需要一个商店或一系列商店以及一个回调函数。

The callback function will contain an array of our stores values and run everytime one of them changes.

回调函数将包含一个存储值数组,并在其中任何一个更改时运行。

In this case our files never change since we’re reading static data from a file, and therefore, the callback will be called everytime our fileSearch ‘s store gets updated.

在这种情况下,由于我们是从文件中读取静态数据,因此文件永远不会更改,因此,每次我们的fileSearch的存储更新时,都会调用该回调。

Now, imagine that your files weren’t static and the app allowed users to add new files. If you were using Firebase to setup a subscription to a files collection, whenever another user would add a new file, this would trigger the subscription, update our filesstore and also rerun our callback and update our search results for the current user. This would allow our app’s state to always stay consistent even when other users make changes, without needing a page refresh.

现在,假设您的文件不是静态的,并且该应用程序允许用户添加新文件。 如果您使用Firebase设置对files集合的订阅,则每当另一个用户添加新文件时,这将触发订阅,更新files存储并重新运行回调并更新当前用户的搜索结果。 这样,即使其他用户进行了更改,我们的应用程序状态也始终保持一致,而无需刷新页面。

In file search mode, we want to display only files, this is done by first always filtering our files array and only returning files with type “file”.

在文件搜索模式下,我们只想显示文件,这是通过首先始终过滤files数组并仅返回“文件” type文件来完成的。

We check if our search is empty, in that case we want to return everything, otherwise, we want to return only the matching results.

我们检查搜索是否为空,在这种情况下,我们想返回所有内容,否则,我们只想返回匹配的结果。

We take care to make both the file’s name and our search input lowercase to allow for case insensitive search. Otherwise we could be searching for readme and the file named README.md would not match. We then return the file only if its name includes our search .

我们注意使文件名和搜索输入都小写,以允许不区分大小写的搜索。 否则,我们可能正在搜索readme文件,并且名为README.md的文件将不匹配。 然后,仅当文件name包含我们的search我们才返回文件。

FileSearch组件 (The FileSearch component)

Create a FileSearch component inside components/file-search

components/file-search内创建一个FileSearch组件

We import our fileSearch store and setup a two-way binding on our input’s value with bind:value={$fileSearch} .

我们导入fileSearch存储,并使用bind:value={$fileSearch}在输入值上设置双向绑定。

This way, our input’s value will always be set to our store’s value, and whenver the user types in, our store’s value will be automatically updated.

这样,我们的输入值将始终设置为商店的值,并且只要用户输入,我们的商店的值就会自动更新。

Again we bind on $fileSearch and not on fileSearch as we want to bind to the store’s value, not the store itself.

再次,我们绑定到$fileSearch而不绑定到fileSearch因为我们想绑定到商店的值,而不是商店本身。

In order for our input element to be focused immediately when the component appears, we first create an input variable to hold a ref to our input element and we use bind:this={input} on our input to bind it to our variable. You can think of it as “bind this element to theinput variable”.

为了使输入元素在组件出现时立即集中,我们首先创建一个input变量以保存对输入元素的引用,然后在输入上使用bind:this={input}将其绑定到变量。 您可以将其视为“将此元素绑定到input变量”。

We use Svelte’s reactive statements to focus our input whenever input changes and exists. This will happen once our input element has actually rendered.

只要input发生更改并存在,我们就使用Svelte的React式语句来集中输入。 一旦我们的输入元素实际呈现,就会发生这种情况。

Svelte’s reactive statements will update when a variable on the right side of the $: changes.

$:右侧的变量更改时,Svelte的React式语句将更新。

$: if(input){
// ...runs when input changes and exists
}

When we enter search mode, our component will render, set input to the actualHTMLElement in the DOM that now exists, this will trigger our focus code.

当我们进入搜索模式时,我们的组件将呈现,将输入设置为现在存在的DOM中的实际HTMLElement ,这将触发我们的焦点代码。

调试失败错误 (The debug failure error)

Sometimes, Rollup won’t be able to resolve your newly created files and error out :

有时,汇总将无法解析您新创建的文件并出错:

Debug Failure. False expression: Expected fileName to be present in command line

If you get this, just stop your development server and restart it with yarn dev

如果得到了,只需停止开发服务器,然后使用yarn dev重新启动它即可

Also if some changes don’t seem to take effect in your browser, you might need to save the file you were working on again or save App.svelte again.

另外,如果某些更改似乎无法在浏览器中生效,则可能需要再次保存正在处理的文件或再次保存App.svelte

This happened to me a few times, not sure what’s causing it.

这在我身上发生过几次,不确定是什么原因造成的。

设置键盘事件监听器以触发文件搜索模式 (Setting up keyboard event listeners to trigger the file search mode)

We will enter the file search mode when the user presses thet key and allow them to exit when they press theEscape key.

当用户按t键时,我们将进入文件搜索模式,并在按Escape键时允许他们退出。

Modifiy App.svelte

修改应用App.svelte

We use the onMount lifecycle method, which we be called once when our component first mounts.

我们使用onMount生命周期方法,该方法在组件首次装入时被调用一次。

We setup our event listener on the keyup event and pass it our event handler which will be in charge of checking which key has been pressed and set the search mode status accordingly.

我们在keyup事件上设置事件侦听器,并将其传递给事件处理程序,该事件处理程序将负责检查按下了哪个键并相应地设置搜索模式状态。

If it’s t or T we go in search mode by setting searching to true , if it was Escape we set it to false to exit search mode.

如果它的t还是T ,我们在搜索模式去设置searchingtrue ,如果它是Escape ,我们将它设置为false退出搜索模式。

We then update the template to render our file search component only when we’re searching with {#if searching}

然后,我们仅searching使用{#if searching} search {#if searching} searching ,才更新模板以呈现文件搜索组件。

onMount can also return a cleanup function, in this case we use it to make sure that when our component is unmounted, we also remove our event listener.

onMount还可以返回清除函数,在这种情况下,我们将使用它来确保卸载组件时也删除事件监听器。

In this case, App would never get unmounted unless we close the tab.

在这种情况下,除非关闭选项卡,否则App永远不会卸载。

Now, imagine if the component that uses onMount to register the event listener was part of a list where elements could change.

现在,假设使用onMount来注册事件侦听器的组件是否是元素可以更改的列表的一部分。

When the list updates, the components that it renders will change, this means that new components will get mounted and those that are not needed anymore will get unmounted.

当列表更新时,它呈现的组件将更改,这意味着将挂载新组件,而不再需要的组件将被挂载。

Every time, new event listeners will get registered on mount and if you did cleanup, the old event listeners will be removed when the component is unmounted.

每次,新的事件侦听器都会在安装时注册,如果您进行了清理,则在卸载组件时,旧的事件侦听器也会被删除。

If you didn’t cleanup your event listeners however, new event listeners will be added everytime a new component is mounted, without removing the old ones which are now unnecessary.

但是,如果您不清理事件侦听器,则每次安装新组件时都会添加新的事件侦听器,而不会删除现在不再需要的旧组件。

This would create a memory leak because everytime we’ll get a keyup event, all the registered listeners will run, even if the components that registered them are no longer present.

这将导致内存泄漏,因为每次我们遇到keyup事件时,所有注册的侦听器都将运行,即使注册它们的组件不再存在。

From there, it’s only a matter of time for your app’s performance to suffer severely. So don’t forget to cleanup!

从那里开始,您的应用程序性能受到严重影响只是时间问题。 所以不要忘记清理!

显示搜索结果 (Displaying search results)

Right now, we’re always displaying our full files list no matter what. We need to change this so that when we’re searching we display our fileSearchResults and otherwise display the full list.

现在,无论如何,我们始终会显示完整的文件列表。 我们需要更改它,以便在搜索时显示文件搜索fileSearchResults ,否则显示完整列表。

Since our search results are already derived from the search input through our fileSearchResults store, we can simply swap the full list for the value of the fileSearchResults store when we are in file search mode.

由于我们的搜索结果已经从通过fileSearchResults存储库进行的搜索输入中得出,因此当我们处于文件搜索模式时,我们可以简单地将完整列表交换为fileSearchResults存储库的值。

Now the search will work!

现在搜索将起作用!

关于组件卸载时的清理 (About clean up when a component unmounts)

This part is only for demonstration if you’re curious about what happens when you don’t cleanup. Remove the code below after you’ve tried it.

如果您对不清理时会发生的事情感到好奇,那么此部分仅用于演示。 试用后,请删除下面的代码。

Add this code to FileItem.svelte to see the behavior of not cleaning up event listeners in action

将此代码添加到FileItem.svelte以查看未清除事件监听器的行为

Open the devtools console and start the search with t

打开devtools控制台并使用t开始搜索

Type something in the search and erase it to make our list update and mount/unmount components.

在搜索中输入内容并将其删除,以使我们的列表更新并安装/卸载组件。

Repeat this a few times, then clear your console and press a key.

重复几次,然后清除控制台并按一个键。

You’ll see one keyup event can now trigger more callback calls than we have components. This quickly gets to a point where one keyup event triggers hundreds of callback calls even though we never have more than 13 FileItem components.

您会看到一个keyup事件现在可以触发比我们拥有的组件更多的回调调用。 即使我们从来没有超过13个FileItem组件,这很快就会达到一个keyup事件触发数百个回调调用的地步。

Now add the cleanup code, and retry the above process

现在添加清理代码,然后重试以上过程

No more useless calls. Keep in mind, event listeners aren’t the only thing you should cleanup, a store subscription could be another example.

没有更多无用的电话了。 请记住,事件监听器不是您应该清除的唯一内容,商店订阅可能是另一个示例。

What’s nice about Svelte’s $ syntax for stores is it also handles unsubscribing from stores automatically.

Svelte为商店提供的$语法的好处是,它还能自动处理从商店退订。

添加搜索词突出显示 (Adding search term highlighting)

We can modify fileSearchResults to return the file names with HTML markup around the matched search term.

我们可以修改fileSearchResults以返回带有匹配搜索词周围HTML标记的文件名。

We do this by wrapping the search term between <mark></mark> tags

为此,我们将搜索字词包裹在<mark></mark>标签之间

First, when our search is not empty, we create Regexp that will match our $fileSearch term. We pass gi as second argument to the Regexp , those are flags that will make our Regexp match globally g , and case-insensitive i

首先,当我们的搜索不为空时,我们将创建与$fileSearch术语匹配的Regexp 。 我们将gi作为第二个参数传递给Regexp ,这些标志将使我们的Regexp全局匹配g ,并且不区分大小写i

After our filter which will return our search results, wemap over each result, and return our original file but except with a new name where our search term will now be wrapped with between <mark></mark> tags.

在将返回搜索结果的过滤器之后,我们会map每个结果,并返回原始文件,但使用新名称除外,在新名称中搜索词现在将用<mark></mark>标签包裹。

For example, searching for “readme” will replace the file name README.md with <mark>README</mark>.md

例如,搜索“自述文件”会将文件名README.md替换为<mark>README</mark>.md

If you try this now, you’ll see that there is no highlighting and just a bunch of <mark> tags inside our file names.

如果立即尝试,您会发现文件名中没有突出显示,只有一堆<mark>标签。

This is because Svelte, like most frontend frameworks, will escape HTML markup by default.

这是因为Svelte与大多数前端框架一样,默认情况下将转义HTML标记。

We need to modify our FileItem component to render our file.name as HTML, since it now possibly contains HTML markup.

我们需要修改FileItem组件以将file.name呈现为HTML,因为它现在可能包含HTML标记。

In Svelte, this is done with the @html directive

在Svelte中,这是通过@html指令完成的

Now we have highlighting because Svelte knows it needs to render file.name as HTML.

现在我们重点介绍一下,因为Svelte知道需要将file.name呈现为HTML。

添加键盘导航 (Adding keyboard navigation)

First, let’s add a nice message that will display the available keyboard controls once we’ve entered search mode.

首先,让我们添加一条不错的消息,该消息将在我们进入搜索模式后显示可用的键盘控件。

Create InfoMessage.svelte inside components/file-search

components/file-search内部创建InfoMessage.svelte

Import it in App.svelte so it displays on top of our search

将其导入App.svelte ,使其显示在搜索结果的顶部

Let’s implement navigation with the arrow keys, we’ll keep track of the currently selected file index with a store.

让我们使用箭头键实现导航,我们将通过商店来跟踪当前选定的文件索引。

First create a new store fileIndex.ts

首先创建一个新的商店fileIndex.ts

Simple writable store to hold our index as a number , we initialize it to 0, so it points to the first file of the list.

简单的可写存储将索引保存为number ,我们将其初始化为0,因此它指向列表的第一个文件。

Now add the listeners for the arrow keys in App.svelte

现在在App.svelte为箭头键添加侦听App.svelte

We import our fileIndex store so we can update it in our listeners.

我们导入fileIndex存储,以便可以在侦听器中对其进行更新。

We added the handleKeyDown listener and registered it on the document on the keydown event.

我们添加了handleKeyDown侦听器,并在keydown事件上将其注册在文档中。

We’re not using keyup so that you don’t have to release the key to keep moving. keydown will fire as long as the key is pressed.

我们不使用keyup ,这样你就不必松开按键,继续前进。 keydown只要按下键就会触发。

When we get our keydown event, handleKeyDown checks whether the key pressed was ArrowUp or ArrowDown and updates the fileIndex store accordingly.

当我们得到keydown事件时, handleKeyDown检查按下的键是ArrowUp还是ArrowDown并相应地更新fileIndex存储。

On ArrowDown , we increment our index and use the modulo % operator so that when we reach the length of our search results we reset our index to 0, so when we reach the last file and press down, we go back to the top of the list.

ArrowDown ,我们增加索引并使用%模运算符,以便在达到搜索结果的长度时,将索引重置为0,因此,当到达最后一个文件并按下时,我们返回到顶部清单。

We do the same thing for ArrowUp by checking if we get a negative value and then resetting our index to be the last file in our results.

我们通过检查我们是否得到负值,然后将索引重置为结果中的最后一个文件,对ArrowUp做同样的事情。

This way the navigation will loop seamlessly and we won’t index out of bound errors.

这样,导航将无缝循环,并且我们不会索引超出范围的错误。

Note that we now also pass down searching as a prop to FileList because we only want to navigate between files when we’re in search mode.

请注意,我们现在还将向下searching作为对FileList的支持,因为我们只希望在处于搜索模式时在文件之间导航。

Let’s modify our FileList to accept the searchingprop and pass an active prop down to our FileItem component.

让我们修改FileList以接受searching道具并将active道具向下传递到FileItem组件。

We import our fileIndex store so we can read it’s value and add an index parameter to our {#each file as file, index (file.id)} block.

我们导入fileIndex存储,以便我们可以读取它的值,并将index参数添加到{#each file as file, index (file.id)}块。

We pass a new active prop to FileItem that is true when our fileIndex equals the current index and we are in search mode, given by the new searching prop.

我们将一个新的active道具传递给FileItem ,当我们的fileIndex等于当前index并且我们处于搜索模式时(由新的searching道具给出),它为true

Now let’s use that prop to apply a light blue background to our selected FileItem

现在让我们使用该道具将浅蓝色背景应用于我们选择的FileItem

We added active as a prop and use the class:className={condition} syntax to apply a conditional class, this class will only be added when condition is true.

我们添加了active作为道具,并使用class:className={condition}语法来应用条件类,只有在condition为true时才添加此类。

In this case, this looks like this class:bg-blue-200={active}

在这种情况下, class:bg-blue-200={active}类似于class:bg-blue-200={active}

Now the arrow keys navigation works!

现在,箭头键导航有效!

There’s just one small thing, if you search for something, then navigate, then change the search, our index won’t reset to 0.

只有一件小事,如果您搜索某事,然后浏览,然后更改搜索,我们的索引将不会重置为0。

To fix that, simply modify our fileSearchResults store to reset the fileIndex whenever the search changes

要解决此问题,只需修改我们的fileSearchResults存储以在搜索更改时重置fileIndex

We’ve imported our fileIndex store and added fileIndex.set(0) since this will run everytime either $fileSearch or $files changes.

我们已经导入了fileIndex存储并添加了fileIndex.set(0)因为这将在$fileSearch$files每次更改时运行。

更新postcss配置,使我们的图标不会在生产中消失 (Updating the postcss configuration so our icons don’t disappear in production)

If you build for production right now, you’ll notice that when you access the app, icons won’t appear anymore.

如果您现在为生产而构建,则会注意到,当您访问该应用程序时,将不再显示图标。

This is because we’re using purgecss to remove unused classes from our CSS bundle.

这是因为我们正在使用purgecss从CSS捆绑包中删除未使用的类。

However, svelte-awesome generates an fa-icon class that gets removed by purgecss in our production bundle because it doesn’t appear in our code.

但是, svelte-awesome生成一个fa-icon类,该类将被我们的产品包中的purgecss删除,因为它未出现在我们的代码中。

We need to whitelist this class so it doesn’t get removed from our CSS bundle

我们需要将此类列入白名单,以便不会将其从CSS包中删除

In postcss.config.js , you’ll see a whitelistPatterns array that only contains /svelte/ , you need to add /fa-icon/ in there as well.

postcss.config.js ,您将看到仅包含/svelte/whitelistPatterns数组,还需要在其中添加/fa-icon/

使用React + Moment.js版本比较包的大小和加载时间 (Comparing bundle size and loading times with the React + Moment.js version)

I want to emphasize that this comparison might not say much about the difference between Svelte and React.

我想强调的是,这种比较可能不会说太多Svelte和React之间的区别。

This is because we’re using date-fns which is about 3 times lighter gzipped than moment. So it’s unclear how much is due to this difference and how much is due to the Svelte/React difference.

这是因为我们使用的date-fns压缩的时间比moment轻约3倍。 因此,尚不清楚该差异是多少,而Svelte / React差异是多少。

I’ve made a few modifications to our app, since we’re not using the global.css file, I deleted it and removed the import in public.html

我对我们的应用做了一些修改,因为我们没有使用global.css文件,所以我删除了它,并删除了public.html的导入。

I’ve aadded a Header component that simply displays “Github File Search” to match the React app more exactly.

我添加了一个Header组件,该组件仅显示“ Github File Search”以更准确地匹配React应用。

I’ll be doing this with the devtools, network tab with “disable cache” checked and incognito mode to ensure extensions are not impacting anything.

我将使用devtools,选中“禁用缓存”的网络标签和隐身模式来执行此操作,以确保扩展不会影响任何内容。

Let’s start with bundle size

让我们从捆绑包大小开始

The React version is shipping two javascript files, a main javascript file (1.9 kB) and a chunk (58.7 kB) and one main CSS file (878 B) and HTML file (1.1 kB) for a total of ~62.5 kB

React版本提供了两个javascript文件,一个主要的javascript文件(1.9 kB)和一个块(58.7 kB),一个主要CSS文件(878 B)和HTML文件(1.1 kB),总计〜62.5 kB

The Svelte version is shipping one file for javascript (12.3 kB) and one for CSS (1.3 kB) and a HTML file (343 B), for a total of ~14 kB

Svelte版本提供了一个用于javascript(12.3 kB)的文件,一个用于CSS(1.3 kB)和HTML文件(343 B)的文件,总计约14 kB

So our Svelte app ships 48.5 kB less and is 77,6% lighter in total

因此,我们的Svelte应用程序的出货量减少了48.5 kB,总重量减轻了77.6%

Let’s see how it affects loading times now

让我们看看它如何影响加载时间

I’ve taken 10 measures for each app through the network tab’s load value, I’ve also removed outliers as those happened when the hosting platform would take more time to respond then usual.

我已通过“网络”标签的负载值为每个应用程序采取了10种措施,还删除了离群值,因为当托管平台需要更多时间来响应通常的情况时,就会发生异常。

This would mean the React app usually loaded in between 100 to 200ms and the Svelte app in between 50 to 60ms.

这意味着React应用通常在100到200毫秒之间加载,而Svelte应用通常在50到60毫秒之间加载。

Average for the React app was 167ms

React应用的平均值为167ms

Average for the Svelte app was 55ms

Svelte应用的平均值为55毫秒

So the Svelte/date-fns app loads about 3 times faster than the React/moment one.

因此,Svelte / date-fns应用程序的加载速度比React / moment加载速度快3倍。

Those are pretty good results, but it’d be more interesting to compare this in a larger app with the same third party libraries.

这些都是相当不错的结果,但是将它与具有相同第三方库的更大应用程序进行比较会更有趣。

结论 (Conclusion)

You can find the completed tutorial code in this github repo

您可以在此github存储库中找到完整的教程代码

翻译自: https://levelup.gitconnected.com/how-to-build-githubs-file-search-functionality-with-svelte-typescript-62ad415742aa

如何安装svelte

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值