本文会用实际列子对三者进行比较
1. HOC
- 定义:高阶组件是接收一个组件为参数,返回新组件的组件。
- 优点:提取公共逻辑,降低耦合度。
例子: 现在有两个公共组件,分别是 处理QQ、手机号码的组件。
// qq 组件
import React, { Component } from 'react'
export default WrappedComponent => {
return class extends Component {
constructor(props) {
super(props.state)
this.state = {
qq: 1234567890
}
}
setQQ = qq => {
this.setState({ qq: qq })
}
render() {
return (
<div>
<WrappedComponent setQQ={this.setQQ} {...this.state} {...this.props} />
</div>
)
}
}
}
// phone 组件
import React, { Component } from 'react'
export default WrappedComponent => {
return class extends Component {
constructor(props) {
super(props.state)
this.state = {
phone: 13427767788
}
}
setPhone = phone => {
this.setState({ phone: phone })
}
render() {
return (
<div>
<WrappedComponent setPhone={this.setPhone} {...this.state} {...this.props} />
</div>
)
}
}
}
// MyForm
import React from 'react'
import QQHOC from '@shared/Common/QQ'
import PhoneHOC from '@shared/Common/Phone'
class MyForm extends React.Component<any> {
render() {
console.log(this.props)
// {"qq":1234567890,"phone":13427767788,setPhone:f(),setQQ:f()}
return (
<div>
<div
onClick={() => {
this.props.getQQ('123123123123123')
}}
>
点击改变 QQ:{this.props.qq}
</div>
<div
onClick={() => {
this.props.getPhone('123123123123123')
}}
>
点击改变 Phone:{this.props.phone}
</div>
</div>
)
}
}
export default PhoneHOC(QQHOC(MyForm))
上面MyForm
组件使用了QQ
、Phone
高阶组件,可以发现以下问题
- 缺点:
①.使用多个高阶组件会一层套一层,即使使用装饰器也不优雅。
②.state、内部方法相同的话会进行覆盖,除非每个组件定义一个唯一的对象包裹需要传递的内容。
③.溯源不清晰,难清晰知道这个参数来自哪里的。
2.render props
- 定义:组件接收一个函数,这个函数获取组件的state实现渲染逻辑。
- 优点:清楚知道这个state来自哪里。
同样用上面例子
// qq 组件
import React, { Component } from 'react'
export default class RenderPropsQQ extends Component<any, any> {
constructor(props) {
super(props.state)
this.state = {
qq: 13427767788
}
}
setQQ = qq => {
this.setState({ qq: qq })
}
render() {
return <div>{this.props.render({ state: this.state, setQQ: this.setQQ })}</div>
}
}
// phone 组件
import React, { Component } from 'react'
export default class RenderPropsPhone extends Component<any, any> {
constructor(props) {
super(props.state)
this.state = {
//定义可复用的状态
phone: 13427767788
}
}
setPhone = phone => {
this.setState({ phone: phone })
}
render() {
return <div>{this.props.render({ state: this.state, setPhone: this.setPhone })}</div>
}
}
// MyForm
import * as React from 'react'
import RenderPropsQQ from '@shared/Common/RenderPropsQQ'
import RenderPropsPhone from '@shared/Common/RenderPropsPhone'
const MyForm = () => {
return (
<div>
<RenderPropsPhone
render={({ state: phoneState, setPhone }) => {
return (
<RenderPropsQQ
render={({ state: qqState, setQQ }) => {
return (
<>
<div
onClick={() => {
setQQ('123123123123123')
}}
>
点击改变 QQ:{qqState.qq}
</div>
<div
onClick={() => {
setPhone('123123123123123')
}}
>
点击改变 Phone:{phoneState.phone}
</div>
</>
)
}}
/>
)
}}
/>
</div>
)
}
export default MyForm
- 缺点:
①.使用起来会嵌套地狱。
②.不能在return外使用数据。例如,我想监听QQ号码的变化,我只能通在过组件内部提供相应的回调函数。
3.hook
- 定义:让我在不编写class的情况下可以使用state。
还是上面的例子
// qq 组件
import { useState } from 'react'
export default function QQHook() {
const [qq, setQQ] = useState('123123123123')
const handleSetQQ = val => {
setQQ(val)
}
return { qq, setQQ: handleSetQQ }
}
// phone 组件
import { useState } from 'react'
export default function PhoneHook() {
const [phone, setPhone] = useState('123123123123')
const handleSetPhone = val => {
setPhone(val)
}
return { phone, setPhone: handleSetPhone }
}
// myFrom
import * as React from 'react'
import useQQHook from '@shared/Common/QQHook'
import usePhoneHook from '@shared/Common/PhoneHook'
const TestHook: React.FC = () => {
const { qq, setQQ } = useQQHook()
const { phone, setPhone } = usePhoneHook()
return (
<div>
<div
onClick={() => {
setQQ('123123123123123')
}}
>
点击改变 QQ:{qq}
</div>
<div
onClick={() => {
setPhone('123123123123123')
}}
>
点击改变 Phone:{phone}
</div>
</div>
)
}
export default TestHook
- 解决了
①:命名冲突,我可以进行重命名。
②:清晰标注来源。
③:可以监听变化。
④:不存在嵌套。
存在即合理
HOC 跟 renderProps也有它的存在理由的。
①.如果组件里面还有其他渲染,不纯粹是处理state的时候可以用HOC。
②.至于renderProps我举个真实场景,我有很多个按钮,每个按钮点击会打开不同的Dialog。一般做法的,通过定义多个visible
的state控制Dialog,如下
...
const [visible1,setVisible1] = useState(false)
const [visible2,setVisible2] = useState(false)
return (
<div>
<Button onClick={()=>setVisible1(true)}>弹框一</Button>
<Button onClick={()=>setVisible2(true)}>弹框二</Button>
{visible1 && <Dialog1 />}
{visible2 && <Dialog2 />}
</div>
)
...
问题
①.每次都重复写Dialog。
②.无法立刻找到Dialog对应是哪个Button控制的。解决
封装一个ButtonDialog
,传递触发Button
和通过回调的handleShow
实现打开Dialog,Dialog具体内容通过children进行传递。
// ButtonDialog
...
return (
<span>
{button(this.show)}
<Modal {...modalProps}>{children}</Modal>
</span>
)
...
// 调用
return (
<ButtonModal
modalProps={modalProps}
button={handleShow => (
<Button
onClick={() => {
handleShow()
}}
>
Button1
</Button>
)}
>
<DialogContent />
</ButtonModal>
)
- 解决
①.不需要创建多个Dialog,只需要关注Dialog具体的内容。
②.控制Dialog的state放在组件内部,更加简洁。
③.哪个Button对应哪个Dialog一目了然。
总结
①.纯粹是复用state,复用多个组件 用hook
。
②.使用单个组件,并且组件有自己的渲染内容可以用HOC
。
③.状态不需要外部使用而且只用单个组件,可以用render props
。
(PS:具体问题具体分析,还是要多做比较,书上得来终觉浅 绝知此事要躬行。)