# React-ts实战梳理
# 1、工程搭建
# 1、安装脚本
npx create-react-app ts-react-app --template typescript
npm i antd axios react-router-dom lodash
npm i @types/react-router-dom @types/lodash -D
npm i redux redux-thunk react-redux
npm i @types/react-redux -D
npm i babel-plugin-import -D
npm i customize-cra react-app-rewired -D
npm i http-server http-proxy-middleware -D
2
3
4
5
6
7
8
# 2、工程结构
public:静态资源,最终会拷贝至根目录下,对静态文件引用需添加public目录:
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
webpack以及启动全部封装在:react-scripts库中,对外不暴露,需使用npm run eject扩展查看
相关组件及库文件
- UI组件库:antd
- http:axios
- router:react-router-dom
- 状态管理:redux redux-thunk react-redux
- mock:http-server、http-proxy-middleware
- 工具类:customize-cra、react-app-rewired、babel-plugin-import、lodash
# 3、antd按需引入
解析:组件按需引入customize-cra、react-app-rewired、babel-plugin-import:在项目中配置webpack,可通过在根目录新建一个名称为config-overrides.js的文件进行覆盖;react-app-rewired的作用就是在不eject的情况下,覆盖create-react-app的配置;package.json中启动配置需将react-scripts修改为react-app-rewired
//config-overrides.js,引入组件时自动引入样式
const { override, fixBabelImports} = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css'
})
);
2
3
4
5
6
7
8
9
# 4、本地mock配置
使用http-server启动静态服务器、使用http-proxy-middleware进行请求代理
- 本地安装http-server启动本地http服务,package.json配置启动脚本:"server": "cd mock && http-server -p 4000 -a localhost"
解析:webpack-dev-server将public目录下文件也放置在静态服务器下,但一般不使用该目录放置mock文件,影响实际工程。
- 安装http-proxy-middleware插件,在src目录下配置setupProxy.js配置文件,脚手架启动时会自动读取
const { createProxyMiddleware } = require("http-proxy-middleware")
module.exports = function(app) {
app.use(createProxyMiddleware('/api/**/*.action', {
target: 'http://localhost:4000',
pathRewrite(path) {
return path.replace('/api', '/').replace('.action', '.json');
}
}));
};
2
3
4
5
6
7
8
9
# 2、组件封装
# 1、组件演化
组件复用方式 | 优势 | 略势 | 状态 |
---|---|---|---|
类组件(class) | 发展时间长,接受度广泛 | 只能继承父类 | 传统模式,长期存在 |
Mixin | 可以复制任意对象的任意多个方法 | 组件相互依赖、耦合,可能产生冲突,不利用维护 | 被抛弃 |
高阶组件(HOC) | 利用装饰器模式,在不改变组件的基础上,动态地为其添加新的能力 | 嵌套过多调试困难,需要遵循某些约定(不改变原始组件,透传props等) | 能力强大,应用广泛 |
Hooks | 代替class,多个Hooks互不影响,避免嵌套地狱,开发效率高 | 切换到新思维需要成本 | React的未来 |
# 2、函数组件
函数组件:传统组件的区别为组件状态指定类型
React.FC<P>
:props隐含children声明,函数属性自动提醒
import React from 'react'
import {Button} from 'antd'
//常规定义
/*
interface Greeting {
name: string;
firstName: string;
lastName: string;
}
const Hello = (props: Greeting) => <Button>Hello {props.name}</Button>
Hello.defaultProps = {
firstName: "",
lastName: ""
}
*/
//函数式组件,官方提供React.FC接口,后续可能废弃,不推荐使用
//优点:props隐含children声明、函数属性自动提示,但默认属性设置时必须为可选属性
//type React.FC<P = {}> = React.FunctionComponent<P>,泛型约束的为组件的state
interface Greeting {
name: string;
firstName?: string;
lastName?: string;
}
const Hello: React.FC<Greeting> = ({ name }) => <Button>Hello {name}</Button>
Hello.defaultProps = {
firstName: "",
lastName: ""
}
export default Hello
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 3、类组件
类组件:传统组件的区别为组件指定类型,包括属性和状态,Component<P,S>
import React, {Component} from 'react'
import {Button} from 'antd'
interface Greeting {
name: string;
firstName?: string;
lastName?: string;
}
interface State {
count: number
}
//react的声明文件中对所有api进行了重新定义
//Component定义为泛型类:class Component<P = {}, S = {}, SS = any>
//P属性类型、S为状态类型,默认都为空对象
class HelloClass extends Component<Greeting, State>{
//初始化状态
state: State = {
count: 0
}
//设置默认值
static defaultProps ={
firstName: '',
lastName: ''
}
//提取函数,放置每次生成新函数,导致子组件渲染
handleClick = () => {
this.setState({count: this.state.count + 1})
}
render(){
return (
<>
<p>Your click num: {this.state.count}</p>
<Button onClick={this.handleClick}>Hello {this.props.name}</Button>
</>
)
}
}
export default HelloClass
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 4、高阶组件
高阶组件,引入ts的泛型进行约束:
- 被包装组件类型:
React.ComponentType<P>)
- 新组件类型:
Component<P & NewProps>
//高级组件,引入ts的泛型进行约束
import { BlockList } from 'net';
import React, {Component} from 'react'
import HelloClass from './HelloClass'
interface Loading {
loading: boolean
}
//type React.ComponentType<P = {}> = React.ComponentClass<P, any> | React.FunctionComponent<P>
//组件类型:既可以是函数组件也可以为类组件,泛型接口,P为属性类型
function HelloHOC<P>(WrappedComponent: React.ComponentType<P>){
return class extends Component<P & Loading> {
render(){
const {loading, ...props} = this.props;
return loading ? <div>Loading...</div> : <WrappedComponent {...props as P}/>
}
}
}
export default HelloHOC(HelloClass)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 5、hooks
hooks:useState<T>
为泛型接口
import React, {useEffect, useState} from 'react'
import { Button } from 'antd'
interface Greeting {
name: string;
firstName: string;
lastName: string;
}
const HelloHooks = (props: Greeting) => {
const [count, setCount] = useState(0);
const [text, setText] = useState<string | null>(null);
const handleClick = () => {
setCount(count + 1)
}
useEffect(()=> {
if(count > 5){
setText('Have a rest')
}
}, [count])
return (
<>
<p>Your click num: {count} {text}</p>
<Button onClick={handleClick}>Hello {props.name}</Button>
</>
)
}
HelloHooks.defaultProps = {
firstName: "",
lastName: ""
}
export default HelloHooks
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 3、事件与类型
dom事件使用React.FormEvent<HTMLInputElement>
类型约束
//1. 受控组件Input
const [name, setName] = useState('');
<Input
placeholder="姓名"
style={{ width: 120 }}
allowClear
value={name}
onChange={handleNameChange}
/>
//泛型接口:interface React.FormEvent<T = Element>
const handleNameChange = (e: React.FormEvent<HTMLInputElement>) => {
setName(e.currentTarget.value)
}
handleNameChange = (e: React.FormEvent<HTMLInputElement>) => {
let name = e.currentTarget.value;
this.setState({
name: name === '' ? undefined : name.trim()
});
}
//2. 受控组件select
const [departmentId, setDepartmentId] = useState<number | undefined>();
<Select
placeholder="部门"
style={{ width: 120 }}
allowClear
value={departmentId}
onChange={handleDepartmentChange}
>
<Option value={1}>技术部</Option>
<Option value={2}>产品部</Option>
<Option value={3}>市场部</Option>
<Option value={4}>运营部</Option>
</Select>
const handleDepartmentChange = (value: number) => {
setDepartmentId(value)
}
handleDepartmentChange = (value: number) => {
this.setState({
departmentId: value
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 4、数据请求
使用interface维护前后端接口:增加可维护性、自动补全、类型检查
//1. 接口定义:interface/employee.ts
//请求接口
export interface EmployeeRequest {
name: string;
departmentId: number | undefined;
}
//返回接口
interface EmployeeInfo {
id: number;
key: number;
name: string;
department: string;
hiredate: string;
level: string;
}
export type EmployeeResponse = EmployeeInfo[] | undefined
//2. 接口使用:component/employee/QueryForm.tsx
import { EmployeeRequest, EmployeeResponse } from '../../interface/employee';
interface Props{
onDataChange(data: EmployeeResponse): void
}
const QueryFormHooks = (props: Props) => {
//不使用redux,将数据放在父组件中,调用父组件的回调方法
const queryEmployee = (param: EmployeeRequest) => {
get(GET_EMPLOYEE_URL, param).then(res => {
props.onDataChange(res.data);
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 5、Redux数据处理
# 1、安装
npm i redux redux-thunk react-redux npm i @types/react-redux -D
# 2、文件结构
-redux
-employee
-index.ts //reducer\actionCreator
-rootReducer.ts //root:combineReducers(reducers);
-store.ts //store
-index.ts //Provider注入
-components/employee/index.tsx //store使用,将组件中的action和state剥离
2
3
4
5
6
7
ts状态类型:
- state:type ReadOnly<{...}>,只对一级属性起作用
- action:type Action = {type: string; payload: any;}
- dispatch: Dispatch,redux提供
# 3、具体实例
//一、创建store
//1. store.ts:store创建,添加异步中间件
import { createStore, compose, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './rootReducer'
const store = createStore(rootReducer,
compose(
applyMiddleware(thunk)
)
)
export default store;
//2. rootReducer:模块解耦,reducer整合
import { combineReducers } from 'redux';
import employee from './employee';
const reducers = {
employee
}
export default combineReducers(reducers);
//3. employee/index.ts:模块使用store,reducer及actionCreator
import { Dispatch } from 'redux';
import { get } from '../../utils/request';
import { GET_EMPLOYEE_URL } from '../../constants/urls';
import { EmployeeRequest, EmployeeResponse } from '../../interface/employee';
//state设置为readonly,后面修改state会报错
//目前readonly只对一级属性有作用,若为数组对数组内元素不起作用
type State = Readonly<{
employeeList: EmployeeResponse
}>
type Action = {
type: string;
payload: EmployeeResponse;
}
//reducer创建
const initialState: State = {
employeeList: undefined
}
export default function(state = initialState, action: Action){
switch (action.type){
case GET_EMPLOYEE_URL:
return {
...state,
employeeList: action.payload
}
default:
return state
}
}
//actionCreators
export function getEmployee(param: EmployeeRequest) {
return (dispatch: Dispatch) => {
get (GET_EMPLOYEE_URL, param).then(res => {
dispatch({
type: GET_EMPLOYEE_URL,
payload: res.data
})
})
}
}
//二、注入系统
import Root from './routers';
import store from './redux/store';
import { Provider } from 'react-redux';
ReactDOM.render(
<Provider store={store}>
<Root />
</Provider>,
document.getElementById('root')
);
//三、模块使用:将state和action全部通过redux外部传入
import { bindActionCreators, Dispatch } from 'redux';
import { connect } from 'react-redux';
import { getEmployee } from '../../redux/employee';
const mapStateToProps = (state: any) => ({
employeeList: state.employee.employeeList
})
const mapDispatchToprops = (dispatch: Dispatch) =>
bindActionCreators({
onGetEmployee: getEmployee
}, dispatch)
interface Props {
onGetEmployee(param: EmployeeRequest): void;
employeeList: EmployeeResponse;
}
class Employee extends Component<Props> {
render() {
const {employeeList, onGetEmployee} = this.props;
return (
<>
{<QueryForm onDataChange={onGetEmployee} />}
{<Table columns={employeeColumns} dataSource={employeeList} className="table" />}
</>
)
}
}
export default connect(
mapStateToProps,
mapDispatchToprops
)(Employee)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104