高阶函数
定义
- 接收一个或多个函数作为输入
- 输出一个组件
在js中常见的高阶函数有 map filter forEach reduce 函数
高阶组件 (Higher-Order Components,简称为 HOC)
定义
1.高阶组件的参数是一个组件 返回值为新组件的一个函数
- 高阶组件本身不是一个组件 而是一个函数 返回一个组件
- 高阶组件就是对传入的组件进行拦截 处理一些特殊逻辑 再把传入组件输出出去
- 高阶组件的参数本身也是一个组件
- 高阶组件并不是ReactApi的一部分 它只是基于React的组合特征而形成的一种设计模式
总结:React高阶组件是一种用于复用组件逻辑的高级技巧。高阶组件本身并不是React API的一部分,它们只是一种基于React的组合特性而形成的设计模式。高阶组件是参数为组件,返回值为新组件的函数。
- 比如在Redux中的connect
- 比如在Redux中的withRouter
export default connect(fn1,fn2)(Home)作用就是 将Redux中的数据 插入到Home组件的props中
import React, { PureComponent } from 'react'
// 定义一个高阶组件
function hoc(Cpn) {
// 1.定义类组件
class NewCpn extends PureComponent {
render() {
return <Cpn name="123"/>
}
}
// 可以修改组件的名称
NewCpn.displayName='newName'
return NewCpn
// 定义函数组件
// function NewCpn2(props) {
// }
// return NewCpn2
}
class HelloWorld extends PureComponent {
render() {
return <h1>Hello World</h1>
}
}
const HelloWorldHOC = hoc(HelloWorld)
export class App extends PureComponent {
render() {
return (
<div>
<HelloWorldHOC/>
</div>
)
}
}
export default App
高阶组件的应用场景
应用一:props的增强
当父组件给他的子代/孙代 来传递数据的时候 可以使用高阶组件进行输入组件的包裹 达到输出的组件也会有父组件传递的参数
import { createContext } from "react"
import ThemeContext from './context/theme_context'
// 定义高阶组件
function withTheme(OriginComponment) {
return (props) => {
return (
<ThemeContext.Consumer>
{
value => {
// 把自身的props 和Context 传递的值 都放到了组件props
return <OriginComponment {...value} {...props}/>
}
}
</ThemeContext.Consumer>
)
}
}
export default withTheme
定义父组件
import React, { PureComponent } from 'react'
import ThemeContext from './context/theme_context'
import Product from './pages/Product'
export class App extends PureComponent {
render() {
return (
<div>
<ThemeContext.Provider value={{color: "red", size: 30}}>
<Product/>
</ThemeContext.Provider>
</div>
)
}
}
子组件使用
import React, { PureComponent } from 'react'
import ThemeContext from '../context/theme_context'
import withTheme from '../hoc/with_theme'
export class Product extends PureComponent {
render() {
const { color, size } = this.props
return (
<div>
<h2>Product: {color}-{size}</h2>
</div>
)
}
}
// 因为导出的时候 用高阶组件包裹了原始组件 所以在原始组件内部可以使用props
export default withTheme(Product)
渲染判断健全
大多数页面都是需要用户登录成功后才可以访问 如果没登录成功 需要返回首页
// 高阶组件
function loginAuth(OriginComponent) {
return props => {
// 从localStorage中获取token
const token = localStorage.getItem("token")
if (token) {
return <OriginComponent {...props}/>
} else {
return <h2>请先登录, 再进行跳转到对应的页面中</h2>
}
}
}
export default loginAuth
//子组件
import React, { PureComponent } from 'react'
import loginAuth from '../hoc/login_auth'
export class Cart extends PureComponent {
render() {
return (
<h2>Cart Page</h2>
)
}
}
export default loginAuth(Cart)
父组件
import React, { PureComponent } from 'react'
import Cart from './pages/Cart'
export class App extends PureComponent {
constructor() {
super()
// this.state = {
// isLogin: false
// }
}
loginClick() {
localStorage.setItem("token", "coderwhy")
// this.setState({ isLogin: true })
// 可以使用setState来进行重新渲染 也可以用forceUpdate方法进行强制重新渲染
this.forceUpdate()
}
render() {
return (
<div>
App
<button onClick={e => this.loginClick()}>登录</button>
<Cart/>
</div>
)
}
}
export default App
应用三 生命周期的劫持
可以劫持通用的生命周期 在生命周期中执行自己的逻辑
import { PureComponent } from "react";
function logRenderTime(OriginComponent) {
return class extends PureComponent {
// 这个生命周期已经在16.3不建议使用了 可以在constructor中定义开始时间
UNSAFE_componentWillMount() {
this.beginTime = new Date().getTime()
}
componentDidMount() {
this.endTime = new Date().getTime()
const interval = this.endTime - this.beginTime
console.log(`当前${OriginComponent.name}页面花费了${interval}ms渲染完成!`)
}
render() {
return <OriginComponent {...this.props}/>
}
}
}
export default logRenderTime
//子组件
import React, { PureComponent } from 'react'
import logRenderTime from '../hoc/log_render_time'
export class Detail extends PureComponent {
render() {
return (
<div>
<h2>Detail Page</h2>
<ul>
<li>数据列表1</li>
<li>数据列表2</li>
<li>数据列表3</li>
<li>数据列表4</li>
<li>数据列表5</li>
<li>数据列表6</li>
<li>数据列表7</li>
<li>数据列表8</li>
<li>数据列表9</li>
<li>数据列表10</li>
</ul>
</div>
)
}
}
export default logRenderTime(Detail)
父组件
import React, { PureComponent } from 'react'
import Detail from './pages/Detail'
export class App extends PureComponent {
render() {
return (
<div>
<Detail/>
</div>
)
}
}
高阶组件的缺陷
在早期的React有提供组件之间的复用就是用的Mixin
Mixin是一种代码复用的方式,它通过将可复用的代码块注入到一个类中来实现代码复用。Mixin是一种在多重继承中复用代码的方式,但是在JavaScript中,由于没有多重继承,所以Mixin通常是通过将一个对象的属性复制到另一个对象中来实现的。
Mixin弊端
- 可能会互相依赖 互相耦合 不便于代码维护
- 不同的Mixin中的方法可能会有冲突
- Mixin非常多时,组件处理起来会比较麻烦,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性
HOC的弊端
- HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难;
- HOC可以劫持props,在不遵守约定的情况下也可能造成冲突 (原始组件本身props自带name。但是被HOC劫持做了修改)
React和Mixin的对比
在React中,高阶组件和Mixin都可以用于代码复用,但是它们的实现方式不同。高阶组件是一个函数,它接受一个组件作为参数,并返回一个新的组件。Mixin是一个对象,它包含了一些可复用的代码块,这些代码块可以被注入到一个组件中。
Hooks的出现,是开创性的,它解决了很多React之前的存在的问题
比如this指向问题、比如hoc的嵌套复杂度问题等等
Protals
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案:
- 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment;
- 第二个参数(container)是一个 DOM 元素;
import React, { PureComponent } from 'react'
import { createPortal } from "react-dom"
import Modal from './Modal'
export class App extends PureComponent {
render() {
return (
<div className='app'>
<h1>App H1</h1>
// 他并没有重新渲染一个新的div标签 而是找到特定的元素下面 进行插入
{
createPortal(<h2>App H2</h2>, document.querySelector("#why"))
}
{/* 2.Modal组件 封装组件 */}
<Modal>
<h2>我是标题</h2>
<p>我是内容, 哈哈哈</p>
</Modal>
</div>
)
}
}
export default App
fragment
在之前的开发中,我们总是在一个组件中返回内容时包裹一个div元素: 如果不需要有一个根的话 可以使用Fragment 来进行一个包裹(类似于Vue中的template) Vue3+TypeScript
React还提供了Fragment的短语法:
- 它看起来像空标签
<> </>; - 但是如果我们需要在Fragment中添加key,那么就不能使用短语法
在vue3中 代码可以不需要一个根元素 在内部也是使用的是Fragment
import React, { PureComponent, Fragment } from 'react'
export class App extends PureComponent {
constructor() {
super()
this.state = {
sections: [
{ title: "哈哈哈", content: "我是内容, 哈哈哈" },
{ title: "呵呵呵", content: "我是内容, 呵呵呵" },
{ title: "嘿嘿嘿", content: "我是内容, 嘿嘿嘿" },
{ title: "嘻嘻嘻", content: "我是内容, 嘻嘻嘻" },
]
}
}
render() {
const { sections } = this.state
return (
<>
<h2>我是App的标题</h2>
<p>我是App的内容, 哈哈哈哈</p>
<hr />
{
sections.map(item => {
return (
<Fragment key={item.title}>
<h2>{item.title}</h2>
<p>{item.content}</p>
</Fragment>
)
})
}
</>
)
}
}
export default App
StrictMode 严格模式
是一个用来突出显示应用程序中潜在问题的工具
- 与 Fragment 一样,StrictMode 不会渲染任何可见的 UI
- 他是作用于后代元素出发额外的警告和检查
- 严格模式紧紧作用于开发环境 不会影响生产构建
import React, { PureComponent, StrictMode } from 'react'
// import { findDOMNode } from "react-dom"
import Home from './pages/Home'
import Profile from './pages/Profile'
export class App extends PureComponent {
render() {
return (
<div>
// Home开启了严格模式
<StrictMode>
<Home/>
</StrictMode>
// Profile没开启严格模式
<Profile/>
</div>
)
}
}
export default App
严格模式检查的是什么
- 一些已经废弃/不安全的生命周期
UNSAFE_componentWillMount - 使用过时的ref
<h2 ref="title">Profile Title</h2> this.$refs.title - 检查意外的副作用
- 这个组件的constructor会被调用两次;
- 这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用;
- 在生产环境中,是不会被调用两次的;
- 在React18之后 使用ReactDevTool 插件时候。第二次打印出来的结果 会偏灰色
- 使用废弃的findDOMNode方法
在之前的React API中,可以通过findDOMNode来获取DOM,不过已经不推 荐使用了。 - 检测过时的context API
早期的Context是通过static属性声明Context对象属性,通过getChildContext返回Context对象等方式来使用Context的
**总结:**一般情况下 如果不引用第三方库api的话 可以开启严格模式 但是因为有一些三方库的api并不符合代码规范 但是还要使用的话。就不建议开启严格模式