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