Skip to content
master
Switch branches/tags
Code

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 对象。

�识:

让我们看下�一段代�:

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学习资料:


2�如何在Redux中使用Immutable

目标:将state -> Immutable化。 关键的库:gajus/redux-immutable 将原æ?¥ Reduxæ??供的combineReducers改由上é?¢çš„库æ??供:

// 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中使用时也会略有��(但这正是我们想�的):

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。借助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 学习资料:


4�游�在体验上的优化

  • 技术:
    • 按下方å?‘键水平移动和竖直移动的触å?‘频率是ä¸?å?Œçš„,游æˆ?å?¯ä»¥å®šä¹‰è§¦å?‘频率,代替原生的事件频率,æº?代ç ?:/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 应用的切æ?¢ã€‚
    • 在任æ„?时刻刷新网页,(比如消除方å?—æ—¶ã€?游æˆ?结æ?Ÿæ—¶ï¼‰ä¹Ÿèƒ½è¿˜åŽŸå½“å‰?状æ€?ï¼›
    • 游æˆ?中唯一用到的图片是image,其他都是CSSï¼›
    • 游æˆ?兼容 Chromeã€?Firefoxã€?IE9+ã€?Edge等;
  • 玩法:
    • å?¯ä»¥åœ¨æ¸¸æˆ?未开始时制定åˆ?始的棋盘(å??个级别)和速度(六个级别);
    • 一次消除1行得100分ã€?2行得300分ã€?3行得700分ã€?4行得1500分;
    • æ–¹å?—掉è?½é€Ÿåº¦ä¼šéš?ç?€æ¶ˆé™¤çš„行数增加(æ¯?20行增加一个级别);

5�开�中的�验梳�

  • 为所有的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使用规范;

6�总结

  • 作为一个 React 的练手应用,在实现的过程中å?‘现å°?å°?的“方å?—â€?还是有很多的细节å?¯ä»¥ä¼˜åŒ–和打磨,这时就是考验一å??å‰?端工程师的细心和功力的时候。
  • 优化的方å?‘既有 React 的本身,比如哪些状æ€?ç”± Redux存,哪些状æ€?给组件的state就好;而跳出框架å?ˆæœ‰äº§å“?的很多特点å?¯ä»¥çŽ©ï¼Œä¸ºäº†è¾¾åˆ°ä½ çš„éœ€æ±‚ï¼Œè¿™äº›éƒ½å°†è‡ªç„¶çš„æŽ¨è¿›æŠ€æœ¯çš„å?‘展。
  • 一个项目从零开始,功能一点一滴慢慢累积,就会盖æˆ?高楼,ä¸?è¦?ç•?难,有想法就敲起æ?¥å?§ã€‚ ^_^

7�控制�程

控制�程


8�开�

安装

npm install

�行

npm start

�览自动打开 http://127.0.0.1:8080/

多语言

在 i18n.json �置多语言环境,使用"lan"�数匹�语言如:https://chvin.github.io/react-tetris/?lan=en

打包编译

npm run build

在build文件夹下生�结果。

About

Use React, Redux, Immutable to code Tetris. 🎮

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published