├── .editorconfig ├── .eslintrc ├── .gitignore ├── .umirc.js ├── LICENSE ├── README.md ├── mock └── .gitkeep ├── package-lock.json ├── package.json ├── src ├── assets │ └── yay.jpg ├── components │ └── Example.js ├── global.css ├── models │ └── example.js ├── pages │ ├── css │ │ ├── index.css │ │ └── index.css.map │ ├── example │ │ ├── components │ │ │ ├── Counter.tsx │ │ │ └── CounterButton.tsx │ │ ├── index.less │ │ ├── models │ │ │ └── counter.tsx │ │ └── page.tsx │ └── index.tsx ├── services │ └── example.js └── utils │ ├── delay.tsx │ └── request.js ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "umi" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # production 7 | /dist 8 | 9 | # misc 10 | .DS_Store 11 | npm-debug.log* 12 | 13 | # umi 14 | .umi 15 | .umi-production 16 | -------------------------------------------------------------------------------- /.umirc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: [ 3 | [ 4 | "umi-plugin-react", 5 | { 6 | dva: true, 7 | antd: true 8 | } 9 | ] 10 | ], 11 | theme: { 12 | "primary-color": "#0aa679" 13 | }, 14 | base: "/react/react-count-game/", 15 | publicPath: "/react/react-count-game/", 16 | routes: [ 17 | { path: "/", redirect: "/example", component: "./index.tsx" }, 18 | { path: "/example", component: "./example/page.tsx" } 19 | ] 20 | }; 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 yyge 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-calculator-count-app 2 | 3 | 4 | ## 项目介绍 5 | + 测试鼠标速度的小游戏 6 | + 基于```react```, ```dva```, ```typescript```, ```antd``` 7 | + 学习 ts 之后的练手小游戏 8 | 9 | 10 | ### 安装依赖 11 | ``` 12 | npm install 13 | ``` 14 | 15 | ### 启动项目 16 | ``` 17 | npm start 18 | 19 | 浏览器地址栏接上 /example 即可玩耍 20 | ``` 21 | 22 | ### 注意 23 | ``` 24 | 非常简单的小游戏, 但是包含了 ts 在 react 中的应用, 25 | tslint.json 可以按需配置 26 | ``` 27 | 28 | ### Enjoy -------------------------------------------------------------------------------- /mock/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddzy/react-count-game/f91055712fa09e8374fc6db1406053dcc4da91bf/mock/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "start": "umi dev", 5 | "build": "umi build", 6 | "lint": "eslint --ext .js src test", 7 | "precommit": "npm run lint" 8 | }, 9 | "dependencies": { 10 | "antd": "^3.6.6", 11 | "dva": "^2.3.1", 12 | "keymaster": "^1.6.2", 13 | "umi": "^2.13.12", 14 | "umi-plugin-dva": "^0.7.1", 15 | "umi-plugin-react": "^1.0.0-beta.16" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^16.4.6", 19 | "@types/react-dom": "^16.0.6", 20 | "eslint": "^4.14.0", 21 | "eslint-config-umi": "^0.1.1", 22 | "eslint-plugin-flowtype": "^2.34.1", 23 | "eslint-plugin-import": "^2.6.0", 24 | "eslint-plugin-jsx-a11y": "^5.1.1", 25 | "eslint-plugin-react": "^7.1.0", 26 | "husky": "^0.12.0", 27 | "ts-loader": "^4.4.2", 28 | "tslint-react": "^3.6.0", 29 | "typings-for-css-modules-loader": "^1.7.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/assets/yay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddzy/react-count-game/f91055712fa09e8374fc6db1406053dcc4da91bf/src/assets/yay.jpg -------------------------------------------------------------------------------- /src/components/Example.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Example = () => { 4 | return ( 5 |
6 | Example 7 |
8 | ); 9 | }; 10 | 11 | Example.propTypes = { 12 | }; 13 | 14 | export default Example; 15 | -------------------------------------------------------------------------------- /src/global.css: -------------------------------------------------------------------------------- 1 | 2 | html, body, :global(#root) { 3 | height: 100%; 4 | font-family: -apple-system, BlinkMacSystemFont, 5 | "Segoe UI", "Roboto", "Oxygen", 6 | "Ubuntu", "Cantarell", "Fira Sans", 7 | "Droid Sans", "Helvetica Neue", sans-serif; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/models/example.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | 4 | namespace: 'example', 5 | 6 | state: {}, 7 | 8 | subscriptions: { 9 | setup({ dispatch, history }) { 10 | }, 11 | }, 12 | 13 | effects: { 14 | *fetch({ payload }, { call, put }) { 15 | yield put({ type: 'save' }); 16 | }, 17 | }, 18 | 19 | reducers: { 20 | save(state, action) { 21 | return { ...state, ...action.payload }; 22 | }, 23 | }, 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /src/pages/css/index.css: -------------------------------------------------------------------------------- 1 | .app-content{box-sizing:border-box;overflow:hidden;width:400px;height:400px;margin:50px auto;border:1px solid #ccc;box-shadow:0 0 4px 4px #dddddd}.counterbox{display:flex;flex-direction:column;box-sizing:border-box;height:100%;padding:10px}.counterbox .counter-show{flex:1;line-height:1.5;font-size:20px;color:#666}.counterbox .counter-currentcount{height:100px;line-height:100px;font-size:30px}.counterbox .counter-button{flex:3;display:flex;justify-content:center;align-items:center;height:100%;margin-top:10px}/*# sourceMappingURL=./index.css.map */ -------------------------------------------------------------------------------- /src/pages/css/index.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../example/index.less"],"names":[],"mappings":"AAEA,aACE,qBAAA,CACA,eAAA,CACA,WAAA,CACA,YAAA,CACA,gBAAA,CACA,qBAAA,CACA,+BAGF,YACE,YAAA,CACA,qBAAA,CACA,qBAAA,CACA,WAAA,CACA,aAIF,WAAY,eACV,MAAA,CACA,eAAA,CACA,cAAA,CACA,WAGF,WAAY,uBACV,YAAA,CACA,iBAAA,CACA,eAGF,WAAY,iBACV,MAAA,CACA,YAAA,CACA,sBAAA,CACA,kBAAA,CACA,WAAA,CACA"} -------------------------------------------------------------------------------- /src/pages/example/components/Counter.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { connect } from 'dva'; 4 | 5 | const styles: NodeRequire = require('../index.less'); 6 | import CounterButton from './CounterButton'; 7 | import { Dispatch } from '../../../../node_modules/redux'; 8 | 9 | 10 | export interface ICounterProps { 11 | current: number; 12 | record?: number; 13 | dispatch: Dispatch<{type: string, paylod?: any}>; 14 | }; 15 | 16 | 17 | const Counter: React.SFC = (props: ICounterProps): JSX.Element => { 18 | const { current, record, dispatch } = props; 19 | 20 | /* The button click handler */ 21 | const handleClick: React.ReactEventHandler = (event: React.MouseEvent): void => { 22 | console.log(event.currentTarget); 23 | dispatch({ 24 | type: 'counter/addRemote', 25 | }); 26 | 27 | }; 28 | 29 | return ( 30 |
31 |

The highest count is: {record}

32 |
33 | Current count is: {current} 34 |
35 |
36 | 37 |
38 |
39 | ); 40 | } 41 | 42 | 43 | const mapStateToProps = (state) => { 44 | const { current, record } = state.counter; // state.counter; 45 | 46 | return { 47 | current, 48 | record, 49 | }; 50 | } 51 | 52 | 53 | export default connect(mapStateToProps)(Counter); -------------------------------------------------------------------------------- /src/pages/example/components/CounterButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Button } from 'antd'; 4 | 5 | 6 | export interface ICounterButtonProps { 7 | onBtnClick: (event: React.MouseEvent) => void; 8 | }; 9 | 10 | 11 | const CounterButton: React.SFC = (props: ICounterButtonProps): JSX.Element => { 12 | 13 | return ( 14 | 15 | ); 16 | }; 17 | 18 | export default CounterButton; -------------------------------------------------------------------------------- /src/pages/example/index.less: -------------------------------------------------------------------------------- 1 | .app-container {} 2 | 3 | .app-content { 4 | box-sizing: border-box; 5 | overflow: hidden; 6 | width: 400px; 7 | height: 400px; 8 | margin: 50px auto; 9 | border: 1px solid #ccc; 10 | box-shadow: 0 0 4px 4px #dddddd; 11 | } 12 | 13 | .counterbox { 14 | display: flex; 15 | flex-direction: column; 16 | box-sizing: border-box; 17 | height: 100%; 18 | padding: 10px; 19 | } 20 | 21 | 22 | .counterbox .counter-show { 23 | flex: 1; 24 | line-height: 1.5; 25 | font-size: 20px; 26 | color: #666; 27 | } 28 | 29 | .counterbox .counter-currentcount { 30 | height: 100px; 31 | line-height: 100px; 32 | font-size: 30px; 33 | } 34 | 35 | .counterbox .counter-button { 36 | flex: 3; 37 | display: flex; 38 | justify-content: center; 39 | align-items: center; 40 | height: 100%; 41 | margin-top: 10px; 42 | } -------------------------------------------------------------------------------- /src/pages/example/models/counter.tsx: -------------------------------------------------------------------------------- 1 | import key from 'keymaster'; 2 | 3 | import delay from "../../../utils/delay"; 4 | 5 | 6 | export default { 7 | 8 | namespace: 'counter', 9 | 10 | state: { 11 | current: 0, /* current count */ 12 | record: 0, /* highest count */ 13 | }, 14 | 15 | reducers: { 16 | add (state) { 17 | const newCurrent = state.current + 1; 18 | return { 19 | ...state, 20 | record: newCurrent > state.record ? newCurrent : state.record, 21 | current: newCurrent, 22 | }; 23 | }, 24 | minus (state) { 25 | const newCurrent = state.current - 1; 26 | return { 27 | ...state, 28 | current: newCurrent, 29 | }; 30 | }, 31 | }, 32 | 33 | effects: { 34 | *addRemote ({ payload }, { call, put }) { 35 | console.log(payload); 36 | yield put({ type: 'add' }); 37 | yield call(delay, 1000); 38 | yield put({ type: 'minus' }); 39 | }, 40 | }, 41 | 42 | subscriptions: { 43 | keyboardWather ({dispatch}) { 44 | key('ctrl+up', () => { dispatch({ type: 'addRemote' }) }); 45 | }, 46 | }, 47 | 48 | }; 49 | 50 | -------------------------------------------------------------------------------- /src/pages/example/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import Counter from './components/Counter'; 4 | 5 | const styles: NodeRequire = require('./index.less'); 6 | 7 | 8 | const App = () => { 9 | return ( 10 |
11 |
12 | 13 |
14 |
15 | ); 16 | }; 17 | 18 | 19 | export default App; -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const HomePage = () => { 4 | return ( 5 | 6 | ); 7 | }; 8 | 9 | 10 | export default HomePage; -------------------------------------------------------------------------------- /src/services/example.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | 3 | export function query() { 4 | return request('/api/users'); 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/delay.tsx: -------------------------------------------------------------------------------- 1 | 2 | export default function delay(time: number): Promise<(resolve?: () => {}, reject?: () => {}) => {}> { 3 | return new Promise((resolve) => { 4 | setTimeout(() => { 5 | resolve(); 6 | }, time); 7 | }); 8 | } -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import fetch from 'dva/fetch'; 2 | 3 | function parseJSON(response) { 4 | return response.json(); 5 | } 6 | 7 | function checkStatus(response) { 8 | if (response.status >= 200 && response.status < 300) { 9 | return response; 10 | } 11 | 12 | const error = new Error(response.statusText); 13 | error.response = response; 14 | throw error; 15 | } 16 | 17 | /** 18 | * Requests a URL, returning a promise. 19 | * 20 | * @param {string} url The URL we want to request 21 | * @param {object} [options] The options we want to pass to "fetch" 22 | * @return {object} An object containing either "data" or "err" 23 | */ 24 | export default function request(url, options) { 25 | return fetch(url, options) 26 | .then(checkStatus) 27 | .then(parseJSON) 28 | .then(data => ({ data })) 29 | .catch(err => ({ err })); 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "moduleResolution": "node", 5 | "allowSyntheticDefaultImports": true, 6 | "experimentalDecorators": true, 7 | "jsx": "preserve", 8 | "noUnusedParameters": true, 9 | "noUnusedLocals": true, 10 | "target": "es6", 11 | "lib": [ 12 | "dom", 13 | "es7" 14 | ] 15 | }, 16 | "include": [ 17 | "src/**/*" 18 | ], 19 | "exclude": [ 20 | "node_modules", 21 | "lib", 22 | "es" 23 | ], 24 | "module": "ESNext" 25 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:latest", 4 | "tslint-react" 5 | ], 6 | "linterOptions": { 7 | "exclude": [ 8 | "node_modules/**/*.ts", 9 | "src/**/*.{ts,tsx}" 10 | ] 11 | }, 12 | "rules": { 13 | "object-literal-sort-keys": false, 14 | "jsx-no-lambda": false, 15 | "eofline": false, 16 | "no-consecutive-blank-lines": false, 17 | "no-var-requires": false, 18 | "quotemark": false, 19 | "space-within-parens": false, 20 | "no-submodule-imports": false, 21 | "no-implicit-dependencies": false, 22 | "ordered-imports": [ false ], 23 | "jsx-boolean-value": false, 24 | "no-trailing-whitespace": false, 25 | "semicolon": false, 26 | "trailing-comma": false, 27 | "space-in-parents": false, 28 | "typedef-whitespace": false, 29 | "whitespace": false, 30 | "interface-over-type-literal": true, 31 | "no-duplicate-imports": false, 32 | "no-namespace": true, 33 | "no-internal-module": true, 34 | "space-before-function-paren": ["ignore"], 35 | "no-console": ["error", { "allow": ["log", "error"] }], 36 | "no-string-literal": false, 37 | "max-line-length": false 38 | } 39 | } --------------------------------------------------------------------------------