<Com/>和Com()的区别以及renderprops的注意事项

74 阅读3分钟

具体可先详见这篇文章dev.to/igor_bykov/…

1、<Com/>和Com()

//有react基础的同学都知道,<Com/>相当于调用了一下这个函数,展示出他的JSX结果
//所以以下二者的视图结果没有区别
import React from 'react'
function APP() {
    const Test = ()=>{
    return (
        <div style={{width:200,height:200,backgroundColor:'yellow'}}>
            Test
        </div>
    )
}
    return (<> 
    <Test></Test>
    {Test()}
    </>);
}

export default APP;

为什么点不动?

但这两个的区别并非就是调用形式的不同

(以下面这个例子或者本文开始的文章中的这个例子codesandbox.io/s/crazy-dew…)为例

录屏2024-12-31 10.27.38.gif

//代码如下例,二者呈现出了不同的表现形式,以<Com/>方式调用的组件没有正确更新
import { Button } from "antd";
import React, { useState } from "react";

const Son = (props: { onClick: () => void }) => {
  const [val, setval] = useState(0);
  return (
    <>
      <div>数据为:{val}</div>
      <Button
        onClick={() => {
          props.onClick();
          setval((i) => i + 1);
        }}
      >
        ➕
      </Button>
    </>
  );
};

function APP() {
  const [total, setTotal] = useState(0);
  const onClick = () => {
    setTotal((i) => i + 1);
  };
  const Test = (props?: { onClick?: () => void }) => {
    return (
      <div style={{ width: 100, height: 100, backgroundColor: "yellow" }}>
        <Son onClick={props?.onClick || function () {}}></Son>
      </div>
    );
  };
  //<Com/>调用的方式没有正确更新
  return (
    <div style={{ margin: "auto", width: "500px" }}>
      <span>total:{total}</span>
      <Son onClick={onClick}></Son>
      <Test onClick={onClick}></Test>
      {Test({ onClick: onClick })}
    </div>
  );
}

export default APP;


原因

上面gif展示的不同其实很好解释

Test组件被我们定义在了另一个组件内,每次刷新后被重新执行,对于React来说并非同一个引用,也就会被认为是不同的组件,因而导致不断的重置,state一直保持为0,页面上也就展示不变

//把上述的Test组件改成这个,滚动<Com/>方式调用的组件,会发现每次点击任意按钮后它都被scroll到顶部
//因为组件被重置了,所以自动展现为初始状态,相当于每次都把组件的key设置了一个新值
    const Test = (props?:{onClick?:()=>void})=>{
    return (
        <div style={{width:100,height:100,backgroundColor:'yellow',overflow:'scroll'}}>
            <div style={{height:300}}>
            <Son onClick={(props?.onClick || function(){})}></Son>
            </div>
        </div>
    )

把Test的定义移到组件之外,就能发现一切正常了

录屏2024-12-31 10.32.39.gif

2、是否做为组件节点

上述例子说明了两种调用方式并非完全一致,Test不能被定义在组件,因为会被重置。但Com(),也就是renderpros形式没有这种问题,为什么?

我们可以打开React Developer Tools去解决这个问题

image.png

可以看到我们只能看到<Com/>节点,而没有Com()这个节点,Com()方式的调用不会让React把他做为组件节点处理,他就纯是一个函数的执行,只是返回的是一段JSX,既然不作为节点,也就没有key不会被重置,自然能正常更新

所以区别1是 : <Com/>会被作为节点处理,而Com()不会,renderprops就只是一个纯粹的接受参数返回JSX的函数

3、状态

上面的例子似乎说明Com()比<Com/>调用更安全,但并非这样

//正如上文所说,renderprops不会被作为节点
//修改一下例子,把Son的逻辑移到Test中
function APP() {
    const [total,setTotal] = useState(0)
    const onClick = ()=>{
        setTotal(i=>i+1)
    }
    const Test = (props:{onClick:()=>void})=>{
        useEffect(()=>{
            return ()=>
                {
                   alert('renderprops卸载')
                }
        },[])
        return (
        (<>
        <div style={{width:100,height:100,backgroundColor:'yellow',overflow:'scroll'}}>
        </div>
        </>)
        )
}
    return (
    <> 
    <div style={{marginTop:30}}>total:{total}</div>
    <Button onClick={onClick}>点击</Button>
    {total<2?Test({onClick:onClick}):<div>none</div>}
    </>
    );
}

export default APP;

会发现当组件应该卸载掉时候确实有弹出,但不是alert,而是报错框

原因也很简单,因为renderprops不会被作为组件处理,因而也不像组件一样具备生命周期,所以不应在renderprops中维护state和生命周期等,尽管在许多正常情况下并不有问题

3、总结

1、组件不能在组件内定义的同时并且用<Com/>的方式调用

2、renderprops不会被作为组件处理,所以renderprops中不应维护自己的状态和生命周期,只应该做为一个纯粹的 function(props)=> UI