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
Commit message
是否符合格式conventional-changelog-cli可以从git metadata
生成变更日志
统一团队的git commit 标准
angular
的git commit
日志作为基本规范validate-commit-msg
工具conventional-changelog-cli
工具cnpm i commitizen validate-commit-msg conventional-changelog-cli -D
commitizen init cz-conventional-changelog --save --save-exact
git cz
.gitignore
node_modules
.vscode
dist
<type>(<scope>):<subject/>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
类型 | 含义 |
---|---|
feat | 新增feature |
fix | 修复bug |
docs | 仅仅修改了文档,比如README、CHANGELOG、CONTRIBUTE等 |
style | 仅仅修改了空格、格式缩进、偏好等信息,不改变代码逻辑 |
refactor | 代码重构,没有新增功能或修复bug |
perf | 优化相关,提升了性能和体验 |
test | 测试用例,包括单元测试和集成测试 |
chore | 改变构建流程,或者添加了依赖库和工具 |
revert | 回滚到上一个版本 |
ci | CI 配置,脚本文件等更新 |
validate-commit-msg
可以来检查我们的commit规范validate-commit-msg
作为一个githook
来验证提交消息cnpm i husky validate-commit-msg --save-dev
"husky": {
"hooks": {
"commit-msg": "validate-commit-msg"
}
}
conventional-changelog-cli
默认推荐的 commit 标准是来自angular项目-i CHANGELOG.md
表示从 CHANGELOG.md
读取 changelog
CHANGELOG.md
为同一文件cnpm i conventional-changelog-cli -D
"scripts": {
"changelogs": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
}
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 | 一个对象数组,指定要引用的项目 |
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源码一样 |
{
"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 | 需要编译的目录 |
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()
],
};
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);
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>
{
"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/**/*"
}
}
cnpm i eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
.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
}
}
}
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
.vscode\settings.json
{
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
cnpm i jest @types/jest ts-jest -D
npx ts-jest config:init
src\sum.tsx
function sum(a: number, b: number) {
return a + b;
}
module.exports = {
sum
}
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);
});
package.json
"scripts": {
+ "test": "jest"
},
GitHub
帐号Travis
会监听这个仓库的所有变化Travis
要求项目的根目录下面,必须有一个.travis.yml
文件。这是配置文件,指定了 Travis 的行为Commit
,Travis 就会去找这个文件,执行里面的命令.travis.yml
文件language: node_js
node_js:
- "11"
install: npm install
script: npm test
npx create-react-app zhufeng-typescript-development
变量名 | 含义 | |
---|---|---|
GH_TOKEN | 用户生成的令牌 | |
GH_REF | 仓库地址 | github.com/zhufengnodejs/zhufeng_typescript_development.git |
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
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;
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;
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;
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>
)
}
}
src\components\Todos\types.tsx
export type Todo = {
id:number;
text:string
}
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;
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: "" });
}
}
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>
)
}
}
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"));
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: "" });
}
}
cnpm i hoist-non-react-statics -S
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);
};
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);
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;
src\models\todos.tsx
export type Todo = {
id: number;
text: string
}
src\models\index.tsx
import { Todo} from './todos';
export {
Todo
}
src\store\action-types.tsx
export const ADD = 'ADD';
export const MINUS = 'MINUS';
export const ADD_TODO = 'ADD_TODO';
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;
}
}
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;
}
}
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;
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 };
}
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 };
}
src\store\actions\index.tsx
import { AddAction,MinusAction} from './counter';
import { AddTodoAction } from './todos';
export type Action = AddAction | MinusAction | AddTodoAction;
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);
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;
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);
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);
src\store\index.tsx
import { createStore } from 'redux'
import combinedReducer from './reducers';
let store = createStore(combinedReducer);
export default store;
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"));
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'));
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);
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);
src\history.tsx
import { createBrowserHistory } from 'history'
const history = createBrowserHistory()
export default history;
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'));
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;
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;
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);
+}
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;
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);
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);
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;
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;