Lekce 6 - Uživatelské rozhraní
V minulém díle, TypeScript, jsem ukazoval, jaké výhody má TypeScript oproti JavaScriptu.
V tomto díle ukáži, jak vytvořím uživatelské rozhraní pomocí Reactu a Material UI.
Nebudu vytvářet nic složitého. V rámci uživatelského rozhraní chci zobrazit výběr barvy bloku a zobrazený text s počtem kostiček. To vše v levé vysouvací liště vedle scény.
Stav
Abychom mohli všechny nové funkčnosti implementovat, musíme změnit strukturu stavu hry. Kromě bloků a poslední akce musíme držet stav uživatelského rozhraní. To budou dvě hodnoty navíc:
- ui.drawer říká, zda je levá lišta vysunutá.
- ui.color je barva, kterou máme aktuálně vybranou.
Takto např. bude vypadat stav hry:
{ "blocks": [ { "id": "00dc4850-98cc-43ed-9965-1f6b08cb93da", "position": { "x": 1, "y": 1, "z": 0 }, "color": "#d74040" }, { "id": "d5a0191d-1c7f-495d-9aa0-65236441f513", "position": { "x": 0, "y": 1, "z": 0 }, "color": "#d74040" } ], "ui": { "drawer": true, "color": "#d74040" }, "lastAction": { "type": "BLOCK_ADD", "newBlock": { "id": "d5a0191d-1c7f-495d-9aa0-65236441f513", "position": { "x": 0, "y": 1, "z": 0 }, "color": "#d74040" } } }
K akcím, co máme, přibudou ještě:
- UI_DRAWER_TOGGLE - zobrazí či skryje levou lištu.
- UI_COLOR_SET - nastaví barvu.
Spravovat takto složitý stav pouze pomocí jednoho reduceru je velmi nepřehledné. Proto využiji funkci Reduxu combineReducers.
index.ts
import { combineReducers } from 'redux' import * as _ from "lodash"; import blocks from './blocks'; import ui from './ui'; function lastAction(previousAction,action){ return action; } const stateReducerInner = combineReducers({ blocks, ui, lastAction }); export enum ActionTypes{ CHANGE_STATE='CHANGE_STATE', } export const createAction = { CHANGE_STATE: (state)=>({type:ActionTypes.CHANGE_STATE,state}), }; export function stateReducer(state, action) { if (action.type === ActionTypes.CHANGE_STATE) { return _.assign({},action.state, {lastAction:action}); } else { return stateReducerInner(state, action); } };
blocks.ts
const defaultBlocks = [ { id:'My first block!!!', position:{x:0,y:0,z:0}, color:'#cccccc' } ]; export default function blocks(blocks = defaultBlocks, action) { switch (action.type) { case 'BLOCK_ADD': return blocks.concat([action.newBlock]); case 'BLOCK_DELETE': return blocks.filter((block)=>block.id!==action.blockId); default: return blocks; } }
ui.ts
const defaultUi = { drawer:false, color: '#cccccc', }; export default function ui(ui=defaultUi, action) { return { drawer: action.type==='UI_DRAWER_TOGGLE'?(!ui.drawer):ui.drawer, color: action.type==='UI_COLOR_SET'?action.value:ui.color, }; }
React
React je Javascriptový framework pro psaní uživatelských rozhraní. Dále v tomto článku budu předpokládat základní znalost Reactu, JSX syntaxe a react-redux. Pokud o tomto frameworku slyšíš poprvé, můžeš si přečíst např. tento článek. Také budu využívat Material UI , abych se nezdržoval stylováním komponent.
Naše React komponenty budou:
- Root bude obsahovat levou lištu, kde budou komponenty Heading a BlockColor. Tato lišta bude vysouvací.
- Heading bude informace o počtu kostiček.
- BlockColor zobrazí aktuální barvu bloku a umožní její změnu.
root.tsx
import * as React from "react"; import {connect} from 'react-redux'; import Drawer from 'material-ui/Drawer'; import MenuItem from 'material-ui/MenuItem'; import RaisedButton from 'material-ui/RaisedButton'; import Divider from 'material-ui/Divider'; import * as FontAwesome from 'react-fontawesome'; import BlockColor from './block-color'; import Heading from './heading'; function mapStateToProps(state){ return { drawer: state.ui.drawer }; } function mapDispatchToProps(dispatch){ return { onMenu: ()=>dispatch({type:'UI_DRAWER_TOGGLE'}), } } function Root({drawer,onMenu}){ return ( <div> <RaisedButton onTouchTap={onMenu} style={{ position: 'fixed', zIndex: 3, top: 0, left: 0, }}> <FontAwesome name="bars"/> </RaisedButton> <Drawer style={{ zIndex: 5 }} open={drawer}> <MenuItem onTouchTap={onMenu} leftIcon={<FontAwesome name="times"/>}>Close</MenuItem> <Heading/> <Divider /> <BlockColor/> </Drawer> </div> ) } export default connect(mapStateToProps, mapDispatchToProps)(Root);
heading.tsx
import * as React from "react"; import {connect} from 'react-redux'; import MenuItem from 'material-ui/MenuItem'; function mapStateToProps(state){ return { size: state.blocks.length }; } function Heading({size}){ return ( <div> <MenuItem> <h2>{size} blocks world</h2> </MenuItem> </div> ) } export default connect(mapStateToProps)(Heading);
block-color.tsx
import * as React from "react"; import {connect} from 'react-redux'; import Subheader from 'material-ui/Subheader'; import MenuItem from 'material-ui/MenuItem'; function mapStateToProps(state){ return { color: state.ui.color }; } function mapDispatchToProps(dispatch){ return { colorChange: (event)=>dispatch({type:'UI_COLOR_SET',value:event.target.value}), } } function BlockColor({color,colorChange}){ return ( <div> <Subheader>Block color</Subheader> <MenuItem> <input type="color" value={color} onChange={colorChange}/> </MenuItem> </div> ) } export default connect(mapStateToProps, mapDispatchToProps)(BlockColor);
Nakonec Root komponentu vyrendrujeme a napojíme na store pomocí react-redux
ReactDOM.render( <Provider store={store}> <MuiThemeProvider> <Root /> </MuiThemeProvider> </Provider>, root );
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, Nasazení na server, ukáži nasazovaní celého projektu na server.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkamiStaženo 576x (8.61 kB)