4. diel - Späť a Vpred
V minulom diele som ukazoval, čo to je stav aplikácie a ako ho držať. V tomto diele ukážem, aké výhody nám to prináša.
Logovanie
Vo chvíli, keď držíme stav aplikácie na jednom mieste, môžeme všetky jeho zmeny veľmi elegantne logovať a tým vidieť, čo sa v aplikácii deje. Vytvoríme si funkciu 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árame store tak, že reducer najskôr "obalíme" touto logovacie funkcií.
const store = Redux.createStore(wrapReducer(stateReducer), defaultState);
Teraz sa po každej akcii do konzoly prehliadača vypíše, čo sa stalo + stav predtým a potom.
Url
Vo webovej aplikácii by bolo dobré, aby sa každá zmena stavu zaznamenávala do URL adresy v prehliadači. Do URL však nemôžeme uložiť príliš veľa informácií a tak môžeme stav ukladať do LocalStorage pod unikátnym IDčkem a dané ID dávať 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}`; }
Teraz budeme počúvať zmeny na store nielen kvôli vykresľovanie, ale aj preto, aby sme 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(); });
Späť a vpred
Teraz ukladáme stav do URL adresy a pri načítaní hry ho z neho načítame. Ak chceme naplno využiť potenciál webové aplikácie, musíme umožniť užívateľovi klikať na tlačidlá späť a vpred v prehliadači.
V stave aplikácie budeme ukladať poslednú akciu a pridáme akciu CHANGE_STATE, ktorá zmení stav podľa toho, čo ju príde.
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 ukladať zmeny stavu iba ak posledná akcia nie je 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)); };
Titulok
Na to, aby naša hra naplno využívala históriu prehliadača, už stačí meniť titulok podľa aktuálneho stavu. Vytvoríme preto funkciu, ktorá vyrába zo stavu titulok 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 túto funkciu pri každej zmene 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); } });
Rozrobenú hru si môžeš stiahnuť pod článkom, alebo ísť do Git repozitára, kde nájdeš najnovšiu verziu zdrojových kódov. Alebo si ju rovno môžeš vyskúšať na webappgames.github.io/web-game. V ďalšom diele ukážem, ako jednotlivé JavaScriptovej súbory spojiť pomocou nástroja WebPack.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 58x (5.56 kB)
Aplikácia je vrátane zdrojových kódov v jazyku JavaScript