# 用 React、Redux、Immutable 做俄罗斯方块
俄罗斯方块是一直各类程序语言热衷实现的经典游戏,JavaScript 的实现版本也有很多,用 React 做好俄罗斯方块则成了我一个目标。
---
### 效果预览
![](https://www.writebug.com/myres/static/uploads/2022/1/1/e7733b69f1a9119635cea5fa4093d549.writebug)
正常速度的录制,体验流畅。
### 响应式
![](https://www.writebug.com/myres/static/uploads/2022/1/1/cf167c148e211b2f544a0be8ec3ffd46.writebug)
不仅指屏幕的自适应,而是 `在PC使用键盘、在手机使用手指的响应式操作`:
![](https://www.writebug.com/myres/static/uploads/2022/1/1/87fd9c51bd8a478ff4530e74ad47e108.writebug)
### 数据持久化
![](https://www.writebug.com/myres/static/uploads/2022/1/1/2befb78b24ffe70cd522ebfd72e5558f.writebug)
玩单机游戏最怕什么?断电。通过订阅 `store.subscribe`,将 state 储存在 localStorage,精确记录所有状态。网页关了刷新了、程序崩溃了、手机没电了,重新打开连接,都可以继续。
### Redux 状态预览([Redux DevTools extension](https://github.com/zalmoxisus/redux-devtools-extension))
![](https://www.writebug.com/myres/static/uploads/2022/1/1/9f63212406d41543283e6f439f0deff6.writebug)
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 对象。
### 初识:
让我们看下面一段代码:
```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.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 改由上面的库提供:
```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, prop2, prop3,
});
// store.js
// 创建store的方法和常规一样
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);
export default store;
```
通过新的 `combineReducers` 将把 store 对象转化成 Immutable,在 container 中使用时也会略有不同(但这正是我们想要的):
```JavaScript
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` 能够以毫秒级精确、高频率的播放音效,这是 `<audio>` 标签所做不到的。在游戏进行中按住方向键移动方块,便可以听到高频率的音效。
![](https://www.writebug.com/myres/static/uploads/2022/1/1/7206b4d1f61f273be8e4fff4383c5c3b.writebug)
`WAA` 是一套全新的相对独立的接口系统,对音频文件拥有更高的处理权限以及更专业的内置音频效果,是 W3C 的推荐接口,能专业处理“音速、音量、环境、音色可视化、高频、音向”等需求,下图介绍了 WAA 的使用流程。
![](https://www.writebug.com/myres/static/uploads/2022/1/1/901c38a6a474b9dc038fda6a3e6901ab.writebug)
其中 Source 代表一个音频源,Destination 代表最终的输出,多个 Source 合成出了 Destination。
源代码:[/src/unit/music.js](https://github.com/chvin/react-tetris/blob/master/src/unit/music.js) 实现了 AJAX 加载 mp3,并转为 WAA,控制播放的过程。
`WAA` 在各个浏览器的最新 2 个版本下的支持情况([CanIUse](http://caniuse.com/#search=webaudio))
![](https://www.writebug.com/myres/static/uploads/2022/1/1/53f8f68f68add82f007e421a63cc3122.writebug)
可以看到 IE 阵营与大部分安卓机不能使用,其他 ok。
Web Audio API 学习资料:
* [Web API 接口 | MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Audio_API)
* [Getting Started with Web Audio
没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
收起资源包目录
100012244-基于 React、Redux、Immutable 制作俄罗斯方块小游戏.zip (95个子文件)
react-tetris
.eslintrc.js 411B
webpack.config.js 359B
src
store
index.js 196B
containers
loader.less 1KB
index.less 2KB
index.js 5KB
unit
music.js 2KB
event.js 957B
block.js 3KB
reducerType.js 810B
const.js 2KB
index.js 3KB
resource
music
music.mp3 53KB
music.wav 1.66MB
image
share.png 6KB
icon.png 2KB
css
loader.css 1KB
components
guide
index.less 2KB
index.js 3KB
next
index.less 72B
index.js 1KB
music
index.less 176B
index.js 530B
point
index.js 2KB
matrix
index.less 120B
index.js 4KB
logo
index.less 904B
index.js 3KB
number
index.less 665B
index.js 2KB
decorate
index.less 863B
index.js 4KB
pause
index.less 176B
index.js 1KB
keyboard
index.less 89B
index.js 5KB
button
index.less 2KB
index.js 1KB
index.js 422B
actions
keyboard.js 819B
index.js 2KB
reducers
speedStart
index.js 485B
next
index.js 461B
startLines
index.js 486B
music
index.js 608B
max
index.js 554B
reset
index.js 371B
drop
index.js 381B
matrix
index.js 469B
clearLines
index.js 468B
cur
index.js 714B
index.js 780B
points
index.js 569B
pause
index.js 385B
keyboard
music.js 279B
right.js 279B
rotate.js 280B
pause.js 279B
left.js 278B
drop.js 278B
reset.js 279B
index.js 429B
down.js 278B
lock
index.js 381B
speedRun
index.js 475B
focus
index.js 313B
control
states.js 6KB
todo
right.js 2KB
rotate.js 2KB
s.js 568B
r.js 753B
left.js 2KB
space.js 2KB
p.js 713B
index.js 286B
down.js 2KB
index.js 855B
i18n.json 2KB
LICENSE 1KB
README-EN.md 13KB
.babelrc 37B
docs
css-1.0.1.css.map 90B
loader.css 1KB
app-1.0.1.js.map 2.85MB
app-1.0.1.js 347KB
index.html 1KB
css-1.0.1.css 7KB
music.mp3 53KB
package.json 2KB
w.config.js 3KB
.gitignore 786B
server
index.html 1KB
index.tmpl.html 938B
README.md 12KB
webpack.production.config.js 361B
共 95 条
- 1
资源评论
神仙别闹
- 粉丝: 2679
- 资源: 7667
下载权益
C知道特权
VIP文章
课程特权
开通VIP
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功