# React 笔记

# 1. 简介

  • React 来自于 Facebook 公司的开源项目
  • React 可以开发单页面应用 (SPA)
  • React 组件化模块化 (开发模式)
  • React 通过对 DOM 的虚拟 (虚拟 DOM), 最大限度地减少与 DOM 的交互 (数据绑定)
  • React 可以与一直的库或框架很好地配合
  • React 基于 jsx 的语法,JSX 是 React 的核心组成部分,使用 XML 标记的方式去声明界面,HTML, JS 混写模式

# 2. React 原理

# 1. 虚拟 DOM

react 将 DOM 抽象为一个 JS 对象,通过这个 JS 对象来描述相关页面中的 真实DOM , 通过 JS 对象来实时更新 真实DOM

# 2. DIFF 算法

  • react 通过 Diff 算法来保证当页面的 DOM 更新的时候,不第一时间去更新真实 DOM , 而是去更新 虚拟DOM
  • 页面操作真实 DOM 的时间相比比较长,所以先更新 虚拟DOM 然后最后统一来更新 真实DOM , 大大提高效率
  • 虚拟 DOM 确保只对界面上发生的真正变化的部分进行时间的 DOM 操作
  • 逐层次的进行节点的比较. (发现和之前有不同时,直接删除,不在进行比较)

# 3.React 入门学习

# 1.Jsx 语法规则

  1. 定义虚拟 DOM 时,不要写引号
  2. 标签中混入 JS 表达式时要用
  3. 样式的类名指定不要用 class, 要用 className
  4. 内联样式,要用 style= {{key:value}} 的形式去写
  5. 只有一个根标签
  6. 标签必须闭合
  7. 标签首字母
    • 若小写字母开头,则将该标签转为 HTML 中同名元素,若 HTML 中无该标签对应的同名元素,则报错
    • 若大写字母开头,react 就去渲染对应的组件,若组件没有定义则报错

# 2.JS 语句 与 JS 表达式

  1. 表达式: 一个表达式会产生一个值,可以放在任何一个需要值的地方
    • 如下均为表达式 :
      • a
      • a + b
      • demo(1)
      • arr.map()
      • function test ()
  2. 语句 (代码) :
    • 如下均为语句 (代码) :
      • if ()
      • for ()
      • switch ()

# 3. 模块与组件,模块化与组件化的理解

# 1. 模块

  1. 理解 : 向外提供特定功能的 JS 程序,一般是一个 JS 文件
  2. 拆解成模块的原因:随着业务逻辑的增加,代码越来越多且复杂
  3. 作用:复用 JS, 简化 JS 编写,提高 JS 运行效率

# 2. 组件

  1. 理解:用来实现局部功能效果的代码和资源的集合 (html/css/js/image 等等)
  2. 原因:一个界面的功能更复杂
  3. 作用:复用编码,简化项目编码,提高运行效率

# 3. 模块化

当应用的 JS 都以模块来编写,这个应用就是一个模块化应用

# 4. 组件化

当应用是以多组件的方式实现,这个应用就是一个组件化的应用

# 5 组件的基本使用

# 1. 两种创建虚拟 DOM 的方式
  1. 使用 JSX 创建虚拟 DOM
const VDOM = (
	<h1 id={MyId.toLocaleUpperCase()}>
    	<span className= "content" style={{fontSize: '50px'}}>占位文本</span>
    </h1>
)
  1. 使用 JS 创建虚拟 DOM
// 1.使用js语法创建虚拟DOM,   React.createElement(标签,标签属性,内容)
const VDOM = React.createElement('h1',{id: 'title'},'hello')

使用 JS 和 JSX 都可以创建虚拟 DOM, 但是 JS 创建虚拟 DOM 比较繁琐,尤其是标签较多的情况下

# 6. 组件

# 1. 函数式组件
  1. 调用 ReactDOM.render() 函数,并传入 <Welcome name='name' /> 作为参数
  2. React 调用 Welcome 组件,并将 {name: 'name'} 作为 props 传入
  3. wecome 组件将 Hello, name 元素作为返回值
  4. React DOM 将 DOM 高效的更新为 Hello name
// 1.先创建函数, 函数可以有参数,也可以没有,但是必须有返回值,返回一个虚拟DOM
function Welcome(props) {
    return <h1>Hello, {props.name}</h1>	
}
// 2. 进行渲染
ReactDOM.Render(<Welcom name='name' />,document.getElementById('root'))
# 2.Class 组件
  1. React 解析组件标签,找到相应的组件
  2. 发现组件是类定义的,然后 new 出类的实例,并通过实例调用原型上的 render 方法
  3. 将 render 返回的虚拟 DOM 转换为真实的 DOM, 然后呈现到页面中
// 必须继承React.Component
// 然后重写Render() 方法,该方法一定要有返回值,返回一个虚拟DOM
class Welcome extends React.Component {
    render () {
        return <h1>Hello, {this.props.name}</h1>
    }
}
// 渲染 
ReactDOM.Render(<Welcom name='name' />,document.getElementById('root'))

# 4. React 面向组件编程

# 1. 组件三大核心属性: state

# 1. 理解
  1. state 是组件最重要的属性,值是对象 (可以包含多个 Key-Value 的组合)
  2. 组件被称为 状态机 ,通过更新组建的 state 来更新对应组件的 state 来更新对应的页面显示 (重新渲染组件)
# 2. 注意事项
  1. 组件中 render 方法中的 this 为组件实例的对象
  2. 组建中自定义方法中 this 为 undefined, 如何解决?
    • 强制绑定 this: 通过函数对象的 bind ()
    • 箭头函数
  3. 状态数据:不能直接修改或更新

# 2. 组件三大属性: props

# 1. 理解
  1. 每个组件对象都会有 props (properties 的简写) 属性
  2. 组件标签的所有属性都保存在 props 中
# 2. 作用
  1. 通过标签前属性从组件外向组件内传递变化的数据
  2. 注意:组件内部不要修改 props 数据

# 3. 组件三大属性: refs

# 1. 理解

组件内的标签可以定义 ref 来表识自己

# 2.ref 三种形式
  1. 字符串形式的 ref (不推荐,官方不建议使用)
<input ref="input2" type="text" />
const {input2} = this.refs
  1. 回调形式的 ref
/**
*  c与this均为当前的组件实例
*  内联形式的回调ref在state更新时会触发两次,第一次为null
*/
saveInput = (e)=> {
   this.input1 = c
}
<input ref={c => this.input1 = c} />
<input ref={this.saveInput} />
  1. createRef 创建 ref 容器 (推荐方式)
// react.createRef 调用后可以返回一个容器,该容器可以存储被ref所标识的节点
myRef = React.createRef();
<input ref={this.myRef} />

# 4. 事件处理与收集表单数据

# 1. 事件处理
  • 通过 onXxx 属性指定事件处理函数 (注意大小写)
    • React 使用的是自定义 (合成时间,而不是使用的原生 DOM 事件) --- 为了更好的兼容性
    • React 中的事件是通过事件委托的方式处理的 (委托给组件最外层的元素) --- 为了更高效
  • 通过 event.target 得到发生事件的 DOM 元素对象 --- 不要过度使用 ref
# 2. 表单组件的分类

就形式上来说,受控组件就是为某个 form 表单组件添加 value 属性;非受控组件就是没有添加 value 属性的组件

  • 受控组件
// 初始化状态
state = {
    username: '',
    password:''
}
// 保存用户名到状态中
saveUsername = (event) => {
    this.setState({username: event.target.value})
}
// 保存密码到状态中
savePassword = (event) => {
    this.setState({password: event.target.value})
}

render() {
    return(
    	<form>
            用户名: <input onChange={this.saveUsername} type="text" name="username"/>
			密码:<input onChange={this.savePassword} type="password" name="password"/>
            <button>登录</button>
        </form>
    )
}
  • 非受控组件
handleSubmit = (event) => {
    const {username,password} = this
}
render() {
    return(
    	<form>
            用户名: <input ref={c => this.username = c} type="text" />
			密码:<input onChange={c => this.password = c} type="password"/>
            <button>登录</button>
        </form>
    )
}

# 5. 高阶函数与函数柯里化

# 1. 高阶函数
  • 符合下面两个规范中的任何一个,那函数就是高阶函数
    • 若 A 函数接收的参数是一个函数,那么 A 函数就是高阶函数
    • 若 A 函数调用的返回值仍然是一个函数,那么 A 就可以称之为高阶函数
  • 常见的高阶函数有: Promise, setTimeout, arr.map () 等等
# 2. 函数的柯里化
  • 通过函数调用继续返回函数的方式,实现对此接收参数最后同一处理的函数编码形式
function sum(a) {return b => {return c => return a+b+c}}
# 3. 不用函数柯里化实现事件的绑定
  • 直接使用回调函数,因为本身就是以一个函数为返回值
<input onChange={event => this.saveFormData('username',event) } type="text"/>

# 5. 生命周期

  1. 组件从创建到思维会经历一些特定的阶段
  2. React 组件中包含一系列狗子函数 (生命周期回调函数), 会在特定的时刻调用
  3. 定义组件时,会在特定的生命周期回调函数中,做特定的工作

# 1.React 生命周期 (旧)

生命周期(旧)

各生命周期函数的调用顺序

  1. 初始化阶段:由 ReactDOM.render () 触发 --- 初次渲染
    • constructor ( ) 构造器: React 数据初始化
    • compinentWillMount ( ) 组件将要被挂载,还未渲染 DOM
    • render ( ) 修改虚拟 DOM 渲染真实 DOM
    • componentDidMount ( ) 组件渲染完成,常用于进行一些初始化动作
  2. 更新阶段:由组件内部的 this.setState ( ) 或者父组件的 render 触发
    • shouldComponentUpdate ( ) 判断组件是否应该更新
    • componentWillUpdate ( ) 组件将要更新
    • render( )
    • componentDidUpdate ( ) 组件更新完成
  3. 卸载组件:由 ReactDOM.unmountComponentAtNode () 触发
    • componentWillUnmount ( ) 组件将要卸载,常用与关闭定时器,取消订阅

# 2.React 生命周期 (新)

生命周期(新)

  1. 初始化阶段:由 ReactDOM.render ( ) 触发 --- 初次渲染
    • constructor( )
    • getDerivedStateFromProps ( ) 从 Props 获得派生状态
    • render( )
    • componentDidMount( )
  2. 更新阶段:组件内部的 this.setState ( ) 或者父组件的 render 触发
    • getDerivedStateFromProps ( ) 从 Props 获得派生状态
    • shouldComponentUpdate ( ) 组件是否更新
    • render( )
    • getSnapshotBeforeUpdate ( ) 更新前获得快照
    • componentDidUpdate ( ) 可接收到 getSnapshotBeforeUpdate 传递的快照值
  3. 卸载组件:由 ReactDOM.unmontComponentAtNode ( ) 触发
    • componentWillUnmount ( ) 常用于关闭定时器,取消订阅消息等收尾工作

# 3. 重要的钩子

  1. render: 初始化渲染或者更新渲染调用
  2. componentDidMount ( ) : 开启监听,发送 AJAX 请求
  3. componentWillUnmount ( ) : 清理资源,关闭定时器等

# 4. 即将废弃的钩子

  1. componentWillMount (新 UNSAFE_componentWillMount)
  2. componentWillReceiveProps (新 UNSAFE_componentWillReceiveProps)
  3. componentWillUpdate (新 UNSAFE_componentWillUpdate)

16.0 后版本使用会出现警告,需要加上 UNSAFE_ 前缀才能使用,后续版本可能会被彻底废弃

# 5.React 中的 key

# 1. 虚拟 DOM 中 key 的作用:
  1. 作用:可以是虚拟 DOM 对象的标识,在更新显示时 key 起着重要的作用

  2. 当状态中的数据发生变化时,react 会根据新数据生成 新的虚拟DOM, 随后 React 进行 新虚拟DOM

    旧虚拟DOM 的 diff 比较.

  3. 旧虚拟 DOM 中找到与新虚拟 DOM 相同的 key

    • 旧虚拟 DOM 中内容没变,直接使用之前的真实 DOM
    • 若虚拟 DOM 中内容改变,则生成新的真实 DOM, 随后替换页面中的真实 DOM
  4. 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key

    • 根据数据创建新的真实 DOM, 随后渲染到页面
# 2.index 作为 key 可能引发的问题
  1. 若对数据进行 逆序添加,逆序删除等破坏顺序操作,会产生没有必要的真实 DOM 更新 (显示正常,效率低)
  2. 如果结构中还包含输入类 DOM, 会产生错误 DOM 更新 (输入类的 DOM 值会错误显示)
  3. 不存在对数据的 逆序添加,逆序删除等破坏顺序的操作,仅用于展示,使用 index 作为 key 没有问题
# 3. 开发时的 key
  1. 使用每条数据的唯一标识作为 key, 例如: id, 手机号,身份证号等
  2. 如果只是展示简单的内容,也可使用 index 也可以

# 6.React 脚手架

# 1. 创建项目并启动

  1. 全局安装: npm i -g create-react-app
  2. node 新版本自带命令: npx create-react-app my-app
  3. 启动项目: npm start

# 2.React 脚手架项目结构

public ---- 静态资源文件夹

​ favicon.icon ------ 网站页签图标

​ index.html -------- 主页面

​ logo192.png ------- logo图

​ logo512.png ------- logo图

​ manifest.json ----- 应用加壳的配置文件

​ robots.txt -------- 爬虫协议文件

src ---- 源码文件夹

​ App.css -------- App组件的样式

​ App.js --------- App****组件

​ App.test.js ---- 用于给App做测试

​ index.css ------ 样式

​ index.js ------- 入口文件

​ logo.svg ------- logo图

​ reportWebVitals.js

​ --- 页面性能分析文件(需要web-vitals库的支持)

setupTests.js

​ ---- 组件单元测试的文件(需要jest-dom库的支持)

# 3. 功能界面的组件化编码流程

  1. 拆分组件:拆分界面,抽取组件
  2. 实现静态组件:使用组件实现静态页面效果
  3. 实现动态组件
    • 动态显示初始化数据
      • 数据类型
      • 数据名称
      • 数据保存位置
    • 交互 (绑定事件进行监听)

# 4.React 配置代理

# 1. 简单代理

在 Package.json 中追加如下配置: proxy : http://localhost:3600

  • 当请求不同端口的数据资源时,可以使用上述配置利用同端口的代理进行请求,请求资源在自身找不到时,会由

    同端口的服务器转发到对应远程服务器,从而解决跨域问题

  • 优点:配置简单,前端请求资源时可以不添加任何前缀

  • 缺点:不能配置多个代理

  • 工作方式:当请求本地资源不存在时,请求会转发给远程服务器 (优先匹配自身资源)

# 2. 方法二:

在 src 文件夹下创建配置文件: src/setupProxy.js

  • 文件名必须是 setupProxy.js , React 项目运行时会自动查找此文件,并将其加入 webpack 配置
  • 优点:可以配置多个代理,可以灵活的控制请求是否走代理
  • 缺点:配置繁琐,前端请求资源时必须添加前缀
// 代码示例
const proxy = require('http-proxy-middleware')
 module.exports = function(app) {
   app.use(
     proxy('/api1', {  //api1 是需要转发的请求 (所有带有 /api1 前缀的请求都会转发给 5000)
       target: 'http://localhost:5000', // 配置转发目标地址 (能返回数据的服务器地址)
       changeOrigin: true, // 控制服务器接收到的请求头中 host 字段的值
       /*
       	changeOrigin 设置为 true 时,服务器收到的请求头中的 host 为:localhost:5000
       	changeOrigin 设置为 false 时,服务器收到的请求头中的 host 为:localhost:3000
       	changeOrigin 默认值为 false,但我们一般将 changeOrigin 值设为 true
       */
       pathRewrite: {'^/api1': ''} // 去除请求前缀,保证交给后台服务器的是正常请求地址 (必须配置)
     }),
     proxy('/api2', { 
       target: 'http://localhost:5001',
       changeOrigin: true,
       pathRewrite: {'^/api2': ''}
     })
   )
}
# 3.ES6 拓展知识

连续解构赋值 & 重命名

let obj = {a: {b: 1}}
const {a} = obj			// 常规结构赋值
const {a:{b}} = obj		// 连续解构赋值
const {a: {b: value}} = obj  // 连续解构赋值并将b重命名为value

defaultChecked 只在页面第一次加载时触发,后续改变只能使用 checked

# 4. 消息订阅与发布 PubSubJS
  1. 先订阅,然后再发布消息
  2. 适用于任意跨组件通讯
  3. 取消订阅时应该在 componentWillUnmount 中进行
import PubSub from 'pubsub-js'
// 订阅消息
this.msg = PubSub.subscribe('Msg Name',(msg,data) => {})
// 取消订阅
PubSub.unsubscribe(this.msg)
// 发布消息
PubSub.publish('Msg Name',data)
# 5. 异步请求 XHR 与 Fetch
  1. fetch 的简单介绍

    • fetch 是一种 HTTP 数据请求的方式,是 XMLHttpRequest 的一种替代方案 **
    • fetch 不是 ajax 的进一步封装,而是原生 js
    • Fetch 函数就是原生 js,没有使用 XMLHttpRequest 对象
  2. XMLHttpRequest API 的缺点

    • 不符合关注分离(Separation of Concerns)的原则
    • 配置和调用方式非常混乱
    • 基于事件的异步模型写起来也没有现代的 Promise,generator/yield,async/await 友好。
# 6.Fetch 发送请求
  1. 关注分离的设计思想
  2. Fetch 是浏览器提供的原生异步请求方式
    • 原生的 XMLHttpRequest 不符合关注分离原则,且在时间模型在异步处理上没有现代的 Promise 的优势
  3. 特点:
    • fetch: 原生函数,不再使用 XMLHttpRequest 对象提交 ajax 请求
    • 旧版本浏览器可能无法支持
    • 使用 fetch 无法取消请求,因为 Fetch API 基于 Promise, Fetch 是典型的异步场景
  4. 直接使用 Fetch 返回的并不是请求的数据,只是一个 HTTP 响应,而不是真的数据,
    • 使用 async + await 等待异步回调的结果
    • 使用 Promise 的链式调用,连续使用两次.then 获取真实数据
// 使用链式 .then
fetch(`/api1/search/users2?q=${keyWord}`).then(
  		response => {
  			console.log('联系服务器成功了');
  			return response.json()
  		},
  		error => {
  			console.log('联系服务器失败了',error);
  			return new Promise(()=>{})
  		}
  	).then(
  		response => {console.log('获取数据成功了',response);},
  		error => {console.log('获取数据失败了',error);}
) 
// 使用 async + await 
try {
  	const response= await fetch(`/api1/search/users2?q=${keyWord}`)
  	const data = await response.json()
  	console.log(data);
  	} catch (error) {
  	onsole.log('请求出错',error);
  	}
}

# 7.React 路由

# 1. 相关概念

# 1.SPA 单页应用
  1. 单页 web 应用 (single page web application , SPA)
  2. 整个应用只有一个完整的页面
  3. 点击页面中的链接不会刷新页面,只会更新对应的路由组件
  4. 数据都需要通过 ajax 请求获取,并在前端异步展现
# 2. 路由的概念

什么是路由

  1. 一个路由就是一个映射关系 (key-value)
  2. key 为路径,value 可能是 function 或 component
# 3. 路由的分类
  1. 后端路由
    • 理解: value 是 function, 用于处理客户端提交的请求
    • 注册路由: router.get (path, (req, res)=>{ })
    • 工作过程:当 node 接收到请求时,根据请求路径匹配对应路由,调用路由中的函数来处理请求,返回响应数据
  2. 前端路由
    • 浏览器端路由,value 是 component, 用于展示页面内容
    • 注册路由: <Route path="/index" component={Index}>
    • 工作过程:通过匹配 path 参数加载对应的路由组件

# 2.react-router-dom

# 1. 相关概念
  1. react-router-dom
    • react 的路由插件
    • 用于实现一个基于 React 的 SPA 应用
  2. 相关 API
    • <BrowserRouter> 路由组件根标签
    • <HashRouter> Hash 路由
    • <Route> 路由组件
    • <Redirect> 错误路由兜底组件
    • <Link> 路由链接
    • <NavLink> 带默认样式的路由拦截
    • <Switch> 多个相同路由仅匹配第一次,提高效率
  3. 相关参数
    • history
    • match
    • withRoute
# 2. 路由的基本使用
  1. 明确界面中的导航区,展示区
  2. 导航区的 a 标签改为 Link 标签
<Link to"/dmeo">Demo</Link>
  1. 展示区使用 Route 标签进行路径匹配
<Route path='/demo' component={Demo} />

4.<App> 的外侧标签包裹 <BrowserRouter> 或 <HashRouter> 用于识别相关路由标签

ReactDom.render(
	<BrowerRouter>
    	<App/>
    </BrowerRouter>,
    document.getElementById('root')
)
# 3. 路由组件与一般组件
  1. 写法不同:
    • 一般组件: <Demo/>
    • 路由组件: <Route path="/demo" component={Demo} />
  2. 存放位置不同:
    • 一般组件: components
    • 路由组件: pages
  3. 接收参数不同
    • 一般组件:通过 props 传递参数
    • 路由组件:接收三个固定属性
//路由属性打印结果展示
history:
	go: ƒ go(n)
	goBack: ƒ goBack()
	goForward: ƒ goForward()
	push: ƒ push(path, state)
	replace: ƒ replace(path, state)
location:
    pathname: "/about"
	search: ""
	state: undefined
	match:
params: { }
	path: "/about"
	url: "/about"
# 4.NavLink 使用与封装
  1. NavLink 可以实现路由链接高亮,通过 activeClassName 指定高亮样式名称 (默认为 .active)
  2. 封装
// 封装示例
// props中的 children属性可以接收到标签体中的内容 
export default class MyNavLink extends Component {
    render() {
        return {
            <NavLink activeClassName='active' 
                className="list-group-item" {...this.props}/>
        }
    }
}

3. 使用与调用

// 原生HTML中, 通过<a> 标签跳转到不同页面
<a className="list-group-item" href="./about.html" >About</a>
<a className="list-group-item active" href="./home.html">Home</a>

// React中嵌套路由链接实现组件切换 --- 编写路由链接
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">About</MyNavLink>
# 5.Switch 使用
  1. 通常情况下,path 和 component 是一一对应的关系
  2. Switch 可以提高路由匹配率 (单一匹配) --- 匹配到对应路由后不再向下匹配
<Switch>
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
    <Route path="/home" component={Test} />
</Switch>
# 6. 解决多级路径刷新页面样式丢失
  1. public/index.html 中引入样式时不使用 ./ 而使用 /
  2. public/index.html 中引入样式时使用 %PUBLIC_URL% (常用,仅在 React 中生效)
  3. 使用 HashRouter (不常用)
# 7. 路由的严格匹配与模糊匹配
  1. 默认使用的是模糊匹配 (【输入的路径】必须包含 【匹配的路径】,且顺序要一致)
  2. 开启严格匹配:使用 exact 属性, <Route exact={true} path="/about" component={About} />
  3. 严格匹配不能随意开启,可能导致无法继续匹配二级路由
  4. Redirect 在路由注册的最下方,用于没有匹配到路由时的跳转路由
// 编写路由链接
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home/sub/nav">Home</MyNavLink>
// 注册路由
<Switch>
	<Route exact path="/about" component={About} />
    <Route exact path="/home" component={Home} />
    <Redirect to="/about"/>		// 没匹配到任何路由时跳转到 /about

</Switch>
# 8. 嵌套路由
  1. 注册子路由时要携带父路由的 path 值
  2. 路由的匹配是按照注册路由的顺序进行的
# 9. 向路由组件传递参数
  1. params 传递参数
    • 路由链接携带参数 : <Link to='/demo/test/tom/18' > 详情</link>
    • 注册路由 (声明接收) : <Route path="/demo/test/:name/:age" component={Test} />
    • 接收参数 : this.props.match.params
// 父组件传递params参数
  <div>
       {/* 向路由组件传递params参数 */}
       <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
       <hr />
       {/* 声明接收params参数 */}
       <Route path="/home/message/detail/:id/:title" component={Detail} />
  </div>

// 字组件从match.params对象中接收参数
const {id,title} = this.props.match.params
  1. Search 参数
    • 路由链接携带参数 : <link to='/demo/test?name=tom&age=18'> 详情</link>
    • 注册路由 (无需声明,正常注册即可) : <Route path="/demo/test" component={Test} />
    • 接收参数 : this.props.location.search
    • 接收到的 search 是 urlencode 编码字符串,需要借助 querystring 解析
// 父组件发送参数
<div>
	{/*向路由组件传递search参数*/}
    <Link to={`/home/message/detai/?id=${msg.id}&title=${msg.title}`}>{msg.title}</Link>
    <hr />
    {/* search参数无需声明接收*/}
    <Route path="/home/message/detail" component={Detail} />
</div>
// 子组件接收参数
import qs from 'querystring'
// 接收search参数
const {search} = this.props.location
const {id, title} = qs.parse(search.slice(1))
  1. State 参数
    • 路由链接携带参数 : <Link to={{pathname: '/demo/test', state: {name: 'tom',age: 18}}}></Link>
    • 注册路由 (无需声明,正常注册) : <Route path="/demo/test" component={Test} />
    • 接收参数 : this.props.location.state
    • 使用 BrowseRouter 刷新时才可以保留参数,使用 HashRouter 刷新后 State 没有 history 来保存参数
// 父组件发送参数
<div>
	{/*向路由组件传递state参数*/}
    <Link to={{pathname: '/home/message/detail', state: {id,title}}}>{title}</Link>
    <hr/>
    {/* state无需声明接收, 正常注册路由即可*/}
    <Route path="/home/message/detail" component={Detail} />
</div>
// 子组件接收参数, 判断如果没有接收到参数时不显示数据,防止报错
const {id,title} = this.props.location.state || {}
# 10. 编程式路由导航

通过 this.props.history 对象提供的 API 操作路由跳转,前进,后退

  1. this.props.history.push ( ) push 一条记录到路由历史中
  2. this.props.history.replace ( ) 推送一条记录替换掉栈顶的路由
  3. this.props.history.goBack ( ) 回退到上一条路由历史
  4. this.props.history.goForward ( ) 路由历史前进一条
  5. this.props.history.go ( ) 路由历史前进或后退 n 条,根据传递的 n 的正负决定前进后退
# 11. withRouter
  1. withRouter 可以加工一般组件,让一般组件可以使用路由组件所特有的 API
  2. withRouter 的返回值是一个新组件 (将非路由组件转化为新的可以使用路由 API 的组件)
class Header extends Component {
    back = () => {this.props.history.goBack()}
    forward = () => {this.props.history.goForward()}
    go = () => {this.props.history.go(-3)}
    
    render() {
        <div>
        	<h2>React Router Demo</h2>
            <button onClick={this.back}>回退</button>
            <button onClick={this.forward}>前进</button>
            <button onClick={this.go}>Go 跳转</button>
        </div>
    }
}
// 使用withRouter让非路由组件可以使用路由API
export default withRouter(Header)
# 13.BrowserRouter 与 HashRouter 的区别

备注: Hash 可以用于解决一些路径错误相关的问题,引用文件时可以使用相对路径

  1. 底层原理不同
    • BrowserRouter 使用的是 H5 的 history API, 不兼容 IE9 以下版本 (常用)
    • HashRouter 使用的是 URL 的哈希值 (尽可能避免使用)
  2. path 的表现形式不同
    • BrowserRouter 的路径中没有 #, 例如: localhost:3000/demo/test
    • HashRouter 的路径包含 #, 例如: localhost:3000/#/demo/test
  3. 刷新后对路由 state 参数的影响
    • BrowserRouter 没有任何影响,因为 state 保存在 history 对象中
    • HashRouter 刷新后会导致路由 state 参数的丢失

# 8.Redux 全局状态管理

# 1.redux 相关概念

# 1. redux 相关文档
  1. 英文文档: https://redux.js.org
  2. 中文文档: https://www.redux.org.cn
  3. GitHub: https://github.com/reduxjs/redux
# 2.redux 的概念
  1. redux 是一个专门用于 状态管理的JS库 (非 react 插件库)
  2. redux 可以用于 react, angular, vue 等项目中,常用与和 react 配合使用
  3. 用途:集中式管理 react 应用中多个组件共享的状态
# 3.redux 使用情景
  1. 某个组件的状态,需要让其他组件可以随时使用 (共享)
  2. 一个组件需要改变另一个组件的状态 (通信)
  3. 总体原则:尽量避免使用,除非万不得已
# 4.redux 工作流程

redux原理图

# 2.redux 的三个核心概念

# 1. action
  1. 动作对象
  2. 包含 2 个属性
    • type: 标识属性,值为字符串,唯一,必要属性
    • data: 数据属性,值类型任意,可选属性
  3. 示例: {type: 'ADD_STUDENT', data: {name: 'tom', age: '18'} }
# 2. reducer
  1. 用于初始化状态,加工状态
  2. 加工时,根据旧的 state 和 action, 产生新的 state 的 纯函数
    • 纯函数:一类特别的函数,相同的形参必定得到相同的输出
    • 必须遵守以下约束
      • 不得改写参数数据
      • 不会产生任何副作用,例如网络请求,输入和输出设备
      • 不能调用 Date.now () 或者 Math.random () 等不纯的函数,方法
    • redux 的 reducer 函数必须是一个纯函数
# 3.store
  1. 将 state, action, reducer 联系在一起的对象
  2. 得到 store 对象
    • import { createStore } from 'redux'
    • import reducer from './reducers'
    • const store = createStore(reducer)
  3. store 对象的作用
    • getState ( ) 得到 state 全局状态
    • dispatch (action) 分发 action, 触发 reducer 调用,产生新的 state
    • subscribe (listener) 注册监听,当产生新的 state 时,自动调用

# 3.redux 的核心 API

# 1.createStore () 与 applyMiddleware ()
  1. createStore ( ) 创建包含指定 reduce 的 store 对象
  2. applyMiddleware ( ) 基于 redux 的中间件 ( 用于创建一步action )
// 引入createStore, 用于创建 redux中的核心 store对象
import {createStore, applyMiddleware} from 'redux'
// 暴露store
export default createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))
export default createStore(reducer, applyMiddleware(thunk))
# 2.store 对象
  1. 作用: redux 库中最核心的状态管理对象
  2. store 对象 内部维护着 state, reducer
  3. 核心方法
    • getState( )
    • dispatch(action)
    • subscribe(listener)
  4. 具体编码示例
    • store.getState( )
    • store.dispatch({ type: 'INCREMENT', data})
    • store.subscribe( render ) 监听redux状态改变时, 触发视图更新
/**
* 该文件撰文用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入汇总后的reducer
import reducer from './reducers'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//引入redux-devtools-extension
import {composeWithDevTools} from 'redux-devtools-extension'
//暴露store
export default createStore(reducer,composeWithDevTools(applyMiddleware(thunk)))
----------------------------index.js 引入store对象--------------------------------
import React from 'react'
import ReactDOM from "react-dom"
import App from './App'
import store from './redux/store'
import {Provider} from 'react-redux'

// redux 版
render(
        <App/>,
    document.getElementById('root')
)

// 必须使用subscribe方法监听store对象的状态, 并调用 render引起DOM更新
store.subscribe(()=>{
    render(
        <App/>,
        document.getElementById('root')
    )
})


// react-redux 插件版
ReactDOM.render(
  /* 此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store */
  <Provider store={store}>
  	<App/>
  </Provider>,
  document.getElementById('root')
)
# 3.CombineReducers( )

combineReducers 函数用于合并多个 reducer 函数

import {combineReducers} from 'redux'
import countReducer from './pages/count'
import personReducer from './pages/person'

// 将所有reducer汇总为一个总的 reducer
export default combineReducer ({
    countReducer, personReducer
})
# 4.redux 异步编程
  1. 使用场景
    • redux 默认是不能进行异步处理的
    • 某些时候应用需要在 redux 中执行异步任务 (ajax, 定时器)
  2. 使用中间件支持异步
    • redux-thunk 中间件: npm install --save redux-thunk
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入汇总后的reducer
import reducer from './reducers'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//引入redux-devtools-extension
import {composeWithDevTools} from 'redux-devtools-extension'
//暴露store
export default createStore(reducer,composeWithDevTools(applyMiddleware(thunk)))

# 4.react-redux

# 1. 相关概念
  • react-redux 是一个 react 插件库
  • 用于简化在 react 应用中使用 redux
# 2.react-Redux 将所有组件分为两类
  1. UI 组件

    • 只负责 UI 的呈现,不带有任何 redux 相关操作逻辑
    • 通过 props 接收数据 (一般状态和函数)
    • UI 组件中不应该出现任何 Redux 的 API
    • UI 组件一般放置在 components 文件夹下,也可以直接定义在容器组件中直接使用
  2. 容器组件

    • 管理 redux 数据和业务逻辑,不负责 UI 组件的呈现
    • 使用 Redux 相关的 API
    • 一般保存在 container 文件夹下
  3. react-redux 相关 API

    • Provider : 让所有组件都可以获取到 store 中存储的数据
    import React from 'react'
    import ReactDOM from "react-dom"
    import App from './App'
    import store from './redux/store'
    import {Provider} from 'react-redux'
    
    ReactDOM.render(
      /* 此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store */
      <Provider store={store}>
      	<App/>
      </Provider>,
      document.getElementById('root')
    )
    
    • connect( )( )
      • 用途:用于包装 UI 组件生成容器组件
      • 示例: connect( mapStateToProps, mapDispatchToProps)(UI组件)
      • connect 方法默认传入 state 与 dispatch
      • 可以省略 dispatch 直接传递 action 方法时,会自动使用 dispatch 调用
    • mapStateToProps
      • 用途:将外部的数据 ( state对象 ) 转换为 UI 组件的 props 属性
      • mapStateToProps 函数返回的是一个对象
      • 返回的对象的 key 作为传递给 UI 组件的 props 属性的 key, value 作为传递给 UI 组件 props 的 value
      • mapStateToProps 用于传递状态
    function mapStateToProps(state) {
        return {count: state}
    }
    
    • mapDispatchToProps
      • 用途:分发 action 函数,转为为 UI 组件的 props 属性
      • mapDisPatchToProps 函数返回的是一个对象
      • 返回的对象的 key 作为传递给 UI 组件的 props 属性的 key, value 作为传递给 UI 组件 props 的 value
      • mapDispatchToProps 用于传递操作状态的方法
      • 可以省略 dispatch, 直接传入 action, api 会自动使用 dispatch 调用 action 方法
    function mapStateToProps(state) {
        return {count: state}
    }
    const mapStateToProps = state => ({count: state})
    
    function mapDispatchToProps(dispatch) {
        return {
            Increment: step => dispatch(Increment(step)),
            Decrement: step => dispatch(Decrement(step)),
            IncrementAsync: (step, time) => dispatch(IncrementAsync(step, time))
        }
    }
    
    const mapDispatchToProps = dispatch => ({
        Increment: step => dispatch(Increment(step)),
        Decrement: step => dispatch(Decrement(step)),
        IncrementAsync: (step, time) => dispatch(IncrementAsync(step, time))
    })
    
    export default connect(
        state => ({count: state}),
        {Increment, Decrement, IncrementAsync}
    )(CountUI)
    
    
# 3.Redux 调试工具
  1. 安装 chrome 浏览器插件 Redux DevTools
  2. 修改 store.js
const composeEnhancers =
    typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
        ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
        : compose;
const enhancer = composeEnhancers(
    applyMiddleware(thunkMiddleware)
    // other store enhancers if any
);
export default createStore(allReducer, enhancer)

# 9.React 拓展

# 1.setState 更新状态的两种方式
  1. setState (stateChange, [callback]) -- 对象式
    • setChange 为状态改变对象 (该对象可以体现出状态的更改)
    • callback 是可选的回调函数,其在状态更新完毕,界面也更新后才会被调用
  2. setState (updater,[callback]) -- 函数式
    • updater 为返回 stateChange 对象的函数
    • updater 可以接收到 state 和 props
    • callback 是可选的回调函数,在状态更新,界面重新渲染后才会不被调用
  3. 对象是的 setState 是函数式 setState 的简写方式
    • 如果不依赖与原状态,优先使用对象式
    • 如果新状态依赖于原状态,有限使用函数式
    • 如果需要在 setState ( ) 执行后获取最新的状态数据,需要在 callback 回调函数中读取
// 更新状态的函数
(state, props) => stateChange
// 使用函数式修改状态的方法
setState(stateChange[, callback])
// 使用对象式修改状态的方法
this.setState({quantity: 2})
# 2.LazyLoad 路由组件懒加载
  1. 懒加载中的组件,随意调用,不会提前加载
  2. 使用懒加载时需要指定一个 fallback, 用于请求过慢或者请求不到组件时显示,通常为组件,也可以是虚拟 DOM
  3. fallback 如果指定为一个组件,则该组件 不能为懒加载组件
// 指定fallback 过渡显示组件
import Loading from './Loading'
// 通过React的lazy函数动态加载路由组件
const Login = lazy(()=> import('@/pages/home'))
// 通过Suspense 指定在加载对应路由文件前显示一个自定义loading界面
<Suspense fallback={<h1>loading...</h1>}>
    <switch>
		<Route path="/home" component={<Home/>}/>
	</switch>    
</Suspense>

# 10. Hooks

# 1.useState( )
  1. useState ( ) 让函数组件也可以有 state 状态,并对状态数据进行修改
  2. 接收一个参数作为状态初始值,返回一个 state 和 用于更新 state 的函数
  3. 函数签名: const [state, setState] = useState (initialState);
  4. 在初始渲染期间,返回的 状态(state) 与 传入的第一个参数 ( initialState ) 值相同
  5. setState 函数用于更新 State, 接收一个新的 state 值并将组件的一次重新渲染加入队列
  6. 后续渲染中,useState 返回的第一个值将始终是更新后的最新的 state
// 返回一个state 以及更新state的函数
const [state, setState] = useState(initialState);
setState(newState)
setState(prevState => {
  // 也可以使用 Object.assign
  return {...prevState, ...updatedValues};
});
# 2.useEffect( )
  1. useEffect 可以在函数式组件中 执行副作用操作,模拟类组件中的生命周期钩子
  2. React 副作用操作:发送 ajax 请求,设置订阅 / 启动定时器,手动修改真实 DOM
  3. useEffect 相当于 componentDidMountcomponentDidUpdatecomponentWillUnmount 的组合。
  4. 函数签名: useEffect(()=>{}, [Property]) 一个方法为执行操作的函数,第二个参数为监听变化的属性
// 如果被监听的属性数组为空, 则函数中的操作只在挂载后和取消操作时执行, 更新时不会被触发
useEffect(()=>{},[])

/**
  * 函数体中的操作相当于componentDidMount 和 componentDidUpdate 
  * 在组件挂载后 和 状态更新重新渲染DOM 时会执行其中的操作
  */
useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  /**
  * 函数的返回值相当于 componentWillUnmount
  * 用于执行 定时器清除, 取消订阅等操作
  */
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [property]); // 仅在 props 发生变化时,重新订阅
# 3.useRef( )
  1. useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数 ( initialValue ),
  2. 函数返回的 ref 对象在组件的整个生命周期内持续存在
  3. 函数签名: const refContainer = useRef(initialVaue)
  4. 本质上, useRef 就像是可以在其 .current 属性中保存一个可变值的 ' 盒子'
  5. useRef 可以很方便地 保存任何可变值 ,类似于 class 中使用实例字段的方式
  6. ref 对象内容发生变化时,useRef 并不会通知,变更 .current 属性不会引发组件重新渲染
  7. 在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,需要使用回调 ref 来实现
function TextInputWithFocusButto() {
    const inputEl = useRef(null)
    const onButtonClick = () =>{
        // current 指向已挂载到DOM上的文本输入元素
        inputEL.current.focus()
    }
    return (
    	<Fragments>
        	<input ref={inputEl} type="text" />
            <button onClick={onButtonClick}>Focus the input</button>
        </Fragments>
    )
}
# 4.Fragment
  1. 用途:可以不必须使用一个真实的 DOM 根标签

  2. 当不得不使用容器去包裹 dom 元素 ( jsx语法要求, 只能有一个根节点 )

  3. 使用 Fragment 后可以取代根节点标签,其编译后会被 react 丢弃,避免造成不必要的层级嵌套

  4. 效果等同于直接写一个 空标签: < > </> , 但 Fragment 可以接收 key 属性作为唯一标识,而空标签

    无法接收任何标签属性

/**
* Fragment 可以作为根标签和循环的外层结构 
* Fragment 不会被渲染到真实DOM中
*/
export default class Demo extends Component {
    render() {
        <Fragment key={'s001'}>
        	<input type="text"/>
            {
                list.map(item =>{
                    return (
                        <Fragment>item.data</Fragment>
                    )
                })
            }
        </Fragment>
    }
}
# 5. Context
  1. Context 是一种组件间通信的方式,常用与 [祖组件] 与 [后代组件] 间通信
  2. 渲染子组件时,外层包裹 xxxContext.Provider, 通过 value 属性给后代组件传递数据
const xxxContext = React.createContext()
// 父组件传递数据
<xxxContext value={data}>
	子组件
</xxxContext>

// 后代组件接收数据的方法
// 第一种方式:  只能用于类组件, 且必须使用 state contextType 声明接收
static contextType = xxxContext
this.context // 读取context中的属性值

// 第二种方式:  函数式组件及类式组件
<xxxContext.Consumer>
    {
    	value =>{ value即为context对象中的属性}
	}
</xxxContext.Consumer>
  1. useContext( )
    • 函数声明: const value = useContext(MyContext)
    • 接收一个 context 对象 ( React.createContext 的返回值) 并返回该 context 的当前值.
    • 当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value 决定
    • 使用 useContext 的组件总会在 context 值变化时重新渲染,可通过 memoization 来优化
const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}
# 6. 组件优化 -- pureComponent
  1. component 的两个问题 (效率低)
    • 只要执行 setState (), 即使不改变状态数据,组件也会重新 render ()
    • 只要当前组件重新 render (), 就会自动重新 render () 子组件,纵使子组件没有用到父组件的任何数据
    • 原因:因为 component 中的 shouldComponentUpdate ( ) 默认总是返回 true
  2. 优化效率的做法
    • 只有当组件的 state 或 props 数据改变时才重新 render ( )
    • 重写 shouldComponentUpdate () 方法,当 state 或 props 变化时才返回 true
    • 使用 PureComponent 替代 Component, PureComponent 重写了 shouldComponentUpdate () 方法
    • PureComponent 只做了浅比较,如果对象内部属性改变,返回 false, 不会更新视图
    • 不要直接修改 state 数据,而是产生新数据,项目中一般使用 PureComponent 做优化
# 7.render props -- 类似 vue 中的 slot 插槽
  1. 如何向组件内部传递带有内容的结构 (标签)
  2. React 中使用 children props, 通过组件标签体传入结构使用 render props,
  3. 通过组件标签属性传入结构同时携带数据,一般 render 函数属性
// children 属性
<A>
	<B>xxx</B>
</A>
{this.props.children: <B />}
需要B组件需要A组件中的数据, 则无法传递

// render props
<A render={data => <C data={data} />} />
A组件:  通过 this.props.render(data) 接收渲染到其中的组件
C组件:  读取A组件传入的数据显示 {this.props.data}

export default class Parent extends Component {
	render() {
		return (
			<div className="parent">
				<h3>我是Parent组件</h3>
				<A render={(name)=><C name={name}/>}/>
			</div>
		)
	}
}

class A extends Component {
	state = {name:'tom'}
	render() {
		console.log(this.props);
		const {name} = this.state
		return (
			<div className="a">
				<h3>我是A组件</h3>
				{this.props.render(name)}
			</div>
		)
	}
}

class B extends Component {
	render() {
		console.log('B--render');
		return (
			<div className="b">
				<h3>我是B组件,{this.props.name}</h3>
			</div>
		)
	}
}
# 8. 错误边界
  1. 错误边界 (Error boundary) : 用于捕获后代组件错误,渲染 error 组件
  2. 只能捕获后代组件生命周期产生的错误,不能捕获自身产生的错误和其他组件在合成事件,定时器中产生的错误
  3. getDerivedStateFromError 配合 componentDidCatch
// 生命周期函数, 一旦组件报错就会触发
static getDerivedStateFromError(error) {
    console.log(error)
    // 在render之前触发
    // 将hasError属性加入到state状态中,用于发生错误时判断渲染的组件
    return {hasError: true}
}

componentDidCatch(error, info) {
    // 统计页面的错误, 发送请求到服务端
    console.log(error, info)
}

render() {
    return (
        <div>
            <h2>我是Parent组件</h2>
            {this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child />}
        </div>
    )
}
# 9. 组件间通信方式总结
  1. 组件间的关系:
    • 父子组件
    • 兄弟组件 (非嵌套组件)
    • 祖孙组件 (跨级组件)
  2. 通信方式:
props:
	-children props
	-render props
消息订阅-发布
	pubsub, event
集中式状态管理:
	redux, dva
context: 
	生产者-消费者模式

# 11.React-router v6

# 1. 一级路由
// 路由链接
<NavLink to="/home" className={activeStyle} >
    <span className="d-inline-block bg-primary rounded-circle link-content"/>
    Home组件
</NavLink>
 <NavLink to="/about" className={activeStyle}>
    <span className="d-inline-block bg-info rounded-circle link-content"/>
    About组件
</NavLink>
// 注册路由组件
<Routes>
    <Route path="/home" caseSensitive element={<Home/>}/>
    <Route path="/about" element={<About/>}/>
    
    <Route
        path="*"
        element={
            // <main style={{ padding: "1rem" }}>
            //     <p>There's nothing here!</p>
            // </main>
            <ErrorRouter />
        }
        />
</Routes>
# 2. 路由重定向

通过 Navigate 标签组件匹配到对应路径时立即执行指向对应页面

{/*路由重定向*/}
<Route path="/" element={<Navigate to="/home" />} />
# 3. 路由高亮

通过 isActive 状态来动态渲染路由高亮样式


function activeStyle({isActive}) {
    console.log(isActive)
    return `${isActive? 'custom-active':''}`
}
<NavLink to="/home" className={activeStyle} >Home</NavLink>
// 内联箭头函数
<NavLink to="/home" className={({isActive})=>isActive?'active':''} >Home</NavLink>
# 4.useRoutes 路由表
 const routes = [
     { path: '/home', element: <Home/>},
     { path: '/about', element: <About/>},
     { path: '/', element: <Navigate to='/about' replace/>},
     { path: '*', element: <ErrorRouter/>}
 ]
 
 const element = useRoutes(routes)
 // 在jsx组件标签中 使用如下方式渲染
 { element } 
 
# 5. 嵌套路由
  • 通过 route 标签嵌套实现路由嵌套
  • 通过路由表的 children 属性实现嵌套路由
// route标签
<Routes>
    <Route path="/home" caseSensitive element={<Home/>}>
    	<Route path="/news" element={<News/>}/>
        <Route path="/messages" element={<Messages/>}/>
    </Route>
    <Route path="/about" element={<About/>}/>
</Routes>
// 路由表
const routes = [
        {
            path: '/home',
            element: <Home/>,
            children: [
                { path: '/home/news', element: <News/>},
                {
                    path: 'messages',
                    element: <Messages/>,
                    children: [
                        // { path: 'detail/:id/:title/:content', element: <Detail/>}
                        { path: 'detail', element: <Detail/>},
                    ]
                }
            ]
        },
        { path: '/about', element: <About/>},
        { path: '/', element: <
         to='/about' replace/>},
        { path: '*', element: <ErrorRouter/>}
    ]
# 6.params 参数
<Link to={`detail/${message.id}/${message.title}/${message.content}`}>
    {message.title}
</Link>
{ path: 'detail/:id/:title/:content', element: <Detail/>}
// params 传递路由参数
const {id, title, content} = useParams()
const match = useMatch('/home/messages/detail/:id/:title/:content')

# 7.search 参数
<Link to={`detail?id=${message.id}&title=${message.title}&content=${message.content}`}>		{message.title}
</Link>
{ path: 'detail', element: <Detail/>}
// query传递路由参数
const [search,setSearch] = useSearchParams()
const id = search.get('id')
const title = search.get('title')
const content = search.get('content')
// 设置search参数
<button onClick={() => setSearch('?id=999&title=一月热榜&content=更衣人偶')}>点击查看新消息</button>
# 8.state 参数
<Link to="detail" state={{
        id: message.id,
        title: message.title,
        content: message.content
    }}>
    {message.title}
</Link>
{ path: 'detail', element: <Detail/>}
const {state: {id,title,content}} = useLocation()
# 9. 编程式路由导航
const navigate = useNavigate()

// 路由到指定路径
function redirectFunc(path) {
    navigate(path,{
        replace: true,
        state: {id: '666', title: 'navigate', content: '编程式导航'}
    })
}
// goForware()
 navigate(1)
// goBack()
 navigate(-1)
// go()
 navigate(n)
# 10. useInRouterContext()
// 判断当前组件是否处于路由上下文中 是则返回 true 否则反之
const flag = useInRouterContext()
declare function useInRouterContext(): boolean;
# 11.useNavigationType()
  • 返回当前的导航类型或用户如何来到当前页面;通过历史堆栈上的弹出、推送或替换操作。
  • 返回值 POP | PUSH | REPLACE ( POP是在浏览器中刷新或URL回车 )
declare function useNavigationType(): NavigationType;

type NavigationType = "POP" | "PUSH" | "REPLACE";
// 在组件中进行输出
console.log(useNavigationType())
# 12. useOutlet( )
  • 用于呈现当前组件中要渲染的路由
  • 返回此路由层次结构级别的子路由的元素。这个钩子在内部被用来渲染子路由。
const result = useOutlet()
console.log(result)
// 如果嵌套路由没有挂载, 则result为null
// 如果嵌套路由已经瓜子啊, 则展示嵌套路由对象
# 13.useResolvedPath()
  • 给定一个 URL 值,解析其中的 path, search, hash 值
  • 此挂钩根据当前位置的路径名解析 pathname 给定 to 值中的位置
  • 这在从相对值构建链接时很有用,例如检查内部 <NavLink> 调用的源 useResolvedPath 以解析链接到的页面的完整路径名。
const obj = useResolvedPath('/about/detail?name=tom&age=18#as5')
// 返回值
{pathname: '/about/detail', search: '?name=tom&age=18', hash: '#as5'}