一、React项目创建
1、项目初始化:
npx create-react-app my-app
cd my-app
npm start
如果初始化时安装过慢,可以尝试设置npm安装镜像,运行以下内容
npm config set registry https://registry.npmmirror.com
npm config get registry // 检测配置是否生效
2、初始化项目文件类型
入口文件:index.js
- registerServiceWorker:PWA 离线页面相关内容
- index.css 页面样式 删除会出现间距
- App.test.js —— 用于自动化测试
- App.js文件 —— 页面初始展现的内容是App.js文件里render函数里的内容
- ReactDom:使得组件可以挂载到页面上
- index.html:页面显示的文件:空内容
注:在react项目里选择查看网页源代码,可以发现浏览器所展示的内容就是public目录下的index.html文件里的内容。
既然源代码是index.html的内容,那我们的react代码为什么会被展示出来呢?
答:是因为我们在App.js文件里,将我们的react代码进行了挂载,直接挂载到了index.html页面中,id为root的div框内,具体代码为:
ReactDOM.render(<App />, document,getElementById('root')) // 将写的所有组件都挂载到root节点上
二、React的基础使用
1、React组件的基本结构
react组件分为两种形式,一种叫做函数组件,一种叫做类组件,两种组件的编写方法以及使用场景都各有不同,其中,类组件的功能更加全面,函数式组件的功能比较单一
类组件
import React from 'react'
class Demo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '测试'
}
}
render() {
return(
<p>这是类组件的写法</p>
)
}
}
export default JSXBaseDemo
类组件的写法类似使用class关键字构建一个类,在类组件中,使用constructor构造器,对整个组件需要使用的变量进行统一的声明,都写在this.state对象里面。
注:这里的this.state的名字不可随意编写
在类组件中,还有render函数,它类似于vue中的template模板,在render函数中,可以return我们需要编写的html代码,return关键字里的代码写法被称为JSX语法,它允许编写html时不需要增加引号,且可以在html代码中插入js变量或者表达式。
注:这里的写法是JSX语法,JSX语法允许js表达式,但不可以写js语句
函数组件
import React from 'react'
function App() {
return (
<div className="App">
11111
</div>
);
}
export default App;
相比于类组件,函数组件的写法相对简单,同时也导致了函数组件的一些限制:
- 函数组件内部没有构造器
- 函数组件内部没有实例——无法使用this
- 函数组件内无render函数
函数组件仅仅是输入props输出一个JSX的方法,不在组件内部实现过多的操作。
2、JSX基本语法
基本语法
-
插值写法:单花括号包裹需要绑定的变量值或者函数名——不同于vue双花括号的写法
-
JSX语法允许写js表达式(三元运算符、Array.map等),但是不允许出现js语句(if判断等)
-
类名写法:JSX中声明类名需要使用className关键字,class无法识别
-
动态类名:同插值写法,单花括号包裹——不同于vue使用:进行绑定
-
style写法:JSX允许在html代码中编写style,写法同插值写法——不同于vue用“”包裹
-
组件加载:JSX需要在头部引入组件后可直接在render函数里使用——不同于vue需要在component里声明
-
JSX事件:为元素绑定事件,小驼峰写法。例如:点击事件:onClick C必须大写
-
JSX中没有v-for、v-model、v-if等关键字,需要程序员自己实现
- v-for可以使用Array.map函数实现
- v-if可以使用if else代替
- v-model需要同时绑定value和onChange事件进行实现
import React from 'react'
import './style.css'
import List from '../List'
class JSXBaseDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '测试',
imgUrl: 'https://img1.mukewang.com/5a9fc8070001a82402060220-140-140.jpg',
flag: true
}
}
render() {
// 获取变量 插值
const pElem = <p>{this.state.name}</p>
return pElem
// // 表达式
// const exprElem = <p>{this.state.flag ? 'yes' : 'no'}</p>
// return exprElem
// // 子元素
// const imgElem = <div>
// <p>我的头像</p>
// <img src="xxxx.png"/>
// <img src={this.state.imgUrl}/>
// </div>
// return imgElem
// class
// <p className={动态类名}></p>
// const classElem = <p className="title">设置 css class</p>
// return classElem
// // style
// const styleData = { fontSize: '30px', color: 'blue' }
// const styleElem = <p style={styleData}>设置 style</p>
// 静态的style
// <p style="font-size: 30px"></p>
// 内联写法,注意 {{ 和 }}
// const styleElem = <p style={{ fontSize: '30px', color: 'blue' }}>设置 style</p>
// return styleElem
// 原生 html
// const rawHtml = '<span>富文本内容<i>斜体</i><b>加粗</b></span>'
// const rawHtmlData = {
// __html: rawHtml // 注意,必须是这种格式
// }
// const rawHtmlElem = <div>
// <p dangerouslySetInnerHTML={rawHtmlData}></p>
// {/* 直接写没有富文本 dangerouslySetInnerHTML写了才有*/}
// <p>{rawHtml}</p>
// </div>
// return rawHtmlElem
// // 加载组件
// const componentElem = <div>
// <p>JSX 中加载一个组件</p>
// <hr/>
// <List/>
// </div>
// return componentElem
// // JSX 事件
// const eventList = <p onClick={this.clickHandler}>
// some text
// </p>
}
}
export default JSXBaseDemo
React中的event事件并非原生
不同于vue以及原生的js,在React中,触发元素事件的节点与事件挂载的节点并不相同,看起来一样,是因为react封装好了的event事件可以完全替代原生的所有功能。
1. event 是 SyntheticEvent ,模拟出来 DOM 事件所有能力
2. event.nativeEvent 是原生事件对象
3. 所有的事件,都被挂载到 document 上
4. 和 DOM 事件不一样,和 Vue 事件也不一样
import React from 'react'
/* this绑定、事件绑定 event不是原生的*/
class EventDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'zhangsan',
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
]
}
// 修改方法的 this 指向
this.clickHandler1 = this.clickHandler1.bind(this)
}
render() {
// // this - 使用 bind
// return <p onClick={this.clickHandler1}>
// {this.state.name}
// </p>
// // this - 使用静态方法
// return <p onClick={this.clickHandler2}>
// clickHandler2 {this.state.name}
// </p>
// event
// return <a href="https://baidu.com/" onClick={this.clickHandler3}>
// click me
// </a>
// 传递参数 - 用 bind(this, a, b)
return <ul>{this.state.list.map((item, index) => {
return <li key={item.id} onClick={this.clickHandler4.bind(this, item.id, item.title)}>
index {index}; title {item.title}
</li>
})}</ul>
}
clickHandler1() {
// console.log('this....', this) // this 默认是 undefined
this.setState({
name: 'lisi'
})
}
// 静态方法,this 指向当前实例 不需要声明绑定
clickHandler2 = () => {
this.setState({
name: 'lisi'
})
}
// 获取 event
clickHandler3 = (event) => {
event.preventDefault() // 阻止默认行为
event.stopPropagation() // 阻止冒泡
console.log('target', event.target) // 指向当前元素,即当前元素触发
console.log('current target', event.currentTarget) // 指向当前元素,假象!!!
// 注意,event 其实是 React 封装的。可以看 __proto__.constructor 是 SyntheticEvent 组合事件
console.log('event', event) // 不是原生的 Event ,原生的 MouseEvent
console.log('event.__proto__.constructor', event.__proto__.constructor)
// 原生 event 如下。其 __proto__.constructor 是 MouseEvent
console.log('nativeEvent', event.nativeEvent)
console.log('nativeEvent target', event.nativeEvent.target) // 指向当前元素,即当前元素触发 触发是触发,但是绑定在document上
console.log('nativeEvent current target', event.nativeEvent.currentTarget) // 指向 document !!! 不同于vue vue触发的节点和绑定的节点相同
}
// 传递参数
clickHandler4(id, title, event) {
console.log(id, title)
console.log('event', event) // 最后追加一个参数,即可接收 event
}
}
export default EventDemo
React中的双向绑定
react中没有双向绑定语法糖,需要手动实现,原理在于绑定value以及change事件,每次修改时同步更新this.state中的值。
<input id="inputName" value={this.state.name} onChange={this.onInputChange}/>
import React from 'react'
class FormDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '测试',
info: '个人信息',
city: 'beijing',
flag: true,
gender: 'male'
}
}
render() {
// // 受控组件(非受控组件 有些没法被state控制) 没有v-model 可以通过控制state的值控制input的值
return <div>
<p>{this.state.name}</p>
<label htmlFor="inputName">姓名:</label> {/* 用 htmlFor 代替 for (js中的保留字)*/}
<input id="inputName" value={this.state.name} onChange={this.onInputChange}/>
</div>
// textarea - 使用 value
// return <div>
// <textarea>{this.state.info}</textarea> 不可以这么写,vue也不允许
// <textarea value={this.state.info} onChange={this.onTextareaChange}/>
// <p>{this.state.info}</p>
// </div>
// // select - 使用 value
// return <div>
// <select value={this.state.city} onChange={this.onSelectChange}>
// <option value="beijing">北京</option>
// <option value="shanghai">上海</option>
// <option value="shenzhen">深圳</option>
// </select>
// <p>{this.state.city}</p>
// </div>
// // checkbox
// return <div>
// <input type="checkbox" checked={this.state.flag} onChange={this.onCheckboxChange}/>
// <p>{this.state.flag.toString()}</p>
// </div>
// // radio
// return <div>
// male <input type="radio" name="gender" value="male" checked={this.state.gender === 'male'} onChange={this.onRadioChange}/>
// female <input type="radio" name="gender" value="female" checked={this.state.gender === 'female'} onChange={this.onRadioChange}/>
// <p>{this.state.gender}</p>
// </div>
// 非受控组件
}
onInputChange = (e) => {
this.setState({
name: e.target.value
})
}
onTextareaChange = (e) => {
this.setState({
info: e.target.value
})
}
onSelectChange = (e) => {
this.setState({
city: e.target.value
})
}
onCheckboxChange = () => {
this.setState({
flag: !this.state.flag
})
}
onRadioChange = (e) => {
this.setState({
gender: e.target.value
})
}
}
export default FormDemo
React中的父子组件传值
父子组件的通信
1、react里也存在父子组件
2、父子组件通过属性进行值得传递,注意插值的写法
<Input submitTit le={this.onSubmitTitle}/>
<List list={this.state.list}/>
<Footer text={this.state.footerInfo} length={this.state.list.length}/>
3、子组件通过props接受参数
onSubmit = () => {
// this.props.submitTitle(this.state.title)
const { submitTitle } = this.props
submitTitle(this.state.title) // 'abc'
this.setState({
title: ''
})
}
完整代码演示:
代码中TodoListDemo作为最外层父组件,引用了List、Input、Footer 三个组件,实现了一个简易todo-list的功能,父向子传值的时候只需要通过属性绑定即可,子组件接受时通过this.props属性名进行接收。
/**
* @description 演示 props 和事件
*/
import React from 'react'
import PropTypes from 'prop-types' // props类型检查插件
class Input extends React.Component {
constructor(props) {
super(props)
this.state = {
title: ''
}
}
render() {
return <div>
<input value={this.state.title} onChange={this.onTitleChange}/>
<button onClick={this.onSubmit}>提交</button>
</div>
}
onTitleChange = (e) => {
this.setState({
title: e.target.value
})
}
onSubmit = () => {
// this.props.submitTitle(this.state.title)
const { submitTitle } = this.props
submitTitle(this.state.title) // 'abc'
this.setState({
title: ''
})
}
}
// props 类型检查
Input.propTypes = {
submitTitle: PropTypes.func.isRequired
}
class List extends React.Component {
constructor(props) {
super(props)
}
render() {
const { list } = this.props
return <ul>{list.map((item, index) => {
return <li key={item.id}>
<span>{item.title}</span>
</li>
})}</ul>
}
}
// props 类型检查
List.propTypes = {
list: PropTypes.arrayOf(PropTypes.object).isRequired
}
class Footer extends React.Component {
constructor(props) {
super(props)
}
render() {
return <p>
{this.props.text}
{this.props.length}
</p>
}
componentDidUpdate() {
console.log('footer did update')
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.text !== this.props.text
|| nextProps.length !== this.props.length) {
return true // 可以渲染
}
return false // 不重复渲染
}
// React 默认:父组件有更新,子组件则无条件也更新!!!
// 性能优化对于 React 更加重要!
// SCU 一定要每次都用吗?—— 需要的时候才优化
}
class TodoListDemo extends React.Component {
constructor(props) {
super(props)
// 状态(数据)提升 所有子组件都用这个数据 把数据放在最高的组件里 统一管理
this.state = {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
],
footerInfo: '底部文字'
}
}
render() {
return <div>
<Input submitTitle={this.onSubmitTitle}/>
<List list={this.state.list}/>
<Footer text={this.state.footerInfo} length={this.state.list.length}/>
</div>
}
onSubmitTitle = (title) => {
this.setState({
list: this.state.list.concat({
id: `id-${Date.now()}`,
title
})
})
}
}
export default TodoListDemo
3、setState是什么?
刚刚的JSX语法中,我们说到要把整个组件中需要用到的变量都统一写在this.state中,引用时可以使用thia.state.变量名即可引用,那我们应该如何修改已经声明的值呢?
注:react中,如果想改变state中的值,不可直接修改,要使用统一方法this.setState()方法
setState()方法有三个特性:
不可变值:操作state的时候不可直接改变其值
数组
- 不能提前对state的值进行修改,会有错误——shouldComponentUpdate
- 复杂操作先生成副本再做修改(深拷贝)——再setState
- 不可以在setstate里使用push pop splice等
对象
- object.assign
- {...object, label:value}
可能是异步更新
// 异步
this.setState({
count: this.state.count + 1
})
console.log(this.state.count) // 0
// 类似于$nextTick
this.setState({
count: this.state.count + 1
},() => {
console.log(this.state.count) // 1
})
// 同步
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
console.log(this.state.count) // 1
}, 0)
// 自己定义的DOM事件——同步
hanshu(){
document.body.addEventListener('click', () => {
this.setState({
count: this.state.count + 1
})
console.log(this.state.count) // 1
})
}
可能会被合并
异步更新——传入对象统一更新,传入函数会执行
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
// 执行三次,结果为1 类似于
this.setState({
count: 1
})
this.setState({
count: 1
})
this.setState({
count: 1
})
// 执行三遍
this.setState((a, b) => {
return {
count: this.state.count + 1
}
})
this.setState((a, b) => {
return {
count: this.state.count + 1
}
})
this.setState((a, b) => {
return {
count: this.state.count + 1
}
})
// 函数不可被合并,只能一个一个执行
concat:追加,返回一个新数组,原数组值不变 = [...array, value]
4、组件生命周期:
三、React高级特性
1、受控组件&非受控组件:
受控组件:
1、value绑定this.state.value的值(checked)
2、onChange事件实现this.state.value的值的双向绑定
非受控组件:
必须操纵DOM时使用——获取上传文件的文件名时、富文本编辑器(传入、开发)
1、defaultValue绑定this.state.value的值(defaultChecked)
2、没有绑定onChange,只能通过ref获取DOM元素中的值
3、ref:
vue:直接绑定字符串,通过字符串拿到所选元素
react:需要在state里生命创建一个ref绑定的值,通过它选中DOM
this.InputNamne = React.createRef() // 创建react的ref对象方法
2、Portals
应对css兼容性问题
-
组件默认会按照既定层次嵌套渲染
-
需要将组件渲染到最外层
- position为fixed的时候,需要放在最外层渲染,兼容更多浏览器
react组件结构不变,写法还是嵌套写法,只是渲染位置变了
// ReactDom.creatPortal(HTML元素,渲染位置)
// protals用法
return ReactDom.createProtal(
<div className="modal">{this.props.children}</div>
document.body // DOM节点
)
// css
.modal{
position: fixed;
color: #ffffff;
......
}
使用场景:
- 父组件设置了BFC
- 父组件z-index太小
- position为fixed
3、Context 上下文
设置公共信息——主题颜色、语言——上传到每个组件中——redux大题小做
context方法分为数据的产生、修改以及消费,数据在(最高级别)父组件中产生
-
使用React.createContext()方法创建一个context,并且可以传入一个默认值。
-
在父组件中,使用ThemeContext.provider标签包裹住的所有标签都赋予了访问context值得权利
-
为ThemeContext.provider绑定一个value值,可以实现对默认值的改变,(数据的产生与修改)
-
数据的消费:
-
类组件的消费
- ThemeButton.contextType = ThemeContext 创建组件后,声明组建的contentType为最外层创建的context对象。
- 在render函数内,直接使用this.context来获取主题内容
-
函数组件的消费
- 由于函数组件内部无实例,故无法使用this,所以需要<ThemeContext.Consumer>标签包裹,同时value作为返回值,可直接获取到context对象中的主题内容。
-
// 创建context对象
const ThemeContext = React.createContext('light')
// 最外层
/**
1、数据产生方
*/
<ThemeContext.provider value={this.state.theme}> //最外层使用
// 第一层
<Tollbar/>
</ThemeContext>
/**
1、数据消费
*/
// 函数组件
function Toolbar(props) {
return {
<div>
// 第二层
<ThemeButton />
<ThemeLink />
</div>
}
}
// class组件
/**
1、数据使用
2、向上查找,直到找到provider
*/
class ThemeButton extends React.Component {
render() {
const theme = this.context
return <div>
<p>button's theme is {theme}</p>
</div>
}
}
ThemeButton.contextType = ThemeContext
// 底层——函数组件
function ThemeLink (props) {
// const theme = this.context // 不可使用、函数组件无实例——没有this
return <ThemeContext.Consumer>
{value => <p>link`s theme is {value}</p>} // value就是ThemeContext.Consumer去找
</ThemeContext.Consumer>
}
4、异步组件
大组件进行异步加载,提高性能
在vue中使用:
- import()方法实现
在react中使用:
- React.lazy
- React.Suspense
import React from 'react'
const ContextDemo = React.lazy(() => import('./ContextDemo'))
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return <div>
<p>引入一个动态组件</p>
<hr />
<React.Suspense fallback={<div>Loading...</div>}>
<ContextDemo/>
</React.Suspense>
</div>
// 1. 强制刷新,可看到 loading (看不到就限制一下 chrome 网速)
// 2. 看 network 的 js 加载
}
}
export default App
5、性能优化
- shouldComponentUpdate(SCU)
// 用法 判断是否继续执行render生命周期 默认返回true
shouldComponentUpdate(nextProps, nextState) {
if (nextState.count !== this.state.count) { // this.props.type
return true // 可以渲染
}
return false// 可以渲染
}
- React 中,父组件有更新,子组件即使没有改变,也会渲染更新。
- 默认SCU会返回true,使得无变化的子组件也触发更新,使得性能下降—— 需要才使用(配合不可变值)
react中直接push pop 。。。修改state中数组值得时候,不规范,违反不可变值,导致生命周期前后值相同,如果写了SCU校验会不通过导致bug
-
PureComponent(纯组件) 和 React.memo(类似备忘录的意思)
- PureComponent:SCU实现浅比较,只取第一层 —— 配合不可变值
- memo:函数组件中的 PureComponent
-
不可变值immutable.js
6、组件公共逻辑的抽离
-
高阶组件 HOC:
- 并不是一种功能,而是一种模式
const HOCFactory = (Component) => {
class HOC extends React.Component {
// 再次定义多个组件的公共逻辑
render(){
return <Component {...this.props} /> // 返回拼装结果
}
}
return HOC
}
const EnhancedComponent1 = HOCFactory(WrappedComponent1)
const EnhancedComponent2 = HOCFactory(WrappedComponent2)
Redux connect 是高阶组件
// connect 部分源码
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
class Connect extends Component {
constructor() {
super()
this.state = {
allProps: {}
}
}
// 省略代码......
render () {
return <WrappedComponent {...this.state.allProps} />
}
}
return Connect
}
- Render Props
7、Redux = Reducer + Flux(以前的设计,允许多个store)
- Redux Flow —— redux工作流程
创建store
index.js
import { createStore } from 'redux'
import reducer from './reducer';
const store = createStore(reducer);
export default store
reducer.js
const defaultState = {
inputValue: '234',
list: [1, 3]
}
export default (state = defaultState, action) => {
return state
}
使用
import store from './store/index'
import React, {Component} from 'react';
class TodoList extends Component {
constructor(props){
super(props);
this.state = store.getState()
console.log(this.state)
}
render() {
return(
<>
<div>{this.state.inputValue}</div>
</>
)
}
}
export default TodoList;
改变store里面的数据
使用
import store from './store/index'
import React, {Component} from 'react';
class TodoList extends Component {
constructor(props){
super(props);
this.state = store.getState()
this.handlestoreChange = this.handlestoreChange.bind(this)
this.handleChange = this.handleChange.bind(this)
store.subscribe(this.handlestoreChange)
}
render() {
return(
<>
<input value={this.state.inputValue} onChange={this.handleChange}/>
<button onClick={this.handleClick.bind(this)}>{"提交"}</button>
{this.state.list.map((item, index) => {
return(
<div key={index}>{item}</div>
)
})}
</>
)
}
handleChange(e) {
/* 创建action */
const action = {
type: 'change_input_value',
value: e.target.value
}
/* 下发action */
store.dispatch(action)
}
/* handleChange = (e) => {
这种写法不能改变store的值
console.log(e.target.value)
this.setState({
inputValue: e.target.value
})
console.log(store.getState())
} */
handleClick = () => {
const action = {
type: 'add_todo_action'
}
store.dispatch(action)
}
handlestoreChange() {
/* 一旦感知到store变化,就使用getState重新拿store数据 */
/* 再使用setState方法,更新页面绑定值 */
this.setState(store.getState())
console.log('store change')
}
}
export default TodoList;
reducer.js
/* eslint-disable import/no-anonymous-default-export */
const defaultState = {
inputValue: '234',
list: [1, 3]
}
export default (state = defaultState, action) => {
/* reducer 手册 记载着要干啥 */
if (action.type === 'change_input_value') {
const newState = JSON.parse(JSON.stringify(state))
/* reducer可以接收state,不能改 */
newState.inputValue = action.value
/* 自动返回给store,store自动替换新数据 */
return newState
}
if(action.type === 'add_todo_action'){
const newState = JSON.parse(JSON.stringify(state))
newState.list.push(newState.inputValue)
newState.inputValue = ''
return newState
}
return state
}
总结:
1、store是唯一得
2、只有store能改变自己的内容
3、reducer必须是纯函数(给固定输入就一定有固定输出,且无任何副作用)不能写ajax,日期函数等。修改了store值就叫做副作用。
8、redux中间件(补充)
单向数据流
可以对dispatch函数进行一些改造:
// 使用
import { applyMiddleware, createStore } from 'redux'
import creatLogger from 'redux-logger'
import thunk from 'redux-thunk'
const logger = createLogger();
const store = createStore(
reducer,
applyMiddleware(thunk, logger) // 可以扩展多个中间件,逗号隔开,会按顺序进行
)
自己修改dispatch
let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
// 加一些逻辑
next(action) // 执行原dispatch
// 一些逻辑
}
9、React-router使用
1、哈希路由:使用:# 2C端使用
2、H5 history路由:无# 2B端可以使用
路由配置
function RouterComponent() {
return(
<Router>
<Switch>
<Route exact path="/">
<Home />
</Router>
<Route path="/project/:id">
<Project />
</Router>
<Route path="*">
<NotFound />
</Router>
</Switch>
</Router>
)
}
import React from 'react'
// Link用于跳转 useParams用来获取动态参数
import { Link, useParams } from 'react-router-dom'
function Project() {
// 获取url参数 如'/Project/100'
const { id } = useParams()
return(
<div>
<Link to="/">首页</Link>
</div>
)
}
// 函数方法跳转路由
import { useHistory } from 'react-router-dom'
function Trash() {
let history = useHistory()
function handleClick() {
history.push('/')
}
return(
<div>
<Button type="primary" onClick={handleClick}>回到首页</Button>
</div>
)
}
懒加载:lazy包裹 suspense包裹
在掘金看到一个大佬写的hook总结也好,建议大家阅读! 地址:juejin.cn/post/711893…