Hooks + Router.6路由 + Redux状态管理 + Axios封装
1.Create React App 搭建项目
创建react项目,需要在电脑上安装 Node >= 8.10 和 npm >= 5.6 终端执行:
npx create-react-app my-app --template typescript // 创建的 TypeScript 项目
npx create-react-app my-app // 默认创建的 javaScript 项目
cd my-app
npm start
编辑器常用缩写
// ### `imrse`
import React, { useState, useEffect } from 'react'
// ### `imr`
import React from 'react'
// ### `rfce` [创建新页面、用于父文件]
import React from 'react'
function $1() {
return <div>$0</div>
}
export default $1
// ### `rafc` [创建组件、用于子组件]
import React from 'react'
export const index = () => { // index 名字自定义 开头大写 例:MenuTop
return (
<div>
</div>
)
}
// 简写 `thenc` [then成功、catch失败、finally最后(成功失败都会走这里)]
.then((result) => {
}).catch((err) => {
}).finally(() => {
})
// 简写 nfn
const name = (params) => {
}
2.工程目录
工程目录截图
别人的目录,我要改成自己的
├── README.md
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── api # 封装axios
│ │ └── request.ts # 封装axios
│ ├── assets # 静态资源
│ ├── components # 通用业务组件[no.1创建]
│ ├── context.ts # 全局配置
│ ├── declaration.d.ts
│ ├── history.ts # history 实例
│ ├── index.tsx # 入口文件
│ ├── layout # 布局
│ ├── locale # 国际化语言包
│ ├── mock # 模拟数据
│ ├── pages # 页面模板
│ ├── redux # 状态管理中心
│ ├── routes.tsx # 路由配置
│ ├── settings.json # 配置文件
│ ├── style # 全局样式
│ └── utils # 工具库[no.2创建]
└── tsconfig.json
.env 文件 自动化切换环境变量
.env // 全局配置,npm run build [打包生产📦] 走这个url地址
.env.development // 开发环境配置 npm start [开发本地运行📦] 走这个url地址
.env.production // 生产环境配置 npm run build [打包生产] 走这个url地址
// **优先级:** 若有相同配置项时,环境配置项会覆盖 > 全局配置项
// 只需要创建 .env 和 .env.development 就好
REACT_APP_API_URL=http://localhost:3001
import React, { useEffect } from 'react'
const apiUrl = process.env.REACT_APP_API_URL // 引入 .env文件 [配置url地址]
export default function Index() {
useEffect(() => {
fetch(`${apiUrl}/report`)
}, [])
return (
<div>0</div>
)
}
React 基础语法
Hooks优点
- hooks不在需要
state
、this
来管理类组件。hooks可以取代三个生命周期函数,抛弃componentWillMount
、componentWillReceiveProps
、componentWillUpdate
- hooks可以把一切功能,都用函数组件来开发,函数组件有着较好的复用性,拼装拆分自己想要的组件。并且hooks多了一个参数,来控制渲染,可以避免函数组件重复执行的副作用
- 也不在需要redux来管理组件。
7大常见のHooks
B站视频.# 9分钟掌握React Hooks正确认知——魔术师卡颂
1.useState
最基本也是最重要的useState用来定义自变量。
2.useEffect
有副作用的因变量 给没有生命周期的组件,添加结束渲染信号。函数组件结束渲染的回调,useEffect在render之后结束回调
3.useMemo 4.useCallback
无副作用的因变量
5.useReducer
为了方便操作更多的自变量有了useReducer
6.useContext
跨组件层级操作自变量有了useContext可以替代redux的功能
7.useRef
让组件变得更加灵活useRef
2.react组件间の通信方式
父子组件间的数据传递
父组件
import React, { useState } from 'react'
import Test from './components/Test'
function App() {
const [title, setTitle] = useState("旧数据")
// 父向子传数据
const onClickTitle = () => {
setTitle("荔枝海盐口味")
}
// 子向父传数据
const onClickTitle_Two = (params) => {
setTitle(params)
}
return (
<div className="App">
<Test title={title} onClickTitle={onClickTitle} onClickTitle_Two={onClickTitle_Two}/>
</div>
);
}
export default App;
子组件
import React from 'react'
function Index({ title, onClickTitle, onClickTitle_Two }) {
return (
<div>
<h1>{title}</h1>
<button onClick={onClickTitle}>父向子传数据</button>
<button onClick={() => onClickTitle_Two("外星人电解质水")}>子向父传数据</button>
{/* 写箭头函数的原因,点击事件之后才执行这个匿名函数,如果不写箭头函数。就直接执行了 */}
</div>
)
}
export default Index
1.useState
useState用来定义自变量。
之前的类组件是用this.state
和this.setState({})
来初始化以及更新类组件的状态
函数组件是使用useState
来定义初始化以及更新状态的函数组件。
计数器
import React, { useRef } from 'react'
function Index({ addItem }) {
const inputRef = useRef(null) // 声明
const submitValue = () => {
const inputValue = inputRef.current.value.trim() // trim() 删除头尾空白
if (inputValue.length === 0) {
return
}
addItem(inputValue) // 把数据传出去
// inputValue.current.value = '' // 清空表单
}
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={submitValue}>添加</button>
</div>
)
}
export default Index
todolist增删小案例【Hook版本】
import React, { useState, useEffect } from 'react'
export default function Index() {
const [inputValue, setInputValue] = useState('')
const [list, setList] = useState([
{ name: 'React-Router6.0.0' },
{ name: 'React-Redux' },
])
const add = () => {
const newList = [...list]
newList.push({ name: inputValue })
setList(newList)
}
const del = (index) => {
console.log('index', index);
const newList = [...list]
newList.splice(index, 1)
setList(newList)
}
return (
<div>
<input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)}/>
<button onClick={add}>查询</button>
<ul>
{
list.map((item, index) => {
return (
<li key={index} onClick={() => del(index)}>{item.name}</li>
)
})
}
</ul>
</div>
)
}
todolist小案例【父子传值版本】
index.jsx父组件
import React, { useState, useEffect } from 'react'
import { List } from './list';
export default function Index() {
const [inputValue, setInputValue] = useState('')
const [list, setList] = useState([
{ name: 'React-Router6.0.0' },
{ name: 'React-Redux' },
])
const add = () => {
const newList = [...list]
newList.push({ name: inputValue })
setList(newList)
}
const del = (index) => {
console.log('index', index);
const newList = [...list]
newList.splice(index, 1)
setList(newList)
}
return (
<div>
<input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)}/>
<button onClick={add}>查询</button>
<List list={list} del={del} />
</div>
)
}
list.jsx子组件
export const List = ({ list, del }) => {
return (
<ul>
{
list.map((item, index) => {
return (
<li key={index} onClick={() => del(index)}>{item.name}</li>
)
})
}
</ul>
)
}
table 表格查询小案例
bababa
4.Axios
数据模拟 json-server
优点: 配置简单30秒启动,增删改查真实模拟
// 安装
yarn global add json-server // json-server 需要全局安装
npm install -g json-server
// 启动 json-server
json-server src/JsonServer/data.json --watch --port 3001
GET /tickets // 列表
GET /tickets/12 // 详情
POST /tickets // 增加
PUT /tickets/12 // 替换
PATCH /tickets/12 // 修改
DELETE /tickets/12 // 删除
fetch()
3.1封装axios
axios请求拦截器和响应拦截器
let whitelist = ['/login'] // 拦截器白名单,登录时不添加 token
// 请求拦截
axios.interceptors.request.use(
config => {
// 拦截白名单以及添加token
if (whitelist.includes(config.url)) {
return config
} else {
let token = localStorage.token
config.headers.token = token
return config
}
},
err => {
return err
}
)
// 响应拦截
axios.interceptors.response.use(
response => {
return response
},
err => {
// 添加超时处理
let isTimeout = err.toString().includes(`timeout of ${axios.defaults.timeout}ms exceeded`)
if (isTimeout) {
return Promise.reject(Message.error('请求超时,请重新尝试'))
}
// 添加404和500
switch (err.response.status) {
case 404:
return Promise.reject(Message.error('服务器中查找不到该资源'))
case 500:
return Promise.reject(Message.error('请求失败,服务端错误,请重新尝试'))
}
return Promise.reject(err)
}
)
useDebounce防抖
防抖:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。
function useDebounce(fn, delay, dep = []) {
const { current } = useRef({ fn, timer: null });
useEffect(function () {
current.fn = fn;
}, [fn]);
return useCallback(function f(...args) {
if (current.timer) {
clearTimeout(current.timer);
}
current.timer = setTimeout(() => {
current.fn.call(this, ...args);
}, delay);
}, dep)
}
useThrottle节流
节流:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。
function useThrottle(fn, delay, dep = []) {
const { current } = useRef({ fn, timer: null });
useEffect(function () {
current.fn = fn;
}, [fn]);
return useCallback(function f(...args) {
if (!current.timer) {
current.timer = setTimeout(() => {
delete current.timer;
}, delay);
current.fn.call(this, ...args);
}
}, dep);
}
备注:createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。
每次组件重新渲染,都会执行一遍所有的hooks,这样debounce高阶函数里面的timer就不能起到缓存的作用(每次重渲染都被置空)。timer不可靠,debounce的核心就被破坏了。使用useRef的目的就是为了解决这个问题。
useRef实现了React组件的缓存机制。
7. React—Router6.0.0 路由
请说出React Router的一些优点
怎么去传值,怎么去匹配路径
页面路由
window.location.href = 'http://www.baidu.com'
Hash路由
window.location.href = "#test1"
H5路由
Link
<Link to={{ pathname: `/assetDetails`, search: '?&assetType=' + item.code }}>
<Button>带参数跳转,link和<a>标签一样</Button>
</Link>
8.Redux状态管理模式
1_redux安装
yarn add redux
yarn add react-redux // 专门为react设计的高阶组件,主要是提供了一个方法connext。将react和我们的组件链接在一起
yarn add redux-thunk // thunk[θʌŋk] 异步的时候用的
yarn add axios qs // 序列化的qs,咱也不懂
// package.json 文件
"devDependencies": {
"redux": "^4.0.5",
"react-redux": "^7.1.1",
"redux-thunk": "^2.3.0"
}
2_redux 基本操作
import { createStore } from 'redux';
// 1.store 存放数据的 "数据仓库"
// 2.state 具体数据 [state存放在store里面]
// 3.action 对象 描述当前是如何操作 state 状态的
const action_AddOne = {
type: 'Add',
num: 1
}
const action_Addtwo = {
type: 'Add',
num: 2
}
const action_Square = {
type: 'Square',
}
// 4.reducer 是一个函数
const reducer_A = (state = 10, action) => {
switch (action.type) {
case 'Add':
return state + action.num
case 'Square':
return state * state
default:
return state
}
}
const store = createStore(reducer_A);
// 5.store.getState() // 去store 找对应的 state
console.log(store.getState()) // 10
// 5.dispatch 是能够更改 state 的唯一方法
// 使用公式:store.dispatch(action) // 公式大意: 告诉 store 我们是以怎样的 action 来更改 state 的值
console.log(store.dispatch(action_AddOne)) // {type: 'Add', num: 1}
console.log(store.getState()) // 10 + 1
console.log(store.dispatch(action_Addtwo)) // {type: 'Add', num: 2}
console.log(store.getState()) // 11 + 2
console.log(store.dispatch(action_Square)) // {type: 'Square'}
console.log(store.getState())// 13 * 13
3_提取action&提取reducer
4_数据在页面显示
7.redux-thunk
Redux的设计思想很简单,就俩句,请牢记
(1) web应用是一个状态机,视图与状态是一一对应的
(2) 所有的状态,保存在一个对象里面
5.TS应用强类型
Ts
是强类型,有更强的类型约束,如要数字类型就只能传 10,字符串“10”就不行。而Js
这种弱类型就可以,
静态类型:声明时确定变量类型就不允许改变
动态类型:变量类型随便发生变化
TS原始类型
6.生命周期
React v16.8的生命周期
抛弃componentWillMount、componentWillReceiveProps、componentWillUpdate,三个生命周期函数
React v16.0前的生命周期
生命周期函数:指在某一时刻,会自动执行的函数
生命周期分为四大部分 初始化。加载。更新。卸载 初始化
constructor(props) {
super(props)
this.state = {
inputValue: '',
list: ['a','b','c','b'],
}
加载 页面初次加载时,只执行一次的生命周期函数
render() {
console.log('页面加载:render,页面加载时执行')
}
componentWillMount() {
console.log('页面加载:componentWillMount,页面即将加载的时候,在render之前调用')
}
//例如:在页面加载之前,看看用户有没有登录啊
componentDidMount() {
console.log('页面加载:componentDidMount,(装载完成)在render之后调用')
}
//例如: 在页面加载,加载一些Ajax网络的请求,3秒倒计时,进入首页啊
数据更新
start
数据更新是有3个生命周期函数
props
子组件数据更改时有4个函数 多一个componentWillReceiveProps函数
写在子组件里
shouldComponentUpdate(nextProps, nextState) {
if(nextProps.content !== this.props.inputValue) {
return true;
}else {
return false;
}
}
// 做性能优化的生命周期函数
// true就是要更新
// false就是不要更新
componentWillUpdate() {
console.log('数据更新:如果shouldComponentUpdate返回ture,则执行componentWillUpdate、如果shouldComponentUpdate返回false,则不执行componentWillUpdate')
}
componentDidUpdate() {
console.log('数据更新:在组件数据更新之后,她会自动执行componentDidUpdate')
}
componentWillReceiveProps() {
console.log('数据更新:componentWillReceiveProps函数只能在子组件里执行,执行条件1.改子组件接收了父组件参数、2.父组件的render函数执行,子组件这个函数才能执行')
}
// props子组件数据更改时使用
卸载
componentWillUnmount() {
console.log('卸载:componentWillUnmount组件即将卸载时,执行')
}
// 清理无效timer;取消未完成的网络请求;清理已注册的订阅
Antd工作遇到的问题
Table表格[拖拽调整列宽]
react+antd实现Table拖拽调整列宽,这种功能案例在antd 3.x版本里面可以找到,安装4.x版本也同样支持的。需要集成 react-resizable 来实现可伸缩列。
import React, { Component } from 'react'
import { Table} from 'antd';
import { Resizable } from 'react-resizable';
import './App.css';
export class App extends Component {
constructor(props) {
super(props)
this.state = {
columns: [
{ title: 'Date', dataIndex: 'date', width: 200},
{ title: 'Amount', dataIndex: 'amount', width: 100 },
{ title: 'Type', dataIndex: 'type', width: 100 },
{ title: 'Note', dataIndex: 'note', width: 100 },
{ title: 'Action', key: 'action', render: () => <a>Delete</a> },
],
dataSource: [
{ key: 0, date: '2018-02-11', amount: 120, type: 'income', note: 'transfer' },
{ key: 1, date: '2018-03-11', type: 'income', note: 'transfer' },
{ key: 2, date: '2018-04-11', amount: 98, type: 'income', note: 'transfer' },
]
}
}
ResizeableTitle = props => {
const { onResize, width, ...restProps } = props;
if (!width) {
return <th {...restProps} />;
}
return (
<Resizable
width={width}
height={0}
onResize={onResize}
draggableOpts={{ enableUserSelectHack: false }}
>
<th {...restProps} />
</Resizable>
);
};
components = {
header: {
cell: this.ResizeableTitle,
},
};
handleResize = index => (e, { size }) => {
this.setState(({ columns }) => {
const nextColumns = [...columns];
nextColumns[index] = {
...nextColumns[index],
width: size.width,
};
return { columns: nextColumns };
});
};
render() {
const columns = this.state.columns.map((col, index) => ({
...col,
onHeaderCell: column => ({
width: column.width,
onResize: this.handleResize(index),
}),
}));
return (
<Table bordered components={this.components} columns={columns} dataSource={this.state.dataSource} />
)
}
}
export default App
.react-resizable {
position: relative;
background-clip: padding-box;
}
.react-resizable-handle {
position: absolute;
width: 10px;
height: 100%;
bottom: 0;
right: -5px;
cursor: col-resize;
z-index: 1;
}
Table表格[对某一列数据排序]
const columns = [
{ title: '省份名称', dataIndex: 'AAA', key: 'AAA', sorter: (a, b) => a.AAA.localeCompare(b.AAA) }, // "北京"
{ title: '省份编码', dataIndex: 'BBB', key: 'BBB', sorter: (a, b) => a.BBB - b.BBB }, // "10100"
{ title: '插入时间', dataIndex: 'CCC', key: 'CCC', sorter: (a, b) => moment(a.CCC, 'YYYY-MM-DD HH:mm:ss') - moment(b.CCC, 'YYYY-MM-DD HH:mm:ss') }, // "2020-11-06T07:44:42.000+0000"
]
<Table columns={columns} />