导航菜单

  • 0.api
  • 0.Async
  • 0.module
  • 1.ES2015
  • 2.Promise
  • 3.Node
  • 4.NodeInstall
  • 5.REPL
  • 6.NodeCore
  • 7.module&NPM
  • 8.Encoding
  • 9.Buffer
  • 10.fs
  • 11.Stream-1
  • 11.Stream-2
  • 11.Stream-3
  • 11.Stream-4
  • 12-Network-2
  • 12.NetWork-3
  • 12.Network-1
  • 13.tcp
  • 14.http-1
  • 14.http-2
  • 15.compress
  • 16.crypto
  • 17.process
  • 18.yargs
  • 19.cache
  • 20.action
  • 21.https
  • 22.cookie
  • 23.session
  • 24.express-1
  • 24.express-2
  • 24.express-3
  • 24.express-4
  • 25.koa-1
  • 26.webpack-1-basic
  • 26.webpack-2-optimize
  • 26.webpack-3-file
  • 26.webpack-4.tapable
  • 26.webpack-5-AST
  • 26.webpack-6-sources
  • 26.webpack-7-loader
  • 26.webpack-8-plugin
  • 26.webpack-9-hand
  • 26.webpack-10-prepare
  • 28.redux
  • 28.redux-jwt-back
  • 28.redux-jwt-front
  • 29.mongodb-1
  • 29.mongodb-2
  • 29.mongodb-3
  • 29.mongodb-4
  • 29.mongodb-5
  • 29.mongodb-6
  • 30.cms-1-mysql
  • 30.cms-2-mysql
  • 30.cms-3-mysql
  • 30.cms-4-nunjucks
  • 30.cms-5-mock
  • 30.cms-6-egg
  • 30.cms-7-api
  • 30.cms-8-roadhog
  • 30.cms-9-yaml
  • 30.cms-10-umi
  • 30.cms-12-dva
  • 30.cms-13-dva-ant
  • 30.cms-14-front
  • 30.cms-15-deploy
  • 31.dva
  • 31.cms-13-dva-antdesign
  • 33.redis
  • 34.unittest
  • 35.jwt
  • 36.websocket-1
  • 36.websocket-2
  • 38.chat-api-1
  • 38.chat-api-2
  • 38.chat-3
  • 38.chat-api-3
  • 38.chat
  • 38.chat2
  • 38.chat2
  • 39.crawl-0
  • 39.crawl-1
  • 39.crawl-2
  • 40.deploy
  • 41.safe
  • 42.test
  • 43.nginx
  • 44.enzyme
  • 45.docker
  • 46.elastic
  • 47.oauth
  • 48.wxpay
  • index
  • 52.UML
  • 53.design
  • index
  • 54.linux
  • 57.ts
  • 56.react-ssr
  • 58.ts_react
  • 59.ketang
  • 59.ketang2
  • 61.1.devops-linux
  • 61.2.devops-vi
  • 61.3.devops-user
  • 61.4.devops-auth
  • 61.5.devops-shell
  • 61.6.devops-install
  • 61.7.devops-system
  • 61.8.devops-service
  • 61.9.devops-network
  • 61.10.devops-nginx
  • 61.11.devops-docker
  • 61.12.devops-jekins
  • 61.13.devops-groovy
  • 61.14.devops-php
  • 61.15.devops-java
  • 61.16.devops-node
  • 61.17.devops-k8s
  • 62.1.react-basic
  • 62.2.react-state
  • 62.3.react-high
  • 62.4.react-optimize
  • 62.5.react-hooks
  • 62.6.react-immutable
  • 62.7.react-mobx
  • 62.8.react-source
  • 63.1.redux
  • 63.2.redux-middleware
  • 63.3.redux-hooks
  • 63.4.redux-saga
  • 63.5.redux-saga-hand
  • 64.1.router
  • 64.2.router-connected
  • 65.1.typescript
  • 65.2.typescript
  • 65.3.typescript
  • 65.4.antd
  • 65.4.definition
  • 66-1.vue-base
  • 66-2.vue-component
  • 66-3.vue-cli3.0
  • 66-4.$message组件
  • 66-5.Form组件
  • 66-6.tree
  • 66-7.vue-router-apply
  • 66-8.axios-apply
  • 66-9.vuex-apply
  • 66-10.jwt-vue
  • 66-11.vue-ssr
  • 66-12.nuxt-apply
  • 66-13.pwa
  • 66-14.vue单元测试
  • 66-15.权限校验
  • 67-1-network
  • 68-2-wireshark
  • 7.npm2
  • 69-hooks
  • 70-deploy
  • 71-hmr
  • 72.deploy
  • 73.import
  • 74.mobile
  • 75.webpack-1.文件分析
  • 75.webpack-2.loader
  • 75.webpack-3.源码流程
  • 75.webpack-4.tapable
  • 75.webpack-5.prepare
  • 75.webpack-6.resolve
  • 75.webpack-7.loader
  • 75.webpack-8.module
  • 75.webpack-9.chunk
  • 75.webpack-10.asset
  • 75.webpack-11.实现
  • 76.react_optimize
  • 77.ts_ketang_back
  • 77.ts_ketang_front
  • 78.vue-domdiff
  • 79.grammar
  • 80.tree
  • 81.axios
  • 82.1.react
  • 82.2.react-high
  • 82.3.react-router
  • 82.4.redux
  • 82.5.redux_middleware
  • 82.6.connected
  • 82.7.saga
  • 82.8.dva
  • 82.8.dva-source
  • 82.9.roadhog
  • 82.10.umi
  • 82.11.antdesign
  • 82.12.ketang-front
  • 82.12.ketang-back
  • 83.upload
  • 84.graphql
  • 85.antpro
  • 86.1.uml
  • 86.2.design
  • 87.postcss
  • 88.react16-1
  • 89.nextjs
  • 90.react-test
  • 91.react-ts
  • 92.rbac
  • 93.tsnode
  • 94.1.JavaScript
  • 94.2.JavaScript
  • 94.3.MODULE
  • 94.4.EventLoop
  • 94.5.文件上传
  • 94.6.https
  • 94.7. nginx
  • 95.1. react
  • 95.2.react
  • 96.1.react16
  • 96.2.fiber
  • 96.3.fiber
  • 97.serverless
  • 98.websocket
  • 100.1.react-basic
  • 101.1.monitor
  • 101.2.monitor
  • 102.java
  • 103.1.webpack-usage
  • 103.2.webpack-bundle
  • 103.3.webpack-ast
  • 103.4.webpack-flow
  • 103.5.webpack-loader
  • 103.6.webpack-tapable
  • 103.7.webpack-plugin
  • 103.8.webpack-optimize1
  • 103.9.webpack-optimize2
  • 103.10.webpack-hand
  • 103.11.webpack-hmr
  • 103.11.webpack5
  • 103.13.splitChunks
  • 103.14.webpack-sourcemap
  • 103.15.webpack-compiler1
  • 103.15.webpack-compiler2
  • 103.16.rollup.1
  • 103.16.rollup.2
  • 103.16.rollup.3
  • 103.16.vite.basic
  • 103.16.vite.source
  • 103.16.vite.plugin
  • 103.16.vite.1
  • 103.16.vite.2
  • 103.17.polyfill
  • 104.1.binary
  • 104.2.binary
  • 105.skeleton
  • 106.1.react
  • 106.2.react_hooks
  • 106.3.react_router
  • 106.4.redux
  • 106.5.redux_middleware
  • 106.6.connected-react-router
  • 106.6.redux-first-history
  • 106.7.redux-saga
  • 106.8.dva
  • 106.9.umi
  • 106.10.ketang
  • 106.11.antdesign
  • 106.12.antpro
  • 106.13.router-6
  • 106.14.ssr
  • 106.15.nextjs
  • 106.16.1.cms
  • 106.16.2.cms
  • 106.16.3.cms
  • 106.16.4.cms
  • 106.16.mobx
  • 106.17.fomily
  • 107.fiber
  • 108.http
  • 109.1.webpack_usage
  • 109.2.webpack_source
  • 109.3.dll
  • 110.nest.js
  • 111.xstate
  • 112.Form
  • 113.redux-saga
  • 114.react+typescript
  • 115.immer
  • 116.pro5
  • 117.css-loader
  • 118.1.umi-core
  • 119.2.module-federation
  • 119.1.module-federation
  • 120.create-react-app
  • 121.react-scripts
  • 122.react-optimize
  • 123.jsx-runtime
  • 124.next.js
  • 125.1.linux
  • 125.2.linux-vi
  • 125.3.linux-user
  • 125.4.linux-auth
  • 125.5.linux-shell
  • 125.6.linux-install
  • 125.7.linux-system
  • 125.8.linux-service
  • 125.9.linux-network
  • 125.10.nginx
  • 125.11.docker
  • 125.12.ci
  • 125.13.k8s
  • 125.14.k8s
  • 125.15.k8s
  • 125.16.k8s
  • 126.11.react-1
  • 126.12.react-2
  • 126.12.react-3
  • 126.12.react-4
  • 126.12.react-5
  • 126.12.react-6
  • 126.12.react-7
  • 126.12.react-8
  • 127.frontend
  • 128.rollup
  • 129.px2rem-loader
  • 130.health
  • 131.hooks
  • 132.keepalive
  • 133.vue-cli
  • 134.react18
  • 134.2.react18
  • 134.3.react18
  • 135.function
  • 136.toolkit
  • 137.lerna
  • 138.create-vite
  • 139.cli
  • 140.antd
  • 141.react-dnd
  • 142.1.link
  • 143.1.gulp
  • 143.2.stream
  • 143.3.gulp
  • 144.1.closure
  • 144.2.v8
  • 144.3.gc
  • 145.react-router-v6
  • 146.browser
  • 147.lighthouse
  • 148.1.basic
  • 148.2.basic
  • 148.3.basic
  • 148.4.basic
  • 148.5.basic
  • 149.1.vite
  • 149.2.vite
  • 149.3.vite
  • 149.4.vite
  • 150.react-window
  • 151.react-query
  • 152.useRequest
  • 153.transition
  • 154.emotion
  • 155.1.formily
  • 155.2.formily
  • 155.3.formily
  • 155.3.1.mobx.usage
  • 155.3.2.mobx.source
  • 156.vue-loader
  • 103.11.mf
  • 157.1.react18
  • 158.umi4
  • 159.rxjs
  • 159.rxjs2
  • 160.bff
  • 161.zustand
  • 162.vscode
  • 163.emp
  • 164.cors
  • 1. TypeScript工程化开发
  • 2.初始化项目
    • 3. git规范和changelog
      • 3.1 良好的git commit好处
      • 3.2 良好的commit
      • 3.3 .gitignore
      • 3.4 提交的格式
      • 3.4 husky
      • 3.5 生成CHANGELOG.md
  • 4. 支持Typescript
  • 5. 支持React
    • 5.1 安装
    • 5.2 tsconfig.json
    • 5.3 webpack.config.js
    • 5.3 src\index.tsx
    • 5.4 src\index.html
    • 5.5 package.json
  • 6. 代码规范
    • 6.1 常见的代码规范文档
    • 6.2 代码检查
      • 6.2.1 模块安装
      • 6.2.2 eslintrc配置文件
      • 6.2.3 代码检查
      • 6.2.4 配置自动修复
  • 7.单元测试
    • 7.1 安装配置
    • 7.2 src\sum.tsx
    • 7.3 tests\sum.spec.tsx
    • 7.4 package.json
  • 8. 持续集成
    • 8.1 登录并创建项目
    • 8.2 .travis.yml
    • 8.3 实战
      • 8.3.1 生成项目并上传github
      • 8.3.2 同步仓库
      • 8.3.3 设置仓库环境变量
      • 8.3.4 Github生成访问令牌 (即添加授权)
      • 8.3.5 .travis.yml
  • 9.React元素
    • 9.1 原生组件
    • 9.2 函数组件
    • 9.3 类组件
  • 10. 创建组件
    • 10.1 Counter.tsx
    • 10.2 types.tsx
    • 10.3 TodoItem.tsx
    • 10.4 TodoInput.tsx
    • 10.5 Todos\index.tsx
    • 10.6 src\index.tsx
  • 11. 默认属性
    • 11.1 TodoInput.tsx
  • 12. 高阶组件
    • 12.1 安装
    • 12.2 src\utils.tsx
    • 12.3 TodoInput.tsx
    • 12.4 src\hoistNonReactStatics.tsx
  • 13. 集成redux
    • 13.1 models\todos.tsx
    • 13.2 models\index.tsx
    • 13.3 action-types.tsx
    • 13.4 reducers\counter.tsx
    • 13.5 reducers\todos.tsx
    • 13.6 reducers\index.tsx
    • 13.7 actions\counter.tsx
    • 13.8 actions\todos.tsx
    • 13.9 actions\index.tsx
    • 13.10 components\Counter.tsx
    • 13.11 TodoItem.tsx
    • 13.12 TodoInput.tsx
    • 13.13 Todos\index.tsx
    • 13.14 store\index.tsx
    • 13.15 src\index.tsx
  • 14. 使用路由
    • 14.1 src\index.tsx
    • 14.2 Counter.tsx
    • 14.3 Todos\index.tsx
  • 15. connected-react-router
    • 15.1 src\history.tsx
    • 15.2 src\index.tsx
    • 15.3 store\index.tsx
    • 15.4 reducers\index.tsx
    • 15.5 actions\counter.tsx
    • 15.6 actions\index.tsx
    • 15.7 Counter.tsx
    • 15.8 Todos\index.tsx
  • 16. redux-thunk
    • 16.1 redux-thunk\index.tsx
    • 16.2 store\index.tsx
  • 参考

下载

1. TypeScript工程化开发 #

  • 前端工程化就是通过流程规范化、标准化提升团队协作效率
  • 通过组件化、模块化提升代码质量
  • 使用构建工具、自动化工具提升开发效率
  • 编译 => 打包(合并) => 压缩 => 代码检查 => 测试 => 持续集成

2.初始化项目 #

mkdir zhufeng_typescript_development
cd zhufeng_typescript_development
npm init
package name: (zhufeng_typescript_development)
version: (1.0.0)
description: TypeScript工程化开发
entry point: (index.js)
test command:
git repository: https://gitee.com/zhufengpeixun/zhufeng_typescript_development
keywords: typescript,react
author: zhangrenyang
license: (ISC) MIT

3. git规范和changelog #

3.1 良好的git commit好处 #

  • 可以加快code review 的流程
  • 可以根据git commit 的元数据生成changelog
  • 可以让其它开发者知道修改的原因

3.2 良好的commit #

  • commitizen是一个格式化commit message的工具
  • validate-commit-msg 用于检查项目的 Commit message 是否符合格式
  • conventional-changelog-cli可以从git metadata生成变更日志

  • 统一团队的git commit 标准

  • 可以使用angular的git commit日志作为基本规范
    • 提交的类型限制为 feat、fix、docs、style、refactor、perf、test、chore、revert等
    • 提交信息分为两部分,标题(首字母不大写,末尾不要加标点)、主体内容(描述修改内容)
  • 日志提交友好的类型选择提示 使用commitize工具
  • 不符合要求格式的日志拒绝提交 的保障机制
    • 需要使用validate-commit-msg工具
  • 统一changelog文档信息生成
    • 使用conventional-changelog-cli工具
cnpm i commitizen  validate-commit-msg conventional-changelog-cli -D
commitizen init cz-conventional-changelog --save --save-exact
git cz

3.3 .gitignore #

.gitignore

node_modules
.vscode
dist

3.4 提交的格式 #

<type>(<scope>):<subject/>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
  • 代表某次提交的类型,比如是修复bug还是增加feature
  • 表示作用域,比如一个页面或一个组件
  • 主题 ,概述本次提交的内容
  • 详细的影响内容
  • 修复的bug和issue链接
类型 含义
feat 新增feature
fix 修复bug
docs 仅仅修改了文档,比如README、CHANGELOG、CONTRIBUTE等
style 仅仅修改了空格、格式缩进、偏好等信息,不改变代码逻辑
refactor 代码重构,没有新增功能或修复bug
perf 优化相关,提升了性能和体验
test 测试用例,包括单元测试和集成测试
chore 改变构建流程,或者添加了依赖库和工具
revert 回滚到上一个版本
ci CI 配置,脚本文件等更新

3.4 husky #

  • validate-commit-msg可以来检查我们的commit规范
  • husky可以把validate-commit-msg作为一个githook来验证提交消息
cnpm i husky  validate-commit-msg --save-dev
  "husky": {
    "hooks": {
      "commit-msg": "validate-commit-msg"
    }
  }

3.5 生成CHANGELOG.md #

  • conventional-changelog-cli 默认推荐的 commit 标准是来自angular项目
  • 参数-i CHANGELOG.md表示从 CHANGELOG.md 读取 changelog
  • 参数 -s 表示读写 CHANGELOG.md 为同一文件
  • 参数 -r 表示生成 changelog 所需要使用的 release 版本数量,默认为1,全部则是0
cnpm i conventional-changelog-cli -D
"scripts": {
    "changelogs": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
}

4. 支持Typescript #

  • tsconfig-json
  • 编译选项
tsc --init

基本参数

参数 解释
target 用于指定编译之后的版本目标
module 生成的模块形式:none、commonjs、amd、system、umd、es6、es2015 或 esnext 只有 amd 和 system 能和 outFile 一起使用 target 为 es5 或更低时可用 es6 和 es2015
lib 编译时引入的 ES 功能库,包括:es5 、es6、es7、dom 等。如果未设置,则默认为: target 为 es5 时: ["dom", "es5", "scripthost"] target 为 es6 时: ["dom", "es6", "dom.iterable", "scripthost"]
allowJs 是否允许编译JS文件,默认是false,即不编译JS文件
checkJs 是否检查和报告JS文件中的错误,默认是false
jsx 指定jsx代码用于的开发环境 preserve指保留JSX语法,扩展名为.jsx,react-native是指保留jsx语法,扩展名js,react指会编译成ES5语法 详解
declaration 是否在编译的时候生成相应的.d.ts声明文件
declarationDir 生成的 .d.ts 文件存放路径,默认与 .ts 文件相同
declarationMap 是否为声明文件.d.ts生成map文件
sourceMap 编译时是否生成.map文件
outFile 是否将输出文件合并为一个文件,值是一个文件路径名,只有设置module的值为amd和system模块时才支持这个配置
outDir 指定输出文件夹
rootDir 编译文件的根目录,编译器会在根目录查找入口文件
composite 是否编译构建引用项目
removeComments 是否将编译后的文件中的注释删掉
noEmit 不生成编译文件
importHelpers 是否引入tslib里的辅助工具函数
downlevelIteration 当target为ES5或ES3时,为for-of、spread和destructuring中的迭代器提供完全支持
isolatedModules 指定是否将每个文件作为单独的模块,默认为true

严格检查

参数 解释
strict 是否启动所有类型检查
noImplicitAny 不允许默认any类型
strictNullChecks 当设为true时,null和undefined值不能赋值给非这两种类型的值
strictFunctionTypes 是否使用函数参数双向协变检查
strictBindCallApply 是否对bind、call和apply绑定的方法的参数的检测是严格检测的
strictPropertyInitialization 检查类的非undefined属性是否已经在构造函数里初始化
noImplicitThis 不允许this表达式的值为any类型的时候
alwaysStrict 指定始终以严格模式检查每个模块

额外检查

参数 解释
noUnusedLocals 检查是否有定义了但是没有使用的变量
noUnusedParameters 检查是否有在函数体中没有使用的参数
noImplicitReturns 检查函数是否有返回值
noFallthroughCasesInSwitch 检查switch中是否有case没有使用break跳出

模块解析检查

参数 解释
moduleResolution 选择模块解析策略,有node和classic两种类型,详细说明
baseUrl 解析非相对模块名称的基本目录
paths 设置模块名到基于baseUrl的路径映射
rootDirs 可以指定一个路径列表,在构建时编译器会将这个路径列表中的路径中的内容都放到一个文件夹中
typeRoots 指定声明文件或文件夹的路径列表
types 用来指定需要包含的模块
allowSyntheticDefaultImports 允许从没有默认导出的模块中默认导入
esModuleInterop 为导入内容创建命名空间,实现CommonJS和ES模块之间的互相访问
preserveSymlinks 不把符号链接解析为其真实路径

sourcemap检查

参数 解释
sourceRoot 调试器应该找到TypeScript文件而不是源文件位置
mapRoot 调试器找到映射文件而非生成文件的位置,指定map文件的根路径
inlineSourceMap 指定是否将map文件的内容和js文件编译在一个同一个js文件中
inlineSources 是否进一步将.ts文件的内容也包含到输出文件中

试验选项

参数 解释
experimentalDecorators 是否启用实验性的装饰器特性
emitDecoratorMetadata 是否为装饰器提供元数据支持

试验选项

参数 解释
files 配置一个数组列表,里面包含指定文件的相对或绝对路径,编译器在编译的时候只会编译包含在files中列出的文件
include include也可以指定要编译的路径列表,但是和files的区别在于,这里的路径可以是文件夹,也可以是文件
exclude exclude表示要排除的、不编译的文件,他也可以指定一个列表
extends extends可以通过指定一个其他的tsconfig.json文件路径,来继承这个配置文件里的配置
compileOnSave 在我们编辑了项目中文件保存的时候,编辑器会根据tsconfig.json的配置重新生成文件
references 一个对象数组,指定要引用的项目

5. 支持React #

5.1 安装 #

cnpm i react react-dom @types/react @types/react-dom react-router-dom @types/react-router-dom   -S
cnpm i webpack webpack-cli webpack-dev-server html-webpack-plugin hoist-non-react-statics -D
cnpm i typescript ts-loader source-map-loader -D
cnpm i redux react-redux @types/react-redux redux-thunk  redux-logger @types/redux-logger -S
cnpm i connected-react-router -S
包名 作用
react @types/react react核心库
react-dom @types/react-dom react操作DOM库
react-router-dom @types/react-router-dom react路由库
webpack webpack核心库
webpack-cli webpack命令行文件
webpack-dev-server webpack开发服务器
html-webpack-plugin webpack用于生成html的插件
redux 全局状态管理库
react-redux @types/react-redux 连接react和redux的库
redux-thunk 可以让store派发一个函数的中间件
redux-logger @types/redux-logger 可以在状态改变前后打印状态的中间件
typescript JavaScript语言扩展
ts-loader 可以让Webpack使用TypeScript的标准配置文件tsconfig.json编译TypeScript代码
source-map-loader 使用任意来自Typescript的sourcemap输出,以此通知webpack何时生成自己的sourcemaps,这让你在调试最终生成的文件时就好像在调试TypeScript源码一样
  • ts-loader可以让Webpack使用TypeScript的标准配置文件tsconfig.json编译TypeScript代码。
  • source-map-loader使用任意来自Typescript的sourcemap输出,以此通知webpack何时生成自己的sourcemaps,这让你在调试最终生成的文件时就好像在调试TypeScript源码一样。

5.2 tsconfig.json #

{
  "compilerOptions": {
    "outDir": "./dist",
    "sourceMap": true,
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "module": "commonjs",
    "target": "es5",
    "jsx": "react",
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  },
  "include": [
    "./src/**/*"
  ]
}
包名 作用
outDir 指定输出目录
sourceMap 把 ts 文件编译成 js 文件的时候,同时生成对应的sourceMap文件
noImplicitAny 如果为true的话,TypeScript 编译器无法推断出类型时,它仍然会生成 JavaScript 文件,但是它也会报告一个错误
module 代码规范
target 转换成es5
jsx react模式会生成React.createElement,在使用前不需要再进行转换操作了,输出文件的扩展名为.js
include 需要编译的目录

5.3 webpack.config.js #

webpack.config.js

const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
  mode: "development",
  devtool:false,
  entry: "./src/index.tsx",
  output: {
    filename: "[name].[hash].js",
    path: path.join(__dirname, "dist"),
  },
  devServer: {
    hot: true,
    contentBase: path.join(__dirname, "dist"),
    historyApiFallback: {
      index: "./index.html",
    },
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"],
    alias: {
      "@": path.resolve("src"), // 这样配置后 @ 可以指向 src 目录
    },
  },

  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: "ts-loader"
      }
    ],
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html"
    }),
    new webpack.HotModuleReplacementPlugin()
  ],
};

5.3 src\index.tsx #

src\index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';
let root = document.getElementById('root');

let props = { className: 'title' };
let element= React.createElement('div', props, 'hello');
ReactDOM.render(element, root);

5.4 src\index.html #

src\index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>typescript</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

5.5 package.json #

{
  "scripts": {
+    "start": "webpack serve --config webpack.config.js",
+    "build": "webpack --config webpack.config.js"
    "eslint": "eslint src --ext .ts",
    "eslint:fix": "eslint src --ext .ts --fix",
    "changelogs": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
    "test": "mocha --require ts-node/register test/**/*"
  }
}

6. 代码规范 #

  • 规范的代码可以促进团队合作
  • 规范的代码可以降低维护成本
  • 规范的代码有助于 code review(代码审查)

6.1 常见的代码规范文档 #

  • airbnb中文版
  • standard中文版
  • 百度前端编码规范
  • styleguide
  • CSS编码规范

6.2 代码检查 #

  • Eslint 是一款插件化的 JavaScript 静态代码检查工具,ESLint 通过规则来描述具体的检查行为

6.2.1 模块安装 #

cnpm i eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev

6.2.2 eslintrc配置文件 #

  • 英文rules
  • 中文rules
  • 需要添加parserOptions以支持模块化的写法

.eslintrc.js

module.exports = {
    "parser":"@typescript-eslint/parser",
    "plugins":["@typescript-eslint"],
    "rules":{
        "no-var":"error",
        "no-extra-semi":"error",
        "@typescript-eslint/indent":["error",2]
    },
    "parserOptions": {
        "ecmaVersion": 6,
        "sourceType": "module",
        "ecmaFeatures": {
          "modules": true
        }
    }
}

6.2.3 代码检查 #

package.json

"scripts": {
    "start": "webpack",
    "build": "tsc",
+    "lint": "eslint src --ext .tsx",
+    "lint:fix": "eslint src --ext .tsx --fix"
  }

src/1.ts

var name2 = 'zhufeng';;;
if(true){
    let a = 10;
}

执行命令

npm run eslint
1:1   error  Unexpected var, use let or const instead      no-var
1:23  error  Unnecessary semicolon                         no-extra-semi
1:24  error  Unnecessary semicolon                         no-extra-semi
3:1   error  Expected indentation of 2 spaces but found 4  @typescript-eslint/indent

6.2.4 配置自动修复 #

  • 安装vscode的eslint插件
  • 配置自动修复参数

.vscode\settings.json

{
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        "typescript",
        "typescriptreact"
    ],
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
    }
  }

7.单元测试 #

7.1 安装配置 #

cnpm i jest @types/jest ts-jest -D
npx ts-jest config:init

7.2 src\sum.tsx #

src\sum.tsx

function sum(a: number, b: number) {
  return a + b;
}
module.exports = {
  sum
}

7.3 tests\sum.spec.tsx #

tests\sum.spec.tsx

let math = require('../src/sum');
test('1+1=2', () => {
  expect(math.sum(1, 1)).toBe(2);
});
test('1+1=2', () => {
  expect(math.sum(1, -1)).toBe(0);
});

7.4 package.json #

package.json

  "scripts": {
+    "test": "jest"
  },

8. 持续集成 #

  • Travis CI 提供的是持续集成服务(Continuous Integration,简称 CI)。它绑定 Github 上面的项目,只要有新的代码,就会自动抓取。然后,提供一个运行环境,执行测试,完成构建,还能部署到服务器
  • 持续集成指的是只要代码有变更,就自动运行构建和测试,反馈运行结果。确保符合预期以后,再将新代码集成到主干
  • 持续集成的好处在于,每次代码的小幅变更,就能看到运行结果,从而不断累积小的变更,而不是在开发周期结束时,一下子合并一大块代码

8.1 登录并创建项目 #

  • Travis CI 只支持 Github,所以你要拥有GitHub帐号
  • 该帐号下面有一个项目,面有可运行的代码,还包含构建或测试脚本
  • 你需要激活了一个仓库,Travis 会监听这个仓库的所有变化

8.2 .travis.yml #

  • Travis 要求项目的根目录下面,必须有一个.travis.yml文件。这是配置文件,指定了 Travis 的行为
  • 该文件必须保存在 Github 仓库里面,一旦代码仓库有新的 Commit,Travis 就会去找这个文件,执行里面的命令
  • 这个文件采用 YAML 格式。下面是一个最简单的 Node 项目的.travis.yml文件
    • language 字段指定了默认运行环境,所有的语言在此
language: node_js
node_js:
  - "11"
install: npm install
script:  npm test  

8.3 实战 #

8.3.1 生成项目并上传github #

npx create-react-app zhufeng-typescript-development

8.3.2 同步仓库 #

  • 登录travis-ci.com选择同步仓库

8.3.3 设置仓库环境变量 #

变量名 含义
GH_TOKEN 用户生成的令牌
GH_REF 仓库地址 github.com/zhufengnodejs/zhufeng_typescript_development.git

8.3.4 Github生成访问令牌 (即添加授权) #

  • 访问令牌的作用就是授权仓库操作权限
  • https://github.com/settings/tokens
  • Github>Developer settings>Personal access tokens> Generate new token > Generate token> Copy Token

8.3.5 .travis.yml #

language: node_js
node_js: 
    - '11'
install:
  - npm install
script:
  - hexo g
after_script:
  - cd ./public
  - git init
  - git config user.name "${USERNAME}"
  - git config user.email "${UESREMAIL}"
  - git add -A
  - git commit -m "Update documents"
  - git push --force  "https://${GH_TOKEN}@${GH_REF}" "master:${GH_BRANCH}"
branches:
  only:
    - master

9.React元素 #

DetailedReactHTMLElements.jpg

9.1 原生组件 #

src\index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';
let root: HTMLElement | null = document.getElementById('root');
interface Props {
  className: string
}
let props: Props = { className: 'title' };
let element: React.DetailedReactHTMLElement<Props, HTMLDivElement> = (
  React.createElement<Props, HTMLDivElement>('div', props, 'hello')
)
ReactDOM.render(element, root);

src\typings.tsx

export interface DOMAttributes {
  children?: ReactNode;
}
export interface HTMLAttributes extends DOMAttributes {
  className?: string;
}

export interface ReactElement<P = any,T extends string> {
  type: T;
  props: P;
}
export interface DOMElement extends ReactElement{}
export interface ReactHTML { div:  HTMLDivElement }
export interface DetailedReactHTMLElement extends DOMElement{
  type: keyof ReactHTML;
}

export type ReactText = string | number;
export type ReactChild = ReactElement | ReactText;
export type ReactNode = ReactChild | boolean | null | undefined;

export declare function createElement<P extends {}>(
  type: string,
  props?: P,
  ...children: ReactNode[]): ReactElement;

9.2 函数组件 #

src\index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';
let root: HTMLElement | null = document.getElementById('root');
interface Props {
  className: string
}
let props: Props = { className: 'title' };
function Welcome(props: Props):React.DetailedReactHTMLElement<Props, HTMLDivElement> {
  return React.createElement<Props, HTMLDivElement>('div', props, 'hello');
}
let element: React.FunctionComponentElement<Props> = (
  React.createElement<Props>(Welcome, props)
)
ReactDOM.render(element, root);

src\typings.tsx

export interface DOMAttributes {
  children?: ReactNode;
}
export interface HTMLAttributes extends DOMAttributes {
  className?: string;
}
+export type JSXElementConstructor<P> = ((props: P) => ReactElement | null)
+export interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string> {
  type: T;
  props: P;
}
export interface DOMElement extends ReactElement{}
export interface ReactHTML { div:  HTMLDivElement }
export interface DetailedReactHTMLElement extends DOMElement{
  type: keyof ReactHTML;
}

export type ReactText = string | number;
export type ReactChild = ReactElement | ReactText;
export type ReactNode = ReactChild | boolean | null | undefined;

+type PropsWithChildren<P> = P & { children?: ReactNode };
+interface FunctionComponent<P = {}> {
+  (props: PropsWithChildren<P>): ReactElement | null;
+}
+interface FunctionComponentElement<P> extends ReactElement<P, FunctionComponent<P>> {}
+export declare function createElement<P extends {}>(
+  type: FunctionComponent<P>,
+  props?: P,
+  ...children: ReactNode[]): FunctionComponentElement<P>;
export declare function createElement<P extends {}>(
  type: string,
  props?: P,
  ...children: ReactNode[]): ReactElement;

9.3 类组件 #

src\index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';
let root: HTMLElement | null = document.getElementById('root');
interface Props {
  className: string
}
interface State {
  count:number
}
class Welcome extends React.Component<Props, State> {
  state = { count: 0 }
  render():React.DetailedReactHTMLElement<Props, HTMLDivElement> {
    return React.createElement<Props, HTMLDivElement>('div', this.props, this.state.count);
  }
}
let props: Props = { className: 'title' };
let element = (
  React.createElement<Props>(Welcome, props)
)
ReactDOM.render(element, root);

src\typings.tsx

export interface DOMAttributes {
  children?: ReactNode;
}
export interface HTMLAttributes extends DOMAttributes {
  className?: string;
}
export type JSXElementConstructor<P> =  
| ((props: P) => ReactElement | null)
+| (new (props: P) => Component<P, any>);

export interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string> {
  type: T;
  props: P;
}
export interface DOMElement extends ReactElement{}
export interface ReactHTML { div:  HTMLDivElement }
export interface DetailedReactHTMLElement extends DOMElement{
  type: keyof ReactHTML;
}

export type ReactText = string | number;
export type ReactChild = ReactElement | ReactText;
export type ReactNode = ReactChild | boolean | null | undefined;

type PropsWithChildren<P> = P & { children?: ReactNode };
interface FunctionComponent<P = {}> {
  (props: PropsWithChildren<P>): ReactElement | null;
}
interface FunctionComponentElement<P> extends ReactElement<P, FunctionComponent<P>> {}

+type ComponentState = any;
+declare class Component<P, S> {
+  setState(state: any): void;
+  render(): ReactNode;
+}
+interface ComponentClass<P = {}, S = ComponentState> {
+  new(props: P): Component<P, S>;
+}
+interface ComponentElement<P> extends ReactElement<P, ComponentClass<P>> {}
+export declare function createElement<P extends {}>(
+  type:  ComponentClass<P>,
+  props?: P,
+  ...children: ReactNode[]): ComponentElement<P>;
export declare function createElement<P extends {}>(
  type: FunctionComponent<P>,
  props?: P,
  ...children: ReactNode[]): FunctionComponentElement<P>;
export declare function createElement<P extends {}>(
  type: string,
  props?: P,
  ...children: ReactNode[]): ReactElement;

10. 创建组件 #

10.1 Counter.tsx #

src\components\Counter.tsx

import * as React from 'react';
export interface Props {
    number: number
}
export default class Counter extends React.Component<Props>{
    render() {
        const { number } = this.props;
        return (
            <div>
                <p>{number}</p>
            </div>
        )
    }
}

10.2 types.tsx #

src\components\Todos\types.tsx

export type Todo = {
    id:number;
    text:string
}

10.3 TodoItem.tsx #

src\components\Todos\TodoItem.tsx

import * as React from "react";
import { Todo } from './types';
const todoItemStyle: React.CSSProperties = {
  color: "red",
  backgroundColor: "green",
};
interface Props {
  todo: Todo;
}
const TodoItem: React.FC<Props> = (props: Props) => (
  <li style={todoItemStyle}>{props.todo.text}</li>
);
TodoItem.defaultProps;

export default TodoItem;

10.4 TodoInput.tsx #

src\components\Todos\TodoInput.tsx

import * as React from "react";
import { Todo } from './types';
interface Props {
  addTodo:(todo:Todo)=>void
}
interface State {
  text:string
}
let id=0;
export default class TodoInput extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      text: "",
    };
  }
  public render() {
    const { text } = this.state;
    const { handleChange, handleSubmit } = this;

    return (
      <form onSubmit={handleSubmit}>
        <input type="text" value={text} onChange={this.handleChange} />
        <button type="submit">添加</button>
      </form>
    );
  }

  handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    this.setState({ text: e.target.value });
  }

  handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    let text = this.state.text.trim();
    if (!text) {
      return;
    }
    this.props.addTodo({id:id++,text});
    this.setState({ text: "" });
  }
}

10.5 Todos\index.tsx #

src\components\Todos\index.tsx

import * as React from 'react';
import TodoInput from './TodoInput';
import TodoItem from './TodoItem';
import { Todo} from './types';
const ulStyle: React.CSSProperties = {
  width: "100px"
};
export interface Props {
  title:string
}
export interface State {
  todos: Todo[]
}
export default class Todos extends React.Component<Props,State>{
  state = {todos:new Array<Todo>()};
  addTodo = (todo:Todo) =>{
    this.setState({todos:[...this.state.todos,todo]});
  }
  render() {
    return (
      <div>
        <h1>{this.props.title}</h1>
        <TodoInput addTodo={this.addTodo}/>
        <ul style={ulStyle}>
          {
            this.state.todos.map(todo=><TodoItem key={todo.id} todo={todo}/>)
          }
        </ul>
      </div>
    )
  }
}

10.6 src\index.tsx #

src\index.tsx

import * as React from "react";
import * as ReactDOM from "react-dom";
import Counter from "./components/Counter";
import Todos from "./components/Todos";
ReactDOM.render(<><Counter number={100} /><Todos title="待办事项"/></>, document.getElementById("root"));

11. 默认属性 #

11.1 TodoInput.tsx #

src\components\Todos\TodoInput.tsx

import * as React from "react";
import { Todo } from './types';

+let defaultProps = {
+  setting:{
+    maxLength: 6,
+    placeholder: '请输入待办事项'
+  }
+}
+export type DefaultProps = Partial<typeof defaultProps>;
+interface OwnProps {
+  addTodo:(todo:Todo)=>void
+}
+type Props = OwnProps & DefaultProps;
interface State {
  text:string
}
let id=0;
export default class TodoInput extends React.Component<Props, State> {
+  static defaultProps: Required<DefaultProps> = defaultProps;
  constructor(props: Props) {
    super(props);
    this.state = {
      text: ""
    };
  }
  public render() {
    const { text } = this.state;
+   const { setting } = this.props as (Props & Required<DefaultProps>);
    const { handleChange, handleSubmit } = this;
    return (
      <form onSubmit={handleSubmit}>
+       <input type="text" maxLength={setting.maxLength} placeholder={setting.placeholder} 
        value={text} onChange={handleChange} />
        <button type="submit">添加</button>
      </form>
    );
  }

  handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    this.setState({ text: e.target.value });
  }

  handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    let text = this.state.text.trim();
    if (!text) {
      return;
    }
    this.props.addTodo({id:id++,text});
    this.setState({ text: "" });
  }
}

12. 高阶组件 #

12.1 安装 #

cnpm i hoist-non-react-statics -S

12.2 src\utils.tsx #

src\utils.tsx

import * as React from 'react';
import  hoistNonReactStatics from 'hoist-non-react-statics';
export const defaultProps = {
  setting: {
    maxLength: 6,
    placeholder: '请输入待办事项'
  }
}

export type DefaultProps = Partial<typeof defaultProps>;
export const withDefaultInputProps =  <Props extends DefaultProps>(OldComponent: React.ComponentType<Props>) => {
  type OwnProps = Omit<Props, keyof DefaultProps>; 
  class NewComponent extends React.Component<OwnProps> {
     public render() {
       let props = { ...defaultProps, ...this.props} as Props;
       return (
         <OldComponent {...props} />
       );
     }
  }
  return hoistNonReactStatics(NewComponent, OldComponent);
};

12.3 TodoInput.tsx #

src\components\Todos\TodoInput.tsx

import * as React from "react";
import { Todo } from './types';
+import {withDefaultInputProps,DefaultProps } from '@/utils';
interface OwnProps {
  addTodo:(todo:Todo)=>void
}
type Props = OwnProps & DefaultProps;
interface State {
  text:string
}
let id=0;
class TodoInput extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      text: ""
    };
  }
  public render() {
    const { text } = this.state;
    const { setting } = this.props as (Props & Required<DefaultProps>);
    const { handleChange, handleSubmit } = this;
    return (
      <form onSubmit={handleSubmit}>
        <input type="text" maxLength={setting.maxLength} placeholder={setting.placeholder} 
        value={text} onChange={handleChange} />
        <button type="submit">添加</button>
      </form>
    );
  }

  handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    this.setState({ text: e.target.value });
  }

  handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    let text = this.state.text.trim();
    if (!text) {
      return;
    }
    this.props.addTodo({id:id++,text});
    this.setState({ text: "" });
  }
}
+ export default withDefaultInputProps<Props>(TodoInput);

12.4 src\hoistNonReactStatics.tsx #

src\hoistNonReactStatics.tsx

import * as React from "react";

function hoistNonReactStatics<T extends React.ComponentType<any>,S extends React.ComponentType<any>
>(
  TargetComponent: T,
  SourceComponent: S
): T & S;
function hoistNonReactStatics<T extends React.ComponentType<any>, S extends React.ComponentType<any>>
(targetComponent:T , sourceComponent: S):T&S {
  let keys = Object.getOwnPropertyNames(sourceComponent);
  for (let i = 0; i < keys.length; ++i) {
    const key = keys[i];
    const descriptor = Object.getOwnPropertyDescriptor(sourceComponent, key);
    Object.defineProperty(targetComponent, key, descriptor!);
  }
  return targetComponent as T&S;
}
interface Props{}
interface State{}
class Old extends React.Component<Props, State> {
  static age1: number = 10;
}

class New extends React.Component<Props, State> {
  static age2: number = 10;
}
let c = hoistNonReactStatics<typeof New, typeof  Old>(New, Old);
c.age1;
c.age2;

13. 集成redux #

13.1 models\todos.tsx #

src\models\todos.tsx

export type Todo = {
    id: number;
    text: string
}

13.2 models\index.tsx #

src\models\index.tsx

import { Todo} from './todos';
export {
    Todo
}

13.3 action-types.tsx #

src\store\action-types.tsx

export const ADD = 'ADD';
export const MINUS = 'MINUS';

export const ADD_TODO = 'ADD_TODO';

13.4 reducers\counter.tsx #

src\store\reducers\counter.tsx

import * as types from '../action-types';
import { Action } from '../actions';
export interface CounterState{
    number:number;
}
let initialState: CounterState = { number: 0 };
export default function (state: CounterState = initialState, action: Action): CounterState {
    switch (action.type) {
        case types.ADD:
            return { ...state, number: state.number + 1 };
        case types.MINUS:
            return { ...state, number: state.number - 1 };
        default:
            return state;
    }
}

13.5 reducers\todos.tsx #

src\store\reducers\todos.tsx

import { ADD_TODO} from '../action-types';
import { Action } from '../actions';
import { Todo} from '../../models';
export interface TodosState {
    list: Array<Todo>;
}
let initialState: TodosState = { list: new Array<Todo>()};
export default function (state: TodosState = initialState, action: Action): TodosState {
    switch (action.type) {
        case ADD_TODO:
            return { list: [...state.list,action.payload]};
        default:
            return state;
    }
}

13.6 reducers\index.tsx #

src\store\reducers\index.tsx

import counter, { CounterState} from './counter';
import todos,{TodosState} from './todos';
import { combineReducers } from 'redux';
let reducers = {
    counter,
    todos
};
type ReducersType = typeof reducers;
type CombinedState = {
    [key in keyof ReducersType]: ReturnType<ReducersType[key]>
}
export { CombinedState, CounterState, TodosState}

let combinedReducer = combineReducers(reducers);
export default combinedReducer;

13.7 actions\counter.tsx #

src\store\actions\counter.tsx

import { ADD, MINUS } from '../action-types';
export interface AddAction {
    type: typeof ADD
}
export interface MinusAction {
    type: typeof MINUS
}

export function add(): AddAction {
    return { type: ADD };
}
export function minus(): MinusAction {
    return { type: MINUS };
}

13.8 actions\todos.tsx #

src\store\actions\todos.tsx

import { ADD_TODO } from '../action-types';
import { Todo} from '@/models';
export interface AddTodoAction {
    type: typeof ADD_TODO,
    payload:Todo
}

export function addTodo(todo:Todo): AddTodoAction {
    return { type: ADD_TODO,payload:todo };
}

13.9 actions\index.tsx #

src\store\actions\index.tsx

import { AddAction,MinusAction} from './counter';
import { AddTodoAction } from './todos';
export type Action = AddAction | MinusAction | AddTodoAction;

13.10 components\Counter.tsx #

src\components\Counter.tsx

import * as React from 'react';
+import { connect } from 'react-redux';
+import { CombinedState, CounterState } from '../store/reducers';
+import * as actions from '@/store/actions/counter';
+type StateProps = ReturnType<typeof mapStateToProps>;
+type DispatchProps = typeof actions;
+type Props = StateProps & DispatchProps;

class Counter extends React.Component<Props>{
    render() {
        const { number } = this.props;
        return (
            <div>
                <p>{number}</p>
                <button onClick={()=>this.props.add()}>+</button>
            </div>
        )
    }
}

+let mapStateToProps = function (state: CombinedState): CounterState {
+    return state.counter;
+}

+export default connect(mapStateToProps, actions)(Counter);

13.11 TodoItem.tsx #

src\components\Todos\TodoItem.tsx

import * as React from "react";
+import { Todo } from '@/models';
const todoItemStyle: React.CSSProperties = {
  color: "red",
  backgroundColor: "green",
};
interface Props {
  todo: Todo;
}
const TodoItem: React.FC<Props> = (props: Props) => (
  <li style={todoItemStyle}>{props.todo.text}</li>
);
TodoItem.defaultProps;

export default TodoItem;

13.12 TodoInput.tsx #

src\components\Todos\TodoInput.tsx

import * as React from "react";
+import { Todo } from '@/models';
import { withDefaultInputProps,DefaultProps } from '@/utils';
interface OwnProps {
  addTodo:(todo:Todo)=>void
}
type Props = OwnProps & DefaultProps;
interface State {
  text:string
}
let id=0;

class TodoInput extends React.Component<Props, State> {
  static age:number = 10;
  constructor(props: Props) {
    super(props);
    this.state = {
      text: ""
    };
  }
  public render() {
    const { text } = this.state;
    const { setting } = this.props as Props & Required<DefaultProps>;
    const { handleChange, handleSubmit } = this;
    return (
      <form onSubmit={handleSubmit}>
        <input type="text" maxLength={setting.maxLength} placeholder={setting.placeholder} 
        value={text} onChange={handleChange} />
        <button type="submit">添加</button>
      </form>
    );
  }

  handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    this.setState({ text: e.target.value });
  }

  handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    let text = this.state.text.trim();
    if (!text) {
      return;
    }
    this.props.addTodo({id:id++,text});
    this.setState({ text: "" });
  }
}
export default withDefaultInputProps<Props>(TodoInput);

13.13 Todos\index.tsx #

src\components\Todos\index.tsx

import * as React from 'react';
import TodoInput from './TodoInput';
import TodoItem from './TodoItem';
+import { Todo} from '@/models';
+import { CombinedState,TodosState  } from '@/store/reducers';
+import * as actions from '@/store/actions/todos';
+import {connect} from 'react-redux';
+const ulStyle: React.CSSProperties = {
+  width: "100px"
+};
+type StateProps = ReturnType<typeof mapStateToProps>;
+type DispatchProps = typeof actions;
+type Props = StateProps & DispatchProps;
export interface State {
  todos: Todo[]
}
class Todos extends React.Component<Props,State>{
  addTodo = (todo:Todo) =>{
    this.props.addTodo(todo);
  }
  render() {
    return (
      <div>
        <TodoInput addTodo={this.addTodo}/>
        <ul style={ulStyle}>
          {
            this.props.list.map(todo=><TodoItem key={todo.id} todo={todo}/>)
          }
        </ul>
      </div>
    )
  }
}
+let mapStateToProps = function (state: CombinedState): TodosState {
+  return state.todos;
+}
+export default connect(mapStateToProps, actions)(Todos);

13.14 store\index.tsx #

src\store\index.tsx

import { createStore } from 'redux'
import combinedReducer from './reducers';
let store = createStore(combinedReducer);
export default store;

13.15 src\index.tsx #

src\index.tsx

import * as React from "react";
import * as ReactDOM from "react-dom";
import Counter from "./components/Counter";
import Todos from "./components/Todos";
+import { Provider } from 'react-redux';
+import store from './store';
+ReactDOM.render(
+<Provider store={store}>
+    <React.Fragment>
+        <Counter />
+        <hr/>
+        <Todos />
+    </React.Fragment>
+</Provider>, document.getElementById("root"));

14. 使用路由 #

14.1 src\index.tsx #

src\index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import Counter from './components/Counter';
import Todos from './components/Todos';
import { Provider } from 'react-redux';
import store from './store';
+import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
ReactDOM.render((
    <Provider store={store}>
        <Router >
            <React.Fragment>
+                <ul>
+                    <li><Link to="/counter/counterName">counter</Link></li>
+                    <li><Link to={{ pathname: "/todos", state: { name: 'todoName' } }}>todos</Link></+li>
+                </ul>
+                <Route path="/counter/:name" component={Counter} />
+                <Route path="/todos" component={Todos} />
            </React.Fragment>
        </Router>

    </Provider>
), document.getElementById('root'));

14.2 Counter.tsx #

src\components\Counter.tsx

import * as React from 'react';
import { connect } from 'react-redux';
import { CombinedState, CounterState } from '../store/reducers';
+import * as actions from '@/store/actions/counter';
+import { RouteComponentProps } from 'react-router-dom';
+import { StaticContext} from 'react-router';
+type StateProps = ReturnType<typeof mapStateToProps>;
+type DispatchProps = typeof actions;
+interface Params { name:string}
+interface LocationState { }
+type Props = StateProps & DispatchProps & RouteComponentProps<Params, StaticContext,LocationState>;

class Counter extends React.Component<Props>{
    render() {
+        const { name} = this.props.match.params;
        const { number } = this.props;
        return (
            <div>
+                <p>名称:{name}</p>
                <p>{number}</p>
                <button onClick={()=>this.props.add()}>+</button>
                <button onClick={() => this.props.history.push({ pathname: "/todos", state: { todoName: 'todoName' } })}>/todos</button>
            </div>
        )
    }
}

let mapStateToProps = function (state: CombinedState): CounterState {
    return state.counter;
}

export default connect(mapStateToProps, actions)(Counter);

14.3 Todos\index.tsx #

src\components\Todos\index.tsx

import * as React from 'react';
import TodoInput from './TodoInput';
import TodoItem from './TodoItem';
import { Todo} from '@/models';
+import { CombinedState, CounterState, TodosState } from '@/store/reducers';
+import { RouteComponentProps } from 'react-router-dom';
+import { StaticContext } from 'react-router';
+import * as actions from '@/store/actions/todos';
+import { connect } from 'react-redux';
+type StateProps = ReturnType<typeof mapStateToProps>;
+type DispatchProps = typeof actions;
+const ulStyle: React.CSSProperties = {
+    width: "100px"
+};
+export interface State {
+    todos: Todo[]
+}
+interface Params {}
+interface LocationState { name:string }
+type Props = StateProps & DispatchProps & RouteComponentProps<Params, StaticContext, LocationState>;
class Todos extends React.Component<Props,State>{
    addTodo = (todo:Todo) =>{
       this.props.addTodo(todo);
    }
    render() {
        return (
            <div>
+               <p>名称:{this.props.location.state.name}</p>
                <TodoInput addTodo={this.addTodo}/>
                <ul style={ulStyle}>
                    {
                        this.props.list.map(todo=><TodoItem key={todo.id} todo={todo}/>)
                    }
                </ul>
            </div>
        )
    }
}
let mapStateToProps = function (state: CombinedState): TodosState {
    return state.todos;
}
export default connect(mapStateToProps, actions)(Todos);

15. connected-react-router #

15.1 src\history.tsx #

src\history.tsx

import { createBrowserHistory } from 'history'
const history = createBrowserHistory()
export default history;

15.2 src\index.tsx #

src\index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import Counter from './components/Counter';
import Todos from './components/Todos';
import { Provider } from 'react-redux';
import store from './store';
import {Route, Link } from 'react-router-dom';
+import history from '@/history';
+import {ConnectedRouter} from 'connected-react-router';
ReactDOM.render((
    <Provider store={store}>
+        <ConnectedRouter history={history} >
            <React.Fragment>
                <ul>
                    <li><Link to="/counter/counterName">counter</Link></li>
                    <li><Link to={{ pathname: "/todos", state: { name: 'todoName' } }}>todos</Link></li>
                </ul>
                <Route path="/counter/:name" component={Counter} />
                <Route path="/todos" component={Todos} />
            </React.Fragment>
+        </ConnectedRouter>
    </Provider>
), document.getElementById('root'));

15.3 store\index.tsx #

src\store\index.tsx

import { createStore, applyMiddleware } from 'redux'
+import combinedReducer from './reducers';
+import { routerMiddleware } from 'connected-react-router';
+import history from '@/history';

+let store =applyMiddleware(routerMiddleware(history))(createStore)(combinedReducer);
export default store;

15.4 reducers\index.tsx #

src\store\reducers\index.tsx

import counter, { CounterState} from './counter';
import todos,{TodosState} from './todos';
import { combineReducers } from 'redux';
+import history from '@/history';
+import { connectRouter } from 'connected-react-router'
let reducers = {
    counter,
    todos,
+    router: connectRouter(history)
};
type ReducersType = typeof reducers;
type CombinedState = {
    [key in keyof ReducersType]: ReturnType<ReducersType[key]>
}
export { CombinedState, CounterState, TodosState}

let combinedReducer = combineReducers(reducers);
export default combinedReducer;

15.5 actions\counter.tsx #

src\store\actions\counter.tsx

import { ADD, MINUS } from '../action-types';
+import { push, CallHistoryMethodAction } from 'connected-react-router';
+import { LocationDescriptorObject,Path} from 'history';
+import { TodosLocationState} from '@/components/Todos';
export interface AddAction {
    type: typeof ADD
}
export interface MinusAction {
    type: typeof MINUS
}
export function add(): AddAction {
    return { type: ADD };
}
export function minus(): MinusAction {
    return { type: MINUS };
}
+export function go(path: LocationDescriptorObject<TodosLocationState>): CallHistoryMethodAction {
+    return push(path);
+}

15.6 actions\index.tsx #

src\store\actions\index.tsx

import { AddAction,MinusAction} from './counter';
import { AddTodoAction } from './todos';
+import { CallHistoryMethodAction } from 'connected-react-router';
+export type Action = AddAction | MinusAction | AddTodoAction | CallHistoryMethodAction;

15.7 Counter.tsx #

src\components\Counter.tsx

import * as React from 'react';
import { connect } from 'react-redux';
import { CombinedState, CounterState } from '../store/reducers';
import * as actions from '@/store/actions/counter';
import { RouteComponentProps } from 'react-router-dom';
+import { StaticContext} from 'react-router';
+import { LocationDescriptorObject} from 'history';
+import { TodosLocationState } from '@/components/Todos';
+type StateProps = ReturnType<typeof mapStateToProps>;
+type DispatchProps = typeof actions;
+interface Params { name:string}
+interface CounterLocationState { }
+type Props = StateProps & DispatchProps & RouteComponentProps<Params, StaticContext, CounterLocationState>;
class Counter extends React.Component<Props>{
    render() {
+       const { name} = this.props.match.params;
        const { number } = this.props;
+        let path: LocationDescriptorObject<TodosLocationState> = { pathname: "/todos", state: { name: 'todoName' } };
        return (
            <div>
+                <p>名称:{name}</p>
                <p>{number}</p>
                <button onClick={()=>this.props.add()}>+</button>
                <button onClick={() => this.props.go(path)}>/todos</button>
            </div>
        )
    }
}

let mapStateToProps = function (state: CombinedState): CounterState {
    return state.counter;
}

export default connect(mapStateToProps, actions)(Counter);

15.8 Todos\index.tsx #

src\components\Todos\index.tsx

import * as React from 'react';
import TodoInput from './TodoInput';
import TodoItem from './TodoItem';
import { Todo} from '@/models';
import { CombinedState, CounterState, TodosState } from '@/store/reducers';
import { RouteComponentProps } from 'react-router-dom';
import { StaticContext } from 'react-router';
import * as actions from '@/store/actions/todos';
import { connect } from 'react-redux';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = typeof actions;
const ulStyle: React.CSSProperties = {
    width: "100px"
};
export interface State {
    todos: Todo[]
}
+interface Params {}
+export interface TodosLocationState { name: string }
+type Props = StateProps & DispatchProps & RouteComponentProps<Params, StaticContext, TodosLocationState>;
class Todos extends React.Component<Props,State>{
    addTodo = (todo:Todo) =>{
       this.props.addTodo(todo);
    }
    render() {
        return (
            <div>
+                <p>名称:{this.props.location.state.name}</p>
                <TodoInput addTodo={this.addTodo}/>
                <ul style={ulStyle}>
                    {
                        this.props.list.map(todo=><TodoItem key={todo.id} todo={todo}/>)
                    }
                </ul>
            </div>
        )
    }
}
let mapStateToProps = function (state: CombinedState): TodosState {
    return state.todos;
}
export default connect(mapStateToProps, actions)(Todos);

16. redux-thunk #

16.1 redux-thunk\index.tsx #

src\redux-thunk\index.tsx

import {
    Middleware,
    Dispatch,
    MiddlewareAPI,
    Action
} from "redux";

export type ThunkAction<R,S,E, A extends Action> = (
    dispatch: ThunkDispatch<S,E, A>,
    getState: () => S
) => void;
export interface ThunkDispatch<S,E, A extends Action> {
    <T extends A>(action: T): T;
    <R>(asyncAction: ThunkAction<R,S, E, A>): R;
}
type ThunkDispatched = ThunkDispatch<{},undefined, Action>;
const thunk: Middleware<
    ThunkDispatched,
    {},
    ThunkDispatched
> = (api: MiddlewareAPI<ThunkDispatched, {}>) => (
    next: Dispatch<Action>
) => (action: Function | Action): any => {
    if (typeof action === "function") {
        return action(api.dispatch, api.getState);
    }
    return next(action);
};
export default thunk;

16.2 store\index.tsx #

src\store\index.tsx

import {
    createStore,
    applyMiddleware,
    Action,
    Store,
    AnyAction,
    StoreEnhancer,
    StoreEnhancerStoreCreator
} from "redux";
import combinedReducer,{CombinedState} from './reducers';
import { routerMiddleware } from 'connected-react-router';
import history from '@/history';
//import thunk, { ThunkAction, ThunkDispatch } from 'redux-thunk';
import thunk, { ThunkAction, ThunkDispatch } from '@/redux-thunk';
interface Ext {
    dispatch: ThunkDispatch<CombinedState, undefined, AnyAction>
}
interface StateExt{}
let storeEnhancer: StoreEnhancer = applyMiddleware(routerMiddleware(history),thunk);

let storeEnhancerStoreCreator: StoreEnhancerStoreCreator = storeEnhancer(createStore);
let store: Store<CombinedState & StateExt, AnyAction> & Ext = storeEnhancerStoreCreator(combinedReducer);
let thunkAction: ThunkAction<void,CombinedState, undefined, AnyAction> = (
    dispatch: ThunkDispatch<CombinedState, undefined, AnyAction>,
    getState: () => CombinedState
): void => { }
store.dispatch(thunkAction);
export default store;

参考 #

  • commit_message_change_log

访问验证

请输入访问令牌

Token不正确,请重新输入