Lekce 4 - Zpět a Vpřed
V minulém díle, Stav hry, jsem ukazoval, co to je stav aplikace a jak ho držet.
V tomto díle ukáži, jaké výhody nám přináší zachování stavu aplikace do URL.
Logování
Ve chvíli, kdy držíme stav aplikace na jednom místě, můžeme všechny
jeho změny velmi elegantně logovat a tím vidět, co se v aplikaci děje.
Vytvoříme si funkci wrapReducer()
:
function wrapReducer(reducer){ // Kontrola pro případ, že bych byl v prostředí node.js if('groupCollapsed' in console){ return function (oldState,action) { console.groupCollapsed(`==[${action.type}]==>`); console.log(oldState); console.log('||'); console.log('||'); console.log(`[${action.type}]`, action); const newState = reducer(oldState, action); console.log('||'); console.log('||'); console.log('\\/'); console.log(newState); console.groupEnd(); return newState; } }else{ return reducer; } }
Potom vytváříme store tak, že reducer nejdříve "obalíme" touto logovací funkcí:
const store = Redux.createStore(wrapReducer(stateReducer), defaultState);
Nyní se po každé akci do konzole prohlížeče vypíše, co se stalo + stav předtím a potom.
URL
Ve webové aplikaci by bylo dobré, aby se každá změna stavu
zaznamenávala do URL adresy v prohlížeči. Do URL však nemůžeme uložit
příliš mnoho informací a tak můžeme stav ukládat do
LocalStorage
pod unikátním IDčkem a dané ID
dávat
do URL adresy.
function loadState(key){ try { const serializedState = localStorage.getItem(key); if (serializedState === null) { return null; } return JSON.parse(serializedState); } catch (err) { return null; } }
saveState(state){ const key = uuid.v4(); const serializedState = JSON.stringify(state); localStorage.setItem(key,serializedState); return key; }
function createStateFromUri(uri){ const key = uri.split('#',2)[1]; const state = loadState(key); if(state){ return state; }else{ return defaultState; } }
function createUriFromState(state){ var key = saveState(state); return `#${key}`; }
Nyní budeme poslouchat změny na store nejen kvůli vykreslování, ale i
proto, abychom si nový stav uložili do LocalStorage
a URL:
const store = Redux.createStore(stateReducer, createStateFromUri(document.location.toString())); const canvas = document.getElementById("scene"); const engine = new BABYLON.Engine(canvas, true); const scene = createScene(canvas, engine); function render() { updateScene(scene, store.getState()); } store.subscribe(render); render(); store.subscribe(()=> { const state = store.getState(); const uri = createUriFromState(state); const title = createTitleFromState(state); document.title = title; history.pushState({}, title, uri); }); engine.runRenderLoop(function () { scene.render(); }); window.addEventListener("resize", function () { engine.resize(); });
Zpět a vpřed
Nyní ukládáme stav do URL adresy a při načtení hry ho z něj načítáme. Pokud chceme plně využít potenciál webové aplikace, musíme umožnit uživateli klikat na tlačítka zpět a vpřed v prohlížeči.
Ve stavu aplikace budeme ukládat poslední akci a přidáme akci
CHANGE_STATE
, která změní stav podle toho, co ji přijde:
function stateReducer(state, action) { switch (action.type) { case 'CHANGE_STATE': return Object.assign({},action.state, {lastAction:action}); case 'BLOCK_ADD': return { blocks: state.blocks.concat([action.newBlock]), lastAction: action }; case 'BLOCK_DELETE': return { blocks: state.blocks.filter((block)=>block.id!==action.blockId), lastAction: action }; default: return state; } }
Do URL budeme ukládat změny stavu pouze pokud poslední akce není
CHANGE_STATE
:
const store = Redux.createStore(wrapReducer(stateReducer), createStateFromUri(document.location.toString())); const root = document.getElementById("root"); const canvas = document.getElementById("scene"); const engine = new BABYLON.Engine(canvas, true); const scene = createScene(canvas, engine, store); engine.runRenderLoop(function () { scene.render(); }); window.addEventListener("resize", function () { engine.resize(); }); function render() { updateScene(scene, store.getState()); } store.subscribe(()=>{ const state = store.getState(); if(state.lastAction.type!=='CHANGE_STATE'){ const uri = createUriFromState(state); history.pushState({}, '', uri); } }); store.subscribe(render); render(); // Vždy když uživatel klikne na zpět nebo vpřed, odešle se akce CHANGE_STATE. window.onpopstate = function () { const state = createStateFromUri(document.location.toString()); store.dispatch(createAction.CHANGE_STATE(state)); };
Titulek
Na to, aby naše hra naplno využívala historii prohlížeče, už stačí měnit titulek podle aktuálního stavu. Vytvoříme proto funkci, která vyrábí ze stavu titulek stránky:
const WEB_NAME = 'Simple web game'; const TITLE_SEPARATOR = ' | '; function createTitleFromState(state) { let titleParts = []; if (state.blocks.length> 1) { titleParts.push(state.blocks.length + ' blocks world'); } titleParts.push(WEB_NAME); return titleParts.join(TITLE_SEPARATOR); }
A tuto funkci při každé změně stavu využijeme:
store.subscribe(()=> const state = store.getState(); if(state.lastAction.type!=='CHANGE_STATE'){ const uri = createUriFromState(state); const title = createTitleFromState(state); document.title = title; history.pushState({}, title, uri); } });
Rozdělanou hru si můžeš stáhnout pod článkem, nebo jít do Git repozitáře, kde najdeš nejnovější verzi zdrojových kódů. Nebo si ji rovnou můžeš vyzkoušet na webappgames.github.io/web-game.
V dalším díle, TypeScript, ukáži, jak jednotlivé Javascriptové soubory spojit pomocí nástroje WebPack.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 58x (5.56 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript