# 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 语法规则
- 定义虚拟 DOM 时,不要写引号
- 标签中混入 JS 表达式时要用
- 样式的类名指定不要用 class, 要用 className
- 内联样式,要用
style= {{key:value}}
的形式去写 - 只有一个根标签
- 标签必须闭合
- 标签首字母
- 若小写字母开头,则将该标签转为 HTML 中同名元素,若 HTML 中无该标签对应的同名元素,则报错
- 若大写字母开头,react 就去渲染对应的组件,若组件没有定义则报错
# 2.JS 语句 与 JS 表达式
- 表达式: 一个表达式会产生一个值,可以放在任何一个需要值的地方
- 如下均为表达式 :
- a
- a + b
- demo(1)
- arr.map()
- function test ()
- 如下均为表达式 :
- 语句 (代码) :
- 如下均为语句 (代码) :
- if ()
- for ()
- switch ()
- 如下均为语句 (代码) :
# 3. 模块与组件,模块化与组件化的理解
# 1. 模块
- 理解 : 向外提供特定功能的 JS 程序,一般是一个 JS 文件
- 拆解成模块的原因:随着业务逻辑的增加,代码越来越多且复杂
- 作用:复用 JS, 简化 JS 编写,提高 JS 运行效率
# 2. 组件
- 理解:用来实现局部功能效果的代码和资源的集合 (html/css/js/image 等等)
- 原因:一个界面的功能更复杂
- 作用:复用编码,简化项目编码,提高运行效率
# 3. 模块化
当应用的 JS 都以模块来编写,这个应用就是一个模块化应用
# 4. 组件化
当应用是以多组件的方式实现,这个应用就是一个组件化的应用
# 5 组件的基本使用
# 1. 两种创建虚拟 DOM 的方式
- 使用 JSX 创建虚拟 DOM
const VDOM = (
<h1 id={MyId.toLocaleUpperCase()}>
<span className= "content" style={{fontSize: '50px'}}>占位文本</span>
</h1>
)
- 使用 JS 创建虚拟 DOM
// 1.使用js语法创建虚拟DOM, React.createElement(标签,标签属性,内容)
const VDOM = React.createElement('h1',{id: 'title'},'hello')
使用 JS 和 JSX 都可以创建虚拟 DOM, 但是 JS 创建虚拟 DOM 比较繁琐,尤其是标签较多的情况下
# 6. 组件
# 1. 函数式组件
- 调用
ReactDOM.render()
函数,并传入<Welcome name='name' />
作为参数 - React 调用
Welcome
组件,并将{name: 'name'}
作为 props 传入 wecome
组件将Hello, name
元素作为返回值- 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 组件
- React 解析组件标签,找到相应的组件
- 发现组件是类定义的,然后 new 出类的实例,并通过实例调用原型上的 render 方法
- 将 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. 理解
- state 是组件最重要的属性,值是对象 (可以包含多个 Key-Value 的组合)
- 组件被称为
状态机
,通过更新组建的 state 来更新对应组件的 state 来更新对应的页面显示 (重新渲染组件)
# 2. 注意事项
- 组件中 render 方法中的 this 为组件实例的对象
- 组建中自定义方法中 this 为 undefined, 如何解决?
- 强制绑定 this: 通过函数对象的 bind ()
- 箭头函数
- 状态数据:不能直接修改或更新
# 2. 组件三大属性: props
# 1. 理解
- 每个组件对象都会有 props (properties 的简写) 属性
- 组件标签的所有属性都保存在 props 中
# 2. 作用
- 通过标签前属性从组件外向组件内传递变化的数据
- 注意:组件内部不要修改 props 数据
# 3. 组件三大属性: refs
# 1. 理解
组件内的标签可以定义 ref 来表识自己
# 2.ref 三种形式
- 字符串形式的 ref (不推荐,官方不建议使用)
<input ref="input2" type="text" />
const {input2} = this.refs
- 回调形式的 ref
/**
* c与this均为当前的组件实例
* 内联形式的回调ref在state更新时会触发两次,第一次为null
*/
saveInput = (e)=> {
this.input1 = c
}
<input ref={c => this.input1 = c} />
<input ref={this.saveInput} />
- 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. 生命周期
- 组件从创建到思维会经历一些特定的阶段
- React 组件中包含一系列狗子函数 (生命周期回调函数), 会在特定的时刻调用
- 定义组件时,会在特定的生命周期回调函数中,做特定的工作
# 1.React 生命周期 (旧)
各生命周期函数的调用顺序
- 初始化阶段:由 ReactDOM.render () 触发 --- 初次渲染
- constructor ( ) 构造器: React 数据初始化
- compinentWillMount ( ) 组件将要被挂载,还未渲染 DOM
- render ( ) 修改虚拟 DOM 渲染真实 DOM
- componentDidMount ( ) 组件渲染完成,常用于进行一些初始化动作
- 更新阶段:由组件内部的 this.setState ( ) 或者父组件的 render 触发
- shouldComponentUpdate ( ) 判断组件是否应该更新
- componentWillUpdate ( ) 组件将要更新
- render( )
- componentDidUpdate ( ) 组件更新完成
- 卸载组件:由 ReactDOM.unmountComponentAtNode () 触发
- componentWillUnmount ( ) 组件将要卸载,常用与关闭定时器,取消订阅
# 2.React 生命周期 (新)
- 初始化阶段:由 ReactDOM.render ( ) 触发 --- 初次渲染
- constructor( )
- getDerivedStateFromProps ( ) 从 Props 获得派生状态
- render( )
- componentDidMount( )
- 更新阶段:组件内部的 this.setState ( ) 或者父组件的 render 触发
- getDerivedStateFromProps ( ) 从 Props 获得派生状态
- shouldComponentUpdate ( ) 组件是否更新
- render( )
- getSnapshotBeforeUpdate ( ) 更新前获得快照
- componentDidUpdate ( ) 可接收到 getSnapshotBeforeUpdate 传递的快照值
- 卸载组件:由 ReactDOM.unmontComponentAtNode ( ) 触发
- componentWillUnmount ( ) 常用于关闭定时器,取消订阅消息等收尾工作
# 3. 重要的钩子
- render: 初始化渲染或者更新渲染调用
- componentDidMount ( ) : 开启监听,发送 AJAX 请求
- componentWillUnmount ( ) : 清理资源,关闭定时器等
# 4. 即将废弃的钩子
- componentWillMount (新 UNSAFE_componentWillMount)
- componentWillReceiveProps (新 UNSAFE_componentWillReceiveProps)
- componentWillUpdate (新 UNSAFE_componentWillUpdate)
16.0 后版本使用会出现警告,需要加上 UNSAFE_ 前缀才能使用,后续版本可能会被彻底废弃
# 5.React 中的 key
# 1. 虚拟 DOM 中 key 的作用:
作用:可以是虚拟 DOM 对象的标识,在更新显示时 key 起着重要的作用
当状态中的数据发生变化时,react 会根据新数据生成
新的虚拟DOM,
随后 React 进行新虚拟DOM
与旧虚拟DOM
的 diff 比较.旧虚拟 DOM 中找到与新虚拟 DOM 相同的 key
- 旧虚拟 DOM 中内容没变,直接使用之前的真实 DOM
- 若虚拟 DOM 中内容改变,则生成新的真实 DOM, 随后替换页面中的真实 DOM
旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key
- 根据数据创建新的真实 DOM, 随后渲染到页面
# 2.index 作为 key 可能引发的问题
- 若对数据进行 逆序添加,逆序删除等破坏顺序操作,会产生没有必要的真实 DOM 更新 (显示正常,效率低)
- 如果结构中还包含输入类 DOM, 会产生错误 DOM 更新 (输入类的 DOM 值会错误显示)
- 不存在对数据的 逆序添加,逆序删除等破坏顺序的操作,仅用于展示,使用 index 作为 key 没有问题
# 3. 开发时的 key
- 使用每条数据的唯一标识作为 key, 例如: id, 手机号,身份证号等
- 如果只是展示简单的内容,也可使用 index 也可以
# 6.React 脚手架
# 1. 创建项目并启动
- 全局安装:
npm i -g create-react-app
- node 新版本自带命令:
npx create-react-app my-app
- 启动项目:
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. 功能界面的组件化编码流程
- 拆分组件:拆分界面,抽取组件
- 实现静态组件:使用组件实现静态页面效果
- 实现动态组件
- 动态显示初始化数据
- 数据类型
- 数据名称
- 数据保存位置
- 交互 (绑定事件进行监听)
- 动态显示初始化数据
# 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
- 先订阅,然后再发布消息
- 适用于任意跨组件通讯
- 取消订阅时应该在 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
fetch 的简单介绍
- fetch 是一种 HTTP 数据请求的方式,是 XMLHttpRequest 的一种替代方案 **
- fetch 不是 ajax 的进一步封装,而是原生 js
- Fetch 函数就是原生 js,没有使用 XMLHttpRequest 对象
XMLHttpRequest API 的缺点
- 不符合关注分离(Separation of Concerns)的原则
- 配置和调用方式非常混乱
- 基于事件的异步模型写起来也没有现代的 Promise,generator/yield,async/await 友好。
# 6.Fetch 发送请求
- 关注分离的设计思想
- Fetch 是浏览器提供的原生异步请求方式
- 原生的 XMLHttpRequest 不符合关注分离原则,且在时间模型在异步处理上没有现代的 Promise 的优势
- 特点:
- fetch: 原生函数,不再使用 XMLHttpRequest 对象提交 ajax 请求
- 旧版本浏览器可能无法支持
- 使用 fetch 无法取消请求,因为 Fetch API 基于 Promise, Fetch 是典型的异步场景
- 直接使用 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 单页应用
- 单页 web 应用 (single page web application , SPA)
- 整个应用只有一个完整的页面
- 点击页面中的链接不会刷新页面,只会更新对应的路由组件
- 数据都需要通过 ajax 请求获取,并在前端异步展现
# 2. 路由的概念
什么是路由
- 一个路由就是一个映射关系 (key-value)
- key 为路径,value 可能是 function 或 component
# 3. 路由的分类
- 后端路由
- 理解: value 是 function, 用于处理客户端提交的请求
- 注册路由: router.get (path, (req, res)=>{ })
- 工作过程:当 node 接收到请求时,根据请求路径匹配对应路由,调用路由中的函数来处理请求,返回响应数据
- 前端路由
- 浏览器端路由,value 是 component, 用于展示页面内容
- 注册路由:
<Route path="/index" component={Index}>
- 工作过程:通过匹配 path 参数加载对应的路由组件
# 2.react-router-dom
# 1. 相关概念
- react-router-dom
- react 的路由插件
- 用于实现一个基于 React 的 SPA 应用
- 相关 API
- <BrowserRouter> 路由组件根标签
- <HashRouter> Hash 路由
- <Route> 路由组件
- <Redirect> 错误路由兜底组件
- <Link> 路由链接
- <NavLink> 带默认样式的路由拦截
- <Switch> 多个相同路由仅匹配第一次,提高效率
- 相关参数
- history
- match
- withRoute
# 2. 路由的基本使用
- 明确界面中的导航区,展示区
- 导航区的 a 标签改为 Link 标签
<Link to"/dmeo">Demo</Link>
- 展示区使用 Route 标签进行路径匹配
<Route path='/demo' component={Demo} />
4.<App> 的外侧标签包裹 <BrowserRouter> 或 <HashRouter> 用于识别相关路由标签
ReactDom.render(
<BrowerRouter>
<App/>
</BrowerRouter>,
document.getElementById('root')
)
# 3. 路由组件与一般组件
- 写法不同:
- 一般组件: <Demo/>
- 路由组件: <Route path="/demo" component={Demo} />
- 存放位置不同:
- 一般组件: components
- 路由组件: pages
- 接收参数不同
- 一般组件:通过 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 使用与封装
- NavLink 可以实现路由链接高亮,通过 activeClassName 指定高亮样式名称 (默认为 .active)
- 封装
// 封装示例
// 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 使用
- 通常情况下,path 和 component 是一一对应的关系
- Switch 可以提高路由匹配率 (单一匹配) --- 匹配到对应路由后不再向下匹配
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={Test} />
</Switch>
# 6. 解决多级路径刷新页面样式丢失
- public/index.html 中引入样式时不使用 ./ 而使用 /
- public/index.html 中引入样式时使用
%PUBLIC_URL%
(常用,仅在 React 中生效) - 使用 HashRouter (不常用)
# 7. 路由的严格匹配与模糊匹配
- 默认使用的是模糊匹配 (【输入的路径】必须包含 【匹配的路径】,且顺序要一致)
- 开启严格匹配:使用 exact 属性,
<Route exact={true} path="/about" component={About} />
- 严格匹配不能随意开启,可能导致无法继续匹配二级路由
- 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. 嵌套路由
- 注册子路由时要携带父路由的 path 值
- 路由的匹配是按照注册路由的顺序进行的
# 9. 向路由组件传递参数
- 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
- 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))
- 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 操作路由跳转,前进,后退
- this.props.history.push ( ) push 一条记录到路由历史中
- this.props.history.replace ( ) 推送一条记录替换掉栈顶的路由
- this.props.history.goBack ( ) 回退到上一条路由历史
- this.props.history.goForward ( ) 路由历史前进一条
- this.props.history.go ( ) 路由历史前进或后退 n 条,根据传递的 n 的正负决定前进后退
# 11. withRouter
- withRouter 可以加工一般组件,让一般组件可以使用路由组件所特有的 API
- 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 可以用于解决一些路径错误相关的问题,引用文件时可以使用相对路径
- 底层原理不同
- BrowserRouter 使用的是 H5 的 history API, 不兼容 IE9 以下版本 (常用)
- HashRouter 使用的是 URL 的哈希值 (尽可能避免使用)
- path 的表现形式不同
- BrowserRouter 的路径中没有 #, 例如: localhost:3000/demo/test
- HashRouter 的路径包含 #, 例如: localhost:3000/#/demo/test
- 刷新后对路由 state 参数的影响
- BrowserRouter 没有任何影响,因为 state 保存在 history 对象中
- HashRouter 刷新后会导致路由 state 参数的丢失
# 8.Redux 全局状态管理
# 1.redux 相关概念
# 1. redux 相关文档
- 英文文档: https://redux.js.org
- 中文文档: https://www.redux.org.cn
- GitHub: https://github.com/reduxjs/redux
# 2.redux 的概念
- redux 是一个专门用于
状态管理的JS库
(非 react 插件库) - redux 可以用于 react, angular, vue 等项目中,常用与和 react 配合使用
- 用途:集中式管理 react 应用中多个组件共享的状态
# 3.redux 使用情景
- 某个组件的状态,需要让其他组件可以随时使用 (共享)
- 一个组件需要改变另一个组件的状态 (通信)
- 总体原则:尽量避免使用,除非万不得已
# 4.redux 工作流程
# 2.redux 的三个核心概念
# 1. action
- 动作对象
- 包含 2 个属性
- type: 标识属性,值为字符串,唯一,必要属性
- data: 数据属性,值类型任意,可选属性
- 示例: {type: 'ADD_STUDENT', data: {name: 'tom', age: '18'} }
# 2. reducer
- 用于初始化状态,加工状态
- 加工时,根据旧的 state 和 action, 产生新的 state 的
纯函数
- 纯函数:一类特别的函数,相同的形参必定得到相同的输出
- 必须遵守以下约束
- 不得改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- 不能调用 Date.now () 或者 Math.random () 等不纯的函数,方法
- redux 的 reducer 函数必须是一个纯函数
# 3.store
- 将 state, action, reducer 联系在一起的对象
- 得到 store 对象
- import { createStore } from 'redux'
- import reducer from './reducers'
- const store = createStore(reducer)
- store 对象的作用
- getState ( ) 得到 state 全局状态
- dispatch (action) 分发 action, 触发 reducer 调用,产生新的 state
- subscribe (listener) 注册监听,当产生新的 state 时,自动调用
# 3.redux 的核心 API
# 1.createStore () 与 applyMiddleware ()
- createStore ( ) 创建包含指定 reduce 的 store 对象
- 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 对象
- 作用: redux 库中最核心的状态管理对象
- store 对象 内部维护着 state, reducer
- 核心方法
- getState( )
- dispatch(action)
- subscribe(listener)
- 具体编码示例
- 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 异步编程
- 使用场景
- redux 默认是不能进行异步处理的
- 某些时候应用需要在 redux 中执行异步任务 (ajax, 定时器)
- 使用中间件支持异步
- redux-thunk 中间件:
npm install --save redux-thunk
- 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 将所有组件分为两类
UI 组件
- 只负责 UI 的呈现,不带有任何 redux 相关操作逻辑
- 通过 props 接收数据 (一般状态和函数)
- UI 组件中不应该出现任何 Redux 的 API
- UI 组件一般放置在
components
文件夹下,也可以直接定义在容器组件中直接使用
容器组件
- 管理 redux 数据和业务逻辑,不负责 UI 组件的呈现
- 使用 Redux 相关的 API
- 一般保存在
container
文件夹下
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 调试工具
- 安装 chrome 浏览器插件
Redux DevTools
- 修改 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 更新状态的两种方式
- setState (stateChange, [callback]) -- 对象式
- setChange 为状态改变对象 (该对象可以体现出状态的更改)
- callback 是可选的回调函数,其在状态更新完毕,界面也更新后才会被调用
- setState (updater,[callback]) -- 函数式
- updater 为返回 stateChange 对象的函数
- updater 可以接收到 state 和 props
- callback 是可选的回调函数,在状态更新,界面重新渲染后才会不被调用
- 对象是的 setState 是函数式 setState 的简写方式
- 如果不依赖与原状态,优先使用对象式
- 如果新状态依赖于原状态,有限使用函数式
- 如果需要在 setState ( ) 执行后获取最新的状态数据,需要在 callback 回调函数中读取
// 更新状态的函数
(state, props) => stateChange
// 使用函数式修改状态的方法
setState(stateChange[, callback])
// 使用对象式修改状态的方法
this.setState({quantity: 2})
# 2.LazyLoad 路由组件懒加载
- 懒加载中的组件,随意调用,不会提前加载
- 使用懒加载时需要指定一个 fallback, 用于请求过慢或者请求不到组件时显示,通常为组件,也可以是虚拟 DOM
- 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( )
- useState ( ) 让函数组件也可以有 state 状态,并对状态数据进行修改
- 接收一个参数作为状态初始值,返回一个 state 和 用于更新 state 的函数
- 函数签名: const [state, setState] = useState (initialState);
- 在初始渲染期间,返回的
状态(state)
与 传入的第一个参数 (initialState
) 值相同 - setState 函数用于更新 State, 接收一个新的 state 值并将组件的一次重新渲染加入队列
- 后续渲染中,useState 返回的第一个值将始终是更新后的最新的 state
// 返回一个state 以及更新state的函数
const [state, setState] = useState(initialState);
setState(newState)
setState(prevState => {
// 也可以使用 Object.assign
return {...prevState, ...updatedValues};
});
# 2.useEffect( )
- useEffect 可以在函数式组件中 执行副作用操作,模拟类组件中的生命周期钩子
- React 副作用操作:发送 ajax 请求,设置订阅 / 启动定时器,手动修改真实 DOM
- useEffect 相当于
componentDidMount
,componentDidUpdate
和componentWillUnmount
的组合。 - 函数签名:
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( )
- useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数 (
initialValue
), - 函数返回的 ref 对象在组件的整个生命周期内持续存在
- 函数签名:
const refContainer = useRef(initialVaue)
- 本质上,
useRef
就像是可以在其.current
属性中保存一个可变值的 ' 盒子' - useRef 可以很方便地
保存任何可变值
,类似于 class 中使用实例字段的方式 - ref 对象内容发生变化时,useRef 并不会通知,变更
.current
属性不会引发组件重新渲染 - 在 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
用途:可以不必须使用一个真实的 DOM 根标签
当不得不使用容器去包裹 dom 元素 (
jsx语法要求, 只能有一个根节点
)使用 Fragment 后可以取代根节点标签,其编译后会被 react 丢弃,避免造成不必要的层级嵌套
效果等同于直接写一个
空标签: < > </>
, 但 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
- Context 是一种组件间通信的方式,常用与 [祖组件] 与 [后代组件] 间通信
- 渲染子组件时,外层包裹 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>
- 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
- component 的两个问题 (效率低)
- 只要执行 setState (), 即使不改变状态数据,组件也会重新 render ()
- 只要当前组件重新 render (), 就会自动重新 render () 子组件,纵使子组件没有用到父组件的任何数据
- 原因:因为 component 中的 shouldComponentUpdate ( ) 默认总是返回 true
- 优化效率的做法
- 只有当组件的 state 或 props 数据改变时才重新 render ( )
- 重写 shouldComponentUpdate () 方法,当 state 或 props 变化时才返回 true
- 使用 PureComponent 替代 Component, PureComponent 重写了 shouldComponentUpdate () 方法
- PureComponent 只做了浅比较,如果对象内部属性改变,返回 false, 不会更新视图
- 不要直接修改 state 数据,而是产生新数据,项目中一般使用 PureComponent 做优化
# 7.render props -- 类似 vue 中的 slot 插槽
- 如何向组件内部传递带有内容的结构 (标签)
- React 中使用 children props, 通过组件标签体传入结构使用 render props,
- 通过组件标签属性传入结构同时携带数据,一般 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. 错误边界
- 错误边界 (Error boundary) : 用于捕获后代组件错误,渲染 error 组件
- 只能捕获后代组件生命周期产生的错误,不能捕获自身产生的错误和其他组件在合成事件,定时器中产生的错误
- 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. 组件间通信方式总结
- 组件间的关系:
- 父子组件
- 兄弟组件 (非嵌套组件)
- 祖孙组件 (跨级组件)
- 通信方式:
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'}