React笔记
第一次学习(失败):挖坑系列
React简介
React是什么?
一个用于构建用户界面的JavaScript库。
强调将数据渲染为HTML视图
React由谁开发的?
Meta(Facebook)
原生JS的问题
- DOM-API操作UI效率低
- JS直接操作DOM会导致浏览器进行大量重绘重排操作
- 原生JS代码复用率低
React的特点
- 声明式编码,组件化模式
- 命令式编码: 你先坐电梯下楼左转,走到自动贩卖机前,买一杯水,按原路返回给我
- 声明式编程: 我渴了
- React Native支持移动端开发
- 虚拟DOM+Diffing算法,减少与DOM的交互,性能好
React入门
Hello React
需要引入依赖:
- babel.js
- ES6 → ES5
- jsx → js
- react.js
- react核心库
- react-dom.js
- react扩展库(dom操作)
- prop-types.js
1 | <div id="test"></div> |
依赖的引入需要按照一定顺序:
1 | <!--引入react核心库 --> |
script标签的src一定要是text/babel
1 | <script type="text/babel"> |
JSX
jsx能够简化js的dom操作
但简化操作只针对编码者而言,实际由babel编译后的jsx依旧是按部就班的在操作dom
jsx
1 | <script type="text/babel"> |
js
1 | <script> |
真实DOM
1 | <div id="test"> |
虚拟DOM
- Object对象
- 挂载的属性数量较少
1 | { |
- 标签中混入js表达式时使用 花括号{} 包裹
1 | let name = 'John' |
- 类名指定使用className
之所以避开class,是由于class与ES6类定义的关键字有冲突
1 | let VDOM = <h1 className='title'>Hello</h1> |
- 内联样式需要注意以下要点:
- style = {{样式内容需要使用双括号包裹}}
- 样式的赋值采用键值对格式
- 样式值采用字符串格式
- 样式名采用驼峰标识
1 | let VDOM = ( |
1 | const data = ['AAA', 'BBB', 'CCC'] |
React组件
组件定义
React提供两种定义组件的方式:
- 函数式定义组件
- 类式定义组件
组件使用函数形式定义,函数名首字母一定要大写
1 | function MyComponent(){ |
定义组件的函数内部,this指向哪里?
答: this指向undefined。
这是因为代码经过babel翻译之后,会进行严格模式规范:
1 |
严格模式禁止自定义函数的this指向window。
组件类一定要继承React.Component类
1 | class MyComponent extends React.Component{ |
- 找到MyComponent组件类
- 创建新实例
1
new MyComponent()
- 通过创建出来的实例调用原型的render方法
- 将render返回的虚拟DOM转为真实DOM呈现在页面上
类组件中的this指向哪里?
答: 组件类实例
组件实例三大属性
- state
- props
- refs
state
state中用于存放组件中的一些状态信息
1 | class MyComponent extends React.Component { |
在render中使用state中的数据,
注意: 每次更新state数据都会重新调用一次render
1 | render(){return( |
dom事件对应的回调函数并非由组件实例调用,
因此会导致回调函数中的this实际上指向的是undefined
因此需要先一步指定事件触发的回调方法的上下文
1 | class MyComponent extends React.Component{ |
1 | class MyComponent extends React.Component{ |
props
props用于存放需由外界传入组件的动态数据
1 | const sayHello = ()=>console.log('sayHello') |
传入组件的参数还可以通过解构赋值实现更简洁的写法
1 | let p = { |
babel在对JSX做转换的时候会对下面这种语法做特殊处理:
1 | <Component {...obj}/> |
实际上就相当于将obj的键值对作为Component组件的属性值
需要引入prop-types.js类型检验库
1 | Person.propTypes = { |
类型检验需要放在组件类的propTypes属性中:
1 | Person.propTypes = { |
类型配置之前需要加上PropTypes类的引用:
1 | { |
多个限制使用链式声明:
1 | { |
数据类型标志与关键字的冲突避免:
- 为防止关键字与数据类型的冲突,将类型首字母小写处理:
- PropTypes.number
- PropTypes.string
- PropTypes.bool
- PropTypes.object
- PropTypes.array
- function由于是关键字,因此简写处理:
- PropTypes.func
1
2
3{
sayHello:PropTypes.func
}
- PropTypes.func
使用组件类的defaultProps属性定义默认值:
1 | Person.defaultProps = { |
注意:props一旦传入类组件后即为只读属性,不可修改
props的语法糖实际上就是使用static静态属性给类本身定义属性
1 | class Character extends React.Component { |
组件函数只能用props,并且propTypes和defaultProps只能定义在函数外
(这是因为函数内的this指向undefined)
函数组件无法使用state和refs
1 | function MyComponent(props){ |
refs
ref用于标识dom容器,
一般有三种定义形式:
- 字符串形式
- 影响效率,不推荐
- 内联回调函数形式
- 官方更为推荐的方法
- 但是重绘时会导致每次render调用两次
- 第一次:传入参数null
- 第二次:传入当前dom元素
- 绑定回调函数形式
- 能够解决重绘参数传入null的问题
- createRef
1 | class MyComponent extends React.Component{ |
有内联和绑定两种定义格式:
1 | class MyComponent extends React.Component{ |
React.createRef调用后返回一个容器,存储被ref标识的节点,
每个容器中只能存储一个dom元素
1 | class MyComponent extends React.Component{ |
受控与非受控组件
非受控组件
组件中的数据现用现取,一般需要依赖ref
1 | myRef = React.createRef() |
受控组件
state中与组件相关联的数据实时更新,类似Vue的双向绑定组件,
受控组件可以减少ref的使用
1 | state = {value:''} |
虚拟DOM的Diffing算法
React中使用key属性对虚拟DOM进行唯一标识,
每次调用render重新渲染前,会将用最新数据生成的虚拟dom和旧的虚拟dom进行比较,
比较是将key值相同的两个dom元素拿来对比其中的内容,
如果内容改变,就用新的虚拟dom代替旧dom,
因此,dom元素的key值必须唯一并且尽量可以保持不变
使用变化key值和不变key值更新dom时的对比:
将key值全部更换
1 | <ul ref='list'> |
使用唯一标识作为key值
1 | <ul ref='list'> |
React生命周期
旧生命周期
组件初始化
constructor
组件挂载前
componentWillMount
渲染组件
render
组件挂载后
componentDidMount
组件卸载前
componentWillUnmount
子组件即将获取到props传参
componentWillReceiveProps
判断组件是否更新
shouldComponentUpdate
组件更新之前
componentWillUpdate
组件重渲染
render
组件更新之后
componentDidUpdate
组件卸载之前
componentWillUnmount
两种操作会引发组件更新:
- setState 更新state
- forceUpdate 强制更新
setState
判断组件是否更新
shouldComponentUpdate
组件更新前
componentWillUpdate
组件重渲染
render
组件更新后
componentDidUpdate
forceUpdate
组件更新前
componentWillUpdate
组件重渲染
render
组件更新后
componentDidUpdate
新生命周期
- ComponentWillMount
- ComponentWillUpdate
React17对弃用的旧钩子函数做了兼容,需要在函数名前加上 UNSAFE_ 前缀
- UNSAFE_componentWillMount
- UNSAFE_componentWillUpdate
三种会触发getDerivedStateFromProps的操作:
- New props 传入新参数
- setState 修改状态
- forceUpdate 强制更新
getDerivedStateFromProps主要用于处理组件的state依赖props的情况
1 | static getDerivedStateFromProps(nextProps,prevState){ |
将更新前页面的状态传递到更新后的生命周期函数里
1 | getSnapshotBeforeUpdate(){ |
组件挂载流程
组件初始化
constructor
状态管理
getDerivedStateFromProps
组件渲染
render
组件挂载后
componentDidMount
组件更新流程
状态管理
getDerivedStateFromProps
判断组件是否更新
shouldComponentUpdate
组件渲染
render
组件即将更新
getSnapshotBeforeUpdate
组件挂载后
componentDidMount
React脚手架
全局安装react脚手架依赖包
1 | npm i -g create-react-app |
使用脚手架创建新项目
1 | create-react-app 新建项目名 |
默认提供四个运行脚本:
- start 运行项目,默认端口3000
- build 项目打包,使用webpack项目管理工具
- test 前端测试
- eject 将隐藏的webpack配置文件暴露出来,此过程不可逆
public文件夹存放一些静态文件
1 |
|
网页做app套壳时的配置文件
1 | { |
网页爬取内容的相关文件
1 | # https://www.robotstxt.org/robotstxt.html |
src为开发使用到的的主要目录,
初始化的src目录结构如下:
- App.css 根组件样式文件
- App.js 根组件js文件
- App.test.js 根组件测试文件
- index.css 全局样式
- index.js 项目入口js文件
- reportWebVitals.js 用于记录页面性能,依赖web-vitals库
- setupTests.js 测试文件,依赖jest-dom库
1 | import React from 'react' |
React Ajax
本机3000端口服务需要请求本机5000端口数据,
可以直接请求吗?
1 | axios.get('http://localhost:5000/simData') |
答: 不能,因为跨域,
到底是在哪一步跨域失败呢?
- 跨域发送请求 √
- 跨域返回数据 ×
解决方法实际上就是在3000(Client) 和 5000(Server) 之间,
再配置一台代理服务器(同样在3000端口),
3000端口同时提供微型代理功能 和 客户端功能。
适合的业务场景:代理目标唯一
1 | "proxy":"http://localhost:5000" |
适合的业务场景:需要代理多个目标地址时
1 | let {createProxyMiddleware} = require('http-proxy-middleware') |
changeOrigin用于决定是否在服务器端暴露请求源
changeOrigin = true 不暴露请求源
此时3000请求5000,服务器端收到响应头中Host如下:
1 | HOST = localhost:5000(服务器端地址) |
changeOrigin = false 暴露请求源
此时3000请求5000,服务器端收到响应头中Host如下:
1 | HOST = localhost:3000(客户端地址) |
表示是否对url的部分路径进行重写,
被替换的部分往往是用于进行代理转发识别的路径段,
比如配置如下:
1 | createProxyMiddleware('/api1', { |
则当请求的url为 http://localhost:3000/api1/simData 时,
url会经历如下处理:
第二次学习(失败):快速系列
1 | npx create-react-app app-name |
- 数据注入
- 列表渲染
- 事件
- 状态处理 useState
- 基础类型
- 数组类型
- 对象类型
- 参数传递
- 父传子:props
- 子传父:函数参数裹挟值传递
- 同级传递:父组件中转
- 多级传递:Context Hook
参数传递场景练习
一个用户评论列表,有三层结构:
List 列表
ListItem 列表项
Button 点赞按钮
context状态项:
theme 控制样式风格
filterOption 筛选项
- userName 用户名
- isSort 是否按照点赞数排序
第三次学习(完结):面向Vue基础系列
jsx基础
组件首字母一定要大写
函数式组件
1
2
3function FunHelloComp(){
return <div>Func Comp</div>
}类式组件
1
2
3
4
5
6
7
8class ClassHelloComp extends React.Component{
constructor(prop) {
super(prop);
}
render(){
return <div>Class Comp</div>
}
}
react中创建组件的2种方法:
- jsx:需要由babel将jsx翻译为js
- React.createElement 实际实现jsx的接口
事件传递
react事件绑定的函数被触发时,
如果不做处理,this的指向是undefined
有如下方式使得this指向当前组件:
- 箭头函数定义事件方法
- 事件绑定时用bind函数处理事件方法
- 行内定义事件方法
传参方式:
- 高级函数
- 使用bind获取新函数
- 第一个参数为this
- 最后一个参数为事件对象event
响应式数据
- vue:监听响应式变量的get和set方法
- react:手动调用setState进行更新
- 往setState内传入一个新对象,其中包含需要更新的数据
- 将传入的新对象与旧state进行合并
- 使用合并后的state对页面视图进行更新
无论数据有没有发生变化,直接调用setState都会触发页面的重绘,
要想优化这一点可以使用PureComponent:
1 | class App extends React.PureComponent{} |
使用PureComponent时,如果响应式数据没有被更新,就不会触发多余的页面重绘。
注意:
使用PureComponent是对数据更新的一种优化
会监听变量的内存地址的变化
因此仅仅是数组和对象的成员变化是监听不到的
必须要对原数组或者对象进行拷贝更新
组件间参数传递
react不需要在子组件内对prop声明,
只要传入的参数, 都可以直接拿来用
- prop类型验证:propTypes
- 类型验证库:proptypes
npm install proptypes –save
1 | ChildComp.propTypes = { |
- 默认值:defaultProps
1 | ChildComp.defaultProps = { |
- 默认插槽
- props.children
- 具名插槽
- 使用HtmlElement元素作为props参数传递
- 作用域插槽哦
- 返回HtmlElement元素的props函数
- 由父组件创建,能获取到子组件值的插槽
样式
- 引用.css后缀的样式文件,作用域为全局。
- 引用.module.css后缀样式文件,相当于vue中的scoped,对全局作用域做了限制
- 这种引入方式需要对样式文件进行命名引用
1
2
3import childStyle from "child.module.css";
//.....
<div className={childStyle.title}></div>
- 这种引入方式需要对样式文件进行命名引用
使用classnames库,能够快速用js变量控制class类名的toggle切换
npm install classnames –save
1 | import classnames from "classnames"; |
也可以通过classnames.bind,将样式文件使用这种方式进行控制
1 | import classnames from 'classnames/bind' |
生命周期
- React.StrictMode模式下,生命周期执行两次
- shouldComponentUpdate
- 表示是否应该更新
- PureComponent优化的原理
- 根据获取到的新值判断是否进行更新
- componentDidMount
- 相当于vue中的mounted
- vue
- 在get和set函数中触发更新
- 在get函数中进行依赖收集
- 在数据更新后,只更新用到该数据的地方
- react
- 调用方法触发更新
- 更新整个组件树
增删改查案例
ref && context
父组件访问子组件参数
1 | const childRef = React.createRef(); |
- 使用 React.createContext 创建context
- 使用ContextName.Provider进行value的传递
- 使用ContextName.Consumer接受参数
- 并用回调函数获取到value值
1 | // 父组件 |
1 | // 子组件 |
1 | // 孙组件 |
函數组件
- 无生命周期
- 不用考虑this
- 通过hook完成操作
- 函数体相当于render函数
- props作为函数的第一个参数
- useState
- useEffect
- 类watch功能
- useMemo
- 类computed功能
- 防止不必要的数据更新操作
- useCallback
- 方法的缓存,防止不必要函数更新操作
- useRef
- useContext
- 更方便获取Context.Provider中提供的值
高阶组件
和高阶函数(返回函数的函数)概念相似,
高阶组件相当于一个返回组件的函数,
主要用于:
- 在多个组件上装载相同的逻辑片段
- 对多个组件进行相同的生命周期处理
- 场景:修改父组件的state数据时,对子组件进行选择性更新(shouldComponentUpdate)
使用一套逻辑封装的鼠标定位组件:
React的性能优化需要开发者手动去实现,
如果不做任何处理,
一个父组件的更新往往会触发它所有子组件的更新。
- React.memo 对组件尽心优化的高阶组件,避免不必要的组件更新
- 相当于类组件中的PureComponent
- useMemo 对静态类进行优化
- useCallback 对静态方法进行优化
React-router
- React-router 服务端渲染SSR
- React-router-dom 浏览器渲染
- React-router-native ReactNative混合开发
首先要给需要路由的组件添加最外层标签包裹:
两种包裹方式:
- HashRouter
- BrowserRouter
1 | <HashRouter> |
路由跳转
- Routes/Route 声明路由跳转的页面
- NavLink 多用于导航栏,最近被点击的navlink会多出active类
- Link 纯路由跳转用标签
路由重定向
- Navigate
子路由
- Outlet
1 | <Route path="/wish" element={token?<Wish/>:<Navigate to="/home"></Navigate>}> |
1 | export default function Wish(props){ |
异步加载路由
- Lazy
- Suspense
1 | const LazyKill = lazy(()=>{return import ("./pages/Kill");}) |
useParams 直接返回params参数
1 | <Routes> |
1 | export default function Work(){ |
useSearchParams 返回类似useState格式的数据
/mine?name=LilyAndAndy
1 | export default function Home(){ |
路由跳转:useNavigate
返回路由跳转时的一些数据:
1 | const location = useLocation() |
路由跳转时,可以跳过params和query方式,传递数据:
1 | {/* state内的数据在nextPage中可以通过location的方式获取到 */} |
- Route.element/Component中做判断:
- 使用Navigate重定向
1 | <Route path="/wish" element={token?<Wish/>:<Navigate to="/home"></Navigate>}> |
- Lazy
- Suspense
1 | // 异步引入组件 |
1 | <Suspense fallback={<h1>加载中。。。</h1>}> |
全局状态管理
- react-redux
- Redux
- @reduxjs/toolkit
- react-mobx
- Mobx
需要手动实现store逻辑的修改,比较麻烦:
- state:管理的状态
- action:操作相关的自定义参数
- 约定俗成type为操作类型,payload为目标值
1 | import {legacy_createStore as createStore} from "redux"; |
拆分成模块合并包装
1 | import {combineReducers} from "redux"; |
Redux中维护的状态并不会像state或props一样,
被监听是否改变并刷新页面。
- 思路一:store.subscribe(不推荐)
- 使用store.subscribe进行监听,每当有数据被修改时,重新渲染组件
- 思路二:使用react-redux
- react-redux提供connect函数,能够在state和props之间建立映射关系,从而实现响应式更新
react-redux使用步骤:
- 对根组件进行Provider包装
- 在调用状态的组件中使用connect进行关联
- connect是一个返回包装后组件的高级组件
- connect关联的状态在新组件中的props中包裹
- connect的2个参数:
- 参数1:对state进行映射返回
- 参数2:对dispatch进行包装返回
1 | import {connect} from "react-redux"; |
toolkit是redux的优化版,不同点在于:
- 状态管理模块创建方式不同:
- redux创建reducer函数,legacy_createStore对store进行包装
- toolkit使用createSlice创建切片slice
- configureStore对切片进行整合
- 调用方式不同
- redux中dispath传递的actions.type类型要亲自定义可选操作和逻辑
- toolkit中根据切片的name和reducers中的成员名进行调用
创建状态切片:
1 | let mesSlice = createSlice({ |
组合切片:
1 | let mesSlice = createSlice({ |
切片reducer的调用
1 | export default function App(){ |
- connect函数
- 通过将props和store.state进行映射关联
- Hook
- useState:组件的state和store.state进行映射关联
- useDispatch:获取到store的可操作方法
useDispatch不需要用action调用到目标方法,可以直接将方法从切片中暴露出来:
1 | // 将方法从切片的actions中暴露出来 |
1 | // 直接在dispatch中调用 |
redux的action中是不支持直接执行异步修改状态的操作的,
要想异步修改状态,必须对异步的三个状态(pending、failing、fulfilled)各自的操作进行定制(好麻烦)
其间需要使用到createAsyncThunk,创建异步执行对象:
1 | /** |
执行对象需要包裹在切片中的extraReducers中,
这是一个专门处理切片内部逻辑(不用于暴露到dispatch)的reducers定义模块:
1 | let numSlice = createSlice({ |
异步操作的使用:
1 |
|
路由权限案例
- 根据用户登录身份,服务端返回不同的路由
- 客户端根据返回的路由生成路由映射
- 当用户访问没有生成的路由时,重定向到默认页面
React生态
- UI组件:Ant Design / Ant Design Mobile
- 应用框架:Umi
官方文档阅读
Hook
memo/useCallback/useMemo
React子组件更新的触发方式有几种:
- 父传子的props改变
- 基础变量类型props(memo包装)
- 函数类型props(useCallback)
- 引用类型props(useMemo)
- 自身state改变
- 内部用到的context改变
- 需要将props的变化范围控制在最小
- 防止一个总会变化的props无效化memo
- 如果不是绘图级别细粒度的组件更新,memo优化程度不大
- 数据视图的展示放在父组件,修改数据方法用useCallback包装传入子组件
useContext
- useContext
- createContext
搭配实现组件对context的订阅
- context支持基础类型、引用类型
- 防止Provider结构过于冗杂,可以将Provider封装成组件
- context可用于在传递对象和函数时进行优化重新渲染
useReducer
useReducer旨在对大量state相关操作进行逻辑集中书写,
reducer函数需要两个参数:
- state:被操作的state数据
- action:选择的操作
reducer函数的返回值就是state被修改的结果
比如需要对同一个列表list进行增删改查操作,
action一共有4种:增删改查
reducer中根据action的不同,集中实现四种操作对应的逻辑,
对外只暴露出4种action
使用reducer往往出于以下几点考虑:
- 代码可读性
- 可调试性
- 可测试性
useEffect
一般用于将组件和外部组件同步
- 常常将重复的useEffect用自定义Hook的方式单独拉出来封装
- 可以定义setup和cleanup操作
- 常用场景
- 封装和第三方组件之间的接口
- 挂载和清理Event事件
- 请求数据
useEffect可以指定依赖项:
- 依赖为[],只执行一次
- 不加依赖数组,每次useEffect内容都会重新执行
- 有依赖项,依赖项改变,useEffect内容会重新执行
- 在使用引用类型(函数/对象)作为依赖项时需要谨慎
forwardRef && useImperativeHandle
用forwardRef进行包装的子组件,可以将ref节点暴露给父组件,
相当于vue中的defineExpose:
这种暴露可以跨辈获取。
配合useImperativeHandle可以自定义子组件暴露的内容
useLayoutEffect
在浏览器重新绘制屏幕之前触发
使用场景:
网页内容的布局有时会根据显示内容而改变,
当希望用页面上一帧的信息计算下一帧的布局时,
就可以在useLayoutEffect中进行
useOptimistic
允许在异步操作时更新state,
增强用户体验,
使用场景:
类似于数据未加载到客户端时的loading作用
useSyncExternalStore
主要是为了在组件中,响应式展示第三方数据源。
需要2个参数:
- subscribe
- 参数为触发组件渲染的函数
- 返回值为clenaup函数
- getSnapshot
- 返回值决定暴露那些数据
使用场景:
- 类似store的用法,作为全局状态管理器
- 监听浏览器的变量变化
组件
- Fragment 用于组合多个组件的空标签
- Profiler 测量组件渲染性能
- StrictMode 严格模式
- Suspense 在组件加载完成之前提供替代渲染内容
API
- createContext 创建上下文
- forwardRef 创建可以将内容暴露给父组件的组件
- memo 创建根据props/state/context进行缓存的组件(而非强制根据父组件的重新渲染而重新渲染)
- lazy 组件懒加载
- startTransition 不阻塞UI的情况下更新state
lazy
用于实现组件懒加载
可以配合Suspense实现组件灵敏加载
综合练习
Hook综合练习
一个聊天软件界面:
- 鼠标移入时,光标变为圆点
- useEffect
- 自定义Hook封装
- 点击人,输入邮件内容,发送邮件
- 聊天内容模块,提供跳转到最早聊天记录/查看最新聊天记录按钮
- 父组件调用子组件方法 forwardRef useImperativeHandle
- 2个主要组件:
- 联系人列表
- prop1 联系人列表
- context1 当前发送信息的联系人
- context1 在此处被修改
- 聊天界面
- context1 当前发送信息的联系人
- 联系人列表
- 全局存储:
- useContext
- 当前发送信息的联系人
- 界面主题颜色
- useContext
- 监听网络:
- useSyncExternalStore监听浏览器网络连接
React提倡组件要保持纯粹,
意思就是组件函数无论被调用创建多少次,渲染结果应当都是一样的,
就和纯函数一样,只要输入值不变,输出值永远相同,
这就意味着不应当引入全局变量等可能出现mutation(突变)的内容来污染组件
这也是为什么在开发环境、严格模式下,
React会将每个组件内容执行2次的原因,
就是为了检查组件的纯粹性
React18 + Redux + React Router
环境配置
安装下面的依赖包:
1 | { |
以前都是手动添加,可以最直接npm下载包
1 | npm i reset-css |
首先安装node库的ts声明配置(为了在ts文件中使用path)
1 | npm i -D @types/node |
在vite.config.ts中配置路径别名:
1 | export default defineConfig({ |
在tsconfig.json中配置,
使编译器中提示路径别名下的目录结构:
配置方法
1 | { |
记住组件和图标是分开安装的就ok
1 | npm install antd @ant-design/icons --save |
路由配置
在src目录下创建router文件,其中创建路由组件index.tsx:
1 | import {BrowserRouter, Route, Routes} from "react-router-dom"; |
其中将根路径的组件设置为App,
其它的组件都是App下的子组件,
因此需要在App组件内添加Outlet组件作为子组件容器:
1 | function App(){ |
在入口组件main.tsx中把Router组件嵌入:
1 | import Router from '@/router' |
这种方式类似于vue-router的路由配置:
1 | import Home from "@/views/Home" |
入口文件中的App照常引入,并用Router包裹:
1 | import React from 'react' |
在App.tsx中使用useRouter引入router:
1 | function App() { |
使用路由懒加载lazy+Suspense组,
可以对loading页面进行封装:
1 | const withLoadingComponent = (comp: JSX.Element) => ( |
和vue-router中同样使用children属性:
1 | const routes = [ |
在路由配置表最后添加一个配置项:
1 | const routes = [ |
Redux
使用浏览器插件Redux DevTools可以协助进行开发
在src目录下创建store文件夹,其中存放创建redux仓库的代码。
store/index.ts
1 | import {legacy_createStore} from "redux"; |
store/reducer.ts
1 | // 默认状态 |
还需要在根组件main.tsx中,添加一个全局数据提供组件对App组件进行包裹:
1 | import {Provider} from 'react-redux' |
在组件中使用仓库数据,需要用到useSelector
1 | import {useSelector} from "react-redux"; |
使用useDispatch调用仓库里定义的方法,
在reducer回调添加action参数,用于判断选择操作的类型,
1 | let reducer = ( |
在页面使用useDispatch调用操作:
1 | const dispatch = useDispatch() |
在全局类型声明文件vite-env.d.ts中添加声明:
1 | declare module "*.ts" |
在src下创建types目录用于存放一些类型声明文件,
创建store.d.ts:
1 | /** |
引用store的数据时,将对应的数据类型定义写上:
1 | // 参数定义为RootState |
将多个store抽取到单独的文件夹下,
在store/index下将多个store集中返回
目录结构:
1 | - store |
使用combineReducers将2个reducer结合起来:
1 | import {combineReducers, legacy_createStore} from "redux"; |