# react-tetris
**Repository Path**: xiongmi/react-tetris
## Basic Information
- **Project Name**: react-tetris
- **Description**: Use React, Redux, Immutable to code Tetris.
- **Primary Language**: JavaScript
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2020-02-23
- **Last Updated**: 2023-09-28
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
### English introduction
Please view [README-EN.md](https://github.com/chvin/react-tetris/blob/master/README-EN.md)
## 用React、Redux、Immutable做俄罗斯方块
俄罗斯方块是一直各类程序语言热衷实现的经典游戏,JavsScript的实现版本也有很多,用React 做好俄罗斯方块则成了我一个目标。
戳:[https://chvin.github.io/react-tetris/](https://chvin.github.io/react-tetris/) 玩一玩!
### 效果预览

正常速度的录制,体验流畅。
### 响应式

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

### 数据持久化

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

Redux设计管理了所有应存的状态,这是上面持久化的保证。
游戏框架使用的是 React + Redux,其中再加入了 Immutable,用它的实例来做来Redux的state。(有关React和Redux的介绍可以看:[React入门实例](http://www.ruanyifeng.com/blog/2015/03/react.html)、[Redux中文文档](https://camsong.github.io/redux-in-chinese/index.html))
## 1、什么是 Immutable?
Immutable 是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。
### 初识:
让我们看下面一段代码:
```js
function keyLog(touchFn) {
let data = { key: 'value' };
f(data);
console.log(data.key); // 猜猜会打印什么?
}
```
不查看f,不知道它对 `data` 做了什么,无法确认会打印什么。但如果 `data` 是 Immutable,你可以确定打印的是 `value`:
```js
function keyLog(touchFn) {
let data = Immutable.Map({ key: 'value' });
f(data);
console.log(data.get('key')); // value
}
```
JavaScript 中的`Object`与`Array`等使用的是引用赋值,新的对象简单的引用了原始对象,改变新也将影响旧的:
```js
foo = {a: 1}; bar = foo; bar.a = 2;
foo.a // 2
```
虽然这样做可以节约内存,但当应用复杂后,造成了状态不可控,是很大的隐患,节约的内存优点变得得不偿失。
Immutable则不一样,相应的:
```js
foo = Immutable.Map({ a: 1 }); bar = foo.set('a', 2);
foo.get('a') // 1
```
### 简洁:
在`Redux`中,它的最优做法是每个`reducer`都返回一个新的对象(数组),所以我们常常会看到这样的代码:
```js
// reducer
...
return [
...oldArr.slice(0, 3),
newValue,
...oldArr.slice(4)
];
```
为了返回新的对象(数组),不得不有上面奇怪的样子,而在使用更深的数据结构时会变的更棘手。
让我们看看Immutable的做法:
```js
// reducer
...
return oldArr.set(4, newValue);
```
是不是很简洁?
### 关于 “===”:
我们知道对于`Object`与`Array`的`===`比较,是对引用地址的比较而不是“值比较”,如:
```js
{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`的做法!
```js
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](https://github.com/chvin/react-tetris/blob/master/src/components/matrix/index.js#L35)
Immutable学习资料:
* [Immutable.js](http://facebook.github.io/immutable-js/)
* [Immutable 详解及 React 中实践](https://github.com/camsong/blog/issues/3)
## 2、如何在Redux中使用Immutable
目标:将`state` -> Immutable化。
关键的库:[gajus/redux-immutable](https://github.com/gajus/redux-immutable)
将原来 Redux提供的combineReducers改由上面的库提供:
``` js
// 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中使用时也会略有不同(但这正是我们想要的):
``` js
const mapStateToProps = (state) => ({
prop1: state.get('prop1'),
prop2: state.get('prop2'),
prop3: state.get('prop3'),
next: state.get('next'),
});
export default connect(mapStateToProps)(App);
```
## 3、Web Audio Api
游戏里有很多不同的音效,而实际上只引用了一个音效文件:[/build/music.mp3](https://github.com/chvin/react-tetris/blob/master/build/music.mp3)。借助`Web Audio Api`能够以毫秒级精确、高频率的播放音效,这是`