使用React-create-app搭建react+ts项目
一、create-react-app脚手架
1、依赖安装create-raect-app
2、项目初始化
**·**快速构建出项目名为react-ts的react+ts的项目
**·**进入项目可以看到默认已将安装好的部分依赖,此时运行npm run strat命令项目默认会在3000端口地址启动,如下图:
**·**项目目录结构如下,此时可以删除一些用不上的文件的
删除之后如下:
**·**react项目默认隐藏了webpack相关配置文件,如果想要暴露在项目中,需要执行npm run eject,此操作无法回退,根据需要执行;
3、配置路径别名
**·**在引入文件是都是…/ …/…/这种相对路径方式引用可读性很差
**·**安装依赖
**·**在项目根目录下创建config-overrids.js文件,添加如下配置:
**·**修改package.json配置,重启项目 npm run serve即可
**·**此时页面组件引用方式可以由…/方式改为@/方式
**·**如果提示找不到类型声明,那么就检查tsconfig.json文件看看是否缺配置
二、引入Ant-design组件库
1、安装依赖
2、在App.tsx文件中引入按钮
**·**查看效果,按钮正常显示说明就是成功了:
3、语言汉化
**·**使用ConfigProvide包裹App根组件
**·**在App组件内引入日期选择组件查看效果,可以看到由默认的英文已经切换到中文
三、引入react路由
1、安装依赖
2、注册路由
**·**在index.tsx引入并注册路由,这里我们使用history模式
3、路由跳转例子
**·**在src下新家你pages文件夹,创建两个路由组件,新建routes文件夹,创建index.tsx文件用于存放路由表,引入路由组件并向外暴露,如下:
**·**为了模拟路由跳转功能,咱们从antd扒一些布局代码,加入到App.tsx组件当中
**·**因为都是写静态的数据,可以自行定义,路由跳转核心是useNavigate,useRouters两个方法,通过useRouters()获取路由表,使用useNavigate()获得navigate函数(navigate是自定义的变量,可以是任意字符串),它接收两个参数,第一个是路径,第二个是可配置对象
import { Layout, theme, Menu } from 'antd';
import type { MenuProps } from 'antd';
import React, { useState } from 'react';
import { UserOutlined, NotificationOutlined, LaptopOutlined } from "@ant-design/icons"
import { useNavigate, useRoutes } from 'react-router-dom';
import routes from './routes';
const { Header, Content, Sider } = Layout
const titleMenu: MenuProps['items'] = ['1', '2', '3'].map((key) => ({
key,
label: `标题${key}`
}))
const sideMenu: MenuProps["items"] = [
{
key: 'home',
icon: <UserOutlined />,
label: '人员管理'
},
{
key: "about",
icon: <NotificationOutlined />,
label: "关于系统",
},
{
key: "info",
icon: <LaptopOutlined />,
label: "信息管理",
children: [
{
key: "info-detail",
label: "信息详情"
},
{
key: "info-look",
label: "信息查询"
},
],
},
{
key: "statistics",
icon: <NotificationOutlined />,
label: "数量统计"
},
]
const App = () => {
const {
token: { colorBgContainer },
} = theme.useToken()
//获得路由表
const routeView = useRoutes(routes);
const navigate = useNavigate()
//面包屑名称
const [breadcrumbName, setBreadcrubName] = useState("home");
//点击菜单
const handleSiderClick: MenuProps["onClick"] = ({ key, keyPath }) => {
const name = keyPath.reverse().join("/") || ""
setBreadcrubName(name)
if (key !== "home" && key !== "about") return
//路由跳转
navigate(key, {
replace: false,
state: {
id: key
}
})
}
return (
<Layout>
<Header className='heder'>
<div className='logo'>
<Menu theme='dark' mode='horizontal' defaultSelectedKeys={['1']} items={titleMenu} />
</div>
</Header>
<Layout>
<Sider width={200} style={{background:colorBgContainer}}>
<Menu mode="inline" defaultSelectedKeys={["1"]} style={{ height: "100%", borderRight: "0" }} items={sideMenu} onClick={handleSiderClick}></Menu>
</Sider>
<Layout style={{padding:"0 24px 24px"}}>
<div style={{ margin: "16px 0" }}>{breadcrumbName}</div>
<Content style={{ padding: 24, margin: 0, minHeight: 200, background: colorBgContainer }}>
{ routeView}
</Content>
</Layout>
</Layout>
</Layout>
);
}
export default App;
**·**最后运行起来就像这样
四、引入状态管理集redux
**·**如果你使用过vue,那么你一定知道vuex或是pina,他们核心就是方便各组件的数据共享,同时也是组件通信方式之一
**·**另外,redux并不是单纯为react使用的js库,其他框架也能使用,但是react框架通常使用它作为状态管理集
1、依赖安装
2、关于react-redux
**·**除了redux,还安装了react-redux,这个库能为我们监测redux数据的变化,从而进行数据更新,因为redux本身不会监测数据变化,手动检测redux数据方式需要使用store实例上的subscribe方法,这里就不模拟了,另外react-redux提供了Provider方便将store实例注入所有组件
**·**关于react-redux的相关概念文档传送门,核心就是引入容器组件和展示组件的概念,它 把需要使用redux数据的组件称为容器组件,在容器组件中,可以使用redux任意的api,方便进行共享数据的获取和操作
**·**总结下,容器组件咱们挡在containers文件夹下,展示组件放在聪明components文件夹,路由租价你放在pages文件夹
3、例子准备
**·**接下来咱们实现一个crud的例子,在src下新建一个contailers文件夹用于存放容器组件,自行创建两个容器组件,我这里叫Count(加减计数的简单组件)和Tiger(展示列表信息的组件),在App.tsx组件中引入
**·**在src目录下新建redux文件夹,并且在此文件夹下新建
1).reducers文件夹,该文件夹下存储容器组件修改数据的方法,并且每个reducer函数都必须四纯函数
2).actions文件夹,存放容器组件对数据操作的方法,例如新增、修改、删除等操作
3).创建store.ts文件,用于创建store仓库
4、组件实例
4.1 Count容器组件相关
**·**在actions文件夹下新增count.ts文件,定义新增,删除方法
export const increment = (data: any) => ({ type: "increment", data })
export const decrement = (data: any) => ({ type: "decrement", data })
//模拟异步操作
export const incrementAsync = (data: any, delay = 500) => {
return (dispatch: (arg0: { type: string; data: any }) => void) => {
setTimeout(() => {
dispatch(increment(data))
},delay)
}
}
**·**在reducers文件夹下新增counts.ts文件,定义修改数据的方法
**·**函数接收两个参数,分别为之前的状态(preState默认定义为0,否则初始值没有的话默认为undefined)动作对象(action包含type和data)
export default function countReducer(
preState = 0,
action: { type: string; data: any }
): number {
const { type, data } = action
switch (type) {
case "increment":
return preState + data
break;
case "decrement":
return preState - data
default:
return preState
}
}
**·**在Count容器组件添加相关代码以及引入redux
import { Button, Select } from "antd";
import React, { useState } from "react";
import { connect } from "react-redux";
import { increment, decrement, incrementAsync } from "@/redux/actions/count";
const Count: React.FC< {
increment: Function
decrement: Function
incrementAsync: Function
count: number
}> = ( { increment, decrement, incrementAsync, count }) => {
const [selectNum, setSelectNum] = useState("1")
const add = () => {
increment(Number(selectNum))
}
const del = () => {
decrement(Number(selectNum))
}
const addAsync = () => {
incrementAsync(Number(selectNum))
}
//切换数字
const handleSelectChange = (value: string) => {
setSelectNum(value)
}
return (
<div>
<h1>总是为:{count}</h1>
<Select
defaultValue={"1"}
style={{ width: 150 }}
onChange={handleSelectChange}
options={[
{ value: "1", label: "1" },
{ value: "2", label: "2" },
{ value: "3", label: "3" }
]}
/>
<p>
<Button onClick={add}>加+</Button>
<Button style={{ margin: "0 8px" }} onClick={del}>减-</Button>
<Button onClick={addAsync}>异步加</Button>
</p>
</div>
)
}
export default connect((state: { count: number }) => ({ count: state.count }), {
increment,
decrement,
incrementAsync
})(Count)
4.2 Tiger容器相关组件
**·**action
import { TigerVo } from "../reducers/tiger";
export const increTIger = (data: TigerVo) => ({ type: "add", data })
export const delTigerById = (data: TigerVo) => ({type:"del", data})
· reducer
关于数组的操作,因为reducer需要是一个纯函数
纯函数原则:
a.不得改写参数数据【unshift方式改写原参数preState】
b.不会产出副作用,例如网络请求,输入输出
c.不能调用Date.now()或Math.random()
export interface TigerVo {
id: string;
name?: string;
age?:number
}
const initList: TigerVo[] = [
{
id: "001",
name: '邹邹',
age:30
},
]
export default function tigerReducer(
preState = initList,
action: { type: string; data: TigerVo }
) {
const { type, data } = action
switch (type) {
case "add":
//preState.unshift(darta) 操作上来说和【preState,...initList】是等价的
//但是【preState,...initList】此方式返回的是一个新的数组, 不会影响元数组
return [data,...preState]
break;
case "del":
return [data, ...preState]
default:
return preState;
}
}
· 在Tiger组件添加样式代码,展示数据,同时通过redux能够实现数据源的共享,因此在Tiger组件中也能够拿到Count组件的总数进行展示
import { increTiger, delTigerById } from '../../redux/actions/tiger'
import { connect } from 'react-redux'
import { TigerVo } from '../../redux/reducers/tiger'
import { Button, List, Typography } from 'antd'
// 用于生成唯一标识的id,使用npm i nanoid进行安装;或者自行定义唯一key值也可以
import { nanoid } from 'nanoid'
function Tiger(props: {
increTiger: Function
delTigerById: Function
tigerArr: TigerVo[]
countNum?: number
}) {
const add = () => {
const obj = {
id: nanoid(),
name: '雄狮院长',
age: 7,
}
props.increTiger(obj)
}
const delOne = (id: string) => {
props.delTigerById({ id })
}
return (
<List
header={
<div>
<Button type='primary' onClick={add}>
添加一个
</Button>
<div>
展示一下<strong>Count组件</strong>的总数: {props.countNum}
</div>
</div>
}
bordered
dataSource={props.tigerArr}
renderItem={(item) => (
<List.Item key={item.id}>
<Typography.Text mark>ID:</Typography.Text>
{item.id}---
<Typography.Text mark>名称:</Typography.Text>
{item.name} ---
<Typography.Text mark>年龄:</Typography.Text>
{item.age}
<Button
type='primary'
danger
style={{ marginLeft: '20px' }}
onClick={() => delOne(item.id)}
>
删除
</Button>
</List.Item>
)}
/>
)
}
export default connect(
(state: { tigerArr: TigerVo[]; count: number }) => ({
tigerArr: state.tigerArr,
countNum: state.count,
}),
{
// 此处定义的名称就是组件调用方法时的名称
increTiger,
delTigerById,
}
)(Tiger)
4.3 统一暴露所有reducer文件
在reducers文件夹下新建index.ts文件,同意暴露所有reducer集合,需要使用combineReducers方法
import { combineReducers } from 'redux'
import countReducer from './count'
import tigerReducer from './tiger'
// 合并所有reducers
export default combineReducers({
count: countReducer,
tigerArr: tigerReducer,
})
4.4 创建store实例
在store.ts文件中创建store实例,通过createStore方法创建
考虑到异步操作,需要银日redux-thunk中间件来支持actions中异步操作,着情况在count组件的sction中存在
// 引入api,creatStore用于创建store对象
import { legacy_createStore as createStore, applyMiddleware } from 'redux'
import allReducers from './reducers/index'
import thunk from 'redux-thunk'
export default createStore(allReducers, applyMiddleware(thunk))
4.5 为组件注入store实例
打开项目入口文件,index.tsx,使用Provider包裹App组件,通过prop的方式传入store
4.6 实现效果