React中的逻辑复用方法(HOC-renderProps-useXXX)

341 阅读3分钟

在使用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 />
)

我个人觉得缺点有如下

  1. 嵌套过深,在上文demo中仅复用了withGetMousePosition(ShowPosition),但倘若我们需要复用更多的逻辑呢?就会发生这样的情况WithXXX(WithXXX(WithXXX(Com)))虽然通过with+XXX的驼峰命名能够清晰的分辨出我们复用了哪些逻辑,但是这种嵌套关系和代码量还是让我依旧认为这是一个缺点。

  2. 命名冲突,我们回到App组件中的这一行代码,<WithGetMousePosition name="123"/>。可以看到我们的ShowPosition组件是需要一个名为name的prop的,如果我们的prop需要的并非name,而是一个“x"呢?这便和withGetMousePosition传递给我们的”x"的prop发生了命名冲突,当然,你完全可以通过自主命名去修改他。

  3. 无法清晰的展示数据来源。接着我们回到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>
  )
}

优点:

  1. 数据源清晰。让我们看App这个组件的内部,<MousePosition render={ mouse => <ShowPosition x={mouse.x} y={mouse.y} name="123"/> }>, 我们能够清楚看到mouse这个prop是通过逻辑复用(render-props)传递的,而name是他自己的prop,弥补了HOC数据源不清不楚的缺点!

缺点:

  1. 更为可怕的嵌套地狱。继续假设我们如果要复用更多的逻辑呢?
render = {({ x, y }) => (
    <Com1>
      {({ x: pageX, y: pageY }) => (
        <Com2>
          {({ terrible }) => {
            // 这该死的阅读性!
          }}
        </Com2>
      )}
    </Com1>
  )}
  1. 被限制的缺点。我们只能在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();

优点:

  1. 更加自由的命名,避免命名冲突

  2. 更加清晰的数据源,很清晰不是吗?

  3. 操作空间更大,你可以在任何能访问到x,y,pageX,pageY的地方操作变量,很香!

  4. 天呐!我们解决掉了令人讨厌的嵌套关系!

以上就是偶对这三种实现逻辑服用的办法的理解了!谢谢大家,欢迎讨论! end~