俄罗斯方块是一直各种程序语言热衷实现的经典游戏,JavsScript的实现版本也有不少,用React 作好俄罗斯方块则成了我一个目标。html
戳 https://chvin.github.io/react-tetris 玩一玩!前端
开源地址:https://github.com/chvin/react-tetrishtml5
正常速度的录制,体验流畅。react
不只指屏幕的自适应,而是在PC使用键盘、在手机使用手指的响应式操做
:webpack
玩单机游戏最怕什么?断电。经过订阅 store.subscribe
,将state储存在localStorage,精确记录全部状态。网页关了刷新了、程序崩溃了、手机没电了,从新打开链接,均可以继续。ios
Redux设计管理了全部应存的状态,这是上面持久化的保证。git
游戏框架使用的是 React + Redux,其中再加入了 Immutable,用它的实例来作来Redux的state。(有关React和Redux的介绍能够看:React入门实例、Redux中文文档)github
Immutable 是一旦建立,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操做都会返回一个新的 Immutable 对象。web
让咱们看下面一段代码:ajax
function keyLog(touchFn) { let data = { key: 'value' }; f(data); console.log(data.key); // 猜猜会打印什么? }
不查看f,不知道它对 data
作了什么,没法确认会打印什么。但若是 data
是 Immutable,你能够肯定打印的是 value
:
function keyLog(touchFn) { let data = Immutable.Map({ key: 'value' }); f(data); console.log(data.get('key')); // value }
JavaScript 中的Object
与Array
等使用的是引用赋值,新的对象简单的引用了原始对象,改变新也将影响旧的:
foo = {a: 1}; bar = foo; bar.a = 2; foo.a // 2
虽然这样作能够节约内存,但当应用复杂后,形成了状态不可控,是很大的隐患,节约的内存优势变得得不偿失。
Immutable则不同,相应的:
foo = Immutable.Map({ a: 1 }); bar = foo.set('a', 2); foo.get('a') // 1
在Redux
中,它的最优作法是每一个reducer
都返回一个新的对象(数组),因此咱们经常会看到这样的代码:
// reducer ... return [ ...oldArr.slice(0, 3), newValue, ...oldArr.slice(4) ];
为了返回新的对象(数组),不得不有上面奇怪的样子,而在使用更深的数据结构时会变的更棘手。
让咱们看看Immutable的作法:
// reducer ... return oldArr.set(4, newValue);
是否是很简洁?
咱们知道对于Object
与Array
的===
比较,是对引用地址的比较而不是“值比较”,如:
{a:1, b:2, c:3} === {a:1, b:2, c:3}; // false [1, 2, [3, 4]] === [1, 2, [3, 4]]; // false
对于上面只能采用 deepCopy、
deepCompare`来遍历比较,不只麻烦且好性能。
咱们感觉来一下Immutable
的作法!
map1 = Immutable.Map({a:1, b:2, c:3}); map2 = Immutable.Map({a:1, b:2, c:3}); Immutable.is(map1, map2); // true // List1 = Immutable.List([1, 2, Immutable.List[3, 4]]); List1 = Immutable.fromJS([1, 2, [3, 4]]); List2 = Immutable.fromJS([1, 2, [3, 4]]); Immutable.is(List1, List2); // true
彷佛有阵清风吹过。
React 作性能优化时有一个大招
,就是使用 shouldComponentUpdate()
,但它默认返回 true
,即始终会执行 render()
方法,后面作 Virtual DOM 比较。
在使用原生属性时,为了得出shouldComponentUpdate正确的true
or false
,不得不用deepCopy、deepCompare来算出答案,消耗的性能很不划算。而在有了Immutable以后,使用上面的方法对深层结构的比较就变的易如反掌。
对于「俄罗斯方块」,试想棋盘是一个二维数组
,能够移动的方块则是形状(也是二维数组)
+坐标
。棋盘与方块的叠加则组成了最后的结果Matrix
。游戏中上面的属性都由Immutable
构建,经过它的比较方法,能够轻松写好shouldComponentUpdate
。源代码:/src/components/matrix/index.js#L35
Immutable学习资料:
目标:将state
-> Immutable化。
将原来 Redux提供的combineReducers改由上面的库提供:
// rootReduers.js // import { combineReducers } from 'redux'; // 旧的方法 import { combineReducers } from 'redux-immutable'; // 新的方法 import prop1 from './prop1'; import prop2 from './prop2'; import prop3 from './prop3'; const rootReducer = combineReducers({ prop1, prop2, prop3, }); // store.js // 建立store的方法和常规同样 import { createStore } from 'redux'; import rootReducer from './reducers'; const store = createStore(rootReducer); export default store;
经过新的combineReducers
将把store对象转化成Immutable,在container中使用时也会略有不一样(但这正是咱们想要的):
const mapStateToProps = (state) => ({ prop1: state.get('prop1'), prop2: state.get('prop2'), prop3: state.get('prop3'), next: state.get('next'), }); export default connect(mapStateToProps)(App);
游戏里有不少不一样的音效,而实际上只引用了一个音效文件:/build/music.mp3。借助Web Audio Api
可以以毫秒级精确、高频率的播放音效,这是<audio>
标签所作不到的。在游戏进行中按住方向键移动方块,即可以听到高频率的音效。
WAA
是一套全新的相对独立的接口系统,对音频文件拥有更高的处理权限以及更专业的内置音频效果,是W3C的推荐接口,能专业处理“音速、音量、环境、音色可视化、高频、音向”等需求,下图介绍了WAA的使用流程。
其中Source表明一个音频源,Destination表明最终的输出,多个Source合成出了Destination。
源代码:/src/unit/music.js 实现了ajax加载mp3,并转为WAA,控制播放的过程。
WAA
在各个浏览器的最新2个版本下的支持状况(CanIUse)
能够看到IE阵营与大部分安卓机不能使用,其余ok。
Web Audio Api 学习资料:
技术:
按下方向键水平移动和竖直移动的触发频率是不一样的,游戏能够定义触发频率,代替原生的事件频率,源代码:/src/unit/event.js ;
左右移动能够 delay 掉落的速度,但在撞墙移动的时候 delay 的稍小;在速度为6级时 经过delay 会保证在一行内水平完整移动一次;
对按钮同时注册touchstart
和mousedown
事件,以供响应式游戏。当touchstart
发生时,不会触发mousedown
,而当mousedown
发生时,因为鼠标移开事件元素能够不触发mouseup
,将同时监听mouseout
模拟 mouseup`。源代码:/src/components/keyboard/index.js;
监听了 visibilitychange
事件,当页面被隐藏切换的时候,游戏将不会进行,切换回来将继续,这个focus
状态也被写进了Redux中。因此当用手机玩来电话
时,游戏进度将保存;PC开着游戏干别的也不会听到gameover,这有点像 ios
应用的切换。
在任意
时刻刷新网页,(好比消除方块时、游戏结束时)也能还原当前状态;
游戏中惟一用到的图片,其余都是CSS;
游戏兼容 Chrome、Firefox、IE9+、Edge等;
玩法:
能够在游戏未开始时制定初始的棋盘(十个级别)和速度(六个级别);
一次消除1行得100分、2行得300分、3行得700分、4行得1500分;
方块掉落速度会随着消除的行数增长(每20行增长一个级别);
为全部的component
都编写了shouldComponentUpdate
,在手机上的性能相对有显著的提高。中大型应用在遇到性能上的问题的时候,写好shouldComponentUpdate 必定会帮你一把。
无状态组件`(Stateless Functional Components)是没有生命周期的。而由于上条因素,全部组件都须要生命周期 shouldComponentUpdate,因此未使用无状态组件。
在 webpack.config.js 中的 devServer属性写入
host: '0.0.0.0'`,能够在开发时用ip访问,不局限在localhost;
redux中的store
并不是只能经过connect将方法传递给container
,能够跳出组件,在别的文件拿出来作流程控制(dispatch),源代码:/src/control/states.js;
用 react+redux 作持久化很是的方便,只要将redux状态储存,在每个reduers作初始化的时候读取就好。
经过配置 .eslintrc.js 与 webpack.config.js
,项目中集成了 ESLint` 检验。使用 ESLint 可使编码按规范编写,有效地控制代码质量。不符规范的代码在开发时(或build时)都能经过IDE与控制台发现错误。 参考:Airbnb: React使用规范;
做为一个 React 的练手应用,在实现的过程当中发现小小的“方块”仍是有不少的细节能够优化和打磨,这时就是考验一名前端工程师的细心和功力的时候。
优化的方向既有 React 的自己,好比哪些状态由 Redux存,哪些状态给组件的state就好;而跳出框架又有产品的不少特色能够玩,为了达到你的需求,这些都将天然的推动技术的发展。
一个项目从零开始,功能一点一滴慢慢累积,就会盖成高楼,不要畏难,有想法就敲起来吧。 ^_^