mirror of https://github.com/sveltejs/svelte
parent
449e1502e7
commit
941995fbe6
@ -0,0 +1,31 @@
|
|||||||
|
<script>
|
||||||
|
let photos = [];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.photos {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
grid-gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure, img {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h1>Photo album</h1>
|
||||||
|
|
||||||
|
<div class="photos">
|
||||||
|
{#each photos as photo}
|
||||||
|
<figure>
|
||||||
|
<img src={photo.thumbnailUrl} alt={photo.title}>
|
||||||
|
<figcaption>{photo.title}</figcaption>
|
||||||
|
</figure>
|
||||||
|
{:else}
|
||||||
|
<!-- this block renders when photos.length === 0 -->
|
||||||
|
<p>loading...</p>
|
||||||
|
{/each}
|
||||||
|
</div>
|
@ -0,0 +1,38 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
let photos = [];
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const res = await fetch(`https://jsonplaceholder.typicode.com/photos?_limit=20`);
|
||||||
|
photos = await res.json();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.photos {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
grid-gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure, img {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h1>Photo album</h1>
|
||||||
|
|
||||||
|
<div class="photos">
|
||||||
|
{#each photos as photo}
|
||||||
|
<figure>
|
||||||
|
<img src={photo.thumbnailUrl} alt={photo.title}>
|
||||||
|
<figcaption>{photo.title}</figcaption>
|
||||||
|
</figure>
|
||||||
|
{:else}
|
||||||
|
<!-- this block renders when photos.length === 0 -->
|
||||||
|
<p>loading...</p>
|
||||||
|
{/each}
|
||||||
|
</div>
|
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
title: onMount
|
||||||
|
---
|
||||||
|
|
||||||
|
Every component has a *lifecycle* that starts when it is created, and ends when it is destroyed. There are a handful of functions that allow you to run code at key moments during that lifecycle.
|
||||||
|
|
||||||
|
The one you'll use most frequently is `onMount`, which runs after the component is first rendered to the DOM. We briefly encountered it [earlier](tutorial/bind-this) when we needed to interact with a `<canvas>` element after it had been rendered.
|
||||||
|
|
||||||
|
We'll add an `onMount` handler that loads some data over the network:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
let photos = [];
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const res = await fetch(`https://jsonplaceholder.typicode.com/photos?_limit=20`);
|
||||||
|
photos = await res.json();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
> It's recommended to put the `fetch` in `onMount` rather than at the top level of the `<script>` because of server-side rendering (SSR). With the exception of `onDestroy`, lifecycle functions don't run during SSR, which means we can avoid fetching data that should be loaded lazily once the component has been mounted in the DOM.
|
||||||
|
|
||||||
|
Lifecycle functions must be called while the component is initialising so that the callback is bound to the component instance — not (say) in a `setTimeout`.
|
||||||
|
|
||||||
|
If the `onMount` callback returns a function, that function will be called when the component is destroyed.
|
@ -0,0 +1,10 @@
|
|||||||
|
<script>
|
||||||
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
|
let seconds = 0;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The page has been open for
|
||||||
|
{seconds} {seconds === 1 ? 'second' : 'seconds'}
|
||||||
|
</p>
|
@ -0,0 +1,5 @@
|
|||||||
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
|
export function onInterval(callback, milliseconds) {
|
||||||
|
// implementation goes here
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
<script>
|
||||||
|
import { onInterval } from './utils.js';
|
||||||
|
|
||||||
|
let seconds = 0;
|
||||||
|
onInterval(() => seconds += 1, 1000);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The page has been open for
|
||||||
|
{seconds} {seconds === 1 ? 'second' : 'seconds'}
|
||||||
|
</p>
|
@ -0,0 +1,9 @@
|
|||||||
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
|
export function onInterval(callback, milliseconds) {
|
||||||
|
const interval = setInterval(callback, milliseconds);
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
clearInterval(interval);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
title: onDestroy
|
||||||
|
---
|
||||||
|
|
||||||
|
To run code when your component is destroyed, use `onDestroy`.
|
||||||
|
|
||||||
|
For example, we can add a `setInterval` function when our component initialises, and clean it up when it's no longer relevant. Doing so prevents memory leaks.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
|
let seconds = 0;
|
||||||
|
const interval = setInterval(() => seconds += 1, 1000);
|
||||||
|
|
||||||
|
onDestroy(() => clearInterval(interval));
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
While it's important to call lifecycle functions during the component's initialisation, it doesn't matter *where* you call them from. So if we wanted, we could abstract the interval logic into a helper function in `utils.js`...
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
|
export function onInterval(callback, milliseconds) {
|
||||||
|
const interval = setInterval(callback, milliseconds);
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
clearInterval(interval);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...and import it into our component:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
import { onInterval } from './utils.js';
|
||||||
|
|
||||||
|
let seconds = 0;
|
||||||
|
onInterval(() => seconds += 1, 1000);
|
||||||
|
</script>
|
||||||
|
```
|
@ -0,0 +1,106 @@
|
|||||||
|
<script>
|
||||||
|
import Eliza from 'elizanode';
|
||||||
|
import { beforeUpdate, afterUpdate } from 'svelte';
|
||||||
|
|
||||||
|
let div;
|
||||||
|
|
||||||
|
beforeUpdate(() => {
|
||||||
|
// determine whether we should auto-scroll
|
||||||
|
// once the DOM is updated...
|
||||||
|
});
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
// ...the DOM is now in sync with the data
|
||||||
|
});
|
||||||
|
|
||||||
|
const eliza = new Eliza();
|
||||||
|
|
||||||
|
let comments = [
|
||||||
|
{ author: 'eliza', text: eliza.getInitial() }
|
||||||
|
];
|
||||||
|
|
||||||
|
function handleKeydown(event) {
|
||||||
|
if (event.which === 13) {
|
||||||
|
const text = event.target.value;
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
comments = comments.concat({
|
||||||
|
author: 'user',
|
||||||
|
text
|
||||||
|
});
|
||||||
|
|
||||||
|
event.target.value = '';
|
||||||
|
|
||||||
|
const reply = eliza.transform(text);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
comments = comments.concat({
|
||||||
|
author: 'eliza',
|
||||||
|
text: '...',
|
||||||
|
placeholder: true
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
comments = comments.filter(comment => !comment.placeholder).concat({
|
||||||
|
author: 'eliza',
|
||||||
|
text: reply
|
||||||
|
});
|
||||||
|
}, 500 + Math.random() * 500);
|
||||||
|
}, 200 + Math.random() * 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.chat {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eliza span {
|
||||||
|
background-color: #eee;
|
||||||
|
border-radius: 1em 1em 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user span {
|
||||||
|
background-color: #0074D9;
|
||||||
|
color: white;
|
||||||
|
border-radius: 1em 1em 0 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="chat">
|
||||||
|
<h1>Eliza</h1>
|
||||||
|
|
||||||
|
<div class="scrollable" bind:this={div}>
|
||||||
|
{#each comments as comment}
|
||||||
|
<article class={comment.author}>
|
||||||
|
<span>{comment.text}</span>
|
||||||
|
</article>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input on:keydown={handleKeydown}>
|
||||||
|
</div>
|
@ -0,0 +1,106 @@
|
|||||||
|
<script>
|
||||||
|
import Eliza from 'elizanode';
|
||||||
|
import { beforeUpdate, afterUpdate } from 'svelte';
|
||||||
|
|
||||||
|
let div;
|
||||||
|
let autoscroll;
|
||||||
|
|
||||||
|
beforeUpdate(() => {
|
||||||
|
autoscroll = div && (div.offsetHeight + div.scrollTop) > (div.scrollHeight - 20);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
if (autoscroll) div.scrollTo(0, div.scrollHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
const eliza = new Eliza();
|
||||||
|
|
||||||
|
let comments = [
|
||||||
|
{ author: 'eliza', text: eliza.getInitial() }
|
||||||
|
];
|
||||||
|
|
||||||
|
function handleKeydown(event) {
|
||||||
|
if (event.which === 13) {
|
||||||
|
const text = event.target.value;
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
comments = comments.concat({
|
||||||
|
author: 'user',
|
||||||
|
text
|
||||||
|
});
|
||||||
|
|
||||||
|
event.target.value = '';
|
||||||
|
|
||||||
|
const reply = eliza.transform(text);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
comments = comments.concat({
|
||||||
|
author: 'eliza',
|
||||||
|
text: '...',
|
||||||
|
placeholder: true
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
comments = comments.filter(comment => !comment.placeholder).concat({
|
||||||
|
author: 'eliza',
|
||||||
|
text: reply
|
||||||
|
});
|
||||||
|
}, 500 + Math.random() * 500);
|
||||||
|
}, 200 + Math.random() * 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.chat {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eliza span {
|
||||||
|
background-color: #eee;
|
||||||
|
border-radius: 1em 1em 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user span {
|
||||||
|
background-color: #0074D9;
|
||||||
|
color: white;
|
||||||
|
border-radius: 1em 1em 0 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="chat">
|
||||||
|
<h1>Eliza</h1>
|
||||||
|
|
||||||
|
<div class="scrollable" bind:this={div}>
|
||||||
|
{#each comments as comment}
|
||||||
|
<article class={comment.author}>
|
||||||
|
<span>{comment.text}</span>
|
||||||
|
</article>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input on:keydown={handleKeydown}>
|
||||||
|
</div>
|
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: beforeUpdate and afterUpdate
|
||||||
|
---
|
||||||
|
|
||||||
|
The `beforeUpdate` function schedules work to happen immediately before the DOM has been updated. `afterUpdate` is its counterpart, used for running code once the DOM is in sync with your data.
|
||||||
|
|
||||||
|
Together, they're useful for doing things imperatively that are difficult to achieve in a purely state-driven way, like updating the scroll position of an element.
|
||||||
|
|
||||||
|
This [Eliza](https://en.wikipedia.org/wiki/ELIZA) chatbot is annoying to use, because you have to keep scrolling the chat window. Let's fix that.
|
||||||
|
|
||||||
|
```js
|
||||||
|
let div;
|
||||||
|
let autoscroll;
|
||||||
|
|
||||||
|
beforeUpdate(() => {
|
||||||
|
autoscroll = div && (div.offsetHeight + div.scrollTop) > (div.scrollHeight - 20);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
if (autoscroll) div.scrollTo(0, div.scrollHeight);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that `beforeUpdate` will first run before the component has mounted, so we need to check for the existence of `div` before reading its properties.
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"title": "Lifecycle"
|
||||||
|
}
|
Loading…
Reference in new issue