• Typescript+React开发环境搭建
  • 配置ESLint和Git Hooks代码检查
  • 使用assert、chai和mocha进行单元测试
  • react、redux、react-router和connected-react-router的类型声明和实战开发
  • 使用Travis CI持续继承React项目

    1、Ts工程化开发专业级React项目

  • 前端工程化就是通过流程规范化、标准化提升团队协作效率

  • 通过组件化、模块化提升代码质量
  • 使用构建工具、自动化工具提升开发效率
  • 编译->打包(合并)->压缩->代码检查->测试->持续集成

    2、项目初始化

    1. mkdir rock_typescript_development
    2. cd rock_typescript_development
    3. npm init
    4. package name: (rock_typescript_development)
    5. version: (1.0.0)
    6. description: TypeScript工程化开发
    7. entry point: (index.js)
    8. test command:
    9. git repository: https://gitee.com/s2265681/rock_typescript_development
    10. keywords: typescript,react
    11. author: rockshang
    12. license: (ISC) MIT

    3、git规范和changelog

    3-1、良好的git commit 好处

  • 可以加快code review 流程

  • 可以根据git commit 的元数据生成change log
  • 可以让其他开发者知道修改的原因

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、pref、test、chore、revert等
  • 提交信息分为两部分,标题(首字母不打些,末尾不加标点)、主体内容(描述修改内容)

  • 日志提交友好的类型选择提示 使用commitize工具

  • 不符合要求格式的日志拒绝提交的保障机制,需要使用validate-commit-msg工具
  • 统一changelog文档信息生成,使用 conventional-changelog-cli 工具
    1. cnpm i commitizen validate-commit-msg conventional-changelog-cli -D
    2. commitizen init cz-conventional-changelog --save --save-exact
    3. git cz

3-3、gitignore

  1. node_modules
  2. .vscode
  3. dist

3-4、提交的格式

  1. <type>(<scope>):<subject/>
  2. <BLANK LINE>
  3. <body>
  4. <BLANK LINE>
  5. <footer>
  • 代表某次提交的类型,比如是修复bug还是增加feature
  • 表示作用域,比如一个页面或一个组件
  • 主题 ,概述本次提交的内容
  • 详细的影响内容
  • 修复的bug和issue链接
类型 说明
feat 新增feature
fix 修复bug
docs 仅仅修改文档如readme合changelog等
style 仅仅修改空格缩进逗号等
refactor 代码重构没有加新功能或者修复bug
perf 优化相关性能体验
test 测试用例单元测试集成测试等
chore 改变构建流程增加依赖库工具等
revert 回滚到上一个版本等
ci CI配置,脚本文件等更新

3-5、husky

  • validate-commit-msg 可以来检查我们的commit规范
  • husky可以把validate-commit-msg 作为一个githook 来验证提交消息

安装

  1. cnpm i husky validate-commit-msg --save-dev

package.json

  1. "husky": {
  2. "hooks": {
  3. "commit-msg": "validate-commit-msg"
  4. }
  5. }

3-6、生成CHANGELOG.md

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

安装

  1. cnpm i conventional-changelog-cli -D

package.json中增加以下脚本

  1. "scripts":{
  2. "commitmsg":"validata-commit-msg",
  3. "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
  4. },
  5. "config": {
  6. "commitizen": {
  7. "path": "cz-conventional-changelog"
  8. }
  9. },

git cz 命令替换 git commit
然后一顿骚操作,就可以按cz的格式提交代码了,
最后 npm run version 就可以生成changelog了

3-7、 git commit 、CHANGELOG 和版本发布的标准自动化文档

https://zhuanlan.zhihu.com/p/51894196

4、支持Typescript

基本参数

参数 解释
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 指定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 outDir 指定输出文件夹
rootDir 编译文件的根目录,编译器会在根目录查找入口文件
composite 是否编译构建引用项目
removeComments 是否将编译后的文件中的注释删掉
noEmit 不生成编译文件
importHelpers 是否引入tslib 里的辅助工具函数
downlevelIteration 当target为ES5 或ES3 时,为for-of 、spread 和destructuring 中的
迭代器提供完全支持
isolatedModules 指定是否将每个文件作为单独的模块,默认为true

5、支持React

5-1、安装

  1. cnpm i react react-dom @types/react @types/react-dom react-router-dom
  2. @types/react-router-dom -S
  3. cnpm i webpack webpack-cli webpack-dev-server html-webpack-plugin hoist-nonreact-
  4. statics -D
  5. cnpm i typescript ts-loader source-map-loader -D
  6. cnpm i redux react-redux @types/react-redux redux-thunk redux-logger
  7. @types/redux-logger -S
  8. cnpm i connected-react-router -S
  1. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1225858/1605692956820-b9d41df2-33ac-4027-aa68-03704df5a70a.png#align=left&display=inline&height=758&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1516&originWidth=1148&size=760469&status=done&style=none&width=574)
  • ts-loader可以让Webpack使用TypeScript的标准配置文件tsconfig.json编译TypeScript代码。
  • source-map-loader使用任意来自Typescript的sourcemap输出,以此通知webpack何时生成自

己的sourcemaps,这让你在调试最终生成的文件时就好像在调试TypeScript源码一样。

5-2、tsconfig.json

  1. {
  2. "compilerOptions": {
  3. "outDir": "./dist",
  4. "sourceMap": true,
  5. "strict": true,
  6. "noImplicitAny": true,
  7. "strictNullChecks": true,
  8. "module": "commonjs",
  9. "target": "es5",
  10. "jsx": "react",
  11. "baseUrl": ".",
  12. "esModuleInterop": true,
  13. "paths": {
  14. "@/*": [
  15. "src/*"
  16. ]
  17. }
  18. },
  19. "include": [
  20. "./src/**/*"
  21. ]
  22. }
  1. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1225858/1605692992827-c3687e04-35f8-47b1-9e92-d272cec21728.png#align=left&display=inline&height=301&margin=%5Bobject%20Object%5D&name=image.png&originHeight=602&originWidth=1136&size=365323&status=done&style=none&width=568)

5-3、webpack.config.js

webpack.config.js

  1. const webpack = require('webpack');
  2. const path = require('path');
  3. const HtmlWebpackPlugin = require('html-webpack-plugin');
  4. module.exports = {
  5. mode:'development',
  6. devtool:false,
  7. entry:'./src/index.tsx',
  8. output:{
  9. path:path.join(__dirname,'dist'),
  10. filename:'[name].[hash:5].js'
  11. },
  12. devServer:{
  13. hot:true,
  14. contentBase:path.join(__dirname,'dist'),
  15. historyApiFallback:{//browserHash 刷新重定向到index.html
  16. index:'./index.html'
  17. }
  18. },
  19. resolve:{
  20. extensions:[".ts",".tsx",".js",".json"],
  21. alias:{
  22. "@":path.resolve("src")
  23. }
  24. },
  25. module:{
  26. rules:[
  27. {
  28. test:/\.tsx?$/,
  29. loader:'ts-loader'
  30. }
  31. ]
  32. },
  33. plugins:[
  34. new HtmlWebpackPlugin({
  35. template:'./src/index.html',
  36. filename:'index.[hash:5].html'
  37. }),
  38. new webpack.HotModuleReplacementPlugin()
  39. ]
  40. }

5-4、src/index.tsx

  1. import * as React from 'react';
  2. import * as ReactDOM from 'react-dom';
  3. let root = document.getElementById('root');
  4. let props = { className: 'title' };
  5. let element= React.createElement('div', props, 'hello');
  6. ReactDOM.render(element, root);

5-5、 src/index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>typescript</title>
  7. </head>
  8. <body>
  9. <div id="root"></div>
  10. </body>
  11. </html>

6、代码规范

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

6-1、常见的代码规范文档

6-2、代码检查

  • Eslint 是一款插件化的Javascript静态代码检查工具,可以通过Vscode安装插件
  • 也可以模块安装
    1. cnpm i eslint typescript @typescript-eslint/parser @typescript-eslint/eslintplugin
    2. --save-dev

6-3、eslintrc配置文件

.eslintrc.js

  1. module.exports = {
  2. "parser":"@typescript-eslint/parser",
  3. "plugins":["@typescript-eslint"],
  4. "rules":{
  5. "no-var":"error",
  6. "no-extra-semi":"error",
  7. "@typescript-eslint/indent":["error",4]
  8. },
  9. "parserOptions": {
  10. "ecmaVersion": 6,
  11. "sourceType": "module",
  12. "ecmaFeatures": {"modules": true}
  13. }
  14. }

6-4、代码检查

package.json

  1. {
  2. "name": "zhufeng_react_typescript",
  3. "version": "1.0.0",
  4. "description": "react+ts+webpack5x",
  5. "main": "index.js",
  6. "scripts": {
  7. "start": "webpack serve",
  8. "build": "webpack",
  9. "lint": "eslint src --ext .tsx",
  10. "lint:fix": "eslint src --ext .tsx --fix",
  11. "test":"jest"
  12. },
  13. "repository": {
  14. "type": "git",
  15. "url": "git+https://github.com/s2265681/rock_react_typescript.git"
  16. },
  17. "keywords": [
  18. "typescript",
  19. "react"
  20. ],
  21. "husky": {
  22. "hooks": {
  23. "commit-msg": "validate-commit-msg"
  24. }
  25. },
  26. "author": "rockshang",
  27. "license": "MIT",
  28. "bugs": {
  29. "url": "https://github.com/s2265681/rock_react_typescript/issues"
  30. },
  31. "homepage": "https://github.com/s2265681/rock_react_typescript#readme",
  32. "devDependencies": {
  33. "@typescript-eslint/eslint-plugin": "^4.8.0",
  34. "@typescript-eslint/parser": "^4.8.0",
  35. "commitizen": "^4.2.2",
  36. "conventional-changelog-cli": "^2.1.1",
  37. "eslint": "^7.13.0",
  38. "hoist-non-react-statics": "^3.3.2",
  39. "html-webpack-plugin": "^4.5.0",
  40. "husky": "^4.3.0",
  41. "source-map-loader": "^1.1.2",
  42. "ts-loader": "^8.0.11",
  43. "typescript": "^4.0.5",
  44. "validate-commit-msg": "^2.14.0",
  45. "webpack": "^5.4.0",
  46. "webpack-cli": "^4.2.0",
  47. "webpack-dev-server": "^3.11.0"
  48. },
  49. "dependencies": {
  50. "@types/react": "^16.9.56",
  51. "@types/react-dom": "^16.9.9",
  52. "@types/react-redux": "^7.1.11",
  53. "@types/react-router-dom": "^5.1.6",
  54. "@types/redux-logger": "^3.0.8",
  55. "connected-react-router": "^6.8.0",
  56. "react": "^17.0.1",
  57. "react-dom": "^17.0.1",
  58. "react-redux": "^7.2.2",
  59. "react-router-dom": "^5.2.0",
  60. "redux": "^4.0.5",
  61. "redux-logger": "^3.0.6",
  62. "redux-thunk": "^2.3.0"
  63. }
  64. }

6-5、VScode 配置自动修复

.vscode/settings.json

  1. {
  2. "eslint.validate": [
  3. "javascript",
  4. "javascriptreact",
  5. "typescript",
  6. "typescriptreact"
  7. ],
  8. "editor.codeActionsOnSave": {
  9. "source.fixAll.eslint": true
  10. }
  11. }

7、单元测试

7-1、安装配置

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

7-2、src/unit.tsx

src/unit.tsx

  1. function sum(a:number,b:number){
  2. return a+b
  3. }
  4. module.exports = {
  5. sum
  6. }

7-3、test/unit.test.tsx

test/unit.test.tsx

  1. let unit = require('../src/unit');
  2. test('1+1=2',()=>{
  3. expect(unit.sum(1,1)).toBe(2);
  4. });
  5. test('1+2=3',()=>{
  6. expect(unit.sum(1,2)).toBe(3);
  7. });

7-4、package.json

  1. "scripts":{
  2. "test":"jest"
  3. }

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就会去这个文件,执行里面的命令
  • language阻断指定了默认运行环境

.travis.yml

  1. language: node_js
  2. node_js:
  3. - '11'
  4. install: npm install
  5. script: npm test

8-4、 实战

生成项目并上传gitbub

  1. npx create-react-app rock-typescript-development

同步仓库
登陆 travis-ci cdn 选择同步仓库

设置仓库环境变量

变量名 含义
GH_TOKEN 用户生成的令牌
GH_REF 仓库地址

Github生成访问令牌 - 添加授权

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

.travis.yml

  1. language: node_js
  2. node_js:
  3. - '11'
  4. cache:
  5. directories:
  6. - node_modules
  7. install:
  8. - npm install
  9. script:
  10. - npm run build
  11. after_script:
  12. - cd ./dist
  13. - git init
  14. - git config user.name "s2265681"
  15. - git config user.email "s2265681@163.com"
  16. - git add .
  17. - git commit -m "react project"
  18. - git push --force --quiet "https://${GH_TOKEN}@github.com/s2265681/rock_react_typescript.git" master:gh-pages
  19. branches:
  20. only:
  21. - master

9、React元素

    ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1225858/1605693399640-9639bced-56b9-4794-b68c-dd9a38ba933b.png#align=left&display=inline&height=243&margin=%5Bobject%20Object%5D&name=image.png&originHeight=486&originWidth=1090&size=176226&status=done&style=none&width=545)

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"));

12、高阶组件

13、集成redux

14、使用路由

15、文档

更多 见文档
react+typescript.pdf