Hello everyone.This is a continuation of the article " Get away from jQuery to Svelte, no pain ."Below I will talk about the difficulties that I encountered, there were not many of them, and only one was so fundamental where I could not cope without the support of the community .Introduction
I planned to rewrite the front-end in pieces, itās not something that would not work out at all, it didnāt work out completely - I had to rewrite in large pieces.First, because the jQuery approach is imperative, the Svelte approach is declarative.Secondly, because using jQuery, our scope is always global, we can access all elements of a web page from any line of code, we access them by ID or CSS selector, while Svelte recommends using components and inside the component we see only the component itself, we have no external or internal elements, and we are not able to access them directly.With Svelte, we get real OOP: we cannot make changes ourselves, we can only tell the component about the need for changes. How these changes will be made, knows only the code inside the component.And that's great :)Svelte has two mechanisms for communicating with components.Binding variables (binds, mapping)
We declare a variable and map it to the component attribute:<script>
import DetailTaskView from './DetailTaskView.svelte';
let task = {};
let isModal = false;
function renderTask(data) {
const isSuccess = data.hasOwnProperty(0);
if (isSuccess) {
isModal = true;
task = data[0];
}
}
import {fade} from 'svelte/transition';
</script>
{#if isModal}
<div transition:fade>
<DetailTaskView bind:isModal="{isModal}" {...task}/>
</div>
{/if}
What have we done?
We declared two local variables ātaskā and āisModalā, ātaskā is information to be displayed in the component, data is only displayed, will not be changed, āisModalā is the flag of component visibility, if the user clicked the cross on the component, then the component should disappear , the cross belongs to the component, so we donāt know anything about the click, but we learn that the value of the āisModalā variable has changed and thanks to the reactivity, we will work out this new value.If we need two-way binding, then we write ābind:ā, a change in the value inside the component will be reported to the āparentā component.We can use an abbreviated form if we only need to tell the component the values, and if the name of the attribute of the component matches the name of the variable, we can write "{task}" or use the destructor "{... task}".Conveniently.But if we have another component embedded in one component, and there is also a third one, then of course there is a bollerplate for scrolling values āāup and down the nesting hierarchy.Event bubbling
I can be mistaken with the terminology, do not kick much.The parent component can handle the events of the child component, but only those events that the child component reports.
<script>
import SearchForm from './SearchForm.svelte';
async function applySample(event) {
const sample = event.detail.sample;
if(sample){
search(sample);
}
if(!sample){
renderPage();
}
}
</script>
<div class="container">
<h1> </h1>
<SearchForm on:search={applySample}/>
</div>
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
let sample = '';
function search(event) {
event.preventDefault();
dispatch('search', {
sample: sample
});
}
</script>
<form class="form-horizontal" id="search"
on:submit={search}>
<div class="form-group">
<label class="control-label col-sm-2" for="sample">
</label>
<div class="col-sm-10">
<input id="sample" class="form-control" type="search"
placeholder=" "
autocomplete="on" bind:value={sample}
/>
</div>
</div>
</form>
What's going on here ?
In the parent component, the "applySample" function is executed by the "on: search" event of the "SearchForm" child component, this function receives the parameters (event.detail) from the event object and processes them.What happens in a component?
The āvalueā attribute of the input element is mapped to the āsampleā variable, by the āon: submitā event (of the āformā element) the āsearchā function is executed, which raises the 'search' event and writes the {sample: sample object to the "detail" property } - that is, the value of the search string.Thus, the value of the search string is passed to the parent component and it is he who decides what to do with this value.The component is only responsible for displaying the input form and transmitting the entered value, the component does not implement the search and displaying the results, so we share responsibility.The beauty!The transition from imperative to declarative
Here, unfortunately, it doesnāt work out to show the difference just as clearly. In words, it sounds like this: if, when using jQuery, I created html markup and then inserted it into the right place, then with Svelte I generate an array with the attributes of the components and then in the loop I add the components with pre-calculated attributes:
<script>
import {createEventDispatcher} from 'svelte';
import OrdinalPlace from './OrdinalPlace.svelte';
let pagingPlaces;
function addPlace(
paging = [], index = Number.NEGATIVE_INFINITY,
text = "", css = "") {
paging.push({index:index,text:text,css:css});
return paging;
}
const dispatch = createEventDispatcher();
function browsePage(event) {
const pageIndex = event.detail.index;
dispatch('move', {
index: pageIndex
});
}
</script>
{#if pagingPlaces.length}
<table class = "table table-hover-cells table-bordered">
<tbody>
<tr>
{#each pagingPlaces as place (place.index)}
<OrdinalPlace on:move="{browsePage}"
{...place}>
</OrdinalPlace>
{/each}
</tr>
</tbody>
</table>
{/if}
<script>
export let index = -1;
export let text = "";
export let css = "";
let number = index +1;
function skip() {
return !(text === "");
}
let letSkip = skip();
let noSkip = !letSkip;
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
function moveTo() {
if(noSkip){
dispatch("move", {index:index});
}
}
</script>
<td class="{css}" on:click="{moveTo}">
{#if letSkip}
{text}
{/if}
{#if noSkip}
{number}
{/if}
</td>
How it works ?
When creating the Paging component, we form an array of āelementsā to go to certain pages - āpagingPlacesā, then cycle through all the elements and insert a component to display one paging position - āOrdinalPlaceā.Again, a declarative approach, we do not form each position ourselves, we tell the component that we need to display a position with such attributes.Here we see a confused case of the emergence of an event. To go to the search results page, the user clicks on the āOrdinalPlaceā component, this component cannot load the page, therefore it creates a āmoveā event with the page index parameter and this event picks up the parent component - āPagingā, which also cannot load the page therefore it raises the 'move' event, and the next parent component already picks it up and somehow processes it.Svelte and the component approach push us to share responsibility and follow SOLID.Biggest ambush
The example above shows a solution to a fundamental problem that I would not be able to handle without a hint. Svelte caches all components and you need to help him track changes in these components.Here is the code in question: {#each pagingPlaces as place (place.index)}
<OrdinalPlace on:move="{browsePage}"
{...place}>
</OrdinalPlace>
{/each}
To display the list of pages in the paging, we ran through the array and Svelte assigned a component an array index to each component, now Svelte makes a decision to redraw the component based on this index, if you do not specify the index while iterating through the array elements, then do not understand what , I tried to understand for a day, then I asked for help from the hall and in the hall I did not immediately find a person who was well acquainted with this rake, but they helped me, thanks to the guys.When working with arrays, keep this in mind: any pass through the array must use an index, again: {#each pagingPlaces as place (place.index)}
"PagingPlaces as place (place.index)" - be sure to use.Of course, if you have previously worked with React / Vue, then you are probably already familiar with this feature.Visual effects
My application used modal windows. jQuery for this sets the markup requirements, without it, the jQuery.modal () method will not work.Svelte makes this easier:{#if isModal}
<div transition:fade>
<DetailTaskView bind:isModal="{isModal}" {...task}/>
</div>
{/if}
Specifically, "transition: fade" is responsible for the disappearance / appearance of elements on the page.No one dictates to us what markup we should have.It's good.In addition to this animation, Svelte has a couple more: fly and tweened , examples from the links in the tutorial.Other
Variable names
The trouble with the naming of variables / parameters / attributes, you have to use one word to call both the property of the object and the variable that you write there, the handset rule when you need to talk about the code on the phone, so that you donāt get confused at that end and understand everything, such repeated names violate.Ajax
This does not apply to Svelte, but concerns the rejection of using jQuery, jQuery.ajax can be replaced with fetch (), I made such a replacement, you can see it in the repository.Conclusion
Moving from using jQuery to using Svelte will require rewriting the logic for creating markup, but it is not as difficult and as long as it may seem, especially if your code doesnāt sin with this.Svelte simplifies your markup and shortens JS code, using Svelte makes your code more reusable and resistant to random errors.Use Svelte, it will be good for you and your customers!References
Official site of SvelteRepository with the transition from using jQuery to using SvelteChannel of the Russian-speaking community Svelte in TelegramThank you for reading.PS: I donāt know why Habr excludes the colon from the link to the community channel, the correct link line is: tg: // resolve? Domain = sveltejs