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设计管ç?†äº†æ‰€æœ‰åº”å˜çš„状æ€?,这是上é?¢æŒ?久化的ä¿?è¯?。
游æˆ?框架使用的是 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应用的切æ?¢ã€‚ - 在
ä»»æ„?时刻刷新网页,(比如消除方å?—æ—¶ã€?游æˆ?结æ?Ÿæ—¶ï¼‰ä¹Ÿèƒ½è¿˜åŽŸå½“å‰?状æ€?ï¼› - 游æˆ?ä¸å”¯ä¸€ç”¨åˆ°çš„图片是
,其他都是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文件夹下生�结果。









