react 函数组件里面自定义组件用标签的形式去渲染,为什么会导致重复渲染

函数式编写页面,其中有ChildRender这个自定义组件,ChildRender放置在整个大的App函数组件里面,组件使用方式是以标签的形式使用<ChildRender/>;但如果这样写的话,我发现每次useState时整个ChildRender组件都会被重新挂载渲染。除非使用useCallback把ChildRender组件包裹一层后,ChildRender才不会重新挂载渲染
image.png

import React,{useState,useCallback} from 'react'
// import ChildRender from './componment/child'


const App = () => {
    const ChildRender = () =>{
        return (
            <div style={{width:'100px',height:'100px',background:'red'}}>
                <div>这是子组件11111</div>
                <img style={{width:'30px',height:'30px'}} src={require('./1.jpg')}/>
            </div>
        )
    }
    const [count,setCount] = useState(0,)
  
    return(
        <div>
            <div>
                <span>this is my app</span>
                <span>{count}</span>
                <button onClick={()=>setCount(count +1)}>点我</button>
            </div>
            <ChildRender/> 
            {/* {ChildRender()} 用函数调用的方式不会重新挂载渲染节点*/} 
        </div>
    )
}

export default App

但非常奇怪的是,如果我不用标签的形式去渲染,而是用函数调用的方式渲染,或者我把整个ChildRender组件放置到App组件之上,或者改用class写法,节点并不会重新挂载渲染,这让我有点迷惑,希望有人能回答一下

阅读 5.6k
3 个回答

image.png
image.png
不知提问者是以何种方式判断“节点是否渲染”,实际上无论是使用函数调用的形式或者使用标签的形式,ChildRender都会被重新调用,而react判断是否需要重新挂载节点是根据fiber的diff算法。

<ChildRender />这样的形式,最终会被编译成

React.createElement(ChildRender, null)

它们执行后返回的是包含<ChildRender />props的一个对象(ReactElement)

export function createElement(type, config, children) {
  // 重点在这里,重新声明了props属性的引用
  const props = {};
  ...
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
 );
}

也就是说,返回结果是一个ReactElement全新的引用,而它里面的props也是一个全新的对象,这也就意味着<ChildRender />的props变了,所以它会重新渲染。

还有一种方式可以避免重新渲染

import React,{useState,useCallback} from 'react'
// import ChildRender from './componment/child'

const ChildRender = () =>{
    return (
        <div style={{width:'100px',height:'100px',background:'red'}}>
            <div>这是子组件11111</div>
            <img style={{width:'30px',height:'30px'}} src={require('./1.jpg')}/>
        </div>
    )
}

const App = props => {
    
    const [count,setCount] = useState(0,)
  
    return(
        <div>
            <div>
                <span>this is my app</span>
                <span>{count}</span>
                <button onClick={()=>setCount(count +1)}>点我</button>
            </div>
            // <ChildRender/>  替换成如下的方式
            {props.children}
        </div>
    )
}

export default App

App使用的地方:

<App>
   <ChildRender/>
</App>

这是由于

ChildRender在App中是以props.children的形式体现出来的。这个props是App的props,而App渲染时,其props并未变化,相应的props.children,它是ChildRender的jsx形式,也没有变化,也就是说ChildRender的props同样没有变化,所以不会重新渲染。

新手上路,请多包涵

函数式组件有两种用法:

{FuncComponent(data)}
<FuncComponent {...data} />

如果data的值变化,FuncComponent函数就会重新执行,所以FuncComponent内的所有"常规函数""变量都会"重新创建。

所以:

  1. 函数式组件都要对影响dom渲染的变量进行useState来创建
  2. 对不影响dom渲染的变量进行useRef来创建
  3. 对函数进行useCallback来创建
  4. 对dom进行useMemo来创建

因为这些hooks会根据deps的变化来决定hooks的变量函数渲染的dom等是否重新执行。

希望对你有帮助!

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题