俄羅斯方塊是一直各種程序語言熱衷實現的經典遊戲,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就好;而跳出框架又有產品的不少特色能夠玩,爲了達到你的需求,這些都將天然的推動技術的發展。
一個項目從零開始,功能一點一滴慢慢累積,就會蓋成高樓,不要畏難,有想法就敲起來吧。 ^_^