在使用react的过程中,比较常用的且官方推荐的逻辑复用方式有三种,分别为HOC,renderProps,以及自定义hook,刚刚我仔细思考了一下这三种的优劣,想和大家讨论一下~
*下面我们以实现复用鼠标坐标为目的:
HOC
function withGetMousePosition(Com){
return function (props){
let [mousePostion,setMousePostion] = useState({
x:null,
y:null
})
useEffect(() => {
document.addEventListener('mousemove',function(e){
let x = e.screenX
let y = e.screenY
setMousePostion({
x,
y
})
})
},[])
return (
<div>
<h1>
i am WithGetMousePosition
</h1>
{<Com x={mousePostion.x} y={mousePostion.y} {...props}/>}
</div>
)
}
}
let WithGetMousePosition = withGetMousePosition(ShowPosition)
function ShowPosition(props){
return (
<>
<p>x:{props.x},y:{props.y}</p>
<h1>name:{props.name}</h1>
</>
)
}
function App(){
return (
<div>
<h1>i am app</h1>
<WithGetMousePosition name="123"/>
</div>
)
}
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
)
我个人觉得缺点有如下
-
嵌套过深,在上文demo中仅复用了
withGetMousePosition(ShowPosition),但倘若我们需要复用更多的逻辑呢?就会发生这样的情况WithXXX(WithXXX(WithXXX(Com)))虽然通过with+XXX的驼峰命名能够清晰的分辨出我们复用了哪些逻辑,但是这种嵌套关系和代码量还是让我依旧认为这是一个缺点。 -
命名冲突,我们回到
App组件中的这一行代码,<WithGetMousePosition name="123"/>。可以看到我们的ShowPosition组件是需要一个名为name的prop的,如果我们的prop需要的并非name,而是一个“x"呢?这便和withGetMousePosition传递给我们的”x"的prop发生了命名冲突,当然,你完全可以通过自主命名去修改他。 -
无法清晰的展示数据来源。接着我们回到
ShowPosition这个组件中,他接受的props有x,y,name,哦天呐,我还以为这三个prop都是父组件传递的呢!很明显,我们无法分辨他!此时的你可能会想到,我只需要去关心withGetMousePosition(ShowPosition)HOC返回的组件!很遗憾,他也没有告诉你props的来源,甚至你都不知道他是否有props!这简直就是维护噩梦!
render props
function MousePosition(props){
let [mousePostion,setMousePostion] = useState({
x:null,
y:null
})
useEffect(() => {
document.addEventListener('mousemove',function(e){
let x = e.screenX
let y = e.screenY
setMousePostion({
x,
y
})
})
},[])
return (
<div>
<h1>
i am MousePosition
</h1>
{props.render(mousePostion)}
</div>
)
}
function ShowPosition(props){
return (
<>
<p>x:{props.x},y:{props.y}</p>
<h1>name:{props.name}</h1>
</>
)
}
function App(){
return (
<div>
<h1>i am app</h1>
<MousePosition render={
mouse => <ShowPosition x={mouse.x} y={mouse.y} name="123"/>
}>
</MousePosition>
</div>
)
}
优点:
- 数据源清晰。让我们看
App这个组件的内部,<MousePosition render={ mouse => <ShowPosition x={mouse.x} y={mouse.y} name="123"/> }>, 我们能够清楚看到mouse这个prop是通过逻辑复用(render-props)传递的,而name是他自己的prop,弥补了HOC数据源不清不楚的缺点!
缺点:
- 更为可怕的嵌套地狱。继续假设我们如果要复用更多的逻辑呢?
render = {({ x, y }) => (
<Com1>
{({ x: pageX, y: pageY }) => (
<Com2>
{({ terrible }) => {
// 这该死的阅读性!
}}
</Com2>
)}
</Com1>
)}
-
被限制的缺点。我们只能在
return语句后使用复用的逻辑变量。让我们来看这句话<MousePosition render={ mouse => <ShowPosition x={mouse.x} y={mouse.y} name="123"/> }>逻辑复用的得出的变量mouse,只能作为render这个函数中使用。自定义hook
function useMousePostion(){
let [mousePostion,setMousePostion] = useState({
x:null,
y:null
})
useEffect(() => {
document.addEventListener('mousemove',function(e){
let x = e.screenX
let y = e.screenY
setMousePostion({
x,
y
})
})
},[])
return mousePostion
}
function ShowPosition(props){
let position = useMousePostion()
return (
<>
<p>x:{position.x},y:{position.y}</p>
<h1>name:{props.name}</h1>
</>
)
}
function App(){
return (
<div>
<h1>i am app</h1>
<ShowPosition name="123"/>
</div>
)
}
显而易见的代码量就少了很多,嵌套关系也少了很多,数据和ui拆分的也更加明确了,不是吗?
const { x, y } = useMousePosition();
const { x: pageX, y: pageY } = usePagePosition();
const { xxx, yyy } = useXXX();
优点:
-
更加自由的命名,避免命名冲突
-
更加清晰的数据源,很清晰不是吗?
-
操作空间更大,你可以在任何能访问到
x,y,pageX,pageY的地方操作变量,很香! -
天呐!我们解决掉了令人讨厌的嵌套关系!
以上就是偶对这三种实现逻辑服用的办法的理解了!谢谢大家,欢迎讨论! end~