高阶组件HOC(High order components)
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
接收函数作为输入,或者输出为函数的一类函数成为高阶函数,相应的高阶组件就是参数为组件,返回也是组件的一类函数,高阶组件本质上是一个函数。
简单的示例:
const wrapper = (Component) => {
const wrappedComponent = (props) => {
const {age} = props
const check = () => {
if(age < 18){
return 'kid'
}else{
return 'adult'
}
}
return <div className='wrapper'>
{check()}
<Component {...props}/>
</div>
}
return wrappedComponent
}
wrapper方法的入参是一个组件,返回定制化的组件wrappedComponent,props是Component组件的入参,定义一个check方法,根据入参做一些逻辑判断,并更新到wrappedComponent组件里。
使用的时候,先定义要作为入参的组件,例如叫ChildA,然后用wrapper方法重新组装一下,生成一个新组件NewComponent,直接使用这个新组件即可,传参也放在newComponent传入。可以创建多个新组件,这些组件共用了check的逻辑。
完整代码:
import './App.css';
import React from 'react';
const wrapper = (Component) => {
const wrappedComponent = (props) => {
const {age} = props
const checkFun = () => {
if(age < 18){
return 'kid'
}else{
return 'adult'
}
}
return <div className='wrapper'>
{checkFun()}
<Component {...props}/>
</div>
}
return wrappedComponent
}
function App() {
const ChildA = (props) => {
return <div>
{props.age}
</div>
}
const NewComponent = wrapper(ChildA)
return(
<div>
<NewComponent age={18}/>
</div>
)
}
export default App;
render props
高阶组件的使用姿势是用“函数”包裹“组件”,而 render props 恰恰相反,它强调的是用“组件”包裹“函数” ,另外并不是该模式叫做render Props就必须使用名为render的props,实际上可以命名任意的props。
上面的例子改写一下:
const RenderComponent = (props) => {
const {age} = props
const check = () => {
if(age < 18){
return 'kid'
}else{
return 'adult'
}
}
return props.checkResult(check())
}
RenderComponent的props有2个参数,一个是age,另外一个是checkResult,这里特意没有用render关键字,而是用checkResult。并且RenderComponent返回的是checkResult,在使用的时候直接用组件RenderComponent包裹checkResult的回调函数。
完整代码
const RenderComponent = (props) => {
const {age} = props
const check = () => {
if(typeof age === 'undefined'){
return 'undefined'
}
if(age < 18){
return 'kid'
}else{
return 'adult'
}
}
return props.checkResult(check())
}
function App() {
return(
<div>
<RenderComponent age={18} checkResult={(data) => {
return (
<div>
{data}
<div>18</div>
</div>
)
}} />
</div>
)
}
render props更加语义化一些是用children替代props参数,即用children替代checkResult ,RenderComponent渲染子组件则用children的方式
const RenderComponent = (props) => {
const {age} = props
const check = () => {
if(typeof age === 'undefined'){
return 'undefined'
}
if(age < 18){
return 'kid'
}else{
return 'adult'
}
}
return props.children(check())
}
function App() {
return(
<div>
<RenderComponent age={18}>
{
(data) => {
return (
<div>
{data}
<div>18</div>
</div>
)
}
}
</RenderComponent>
</div>
)
}
export default App;
这样看起来更容易理解一些。
render props比HOC更灵活
在高阶组件中,目标组件对于数据的获取没有主动权,数据的分发逻辑全部收敛在高阶组件的内部;而在 render props 中,除了父组件可以对数据进行分发处理之外,子组件也可以选择性地对数据进行接收。
在软件设计模式中,有一个非常重要的原则,叫“开放封闭原则”。一个好的模式,应该尽可能做到对拓展开放,对修改封闭。
不足
无论是高阶组件,还是 render props,两者的出现都是为了弥补类组件在“逻辑复用”这个层面的不灵活性。它们各自都有着自己的不足,这些不足包括但不限于以下几点:
- 嵌套地狱问题,当嵌套层级过多后,数据源的追溯会变得十分困难
- 较高的学习成本
- props 属性命名冲突问题
当设计模式解决不了问题时,我们本能地需要从编程模式上寻找答案,于是Hooks大行其道。Hooks 能够很好地规避掉旧时类组件中各种设计模式带来的弊端,比如说它不存在嵌套地狱,允许属性重命名、允许我们在任何需要它的地方引入并访问目标状态等。