├── .eslintignore ├── src ├── pages │ ├── Home │ │ ├── style.module.css │ │ ├── flow.ts │ │ └── index.tsx │ └── shared.d.ts ├── themes │ ├── development.module.css │ ├── production.module.css │ ├── testnet.module.css │ └── index.ts ├── react-app-env.d.ts ├── third_party │ └── index.ts ├── shared │ └── index.ts ├── components │ └── changeColor │ │ ├── style.module.css │ │ └── index.tsx ├── typings │ └── local.d.ts ├── services │ ├── index.ts │ ├── transport.ts │ └── authorization.ts ├── App.test.tsx ├── store │ ├── shared.d.ts │ ├── global.ts │ └── index.ts ├── App.css ├── index.css ├── index.tsx ├── App.tsx ├── serviceWorker.ts └── assets │ └── logo.svg ├── public ├── robots.txt ├── favicon.png ├── manifest.json ├── index.html └── css │ └── normalize.css ├── .env.development ├── .env.production ├── tools ├── doc.png ├── action.png ├── profiler.gif ├── components.gif ├── performance.png └── redux_timer.gif ├── .eslintrc.json ├── config-overrides.js ├── .editorconfig ├── tsconfig.json ├── .gitignore ├── package.json ├── README.md └── LICENSE /.eslintignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/Home/style.module.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/themes/development.module.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/themes/production.module.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/themes/testnet.module.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/third_party/index.ts: -------------------------------------------------------------------------------- 1 | export const thirdParty = "thirdParty"; 2 | -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export enum Log { 2 | Ok = 0, 3 | Error = 1 4 | } 5 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | REACT_APP_RUN_MODE=development 2 | REACT_APP_API_URL=https://blockflats.com 3 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | REACT_APP_RUN_MODE=production 2 | REACT_APP_API_URL=https://blockflats.com 3 | -------------------------------------------------------------------------------- /tools/doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightningminers/typescript-react-starter/HEAD/tools/doc.png -------------------------------------------------------------------------------- /tools/action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightningminers/typescript-react-starter/HEAD/tools/action.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightningminers/typescript-react-starter/HEAD/public/favicon.png -------------------------------------------------------------------------------- /tools/profiler.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightningminers/typescript-react-starter/HEAD/tools/profiler.gif -------------------------------------------------------------------------------- /tools/components.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightningminers/typescript-react-starter/HEAD/tools/components.gif -------------------------------------------------------------------------------- /tools/performance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightningminers/typescript-react-starter/HEAD/tools/performance.png -------------------------------------------------------------------------------- /tools/redux_timer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightningminers/typescript-react-starter/HEAD/tools/redux_timer.gif -------------------------------------------------------------------------------- /src/components/changeColor/style.module.css: -------------------------------------------------------------------------------- 1 | .long{ 2 | color: rgb(51, 153, 71); 3 | } 4 | .short{ 5 | color: red; 6 | } 7 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app", 3 | "rules": { 4 | "jsx-a11y/alt-text": 0, 5 | "jsx-a11y/anchor-is-valid": 0 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/shared.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { RouteComponentProps } from "react-router-dom"; 3 | 4 | export interface IPageRoute extends RouteComponentProps {} -------------------------------------------------------------------------------- /src/typings/local.d.ts: -------------------------------------------------------------------------------- 1 | 2 | export {} 3 | 4 | declare global { 5 | interface Window{ 6 | __REDUX_DEVTOOLS_EXTENSION__: Function; 7 | TRS_STORE: any; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | import transport, { REACT_APP_API_URL } from "./transport"; 2 | 3 | /** 4 | * 获取所有测试 5 | */ 6 | export const countryAll = () => { 7 | const params = { 8 | url: `${REACT_APP_API_URL}/v1/all` 9 | }; 10 | return transport(params); 11 | } 12 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/store/shared.d.ts: -------------------------------------------------------------------------------- 1 | import { IGlobal } from "./global"; 2 | import { IHomePage } from "../pages/home/flow"; 3 | 4 | 5 | export interface IStoreState { 6 | global: IGlobal, 7 | homePage: IHomePage, 8 | } 9 | 10 | export interface IAction { 11 | type: string; 12 | payload: T; 13 | } 14 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "DueDEX", 3 | "name": "DueDEX", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | } 8 | 9 | .App-header { 10 | background-color: #282c34; 11 | min-height: 100vh; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | font-size: calc(10px + 2vmin); 17 | color: white; 18 | } 19 | 20 | .App-link { 21 | color: #09d3ac; 22 | } 23 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 750px; 3 | margin: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | module.exports = function override(config, env) { 2 | // do stuff with the webpack config... 3 | require('react-app-rewire-postcss')(config, { 4 | plugins: loader => [ 5 | require('postcss-px-to-viewport')({ 6 | viewportWidth: 750, 7 | unitPrecision: 3, 8 | viewportUnit: 'vw', 9 | minPixelValue: 1, 10 | mediaQuery: false, 11 | selectorBlackList: ['.ignore'] 12 | }) 13 | ], 14 | }) 15 | return config; 16 | }; -------------------------------------------------------------------------------- /src/themes/index.ts: -------------------------------------------------------------------------------- 1 | import development from "./development.module.css"; 2 | import testnet from "./testnet.module.css"; 3 | import production from "./production.module.css"; 4 | 5 | const theme = { 6 | development, 7 | testnet, 8 | production 9 | }; 10 | 11 | type themeKey = "development" | "testnet" | "production"; 12 | 13 | /** 14 | * 主题只能处理颜色,字体大小等 15 | * @param key 16 | */ 17 | const chooseTheme = (key: themeKey) => { 18 | return theme[key]; 19 | } 20 | 21 | export default chooseTheme; 22 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | 9 | # Matches multiple files with brace expansion notation 10 | # Set default charset 11 | [*.{ts,tsx}] 12 | charset = utf-8 13 | indent_style = space 14 | indent_size = 2 15 | trim_trailing_whitespace = true 16 | 17 | # Matches the exact files either package.json or .travis.yml 18 | [{package.json}] 19 | indent_style = space 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/components/changeColor/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "./style.module.scss"; 3 | 4 | interface IProps { 5 | value: string; 6 | } 7 | 8 | /** 9 | * 依据Long和Short改变字体颜色 10 | * @description Long 绿色 Short 红色 如果远程的字段不是 Long 或 Short 显示原始 11 | * @param props 12 | */ 13 | const ChangeColor: React.SFC = props => { 14 | const { value, children } = props; 15 | const side = value.toLowerCase(); 16 | if (side === "long" || side === "short") { 17 | return
{children}
; 18 | } else { 19 | return <>{children}; 20 | } 21 | }; 22 | 23 | export default ChangeColor; 24 | -------------------------------------------------------------------------------- /src/services/transport.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig } from "axios"; 2 | import { getToken } from "./authorization"; 3 | 4 | export const { REACT_APP_API_URL } = process.env; 5 | 6 | const service = (params: AxiosRequestConfig, token?: string) => { 7 | if (token) { 8 | const { headers = {} } = params; 9 | headers["Authorization"] = `Bearer ${token}`; 10 | } 11 | return axios(params); 12 | } 13 | 14 | const transport = (params: AxiosRequestConfig) => { 15 | const token = getToken(); 16 | if (token) { 17 | return service(params, token); 18 | } else { 19 | return service(params); 20 | } 21 | } 22 | 23 | export default transport; 24 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { Provider } from "react-redux"; 4 | import "./index.css"; 5 | import App from "./App"; 6 | import createMyStore from "./store"; 7 | // import * as serviceWorker from './serviceWorker'; 8 | 9 | const store = createMyStore(); 10 | 11 | window.TRS_STORE = store; 12 | 13 | ReactDOM.render( 14 | 15 | 16 | , 17 | document.getElementById('root') 18 | ); 19 | 20 | // If you want your app to work offline and load faster, you can change 21 | // unregister() to register() below. Note this comes with some pitfalls. 22 | // Learn more about service workers: https://bit.ly/CRA-PWA 23 | // serviceWorker.unregister(); 24 | -------------------------------------------------------------------------------- /src/services/authorization.ts: -------------------------------------------------------------------------------- 1 | let cacheToken = ""; 2 | 3 | const LOCAL_STORAGE_TOKEN = "LOCAL_STORAGE_TOKEN"; 4 | 5 | export const getToken = (): string => { 6 | if (cacheToken) { 7 | return cacheToken; 8 | } 9 | const tokenString = localStorage.getItem(LOCAL_STORAGE_TOKEN); 10 | if (tokenString) { 11 | try{ 12 | const json = JSON.parse(tokenString); 13 | cacheToken = json.token; 14 | return cacheToken; 15 | } catch(e) {} 16 | } 17 | return ""; 18 | } 19 | 20 | export const removeToken = () => { 21 | cacheToken = ""; 22 | localStorage.removeItem(LOCAL_STORAGE_TOKEN); 23 | } 24 | 25 | export const setToken = (json: any) => { 26 | cacheToken = json.token; 27 | localStorage.setItem(LOCAL_STORAGE_TOKEN, JSON.stringify(json)); 28 | } 29 | -------------------------------------------------------------------------------- /src/store/global.ts: -------------------------------------------------------------------------------- 1 | import { IAction } from "./shared"; 2 | 3 | export type UpdateUserId = (userId: number) => IAction; 4 | 5 | export interface IGlobal { 6 | userId: number; 7 | } 8 | 9 | const GLOBAL_UPDATE_USERID = "GLOBAL_UPDATE_USERID"; 10 | 11 | export const updateUserId = (userId: number) => ({ 12 | type: GLOBAL_UPDATE_USERID, 13 | payload: userId, 14 | }); 15 | 16 | const initialState: IGlobal = { 17 | userId: 0, 18 | }; 19 | 20 | const globalReducer = (state = initialState, action: IAction) => { 21 | const { type, payload } = action; 22 | switch(type) { 23 | case GLOBAL_UPDATE_USERID: { 24 | return {...state, userId: payload}; 25 | } 26 | default: { 27 | return {...state}; 28 | } 29 | } 30 | } 31 | 32 | export default globalReducer; 33 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { BrowserRouter as Router, Route } from "react-router-dom"; 4 | import { IStoreState } from "./store/shared"; 5 | import { updateUserId } from "./store/global"; 6 | import Home from "./pages/home"; 7 | 8 | const App = () => { 9 | const global = useSelector((state: IStoreState) => ({ 10 | ...state.global 11 | })); 12 | const dispatch = useDispatch(); 13 | return ( 14 | 15 | 16 |
17 | 22 |
23 |
24 | ) 25 | } 26 | 27 | export default App; 28 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers, applyMiddleware, compose } from "redux"; 2 | import logger from "redux-logger"; 3 | import thunk from "redux-thunk" 4 | import globalReducer from "./global"; 5 | import homeReducer from "../pages/home/flow"; 6 | import { IStoreState } from "./shared"; 7 | 8 | const reducers = combineReducers({ 9 | global: globalReducer, 10 | homePage: homeReducer, 11 | }); 12 | 13 | const createMyStore = () => { 14 | const store = process.env.REACT_APP_RUN_MODE === "production" ? ( 15 | createStore(reducers, applyMiddleware(thunk)) 16 | ) : ( 17 | window.__REDUX_DEVTOOLS_EXTENSION__? ( 18 | createStore(reducers, compose(applyMiddleware(thunk, logger), window.__REDUX_DEVTOOLS_EXTENSION__({}))) 19 | ) : ( 20 | createStore(reducers, applyMiddleware(thunk, logger)) 21 | ) 22 | ); 23 | return store; 24 | } 25 | 26 | export default createMyStore; 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # # TypeScript v1 declaration files 40 | # typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | 64 | # dist 65 | dist/ -------------------------------------------------------------------------------- /src/pages/Home/flow.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from "redux"; 2 | import { IAction } from "../../store/shared"; 3 | 4 | export type UpdateCount = (count: number) => void; 5 | export type AsyncUpdateCount = (count: number) => void; 6 | 7 | export interface IHomePage { 8 | count: number; 9 | } 10 | 11 | const HOME_UPDATE_COUNT = "HOME_UPDATE_COUNT"; 12 | 13 | /** 14 | * 同步更新计数器 15 | * @param count number 16 | */ 17 | export const updateCount = (count: number) => { 18 | const newCount = count + 1; 19 | return { 20 | type: HOME_UPDATE_COUNT, 21 | payload: newCount, 22 | } 23 | } 24 | 25 | 26 | /** 27 | * 异步更新计数器 28 | * @param count number 29 | * @param dispatch Dispatch 30 | */ 31 | export const asyncUpdateCount = (count: number) => (dispatch: Dispatch) => { 32 | setTimeout(() => { 33 | const newCount = count + 1; 34 | dispatch({ 35 | type: HOME_UPDATE_COUNT, 36 | payload: newCount 37 | }) 38 | }, 1000); 39 | } 40 | 41 | const initialState: IHomePage = { 42 | count: 0, 43 | } 44 | 45 | const homeReducer = (state = initialState, action: IAction) => { 46 | const { type, payload } = action; 47 | switch(type) { 48 | case HOME_UPDATE_COUNT: { 49 | return { 50 | ...state, count: payload 51 | }; 52 | } 53 | default: { 54 | return {...state}; 55 | } 56 | } 57 | } 58 | 59 | export default homeReducer; 60 | -------------------------------------------------------------------------------- /src/pages/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { IPageRoute } from "../shared"; 4 | import { IGlobal } from "../../store/global"; 5 | import { IStoreState } from "../../store/shared"; 6 | import { 7 | IHomePage, 8 | UpdateCount, 9 | AsyncUpdateCount, 10 | updateCount, 11 | asyncUpdateCount 12 | } from "./flow"; 13 | import logoSvg from "../../assets/logo.svg"; 14 | 15 | interface IProps extends IPageRoute, IHomePage { 16 | global: IGlobal; 17 | updateCount: UpdateCount; 18 | asyncUpdateCount: AsyncUpdateCount; 19 | } 20 | 21 | class Home extends React.Component{ 22 | onClick = () => { 23 | this.props.updateCount(this.props.count) 24 | } 25 | 26 | onClickAsync = () => { 27 | this.props.asyncUpdateCount(this.props.count); 28 | } 29 | render(){ 30 | return( 31 |
32 | 33 | 34 |
{this.props.count}
35 | 36 |
37 | ); 38 | } 39 | } 40 | 41 | const mapStateToProps = (state: IStoreState) => ({ 42 | global: state.global, 43 | ...state.homePage 44 | }); 45 | 46 | const mapDispatchToProps = { 47 | updateCount, 48 | asyncUpdateCount 49 | } 50 | 51 | export default connect(mapStateToProps, mapDispatchToProps)(Home); 52 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | 23 | 自由远程系统 24 | 25 | 26 | 27 |
28 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-react-starter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@types/jest": "24.0.18", 7 | "@types/node": "12.7.9", 8 | "@types/react": "16.9.4", 9 | "@types/react-dom": "16.9.1", 10 | "@types/react-redux": "^7.1.4", 11 | "@types/react-router-dom": "^5.1.0", 12 | "@types/redux-logger": "^3.0.7", 13 | "axios": "^0.19.0", 14 | "react": "^16.10.1", 15 | "react-dom": "^16.10.1", 16 | "react-redux": "^7.1.1", 17 | "react-router-dom": "^5.1.2", 18 | "react-scripts": "3.2.0", 19 | "redux": "^4.0.4", 20 | "redux-logger": "^3.0.6", 21 | "redux-saga": "^1.1.1", 22 | "redux-thunk": "^2.3.0", 23 | "typescript": "3.6.3" 24 | }, 25 | "devDependencies": { 26 | "customize-cra": "^0.8.0", 27 | "env-cmd": "^10.0.1", 28 | "husky": "^3.0.8", 29 | "lint-staged": "^9.4.1", 30 | "postcss-px-to-viewport": "^1.1.1", 31 | "react-app-rewire-postcss": "^3.0.2", 32 | "react-app-rewired": "^2.1.3" 33 | }, 34 | "scripts": { 35 | "start": "react-app-rewired start", 36 | "build": "react-app-rewired build", 37 | "build:development": "env-cmd -f .env.development npm run build", 38 | "build:production": "env-cmd -f .env.production npm run build", 39 | "test": "react-app-rewired test", 40 | "eject": "react-scripts eject" 41 | }, 42 | "eslintConfig": { 43 | "extends": "react-app" 44 | }, 45 | "browserslist": { 46 | "production": [ 47 | ">0.2%", 48 | "not dead", 49 | "not op_mini all" 50 | ], 51 | "development": [ 52 | "last 1 chrome version", 53 | "last 1 firefox version", 54 | "last 1 safari version" 55 | ] 56 | }, 57 | "husky": { 58 | "hooks": { 59 | "pre-commit": "lint-staged" 60 | } 61 | }, 62 | "lint-staged": { 63 | "src/**/*.{js,jsx,ts,tsx,md}": [ 64 | "git add" 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # typescript-react-starter 2 | 3 | ![license](https://img.shields.io/github/license/icepy/typescript-react-starter) 4 | ![forks](https://img.shields.io/github/forks/icepy/typescript-react-starter) 5 | ![stars](https://img.shields.io/github/stars/icepy/typescript-react-starter) 6 | ![issues](https://img.shields.io/github/issues/icepy/typescript-react-starter) 7 | 8 | `typescript-react-starter` 是一个使用CRA编写的 TypeScript Starter 工程,集成了 [ React + React-Router + Redux + Redux-Thunk ],旨在为移动 Web 应用开发者提供 “开箱即用” 的 TypeScript 样板工程,开发者只需下载此项目,根据范例即可编写复杂大型的 React 应用。 9 | 10 | ## Install 11 | 12 | ```bash 13 | $ git clone git@github.com:icepy/typescript-react-starter.git 14 | $ cd typescript-react-starter 15 | $ yarn 16 | $ npm start 17 | ``` 18 | 19 | 使用浏览器访问 `http://localhost:3000/`。(更多命令可查看 package.json 的 scripts 字段) 20 | 21 | ## 工程结构 22 | 23 | - assets 放置图片等文件资源 24 | - components 放置被共享的组件 25 | - pages 放置页面级别的组件 26 | - services 放置本工程依赖的所有请求服务 27 | - store 放置本工程被管理的数据流 28 | - themes 放置本工程主题文件 29 | - third_party 放置依赖的第三方 30 | - typings 放置类型增强 31 | - shared 共享的集合 32 | - App.tsx 应用的容器文件 33 | - index.tsx 入口文件 34 | 35 | ## 函数式编程 36 | 37 | 函数式的好处,非常多,举例不拘。不管是样式还是组件逻辑,我们只有一个原则:组合,我们需要从这样的角度来考虑任何问题。 38 | 39 | ## 适配方案 40 | 41 | 基于iPhone 6来完成设计稿,即 1334 * 750,在编写的时候直接使用 `px` 单位即可。 42 | 43 | ## 必要的注释 44 | 45 | ![img](./tools/action.png) 46 | ![img](./tools/doc.png) 47 | 48 | ## 时间旅行&操作日志 49 | 50 | 操作的回溯让我们对某一个业务的变化了如指掌 51 | 52 | ![img](./tools/redux_timer.gif) 53 | 54 | ## Component 55 | 56 | 节点可以打上 tag 或 name,方便于任何一个人来理解业务 57 | 58 | ![img](./tools/components.gif) 59 | 60 | ## Profiler 61 | 62 | > 优化是另一个问题,我们会基于此来展开优化,包括网络,资源文件,store 的大小,react 组合,分割等等方面。 63 | 64 | ![img](./tools/profiler.gif) 65 | 66 | ![img](./tools/performance.png) 67 | 68 | ## 远程回溯 69 | 70 | > 这个事情就能做一个大系统 71 | 72 | 当用户端发生一个错误时,我们可以将当前节点的数据快照传输到服务端,然后进行错误分析。 73 | 74 | ## CI/CD 75 | 76 | > 我之前一直使用的是gitlab,所以CI/CD是基于 runner 做的。 77 | 78 | ## LICENSE 79 | 80 | GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 81 | -------------------------------------------------------------------------------- /src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | (process as { env: { [key: string]: string } }).env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener('load', () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | 'This web app is being served cache-first by a service ' + 54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === 'installed') { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | 'New content is available and will be used when all ' + 82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log('Content is cached for offline use.'); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error('Error during service worker registration:', error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl) 112 | .then(response => { 113 | // Ensure service worker exists, and that we really are getting a JS file. 114 | const contentType = response.headers.get('content-type'); 115 | if ( 116 | response.status === 404 || 117 | (contentType != null && contentType.indexOf('javascript') === -1) 118 | ) { 119 | // No service worker found. Probably a different app. Reload the page. 120 | navigator.serviceWorker.ready.then(registration => { 121 | registration.unregister().then(() => { 122 | window.location.reload(); 123 | }); 124 | }); 125 | } else { 126 | // Service worker found. Proceed as normal. 127 | registerValidSW(swUrl, config); 128 | } 129 | }) 130 | .catch(() => { 131 | console.log( 132 | 'No internet connection found. App is running in offline mode.' 133 | ); 134 | }); 135 | } 136 | 137 | export function unregister() { 138 | if ('serviceWorker' in navigator) { 139 | navigator.serviceWorker.ready.then(registration => { 140 | registration.unregister(); 141 | }); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | 40 | h1 { 41 | font-size: 2em; 42 | margin: 0.67em 0; 43 | } 44 | 45 | /* Grouping content 46 | ========================================================================== */ 47 | 48 | /** 49 | * 1. Add the correct box sizing in Firefox. 50 | * 2. Show the overflow in Edge and IE. 51 | */ 52 | 53 | hr { 54 | box-sizing: content-box; /* 1 */ 55 | height: 0; /* 1 */ 56 | overflow: visible; /* 2 */ 57 | } 58 | 59 | /** 60 | * 1. Correct the inheritance and scaling of font size in all browsers. 61 | * 2. Correct the odd `em` font sizing in all browsers. 62 | */ 63 | 64 | pre { 65 | font-family: monospace, monospace; /* 1 */ 66 | font-size: 1em; /* 2 */ 67 | } 68 | 69 | /* Text-level semantics 70 | ========================================================================== */ 71 | 72 | /** 73 | * Remove the gray background on active links in IE 10. 74 | */ 75 | 76 | a { 77 | background-color: transparent; 78 | } 79 | 80 | /** 81 | * 1. Remove the bottom border in Chrome 57- 82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 83 | */ 84 | 85 | abbr[title] { 86 | border-bottom: none; /* 1 */ 87 | text-decoration: underline; /* 2 */ 88 | text-decoration: underline dotted; /* 2 */ 89 | } 90 | 91 | /** 92 | * Add the correct font weight in Chrome, Edge, and Safari. 93 | */ 94 | 95 | b, 96 | strong { 97 | font-weight: bolder; 98 | } 99 | 100 | /** 101 | * 1. Correct the inheritance and scaling of font size in all browsers. 102 | * 2. Correct the odd `em` font sizing in all browsers. 103 | */ 104 | 105 | code, 106 | kbd, 107 | samp { 108 | font-family: monospace, monospace; /* 1 */ 109 | font-size: 1em; /* 2 */ 110 | } 111 | 112 | /** 113 | * Add the correct font size in all browsers. 114 | */ 115 | 116 | small { 117 | font-size: 80%; 118 | } 119 | 120 | /** 121 | * Prevent `sub` and `sup` elements from affecting the line height in 122 | * all browsers. 123 | */ 124 | 125 | sub, 126 | sup { 127 | font-size: 75%; 128 | line-height: 0; 129 | position: relative; 130 | vertical-align: baseline; 131 | } 132 | 133 | sub { 134 | bottom: -0.25em; 135 | } 136 | 137 | sup { 138 | top: -0.5em; 139 | } 140 | 141 | /* Embedded content 142 | ========================================================================== */ 143 | 144 | /** 145 | * Remove the border on images inside links in IE 10. 146 | */ 147 | 148 | img { 149 | border-style: none; 150 | } 151 | 152 | /* Forms 153 | ========================================================================== */ 154 | 155 | /** 156 | * 1. Change the font styles in all browsers. 157 | * 2. Remove the margin in Firefox and Safari. 158 | */ 159 | 160 | button, 161 | input, 162 | optgroup, 163 | select, 164 | textarea { 165 | font-family: inherit; /* 1 */ 166 | font-size: 100%; /* 1 */ 167 | line-height: 1.15; /* 1 */ 168 | margin: 0; /* 2 */ 169 | } 170 | 171 | /** 172 | * Show the overflow in IE. 173 | * 1. Show the overflow in Edge. 174 | */ 175 | 176 | button, 177 | input { /* 1 */ 178 | overflow: visible; 179 | } 180 | 181 | /** 182 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 183 | * 1. Remove the inheritance of text transform in Firefox. 184 | */ 185 | 186 | button, 187 | select { /* 1 */ 188 | text-transform: none; 189 | } 190 | 191 | /** 192 | * Correct the inability to style clickable types in iOS and Safari. 193 | */ 194 | 195 | button, 196 | [type="button"], 197 | [type="reset"], 198 | [type="submit"] { 199 | -webkit-appearance: button; 200 | } 201 | 202 | /** 203 | * Remove the inner border and padding in Firefox. 204 | */ 205 | 206 | button::-moz-focus-inner, 207 | [type="button"]::-moz-focus-inner, 208 | [type="reset"]::-moz-focus-inner, 209 | [type="submit"]::-moz-focus-inner { 210 | border-style: none; 211 | padding: 0; 212 | } 213 | 214 | /** 215 | * Restore the focus styles unset by the previous rule. 216 | */ 217 | 218 | button:-moz-focusring, 219 | [type="button"]:-moz-focusring, 220 | [type="reset"]:-moz-focusring, 221 | [type="submit"]:-moz-focusring { 222 | outline: 1px dotted ButtonText; 223 | } 224 | 225 | /** 226 | * Correct the padding in Firefox. 227 | */ 228 | 229 | fieldset { 230 | padding: 0.35em 0.75em 0.625em; 231 | } 232 | 233 | /** 234 | * 1. Correct the text wrapping in Edge and IE. 235 | * 2. Correct the color inheritance from `fieldset` elements in IE. 236 | * 3. Remove the padding so developers are not caught out when they zero out 237 | * `fieldset` elements in all browsers. 238 | */ 239 | 240 | legend { 241 | box-sizing: border-box; /* 1 */ 242 | color: inherit; /* 2 */ 243 | display: table; /* 1 */ 244 | max-width: 100%; /* 1 */ 245 | padding: 0; /* 3 */ 246 | white-space: normal; /* 1 */ 247 | } 248 | 249 | /** 250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 251 | */ 252 | 253 | progress { 254 | vertical-align: baseline; 255 | } 256 | 257 | /** 258 | * Remove the default vertical scrollbar in IE 10+. 259 | */ 260 | 261 | textarea { 262 | overflow: auto; 263 | } 264 | 265 | /** 266 | * 1. Add the correct box sizing in IE 10. 267 | * 2. Remove the padding in IE 10. 268 | */ 269 | 270 | [type="checkbox"], 271 | [type="radio"] { 272 | box-sizing: border-box; /* 1 */ 273 | padding: 0; /* 2 */ 274 | } 275 | 276 | /** 277 | * Correct the cursor style of increment and decrement buttons in Chrome. 278 | */ 279 | 280 | [type="number"]::-webkit-inner-spin-button, 281 | [type="number"]::-webkit-outer-spin-button { 282 | height: auto; 283 | } 284 | 285 | /** 286 | * 1. Correct the odd appearance in Chrome and Safari. 287 | * 2. Correct the outline style in Safari. 288 | */ 289 | 290 | [type="search"] { 291 | -webkit-appearance: textfield; /* 1 */ 292 | outline-offset: -2px; /* 2 */ 293 | } 294 | 295 | /** 296 | * Remove the inner padding in Chrome and Safari on macOS. 297 | */ 298 | 299 | [type="search"]::-webkit-search-decoration { 300 | -webkit-appearance: none; 301 | } 302 | 303 | /** 304 | * 1. Correct the inability to style clickable types in iOS and Safari. 305 | * 2. Change font properties to `inherit` in Safari. 306 | */ 307 | 308 | ::-webkit-file-upload-button { 309 | -webkit-appearance: button; /* 1 */ 310 | font: inherit; /* 2 */ 311 | } 312 | 313 | /* Interactive 314 | ========================================================================== */ 315 | 316 | /* 317 | * Add the correct display in Edge, IE 10+, and Firefox. 318 | */ 319 | 320 | details { 321 | display: block; 322 | } 323 | 324 | /* 325 | * Add the correct display in all browsers. 326 | */ 327 | 328 | summary { 329 | display: list-item; 330 | } 331 | 332 | /* Misc 333 | ========================================================================== */ 334 | 335 | /** 336 | * Add the correct display in IE 10+. 337 | */ 338 | 339 | template { 340 | display: none; 341 | } 342 | 343 | /** 344 | * Add the correct display in IE 10. 345 | */ 346 | 347 | [hidden] { 348 | display: none; 349 | } 350 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | --------------------------------------------------------------------------------