# Rush: Svelte :::info đź”—**References**: - https://svelte.dev/ ::: :::success :::spoiler **Table of content** [ToC] ::: <hr> ## 1. # Rush: Svelte :::info đź”—**References**: - https://svelte.dev/ ::: :::success :::spoiler **Table of content** [ToC] ::: <hr> ## 1. Fundamentals ### Svelte Component ```xml <script> (1) <-- Business Logic </script> (2) <-- Markups <style> (3) <-- Styling </style> ``` Every component needs to be putted into a `.svelte` file #### `<script>` block - `export` keyword mark a variable declaration as a **prop** (*component consumer is able to use it*). - export `const`, `class`, `function`: will be **read-only** - use cannot `export default` any props. - Assignments are **reactive**: re-render component when a value changed - Assignments are the trigger for re-render, using array-methods (like `.push()`, `.splice()`) do not trigger it. - `$` marks a statement reactive: - **Eg**: if `title` changed, `document.title` would change respectively ```jsx <script> $: document.title = title </script> ``` - `$storeValue`: access the store in ***store contract***. - A stored value must be declared at **outmost scope**. - To assign new value: use `storeValue.set(newValue)` - `<script context="module">`: - make the content ***run once when the module firstly evaluted*** rather than each components. #### `<style>` block To store CSS :v obviously - `:global(X)`: to apply CSS for X component for all, not just inside this component ### Basic component #### Import & use a component ```jsx <script> import Widget from './Widget.svelte' </script> <div> <Widget /> /* <-- this is a component */ </div> ``` #### Attributes Work as same as HTML counterpart. However, there are some empowered syntatic sugars: - Embedding attribute / content value: ```jsx <a href="/account/{ id }"> { id } </a> ``` - Appearance of boolean attributes based on boolean (truth / falsy) value: ```jsx <a href="/account/{ id }" disabled={ isBanned }> { id } </a> ``` (*As you can see, when `isBanned` is falsy, the attribute `disabled` will not be included*) - Respectively, an attribute may not be included when passed a nullish value (`null`, `undefined`) ```jsx <a href="/account/{ id }" id={ null }> { id } </a> ``` - Shorthand syntax for the attributes which has the same name with passed values: ```jsx <a href="/account/{ id }" { id } { disabled } > { id } </a> ``` #### Props As same as attributes, we pass them into a component like this: ```jsx <Component value={31} { flag } otherValue="testValue" /> ``` Or can be written using ***Spread attributes*** ```jsx <Component {...args} /> ``` It's replacable with `$$props` - a reference to all props which are passed into the component. ```jsx <Component {...$$props} /> ``` - **`$$restProps`**: refers to passed props without `export` declaration. #### Branching statement ```jsx {#if expression} ... {:else if expression} ... {:else} ... {/if} ``` #### For-each loop ```jsx {#each expression as name} ... {:else} ... {/each} ``` The way we write the `each` open-statement can be differential: ```jsx {#each expression as name, index} {#each expression as name (key)} {#each expression as name, index (key)} ``` We can also use **destructuring & rest patterns**: ```jsx {#each objects as { id, ...rest }} <li><span>{id}</span><MyComponent {...rest} /></li> {/each} ``` ```jsx {#each items as [id, ...rest]} <li><span>{id}</span><MyComponent values={rest} /></li> {/each} ``` #### Asynchronous with Promise ```jsx {#await expression}...{:then name}...{:catch name}...{/await} ``` We can rewrite the `await` clause like this: ```jsx {#await expression then name}...{/await} {#await expression catch name}...{/await} ``` #### Recreation Key blocks destroy and recreate their contents when the value of an expression changes. ```hbs {#key expression} ... {/key} ``` ### Special tags - **`{@html validHtmlExpression}`**: render escaped HTML expression - **`{@debug val}`**: equivalents with `console.log(X)` ## SvelteKit A **framework** for Svelte (*as same as NextJS for React*) To create a new project, run: `npm create svelte@latest <APP_NAME>` ### Project structure - `src/lib`: library code (ultilities & components), which can be imported by using **`$lib`** - `src/param`: param matchers - `src/routes`: routing (*optional*) ### Core concepts: Routing **Filesystem-based routing approach.** Each route dir contains 1/n **route files** (with `+` prefix). :::warning Note that SvelteKit uses `<a>` elements to navigate between routes, rather than a framework-specific `<Link>` component. ::: - `src/routes/`: root route. - `src/routes/+page.svelte`: component defines a page for the app - `src/routes/+page.js`: contains **exported `load()` function**, use for pre-rendering logics (*such as loading data*) - `src/routes/+page.server.js`: to identify that the `load()` function is server-only runnable - `src/routes/+error.svelte`: default error page when `load()` has failed. - `src/routes/+error.server.js`: respectively - `src/routes/about`: create an `/about` route - `src/routes/products/[product-name]`: create a `/products` route with path param `[product-name]`. <br> - `src/routes/+layout.svelte`: create a layout that applies to every page. Used with `<slot>` tag. - `src/routes/+layout.js`: as same as other `.js` files 1. You define your `+page.js` file to load some data: ```typescript /** @type {import('./$types').PageLoad} */ export function load({ params }) { return { post: { title: `Title for ${params.slug} goes here`, content: `Content for ${params.slug} goes here` } }; } ``` 2. You consume the data and render it in `+page.svelte`: ```jsx <script> /** @type {import('./$types').PageData} */ export let data; </script> <h1>{data.post.title}</h1> <div>{@html data.post.content}</div> ``` #### Get the data of parent `load()` - `src/routes/+layout.js` ```typescript /** @type {import('./$types').LayoutLoad} */ export function load() { return { a: 1 }; } ``` - `src/routes/abc/+layout.js` ```typescript /** @type {import('./$types').LayoutLoad} */ export async function load({ parent }) { const { a } = await parent(); return { b: a + 1 }; } ``` ### Core concepts: Form actions ```jsx <form method="POST" use:enhance={({ formElement, formData, action, cancel, submitter }) => { // `formElement` is this `<form>` element // `formData` is its `FormData` object that's about to be submitted // `action` is the URL to which the form is posted // calling `cancel()` will prevent the submission // `submitter` is the `HTMLElement` that caused the form to be submitted return async ({ result, update }) => { // `result` is an `ActionResult` object // `update` is a function which triggers the default logic that would be triggered if this callback wasn't set }; }} > ``` - **`formElement`**: refers to `<form>` element - **`formData`**: is its `FormData` object that's about to be submitted - **`action`**: is the URL to which the form is posted - calling `cancel()` will prevent the submission - **`submitter`**: is the `HTMLElement` that caused the form to be submitted ### Core concept: State management There are some rules: 1. Browser are ***stateful*** while server is ***stateless*** 2. No side-effects in `load()` **NEVER DO THIS:** ```typescript import { user } from '$lib/user'; /** @type {import('./$types').PageLoad} */ export async function load({ fetch }) { const response = await fetch('/api/user'); // NEVER DO THIS! user.set(await response.json()); } ``` Use **CONTEXT API** instead. - `src/routes/+layout.svelte` ```typescript <script> import { setContext } from 'svelte'; import { writable } from 'svelte/store'; /** @type {import('./$types').LayoutData} */ export let data; // Create a store and update it when necessary... const user = writable(); $: user.set(data.user); // ...and add it to the context for child components to access setContext('user', user); </script> ``` - `src/routes/+page.svelte` ```jsx <script> import { getContext } from 'svelte'; // Retrieve user store from context const user = getContext('user'); </script> <p>Welcome {$user.name}</p> ``` ### Hooks Hooks are **app-wide functions** which will be called in response to specific events. You can declare within 2 files: - `src/hooks.servers.js` - `src/hooks.client.js` ### Svelte Component ```xml <script> (1) <-- Business Logic </script> (2) <-- Markups <style> (3) <-- Styling </style> ``` Every component needs to be putted into a `.svelte` file #### `<script>` block - `export` keyword mark a variable declaration as a **prop** (*component consumer is able to use it*). - export `const`, `class`, `function`: will be **read-only** - use cannot `export default` any props. - Assignments are **reactive**: re-render component when a value changed - Assignments are the trigger for re-render, using array-methods (like `.push()`, `.splice()`) do not trigger it. - `$` marks a statement reactive: - **Eg**: if `title` changed, `document.title` would change respectively ```jsx <script> $: document.title = title </script> ``` - `$storeValue`: access the store in ***store contract***. - A stored value must be declared at **outmost scope**. - To assign new value: use `storeValue.set(newValue)` - `<script context="module">`: - make the content ***run once when the module firstly evaluted*** rather than each components. #### `<style>` block To store CSS :v obviously - `:global(X)`: to apply CSS for X component for all, not just inside this component ### Basic component #### Import & use a component ```jsx <script> import Widget from './Widget.svelte' </script> <div> <Widget /> /* <-- this is a component */ </div> ``` #### Attributes Work as same as HTML counterpart. However, there are some empowered syntatic sugars: - Embedding attribute / content value: ```jsx <a href="/account/{ id }"> { id } </a> ``` - Appearance of boolean attributes based on boolean (truth / falsy) value: ```jsx <a href="/account/{ id }" disabled={ isBanned }> { id } </a> ``` (*As you can see, when `isBanned` is falsy, the attribute `disabled` will not be included*) - Respectively, an attribute may not be included when passed a nullish value (`null`, `undefined`) ```jsx <a href="/account/{ id }" id={ null }> { id } </a> ``` - Shorthand syntax for the attributes which has the same name with passed values: ```jsx <a href="/account/{ id }" { id } { disabled } > { id } </a> ``` #### Props As same as attributes, we pass them into a component like this: ```jsx <Component value={31} { flag } otherValue="testValue" /> ``` Or can be written using ***Spread attributes*** ```jsx <Component {...args} /> ``` It's replacable with `$$props` - a reference to all props which are passed into the component. ```jsx <Component {...$$props} /> ``` - **`$$restProps`**: refers to passed props without `export` declaration. #### Branching statement ```jsx {#if expression} ... {:else if expression} ... {:else} ... {/if} ``` #### For-each loop ```jsx {#each expression as name} ... {:else} ... {/each} ``` The way we write the `each` open-statement can be differential: ```jsx {#each expression as name, index} {#each expression as name (key)} {#each expression as name, index (key)} ``` We can also use **destructuring & rest patterns**: ```jsx {#each objects as { id, ...rest }} <li><span>{id}</span><MyComponent {...rest} /></li> {/each} ``` ```jsx {#each items as [id, ...rest]} <li><span>{id}</span><MyComponent values={rest} /></li> {/each} ``` #### Asynchronous with Promise ```jsx {#await expression}...{:then name}...{:catch name}...{/await} ``` We can rewrite the `await` clause like this: ```jsx {#await expression then name}...{/await} {#await expression catch name}...{/await} ``` #### Recreation Key blocks destroy and recreate their contents when the value of an expression changes. ```hbs {#key expression} ... {/key} ``` ### Special tags - **`{@html validHtmlExpression}`**: render escaped HTML expression - **`{@debug val}`**: equivalents with `console.log(X)` ## SvelteKit A **framework** for Svelte (*as same as NextJS for React*) To create a new project, run: `npm create svelte@latest <APP_NAME>` ### Project structure - `src/lib`: library code (ultilities & components), which can be imported by using **`$lib`** - `src/param`: param matchers - `src/routes`: routing (*optional*) ### Core concepts: Routing **Filesystem-based routing approach.** Each route dir contains 1/n **route files** (with `+` prefix). :::warning Note that SvelteKit uses `<a>` elements to navigate between routes, rather than a framework-specific `<Link>` component. ::: - `src/routes/`: root route. - `src/routes/+page.svelte`: component defines a page for the app - `src/routes/+page.js`: contains **exported `load()` function**, use for pre-rendering logics (*such as loading data*) - `src/routes/+page.server.js`: to identify that the `load()` function is server-only runnable - `src/routes/+error.svelte`: default error page when `load()` has failed. - `src/routes/+error.server.js`: respectively - `src/routes/about`: create an `/about` route - `src/routes/products/[product-name]`: create a `/products` route with path param `[product-name]`. <br> - `src/routes/+layout.svelte`: create a layout that applies to every page. Used with `<slot>` tag. - `src/routes/+layout.js`: as same as other `.js` files 1. You define your `+page.js` file to load some data: ```typescript /** @type {import('./$types').PageLoad} */ export function load({ params }) { return { post: { title: `Title for ${params.slug} goes here`, content: `Content for ${params.slug} goes here` } }; } ``` 2. You consume the data and render it in `+page.svelte`: ```jsx <script> /** @type {import('./$types').PageData} */ export let data; </script> <h1>{data.post.title}</h1> <div>{@html data.post.content}</div> ``` #### Get the data of parent `load()` - `src/routes/+layout.js` ```typescript /** @type {import('./$types').LayoutLoad} */ export function load() { return { a: 1 }; } ``` - `src/routes/abc/+layout.js` ```typescript /** @type {import('./$types').LayoutLoad} */ export async function load({ parent }) { const { a } = await parent(); return { b: a + 1 }; } ``` ### Core concepts: Form actions ```jsx <form method="POST" use:enhance={({ formElement, formData, action, cancel, submitter }) => { // `formElement` is this `<form>` element // `formData` is its `FormData` object that's about to be submitted // `action` is the URL to which the form is posted // calling `cancel()` will prevent the submission // `submitter` is the `HTMLElement` that caused the form to be submitted return async ({ result, update }) => { // `result` is an `ActionResult` object // `update` is a function which triggers the default logic that would be triggered if this callback wasn't set }; }} > ``` - **`formElement`**: refers to `<form>` element - **`formData`**: is its `FormData` object that's about to be submitted - **`action`**: is the URL to which the form is posted - calling `cancel()` will prevent the submission - **`submitter`**: is the `HTMLElement` that caused the form to be submitted ### Core concept: State management There are some rules: 1. Browser are ***stateful*** while server is ***stateless*** 2. No side-effects in `load()` **NEVER DO THIS:** ```typescript import { user } from '$lib/user'; /** @type {import('./$types').PageLoad} */ export async function load({ fetch }) { const response = await fetch('/api/user'); // NEVER DO THIS! user.set(await response.json()); } ``` Use **CONTEXT API** instead. - `src/routes/+layout.svelte` ```typescript <script> import { setContext } from 'svelte'; import { writable } from 'svelte/store'; /** @type {import('./$types').LayoutData} */ export let data; // Create a store and update it when necessary... const user = writable(); $: user.set(data.user); // ...and add it to the context for child components to access setContext('user', user); </script> ``` - `src/routes/+page.svelte` ```jsx <script> import { getContext } from 'svelte'; // Retrieve user store from context const user = getContext('user'); </script> <p>Welcome {$user.name}</p> ``` ### Hooks Hooks are **app-wide functions** which will be called in response to specific events. You can declare within 2 files: - `src/hooks.servers.js` - `src/hooks.client.js`