React组件渲染优化

CartList.js

import React, { Component, PureComponent } from 'react';

class CartList extends Component {
    constructor(props) {
        super(props);
        this.state = {
            goodsList: []
        }
    }

    componentDidMount() {
        // 每秒改变一次数据
        setInterval(() => {
            this.setState(() => ({
                goodsList: [
                    {
                        name: '商品1',
                        price: 10
                    }
                ]
            }))
        }, 1000);
    }

    render() {
        return (
            <div>
                {
                    this.state.goodsList.map(item => {
                        return <CartItem goods={item} key={item.name}></CartItem>
                    })
                }
            </div>
        )
    }
}

export default CartList;

CartItem.js

function CartItem(props) {
    console.log('render');
    
    const { name } = props.goods;
    return (
        <div>{name}</div>
    )
}

CartItem中,每秒打印两次render,两次是因为使用了严格模式,再index.js中去掉React.StrictMode即可


上面的渲染方式,每秒改变一次数据,虽然实际内容并没有发生变化,但还是会重新渲染,这会带来不必要的性能消耗
改写 CartItem,使其继承 PureComponent,在数据不变的情况下,不会重新渲染

注意:PureComponent只进行浅比较,下面这种写法props接收的是goods对象,浅比较是比较的内存地址,每次的goods对象都是一个新的地址,所以还是会重新渲染

class CartItem extends PureComponent {
    render() {
        console.log('render');
        const { name, price } = this.props.goods
        return (
            <div>
                <p>{name}</p>
                <p>{price}</p>
            </div>
        );
    }
}

为解决上面的问题,需要改变CartItem的传值方式,改为接收对象中的每个属性值

return <CartItem name={item.name} price={item.price} key={item.name}></CartItem>
// 也能简写为
return <CartItem {...item} key={item.name}></CartItem>
class CartItem extends PureComponent {
    render() {
        console.log('render');
        const { name, price } = this.props
        return (
            <div>
                <p>{name}</p>
                <p>{price}</p>
            </div>
        );
    }
}

PureComponent使用原则

1、PureComponent一般会用在一些纯展示组件上。切记props和state不能使用同一个引用,否则无法更新组件 2、在通过PureComponent进行组件的创建的时候不能够再写shouldComponentUpdate,否则会引发警告
<think>好的,我现在要解决用户的问题:React组件如何渲染children属性以及相关机制。首先,我需要整理已有的知识,并结合用户提供的引用内容来构建一个清晰的回答。 用户提到了几个引用,其中引用[1]讨论了通过props传递children来避免不必要的重新渲染。引用[3][4]也提到将状态管理与子组件分离,使用children作为props来防止子组件受父组件状态变化的影响。引用[2]提到了React渲染分为rendercommit阶段,这可能children的渲染机制有关联。 首先,我需要明确children在React中的基本概念。children可以是任何有效的React元素,包括组件、字符串、数字等。当父组件渲染时,children作为props的一部分传递,但它们的渲染行为可能有特殊之处。 根据引用[1],当children作为props传递时,父组件的重新渲染不会导致这些children重新渲染,除非它们的props发生变化。例如,在用户提供的代码示例中,SplitComponent将VerySlowComponent作为children传递给ComponentWithClick。当ComponentWithClick内部状态变化时,VerySlowComponent不会重新渲染,因为children在父组件第一次渲染时已经被创建,之后每次渲染都复用相同的引用。 接下来,我需要解释React渲染机制。引用[2]提到React渲染分为render阶段commit阶段。在render阶段,React会生成虚拟DOM树,比较新旧树的结构来确定需要更新的部分。如果父组件重新渲染,默认情况下所有子组件都会重新渲染,但如果子组件的props没有变化且使用React.memo或shouldComponentUpdate优化,则可以避免不必要的渲染。 但children的情况有些特殊。当children作为props传递时,它们是否重新渲染取决于父组件如何传递它们。如果父组件每次渲染都创建新的children元素(比如内联定义),那么即使使用React.memo,子组件也会重新渲染。反之,如果children在父组件外部定义或通过状态保持稳定,则不会触发重新渲染。 例如,用户提供的例子中,SplitComponent将VerySlowComponent作为children传递给ComponentWithClick。由于children是在SplitComponent的第一次渲染中创建的,当ComponentWithClick内部状态更新时,SplitComponent不会重新渲染(假设SplitComponent没有自身状态变化),因此children保持相同的引用,VerySlowComponent不会重新渲染。 接下来,我需要讨论children的不同用法,比如默认插槽、具名插槽(通过props传递多个children)以及Render Props模式。引用[4]提到React没有Vue的插槽机制,但可以通过propschildren实现类似功能。例如,具名插槽可以通过传递不同的props来实现,比如<Layout header={<Header/>} content={<Content/>} />。 同时,需要强调children的渲染优化策略。根据引用[1][3],将状态提升到更小的组件中,并通过children传递静态内容可以避免不必要的渲染。例如,将状态管理封装在父组件内部,而将子组件作为children传递,这样当父组件状态变化时,children不会受到影响。 另外,要注意到使用React.memo优化组件时,如果children是不稳定的(比如每次渲染都创建新的元素),即使使用memo也可能失效。因此,保持children的稳定性是关键,可以通过useMemo或useCallback来缓存它们。 最后,需要总结children的渲染机制,包括默认行为、优化方法以及常见的使用场景,并结合用户提供的例子详细说明。还要注意引用用户提供的代码示例,说明为什么VerySlowComponent不会重新渲染,因为children作为props传递时,父组件的状态变化不会导致它们重新渲染,除非父组件本身重新渲染并改变了children的引用。 可能还需要解答用户潜在的问题,比如如何避免children的不必要渲染,如何实现动态内容插入,以及children其他渲染模式(如Render Props)的区别。同时,确保回答结构清晰,分点说明机制、优化策略实际应用,帮助用户全面理解。</think>#### React组件children渲染机制深度解析 ### #### 一、children的底层渲染逻辑 1. **虚拟DOM比较机制** React通过`React.createElement()`创建虚拟DOM节点,当父组件更新时: ```jsx // 父组件 <Parent> <Child /> {/* 相当于 React.createElement(Child) */} </Parent> ``` - 每次父组件render时都会创建**新的Child虚拟节点** - React通过`Object.is`比较新旧children的引用地址[^1] 2. **稳定性的关键作用** 示例对比: ```jsx // 方案A:直接内联(不稳定) <Parent><Child /></Parent> // 方案B:通过变量缓存(稳定) const memoChild = useMemo(() => <Child />, []); <Parent>{memoChild}</Parent> ``` - 方案A每次父组件更新都会触发Child重新渲染 - 方案B通过`useMemo`保持Child引用稳定 ### #### 二、children的3种核心用法模式 1. **默认插槽模式** 通过`props.children`接收内容: ```jsx function Card({ children }) { return <div className="card">{children}</div>; } // 使用 <Card> <h2>标题</h2> <p>内容...</p> </Card> ``` 2. **具名插槽模式** 通过自定义props实现: ```jsx function Layout({ header, content }) { return ( <div> <header>{header}</header> <main>{content}</main> </div> ); } // 使用 <Layout header={<Header />} content={<MainContent />} /> ``` 3. **Render Props模式** 动态生成children内容: ```jsx function MouseTracker({ render }) { const [pos, setPos] = useState({x:0, y:0}); return render(pos); } // 使用 <MouseTracker render={({x, y}) => ( <p>当前坐标:{x}, {y}</p> )}/> ``` ### #### 三、关键渲染优化策略(基于引用[1][3]) 1. **状态隔离模式** 将状态管理与children分离: ```jsx // 优化前:状态变化导致所有子组件更新 function Parent() { const [count, setCount] = useState(0); return ( <div> <button onClick={() => setCount(c => c+1)}>{count}</button> <ExpensiveComponent /> {/* 每次都会重新渲染 */} </div> ); } // 优化后:通过children隔离状态影响 function StateManager({ children }) { const [count, setCount] = useState(0); return ( <div onClick={() => setCount(c => c+1)}> {count} {children} {/* 状态变化不会导致children重渲染 */} </div> ); } ``` 2. **引用稳定性控制** 使用`useMemo`保护children结构: ```jsx function Parent() { const memoChildren = useMemo(() => ( <ExpensiveComponent /> ), []); // 空依赖数组保持永久稳定 return <Container>{memoChildren}</Container>; } ``` 3. **高阶组件模式** 通过HOC包裹优化: ```jsx const withStableChildren = (WrappedComponent) => { return function(props) { const memoChildren = useMemo(() => props.children, [props.children]); return <WrappedComponent {...props} children={memoChildren} />; }; }; ``` ### #### 四、特殊场景处理 1. **动态插槽更新检测** 当需要根据children内容变化执行操作时: ```jsx function DynamicSlot({ children }) { const prevChildren = useRef(); useEffect(() => { if (prevChildren.current !== children) { console.log('插槽内容发生变化'); prevChildren.current = children; } }, [children]); return <div>{children}</div>; } ``` 2. **Fragment控制渲染粒度** 使用`<React.Fragment>`避免额外DOM层级: ```jsx function Grid({ children }) { return ( <div className="grid"> {React.Children.map(children, child => ( <div className="grid-item">{child}</div> ))} </div> ); } ``` ### #### 五、性能分析工具验证 通过React DevTools Profiler验证优化效果: 1. 记录父组件点击操作前后的渲染情况 2. 观察`ExpensiveComponent`的渲染次数 3. 使用"Highlight updates"功能可视化渲染范围 优化前效果:父组件状态变化时所有子组件高亮 优化后效果:仅状态管理部分高亮,children保持稳定 --- --相关问题--: 1. 如何通过React.Children API动态操作子元素? 2. 使用children与render props的性能差异是什么? 3. 在类组件中如何实现类似的children优化? 4. 如何检测children的实际变化内容(非引用变化)? [^1]: 子组件作为props传递时的引用稳定性机制 [^2]: React虚拟DOM对比算法原理 [^3]: 状态隔离与children渲染优化的实践方案
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值