На сайте React'a есть туториал, в котором описывается разработка игры Tic Tac Toe. Я решил повторить разработку этой игры на Svelte. Статья охватывает только первую половину туториала, до реализации истории ходов. Для целей ознакомления с фреймворком этого вполне достаточно. Каждый раздел статьи соответствует разделу туториала, содержит ссылки на исходный код обоих фреймворков.
Inspecting the Starter Code
<script>
import Board from './Board.svelte';
</script>
<div class="game">
<div class="game-board">
<Board />
</div>
<div class="game-info">
<div></div>
<ol></ol>
</div>
</div>
<style>
.game {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
ol {
padding-left: 30px;
}
</style>
<script>
import Square from './Square.svelte';
</script>
<div class="status">Next player: X</div>
<div class="board">
{#each Array(9) as square, i}
<Square value={i}/>
{/each}
</div>
<style>
.board {
width: 102px;
}
.status {
margin-bottom: 10px;
}
</style>
<script>
export let value = '';
let state = '';
function handleClick() {
state = 'X';
}
</script>
<button on:click={handleClick}>
{state}
</button>
<style>
button {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
margin-bottom: -1px;
padding: 0;
text-align: center;
width: 34px;
}
button:focus {
outline: none;
}
</style>
Каждый компонент выполняется в отдельном файле. Компонент может содержать в себе код, html разметку и css стили. Показано использование вложенных компонентов, блока each. Стили меняются редко, поэтому разместил их после html разметки, чтобы лишний раз не пролистывать их.
Passing Data Through Props
React — Svelte
Объявлено свойство value в Square. В Board показано использование индексов массива для заполнения клеток.
Making an Interactive Component
React — Svelte
По клику в клетке появляется крестик. В Square добавлен обработчик события DOM.
Lifting State Up
React — Svelte
До этого момента состояние клеток хранилось в них самих, сейчас они переведены в один массив, который размещен в компоненте Board, т.е. в Board сейчас хранится состояние всей игры. Обработчик клика handleClick перенесен в компонент Board. Square теперь снова отображает состояние клетки с помощью свойства value.
Taking Turns
React — Svelte
Добавлено появление нолика после крестика.
Declaring a Winner
React — Svelte
Добавлена функция определения победителя, добавлен запрет клика по уже установленным клеткам и после победы.
Дальше проходить туториал не планирую, с фреймворком ознакомился. Сейчас больше интересует взаимодействие с бэкендом.
UPDATE: Исправлена статья и исходные коды в соответствии с замечаниями в комментариях.
Комментарии (19)
nomhoi Автор
18.06.2019 14:07Спасибо за подсказку! В ближайшие дни я исправлю примеры и обновлю статью.
afrokick
18.06.2019 16:56А зачем в beforeUpdate каждый раз проверять статус? Если можно при изменении перерасчет сделать.
Svelte: reactive declarations
$: winner = calculateWinner(state.squares); $: status = winner ? `Winner: ${winner}` : `Next player: ${(state.xIsNext ? 'X' : 'O')}`; //или $: status = (() => { const winner = calculateWinner(state.squares); return winner ? `Winner: ${winner}` : `Next player: ${(state.xIsNext ? 'X' : 'O')}`; })();
И было бы круто примеры кода сразу в статье видеть. Утомляет каждый раз по ссылке переходить.PaulMaly
19.06.2019 09:06$: status = (() => {
const winner = calculateWinner(state.squares);
return winner? `Winner: ${winner}`: `Next player: ${(state.xIsNext? 'X': 'O')}`;
})();
Воу, зачем так сложно)) Это же не JSX какой-то, а обычны JS (по крайней мере синтаксически ;-) ). Можно делать блочные реактивные декларации:
let status; $: { const winner = calculateWinner(squares); status = winner ? `Winner: ${winner}` : `Next player: ${xIsNext ? 'X' : 'O'}`; }
Но в целом, я бы не стал увлекаться конкатенацией строк в скриптах и полностью перевел бы это дело в шаблон. На случай если вывод статуса нужно будет как-то дополнительно задизайнить, например `Winner: ${winner}` выводить жирным:
<div class="status"> {#if winner} <b>Winner: {winner}</b> {:else} Next player: {xIsNext ? 'X' : 'O'} {/if} </div>
Все таки в Svelte у нас html-first и прекрасный DSL для этого.nomhoi Автор
19.06.2019 15:09Понятно, а если еще добавить статус ничьи?
afrokick
19.06.2019 15:16это еще один статус\состояние игры. Победитель, ничья, следующий ход.
я бы так делал:
{#if state === 'победа' } ... {:else if state === 'ничья'} ... {:else if state === 'ход'} ... {/if}
PaulMaly
19.06.2019 16:25+1Ну да, типа того. Возможно если статусов станет больше и одного лишь наличия winner будет недостаточно, чтобы понять какую часть шаблона нужно отрисовать, то придетяся считаться какой-то status в скрипте. Вообще сильно зависит от логики определения. Например, возможно будет достаточно такой конструкции:
<script> ... $: winner = calculateWinner(squares); $: draw = ! squares.includes('') && ! winner; ... </script> <div class="status"> {#if winner} Winner: {winner} {:else if draw} Draw! {:else} Next player: {xIsNext ? 'X' : 'O'} {/if} </div>
Обновил мой пример. Тот же draw пригождается еще и для отмены click()
nomhoi Автор
19.06.2019 15:08+1Да, beforeUpdate можно было бы и не вводить. Кстати, в туториале для React'a статус тоже почему-то определяется в методе render.
Я, думаю, сделаю новую редакцию статьи и там размещу код. Здесь как, принято ли новую редакцию статьи в виде отдельной статьи оформлять? Если исправить прямо здесь, то новым читателям уже не будут понятны старые комментарии.
nomhoi Автор
20.06.2019 16:03Начал исправлять статью и исходный код в соответствии с замечаниями. Пока еще не до конца.
PaulMaly
Спасибо за статью! Рад что интерес к Svelte ростет.))
Несколько ремарок:
Svelte умеет ровно также:
Поэтому диспатчер тут лишний. Он нужен только для создания полностью кастомных событий компонентов. Более того, в вашем случае можно вообще автоматически «всплывать» событие клика на компоненте Square, а не вызывать коллбек. Делается это так:
Далее можно ловить клик прямо на Square и вообще не передавать i в Square:
Заранее извиняюсь, но позволил себе переписать ваш пример немного более рационально.
DeniSun
Ваш вариант не рабочий.
«0» не поставить, победитель не определяется
PaulMaly
Блин, в примере, который я правил не было нуля. Только крестники ставились.))) Короче не последняя версия видимо. Исправил пример на скорую руку. Можно конечно и еще проще написать. Спасибо что заметили!
faiwer
Подскажите, а в чём суть этого финта с копией массива. Если написать это более простым способом явно поменяв по ключу напрямую — так не сработает?
Попробовал сам так:
кажется всё работает.
PaulMaly
Иммутабельность же. Помогает всем фреймворка и Svelte в том числе лучше «понимать» что изменилось в объекте. В Svelte есть специальная опция компилятора immutable: true, которую мы обычно используем в проектах. И всем советую. Собственно с этой опцией ваш способ работать не будет.
faiwer
Понятно. И immer какой-нибудь наверное сюда уже не подключишь, Svelte ведь компилятор :( После стандартных redux-их {… } простыней видеть эти костыли в Svelte, конечно, неприятно.
PaulMaly
C immer прекрасно работает. Не понимаю какое вообще отношение к этому имеет компиляция.
Так используйте {… } никто также не мешает. Как вы обеспечиваете иммутабильностью Svelte вообще не волнует. Для реактивности важен только факт присвоения значения, а иммутабильность не обязательна и работает внутри. Флагом immutable: true вы лишь говорите Svelte — «тебе не стоит парится на объектами, если ссылка на них не изменилась».
И было бы интересно узнать что из примера выше вы считаете «костялем»? Вроде бы обычный js.
faiwer
Очевидно же — две строки из 3-х. Натужная иммутабельность, которая плохо сказывается на читаемости, написании и поддержки кода. Пассаж про обычный js не понял, обычный js не запрещает писать костыли :)
Гхм… Вы меня наверное недопоняли. От них после redux уже тошно, а вы мне их ещё и в svelte тащить предлагаете? :) Я собственно потому и упомянул immer.
Ну тут надо пробовать. Я пока близко в Svelte не присматривался. Насколько я понимаю, он выискивает все мутабельные операции и добавляет к ним явные setter-ы. Так что в случае immer тут будет всё зависеть от того, сможет ли он подхватить их, будет ли там callback, как на него отреагирует svelte. Нужно ли будет помещать такой блок в $: {} кодовый блок. Как я и написал выше — надо пробовать и смотреть что получится… Всё таки это компилятор и надо понимать что получится в итоговом js-коде, какие будут обёртки и стоит ли игра свеч.
nomhoi Автор
Если мы собираемся записывать каждое состояние игры в историю, то необходимо полное копирование массива. Если история не нужна, то можно и не создавать копию.