はじめに
Svelte Advent Calendar 2020 2日目の担当です。
今回はReact HooksのTutorialであるHow to build a movie search app using React Hooks
をSvelteで試してみたいと思います。
React hooksの部分についてですが、今回はreact-hooks-in-svelteを参考に代替することにしています。
完成物
Tutorialを試してみる
Header.svelte
<script>
export let title;
</script>
<style>
.App-header {
background-color: #282c34;
height: 70px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
padding: 20px;
cursor: pointer;
}
.App-header h2 {
margin: 0;
}
</style>
<header class="App-header">
<h2>{title}</h2>
</header>
{title}
になっている箇所はApp.svelte
から値を渡すようにしています。
内容的にはReactのTutorialとほぼ変わらないですね。
Movie.svelte
<script>
const DEFAULT_PLACEHOLDER_IMAGE =
"https://m.media-amazon.com/images/M/MV5BMTczNTI2ODUwOF5BMl5BanBnXkFtZTcwMTU0NTIzMw@@._V1_SX300.jpg";
export let movie;
$: poster = movie.Poster === "N/A" ? DEFAULT_PLACEHOLDER_IMAGE : movie.Poster;
</script>
<style>
.movie {
padding: 5px 25px 10px 25px;
max-width: 25%;
}
@media screen and (min-width: 694px) and (max-width: 915px) {
.movie {
max-width: 33%;
}
}
@media screen and (min-width: 652px) and (max-width: 693px) {
.movie {
max-width: 50%;
}
}
@media screen and (max-width: 651px) {
.movie {
max-width: 100%;
margin: auto;
}
}
</style>
<div class="movie">
<h2>{movie.Title}</h2>
<div>
<img
width="200"
alt={`The movie titled: ${movie.Title}`}
src={poster}
/>
</div>
<p>({movie.Year})</p>
</div>
Movieを表示する部分になります。
ここでは$:
Reactive Statementsを使用して値をリアクティブしています。
今回はmovie一つ一つのposterの有無を確認するような処理になっておりますが、一つのコンポーネント内でReactive Statementsを使うことも可能です。
Search.svelte
<script>
export let query = '';
</script>
<style>
.search > input[type="text"]{
width: 40%;
min-width: 170px;
}
.search {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
margin-top: 10px;
}
</style>
<form class="search">
<input
bind:value={query}
type="text"
on:keyup
placeholder="title" />
</form>
Search.svelteではbind:
, on:
ディレクティブを使用してApp.svelteに値を渡したり、関数を呼び出ししています。
App.svelte
<script>
import Header from './Header.svelte';
import Movie from './Movie.svelte';
import Search from './Search.svelte';
import { onMount } from 'svelte'
const MOVIE_API_URL = "https://www.omdbapi.com/?s=man&apikey=4a3b711b"; // you should replace this with yours
let query = '';
let movies = [];
let loading = null;
let errorMessage = '';
onMount(async () => {
fetch(MOVIE_API_URL)
.then(response => response.json())
.then(jsonResponse => {
if (jsonResponse.Response === "True") {
movies = jsonResponse.Search;
} else {
errorMessage = jsonResponse.Error;
}
});
});
function searchMovies() {
loading = true;
fetch(`https://www.omdbapi.com/?s=${query}&apikey=4a3b711b`)
.then(response => response.json())
.then(jsonResponse => {
if (jsonResponse.Response === "True") {
movies = jsonResponse.Search;
loading = false;
} else {
errorMessage = jsonResponse.Error;
loading = false;
}
});
}
</script>
<style>
.App {
text-align: center;
}
.App-intro {
font-size: large;
}
/* new css for movie component */
* {
box-sizing: border-box;
}
.movies {
display: flex;
flex-wrap: wrap;
flex-direction: row;
}
.errorMessage {
margin: auto;
font-weight: bold;
color: rgb(161, 15, 15);
}
</style>
<div class="App">
<Header title="SVELTE HOOKED" />
<Search bind:query on:click={searchMovies} />
<p class="App-intro">Sharing a few of our favourite movies</p>
<div class="movies">
{#if loading && !errorMessage }
<span>loading...</span>
{:else if errorMessage }
<div class="errorMessage">{errorMessage}</div>
{:else}
{#each movies as movie}
<Movie movie={movie} />
{/each}
{/if}
</div>
</div>
Tutorialに記載されているuseEffect
の箇所は今回はonMount
を使っています。
一覧表示の条件としてloadingがtrueであれば、loading...と表示して、エラーがあればエラー内容の表示、それ以外は一覧を表示するようになっています。
エラーメッセージはAPIレスポンスがTrue
意外の時にError
から取得しています。
bind:query
の部分ですが名前と値が一致する場合省略可能となるため、省略するようにしています。
おわりに
今回作成したAppをSvelte REPLにて公開しております。
最後にSvelteに少しでも興味を持った方はSvelte Tutorialをご覧ください!
参考資料
How to build a movie search app using React Hooks
react-hooks-in-svelte
Svelte API