chvin /react-tetris

Use React, Redux, Immutable to code Tetris. 🎮

★ 8,735⑂ 2,286👁 163⑃ 23● 34▤ 0
JavaScriptCreated 2016-12-20chvin.github.io/react-tetris/?lan=en
8,735
stars
+0+-2+-4
2,286
forks
163
watchers
23
prs
34
issues
0
releases
–
discussions

README

English introduction
Please view README-EN.md

----
 用React、Redux、Immutable做俄罗斯方块

----
俄罗斯方块是一直各类程序语言热衷实现的经典游戏,JavaScript的实现版本也有很多,用React 做好俄罗斯方块则成了我一个目标。

戳:https://chvin.github.io/react-tetris/ 玩一玩!

----
 效果预览
!效果预览

正常速度的录制,体验流畅。

 响应式
!响应式

不仅指屏幕的自适应,而是在PC使用键盘、在手机使用手指的响应式操作:

!手机

 数据持久化
!数据持久化

玩单机游戏最怕什么?断电。通过订阅 store.subscribe,将state储存在localStorage,精确记录所有状态。网页关了刷新了、程序崩溃了、手机没电了,重新打开连接,都可以继续。

 Redux 状态预览(Redux DevTools extension)
!Redux状态预览

Redux设计管理了所有应存的状态,这是上面持久化的保证。

----
游戏框架使用的是 React + Redux,其中再加入了 Immutable,用它的实例来做来Redux的state。(有关React和Redux的介绍可以看:React入门实例、Redux中文文档)

 1、什么是 Immutable?
Immutable 是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。

 初识:
让我们看下面一段代码:
 JavaScript
function keyLog(touchFn) {
  let data = { key: 'value' };
  f(data);
  console.log(data.key); // 猜猜会打印什么?
}

不查看f,不知道它对 data 做了什么,无法确认会打印什么。但如果 data 是 Immutable,你可以确定打印的是 value:
 JavaScript
function keyLog(touchFn) {
  let data = Immutable.Map({ key: 'value' });
  f(data);
  console.log(data.get('key'));  // value
}


JavaScript 中的Object与Array等使用的是引用赋值,新的对象简单的引用了原始对象,改变新也将影响旧的:
 JavaScript
foo = {a: 1};  bar = foo;  bar.a = 2;
foo.a // 2

虽然这样做可以节约内存,但当应用复杂后,造成了状态不可控,是很大的隐患,节约的内存优点变得得不偿失。

Immutable则不一样,相应的:
 JavaScript
foo = Immutable.Map({ a: 1 });  bar = foo.set('a', 2);
foo.get('a') // 1


 简洁:
在Redux中,它的最优做法是每个reducer都返回一个新的对象(数组),所以我们常常会看到这样的代码:
 JavaScript
// reducer
...
return [
   ...oldArr.slice(0, 3),
   newValue,
   ...oldArr.slice(4)
];

为了返回新的对象(数组),不得不有上面奇怪的样子,而在使用更深的数据结构时会变的更棘手。
让我们看看Immutable的做法:
 JavaScript
// reducer
...
return oldArr.set(4, newValue);

是不是很简洁?

 关于 “===”:
我们知道对于Object与Array的===比较,是对引用地址的比较而不是“值比较”,如:
 JavaScript
{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的做法!
 JavaScript
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.jsL35

Immutable学习资料:
 Immutable.js
 Immutable 详解及 React 中实践


----
 2、如何在Redux中使用Immutable
目标:将state - Immutable化。
关键的库:gajus/redux-immutable
将原来 Redux提供的combineReducers改由上面的库提供:
 JavaScript
// rootReducers.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,

Snapshot: 2026-11-04T14:00:00Z · Data license Apache-2.0 · JSON