|
react中使用typescript创建项目
create-react-app react-ts --scripts-version=react-scripts-ts
输入y,不要点回车,成功后的项目目录如下:

node_modules直接装好了,cd react-ts npm run start项目跑起来。
tsconfig.json包含了工程里TypeScript特定的选项,是TypeScript的配置文件,项目要想使用TypeScript需要增加这个文件。tslint.json保存了要使用的代码检查器的设置,TSLint。package.json包含了依赖,还有一些命令的快捷方式,如测试命令,预览命令和发布应用的命令。public包含了静态资源如HTML页面或图片。除了index.html文件外,其它的文件都可以删除。src包含了TypeScript和CSS源码。index.tsx是强制使用的入口文件。
{
"compilerOptions": {
// import的相对起使路径
"baseUrl": ".",
"outDir": "build/dist",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"importHelpers": true,
"strictNullChecks": true,
// 开启装饰器的使用
"experimentalDecorators": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true
},
"exclude": [
"node_modules",
"build",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts"
]
}
"include": [
"./src/**/*"
]
- 读取所有可识别的
src目录下的文件(通过include)。 - 接受JavaScript做为输入(通过
allowJs)。 - 生成的所有文件放在
built目录下(通过outDir)。 - 将JavaScript代码降级到低版本比如ECMAScript 5(通过
target)
Webpack
Webpack集成非常简单。 你可以使用awesome-typescript-loader,它是一个TypeScript的加载器,结合source-map-loader方便调试。 运行:
npm install awesome-typescript-loader source-map-loader
并将下面的选项合并到你的webpack.config.js文件里:
module.exports = {
entry: "./src/index.ts",
output: {
filename: "./dist/bundle.js",
},
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
},
module: {
loaders: [
// All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
{ test: /\.tsx?$/, loader: "awesome-typescript-loader" }
],
preLoaders: [
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ test: /\.js$/, loader: "source-map-loader" }
]
},
// Other options...
};
要注意的是,awesome-typescript-loader必须在其它处理.js文件的加载器之前运行。
这与另一个TypeScript的Webpack加载器ts-loader是一样的。 你可以到这里了解两者之间的差别。
注:如果要在项目中使用ts 就需要把js文件 改成 ts文件 jsx文件改为tsx文件,增加tsconfig.json文件配置,增加webpack配置。
index.tsx文件
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(
<App />,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();
来写一个button组件
import * as React from 'react'
class Button extends React.Component {
public render() {
return (
<div>
<button>点击事件</button>
</div>
)
}
}
export default Button;
如果直接写render函数,会有下面的提示信息

需要在render函数前面加public,App.tsx ts中的类有严格的格式要求,每一个属性都需要加上private, public,或者 protected
import * as React from 'react';
import './App.css';
import Button from './Button'
import logo from './logo.svg';
class App extends React.Component {
public render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.tsx</code> and save to reload.
</p>
<Button name={'麦乐'} />
</div>
);
}
}
export default App;
button组件需要定义两个接口,接口的名称必须大写,这样组件内部才可以访问props和state
interface IPros {
name: string
}
interface IState {
age: number
}
import * as React from 'react'
interface IPros {
name: string
}
interface IState {
age: number
}
class Button extends React.Component<IPros, IState> {
public constructor(props: IPros) {
super(props)
this.state = {
age: 18
}
}
public render() {
return (
<div>
<button>点击事件{this.props.name}{this.state.age}</button>
</div>
)
}
}
export default Button;
增加点击事件 会有下面提示信息

tslint规则,由于渲染性能的影响,表达式是被禁止使用的。所以我们需要改一下代码。
import * as React from 'react'
interface IPros {
name: string
}
interface IState {
age: number
}
class Button extends React.Component<IPros, IState> {
public constructor(props: IPros) {
super(props)
this.handlerClick = this.handlerClick.bind(this)
this.state = {
age: 18
}
}
public handlerClick(e: React.MouseEvent<HTMLButtonElement>) {
console.log('我被点击了')
}
public render() {
return (
<div>
<button onClick={this.handlerClick}>点击事件{this.props.name}{this.state.age}</button>
</div>
)
}
}
export default Button;
这是页面中会看到console.log这里画这红色的波浪线,这是因为tslint的原因
在tslint.json文件中增加配置 "no-console":false
{
"extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
"rules": {
"no-console":false
},
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts",
"coverage/lcov-report/*.js"
]
}
}
增加生命周期函数,使用方法没有区别,需要加上共有还是私有。
public componentDidMount() {
console.log('组件加载完成')
}
我们来写一个todoList
TodoList组件
import * as React from 'react'
import { FriendItem } from './types'
type onDel = (index: number) => void
interface IProps {
list: FriendItem[];
onDel: onDel;
}
interface IState {
age: number;
}
class TodoList extends React.Component<IProps, IState> {
public constructor(props: IProps) {
super(props)
this.state = {
age: 18,
}
}
public render() {
const { list, onDel } = this.props
return (
<div>
{
list.map((item: FriendItem, index: number) =>
<div key={index}>
<span>{item.name}</span> <button onClick={() => {
onDel(index)
}
}>删除</button>
</div>
)
}
</div>
)
}
public componentDidMount() {
console.log('组件加载完成')
}
}
export default TodoList;
action 组件
import * as React from 'react'
type onAdd = (value: string) => void
type onValueChange = (value: string) => void
interface IProps {
onAdd: onAdd;
onValueChange: onValueChange;
value: string;
}
class Action extends React.Component<IProps> {
public render() {
const { value, onAdd, onValueChange } = this.props
return (
<div>
<input type="text" value={value} onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
onValueChange(e.target.value)
}}/>
<button onClick={() => {
onAdd(value)
}}>add</button>
</div>
)
}
public componentDidMount() {
console.log('组件加载完成')
}
}
export default Action;
暴露一个接口
export interface FriendItem {
name: string;
age: number;
}
App.jsx
import * as React from 'react';
import './App.css';
import Actions from './action'
import TodoList from './todoList'
import logo from './logo.svg';
import { FriendItem } from './types'
interface IState {
value: string;
list: FriendItem[];
}
class App extends React.Component<Object, IState> {
public constructor(props: Object) {
super(props)
this.state = {
list: [{
age: 1,
name: ''
}],
value: ''
}
}
public render() {
const { list, value } = this.state
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<Actions onValueChange={(value: string) => {
this.setState({
value
})
}} onAdd={(value: string) => {
list.push({name: value, age: 3})
this.setState({
list: [...list],
value: ''
})
}} value={value}/>
<TodoList list={list} onDel={(index: number) => {
list.splice(index,1)
this.setState({
list: [...list]
})
}}/>
</div>
);
}
}
export default App;
tslint.json文件做了一点修改,可选择配置。
"rules": {
"no-console":false,
"jsx-no-lambda": false,
"ban-types": false, // 是否禁止使用特定的类型 例如Object,
"interface-name": false, //收否要求接口名称大写
"no-shadowed-variable":false,
"ordered-imports": false // 收否按照字母循序引入
},
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts",
"coverage/lcov-report/*.js"
]
}
}
react中配置mobx
npm install mobx --save
npm install mobx-react --save
创建store文件夹
下面创建friend.ts文件
import { observable, computed } from 'mobx'
import post from './post'
import { PostListItem } from './type'
class Friend {
@observable public list: string[] = ['nhao', '今天好开心']
@observable public id: number = 0
@computed
get friendPost() {
return post.list.filter((item: PostListItem) => item.friendId === this.id)
}
}
const friend = new Friend()
export default friend
定义了响应式变量list和id, 还有一个计算属性 friendPost,它的值是又post里面的list和friend里面的id共同计算得来的。只要二者发生变化,就会重新计算。
post.ts
import { observable } from 'mobx'
import { PostListItem } from './type'
class Post {
@observable public list: PostListItem[] = [
{
title: '你好',
content: '今天是周二',
id: 1,
friendId: 1
},
{
title: '你好',
content: '今天是周三',
id: 2,
friendId: 2
}
]
}
export default new Post()
这个文件中只放了一个列表
把接口定义在type.ts中
export interface PostListItem {
title: string;
content: string;
id: number;
friendId: number;
}
export interface FriendListItem {
name: string;
id: number;
}
export interface Friend {
list: FriendListItem[];
activeId: number;
friendPost: PostListItem;
}
export interface PostList {
list: PostListItem[];
}
export interface Store {
friend: Friend;
post: PostList;
}
export interface IMobxStore {
name: string;
greeting: string;
setName(name:string): void;
}
下面是index.ts
import friend from './friend'
import post from './post'
export default {
friend,
post
}
如果直接这样定义App组件,会发现一直提示下面的错信息,
//编译时错误代码:
“Property 'friend' is missing in type '{}' but required in type 'Readonly<IPropsFromParent>'”
index.tsx中并没有对App组件传递属性。这是因为,App组件会一直去passedProps中找这个值,而没有去store中找。类似下面的
都是同一个问题。有两种解决方法:

一:换一种写法
举例
// 创建store
import {action, computed, observable} from 'mobx'
import { IMobxStore } from './type'
class MobxStore implements IMobxStore {
@observable public name: string = "world"
@computed
public get greeting(): string {
return `hello ${this.name}`
}
@action.bound
public setName(name: string): void{
this.name = name
}
}
export default new MobxStore()
implements是一个类实现一个接口用的关键字, 接口如下
export interface IMobxStore {
name: string;
greeting: string;
setName(name:string): void;
}
实现一个App组件 主要改变的就是props接口的定义方法。
// 使用Store
// App.tsx 在src/App.tsx中 使用store, 代码如下:
import * as React from 'react'
import './App.css'
import { IMobxStore } from './store/type'
import { inject, observer } from 'mobx-react'
interface IAppProps {
person?: IMobxStore // 这里比较关键 ?表示可或缺,如果没有就会报错。
}
@inject('person')
@observer
class App extends React.Component<IAppProps, {}> {
constructor(props: IAppProps) {
super(props)
this.clickHandler = this.clickHandler.bind(this);
}
public render() {
const {greeting} = this.props.person!; // 这里要加!
return (
<div className="App">
<header className="App-header">
{greeting}
<button onClick={this.clickHandler}>Change Greeting</button>
</header>
</div>
);
}
private clickHandler = (): void =>{
console.log(this.props)
const { setName } = this.props.person!; // 这里也是
setName("Bob");
}
}
export default App
index.tsx 不用修改
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import registerServiceWorker from './registerServiceWorker';
import { Provider } from "mobx-react"
import store from "./store"
ReactDOM.render(
<Provider {...store}>
<App />
</Provider>,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();
这样可以解决上面的问题。
二:换一个插件
npm install ts-mobx-react --save
修改App.tsx,
import * as React from 'react';
import './App.css';
import Friends from './components/friend'
import { observer } from 'ts-mobx-react'
import Post from './components/post'
interface IPropsFromParent {
age: string
}
@observer
class App extends React.Component<{} & IPropsFromParent, {}> {
public render() {
return (
<div className="App">
<Friends />
<Post />
</div>
);
}
}
export default App;
friend.tsx内部
import * as React from 'react'
import { observer, inject} from 'ts-mobx-react'
import { FriendListItem, Friend } from '../store/type'
@observer
class Friends extends React.Component<{}> {
@inject('friend') public friend: Friend;
public render() {
return (
<div>
{
this.friend.list.map((item: FriendListItem, index: number) => {
return <span key={ index } onClick={() => {
this.friend!.changeActiveId(item.id)
console.log(this.friend.activeId)
}} >{item.name}|</span>
})
}
</div>
)
}
}
export default Friends
index.tsx 把mobx-react都换成 ts-mobx-react
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import registerServiceWorker from './registerServiceWorker';
import { Provider } from "ts-mobx-react"
import store from "./store"
ReactDOM.render(
<Provider {...store}>
<App age="8"/>
</Provider>,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();
这样就可以拿到store中的数据,但是,这个插件有一个问题,那就是数据不再是响应式的,this.friend的改变并不能改变store中数据,看了源码发现
export function inject(dataIndex?: string) {
return function(prototype: any, propertyName: string) {
const constructor = prototype.constructor;
const stores: string[] =
Reflect.getOwnMetadata(StoreNamesSymbol, constructor) || [];
if (!dataIndex) {
dataIndex = propertyName;
}
Reflect.defineMetadata(
StoreNamesSymbol,
stores.concat(dataIndex),
constructor
);
Object.defineProperty(prototype, propertyName, {
enumerable: true,
configurable: true,
get() {
return this.props[addPrefix(dataIndex!)];
}
});
};
}
函数内部是设置了监听,但是只监听了属性的获取,属性值改变的时候并没有做任何操作。所以这个这个插件还是不要使用了。
修改friend.tsx组件
import * as React from 'react'
import { observer, inject} from 'mobx-react'
import { FriendListItem, Friend } from '../store/type'
interface StoreProps {
friend?: Friend
}
@inject('friend')
@observer
class Friends extends React.Component<StoreProps> {
public render() {
const { list, changeActiveId } = this.props.friend!
return (
<div>
{
list.map((item: FriendListItem, index: number) => {
return <span key={ index } onClick={() => {
changeActiveId(item.id)
}} >{item.name}|</span>
})
}
</div>
)
}
}
export default Friends
修改post组件
import * as React from 'react'
import { observer, inject } from 'mobx-react'
import { Friend } from '../store/type'
interface StoreProps {
friend?: Friend
}
@inject('friend')
@observer
class Post extends React.Component<StoreProps> {
public render() {
const { friendPost } = this.props.friend!;
return (
<div>
<p>{friendPost[0].title}</p>
<p>{friendPost[0].content}</p>
</div>
)
}
}
export default Post
App.tsx中和indx.tsx中的ts-mobx-react换成mobx-react
如果运行过程中遇到这样的报错。
Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i @types/jest` or `npm i @types/mocha`.

你需要将tsconfig.json中的typeRoots这个属性路径配置对就可以了。
"typeRoots": [
"../node_modules/@types/",
"../@types/"
]
需要进一步了解,可以看官网:https://www.tslang.cn/docs/handbook/tsconfig-json.html#types-typeroots-and-types
大致意思就是说
@types,typeRoots和types
默认所有可见的"@types"包会在编译过程中被包含进来。 node_modules/@types文件夹下以及它们子文件夹下的所有包都是可见的; 也就是说, ./node_modules/@types/,../node_modules/@types/和../../node_modules/@types/等等。
如果指定了typeRoots,只有typeRoots下面的包才会被包含进来。 比如:
{
"compilerOptions": {
"typeRoots" : ["./typings"]
}
}
这个配置文件会包含所有./typings下面的包,而不包含./node_modules/@types里面的包。
项目中使用sass, tnpm run eject 展开webpack配置。tnpm i sass-loader node-sass 安装相应的插件
webpack.config.dev.js文件中添加配置,加在rules属性配置里面。webpack.config.prod.js做同样的操作。
{
//这里是新加的
test: /\.scss$/,
loaders: ['style-loader', 'css-loader', 'sass-loader'],
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/, /\.scss$/],
loader: require.resolve('file-loader'),
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
运行的时候遇到下面的报错。
this.getResolve is not a function
将 "sass-loader": "^8.0.0",更换成了 "sass-loader": "^7.3.1",
这样直接在tsx文件中引入scss文件,就生效了。
引入antd design
如果按照管网上给出的方法,会发现如论是增加.bebelrc还是在webpack中加配置都不生效。下面直接给出一种生效的方法。
npm install antd babel-plugin-import --save
npm install react-app-rewired react-app-rewire-less --save
npm install ts-import-plugin react-scripts-ts --save
react-app-rewired@1.6.2 这个插件要指定版本,不然会报错。
项目根目录下增加 config-overrides.js文件
const tsImportPluginFactory = require('ts-import-plugin');
const { getLoader } = require('react-app-rewired');
const rewireLess = require('react-app-rewire-less');
module.exports = function override(config, env) {
const tsLoader = getLoader(
config.module.rules,
rule => rule.loader && typeof rule.loader === 'string' && rule.loader.includes('ts-loader')
);
tsLoader.options = {
getCustomTransformers: () => ({
before: [
tsImportPluginFactory({
libraryDirectory: 'es',
libraryName: 'antd',
style: "css",
}),
],
}),
};
config = rewireLess.withLoaderOptions({
javascriptEnabled: true,
modifyVars: {
'@primary-color': '#1DA57A', // 主题色
},
})(config, env);
return config;
};
package.json文件中的scripts属性换一下。
"scripts": {
"start": "react-app-rewired start --scripts-version react-scripts-ts",
"build": "react-app-rewired build --scripts-version react-scripts-ts",
"test": "react-app-rewired test --env=jsdom --scripts-version react-scripts-ts"
},
tsx文件中引入你需要使用的组件import { DatePicker } from 'antd';
重启项目,就能看到进入成功了。
如果遇到下面错误

可以在tsconfig.json文件中增加"skipLibCheck": true配置,这只是一个应对方法。
项目重启后发现,样式失效了,是因为我原来使用sass,这里配置了less,覆盖掉了,直接把scss文件改成less文件就可以了。

|