MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

React 组件间通信与生命周期方法的关系

2022-06-305.7k 阅读

React 组件间通信基础

在 React 应用开发中,组件间通信是构建复杂用户界面的关键。React 中主要存在几种常见的组件间通信方式:父子组件通信、兄弟组件通信以及跨层级组件通信。

父子组件通信

父子组件通信是 React 中最直接的通信方式。父组件通过向子组件传递 props 来实现数据的向下传递。例如,我们有一个 Parent 组件和一个 Child 组件:

import React from 'react';

// Child 组件接收来自父组件的 name prop
const Child = ({ name }) => {
  return <div>Hello, {name}</div>;
};

const Parent = () => {
  const username = 'John';
  return <Child name={username} />;
};

export default Parent;

在上述代码中,Parent 组件将 username 作为 name prop 传递给 Child 组件。Child 组件通过解构的方式获取 name prop 并展示在界面上。

兄弟组件通信

兄弟组件之间并没有直接的父子关系,但它们通常共享一个共同的父组件。因此,可以通过父组件作为中间桥梁来实现兄弟组件间的通信。例如,我们有 Brother1Brother2 两个兄弟组件,它们的父组件为 Parent

import React, { useState } from'react';

const Brother1 = ({ onSendMessage }) => {
  const [message, setMessage] = useState('');
  const handleSubmit = (e) => {
    e.preventDefault();
    onSendMessage(message);
    setMessage('');
  };
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={message}
        onChange={(e) => setMessage(e.target.value)}
      />
      <button type="submit">Send to Brother2</button>
    </form>
  );
};

const Brother2 = ({ message }) => {
  return <div>Received: {message}</div>;
};

const Parent = () => {
  const [sharedMessage, setSharedMessage] = useState('');
  const handleMessageFromBrother1 = (msg) => {
    setSharedMessage(msg);
  };
  return (
    <div>
      <Brother1 onSendMessage={handleMessageFromBrother1} />
      <Brother2 message={sharedMessage} />
    </div>
  );
};

export default Parent;

在这段代码中,Brother1 组件通过 onSendMessage 回调函数将数据传递给 Parent 组件,Parent 组件再将数据通过 message prop 传递给 Brother2 组件,从而实现了兄弟组件间的通信。

跨层级组件通信

当组件层级较深时,层层传递 props 会变得繁琐。此时可以使用 Context 来实现跨层级组件通信。Context 提供了一种在组件树中共享数据的方式,而不必在每个层级手动传递 props。以下是一个简单的示例:

import React, { createContext, useState } from'react';

// 创建 Context
const MyContext = createContext();

const GrandChild = () => {
  const value = React.useContext(MyContext);
  return <div>Value from context: {value}</div>;
};

const Child = () => {
  return <GrandChild />;
};

const Parent = () => {
  const [contextValue, setContextValue] = useState('Initial value');
  return (
    <MyContext.Provider value={contextValue}>
      <Child />
      <button onClick={() => setContextValue('New value')}>
        Update context value
      </button>
    </MyContext.Provider>
  );
};

export default Parent;

在上述代码中,Parent 组件通过 MyContext.Provider 提供了一个 value,其子孙组件 GrandChild 可以通过 React.useContext(MyContext) 获取到这个 value,而无需经过中间的 Child 组件传递 props。

React 生命周期方法概述

React 组件的生命周期可以分为三个阶段:挂载阶段、更新阶段和卸载阶段。每个阶段都有相应的生命周期方法。

挂载阶段

  • constructor(props):在组件创建时调用,通常用于初始化 state 和绑定方法。例如:
import React, { Component } from'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Increment</button>
      </div>
    );
  }
}

export default MyComponent;

constructor 中,我们初始化了 state 中的 count 为 0,并将 handleClick 方法绑定到当前组件实例。

  • componentDidMount():组件挂载到 DOM 后调用。常用于执行副作用操作,如发起网络请求、添加事件监听器等。例如:
import React, { Component } from'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: null
    };
  }
  componentDidMount() {
    fetch('https://example.com/api/data')
    .then(response => response.json())
    .then(data => this.setState({ data }));
  }
  render() {
    return (
      <div>
        {this.state.data? <p>{JSON.stringify(this.state.data)}</p> : <p>Loading...</p>}
      </div>
    );
  }
}

export default MyComponent;

componentDidMount 中,我们发起了一个网络请求并在数据获取后更新 state

更新阶段

  • shouldComponentUpdate(nextProps, nextState):在组件接收到新的 props 或 state 时调用,返回一个布尔值,决定组件是否需要更新。例如:
import React, { Component } from'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  shouldComponentUpdate(nextProps, nextState) {
    // 只有当 count 变化时才更新组件
    return nextState.count!== this.state.count;
  }
  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Increment</button>
      </div>
    );
  }
}

export default MyComponent;

在上述代码中,shouldComponentUpdate 方法确保只有当 state 中的 count 发生变化时组件才会更新,避免了不必要的渲染。

  • componentDidUpdate(prevProps, prevState):组件更新后调用。可以在此处执行依赖于 DOM 更新后的操作,如操作更新后的 DOM,或对比前后 propsstate 的变化。例如:
import React, { Component } from'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }
  componentDidUpdate(prevProps, prevState) {
    if (this.state.count!== prevState.count) {
      console.log('Count has been updated');
    }
  }
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Increment</button>
      </div>
    );
  }
}

export default MyComponent;

componentDidUpdate 中,我们通过对比当前 state 和之前的 state,判断 count 是否更新,并在更新时打印日志。

卸载阶段

  • componentWillUnmount():组件从 DOM 中移除前调用。常用于清理副作用操作,如取消网络请求、移除事件监听器等。例如:
import React, { Component } from'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.timer = null;
  }
  componentDidMount() {
    this.timer = setInterval(() => {
      console.log('Timer is running');
    }, 1000);
  }
  componentWillUnmount() {
    clearInterval(this.timer);
  }
  render() {
    return <div>Component with a timer</div>;
  }
}

export default MyComponent;

componentDidMount 中,我们设置了一个定时器,在 componentWillUnmount 中,我们清除了这个定时器,防止内存泄漏。

React 组件间通信与生命周期方法的关联

  1. 父子组件通信与生命周期
    • 父组件传递 props 引发子组件更新:当父组件传递新的 props 给子组件时,子组件会进入更新阶段。shouldComponentUpdate 方法可以用于优化子组件的更新。例如,如果子组件只关心某个特定 prop 的变化,可以在 shouldComponentUpdate 中进行判断:
import React, { Component } from'react';

class ChildComponent extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    return nextProps.specificProp!== this.props.specificProp;
  }
  render() {
    return <div>{this.props.specificProp}</div>;
  }
}

class ParentComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: 'Initial data'
    };
  }
  handleClick() {
    this.setState({ data: 'New data' });
  }
  render() {
    return (
      <div>
        <ChildComponent specificProp={this.state.data} />
        <button onClick={() => this.handleClick()}>Update prop</button>
      </div>
    );
  }
}

export default ParentComponent;

在上述代码中,ChildComponentshouldComponentUpdate 方法只在 specificProp 变化时才允许更新,这样可以避免不必要的渲染。

  • 子组件通过回调通知父组件:子组件调用父组件传递的回调函数时,父组件的 state 可能会更新,从而引发父组件自身以及其后代组件的更新。在父组件更新过程中,生命周期方法如 shouldComponentUpdatecomponentDidUpdate 等会按顺序执行。例如:
import React, { Component } from'react';

class ChildComponent extends Component {
  handleClick() {
    this.props.onChildClick();
  }
  render() {
    return <button onClick={() => this.handleClick()}>Notify parent</button>;
  }
}

class ParentComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isNotified: false
    };
  }
  handleChildClick() {
    this.setState({ isNotified: true });
  }
  componentDidUpdate(prevProps, prevState) {
    if (prevState.isNotified!== this.state.isNotified) {
      console.log('Parent state has been updated by child');
    }
  }
  render() {
    return (
      <div>
        <ChildComponent onChildClick={() => this.handleChildClick()} />
        {this.state.isNotified && <p>Received notification from child</p>}
      </div>
    );
  }
}

export default ParentComponent;

在这个例子中,ChildComponent 点击按钮调用 onChildClick 回调通知 ParentComponentParentComponent 更新 state 后,componentDidUpdate 方法会被调用,我们可以在其中执行相关操作。

  1. 兄弟组件通信与生命周期
    • 通过父组件作为桥梁传递数据:当一个兄弟组件通过父组件向另一个兄弟组件传递数据时,父组件的 state 更新会触发父组件及其所有子组件的更新。例如,在之前兄弟组件通信的示例中,Brother1 传递数据给 ParentParent 再传递给 Brother2。在这个过程中,Parent 组件的 shouldComponentUpdatecomponentDidUpdate 方法会被调用。shouldComponentUpdate 可以用于优化 Parent 组件自身的更新,而 componentDidUpdate 可以用于在数据传递完成后执行一些额外操作,比如记录日志。
import React, { Component } from'react';

class Brother1 extends Component {
  constructor(props) {
    super(props);
    this.state = {
      message: ''
    };
  }
  handleSubmit(e) {
    e.preventDefault();
    this.props.onSendMessage(this.state.message);
    this.setState({ message: '' });
  }
  render() {
    return (
      <form onSubmit={(e) => this.handleSubmit(e)}>
        <input
          type="text"
          value={this.state.message}
          onChange={(e) => this.setState({ message: e.target.value })}
        />
        <button type="submit">Send to Brother2</button>
      </form>
    );
  }
}

class Brother2 extends Component {
  render() {
    return <div>Received: {this.props.message}</div>;
  }
}

class Parent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      sharedMessage: ''
    };
  }
  handleMessageFromBrother1(msg) {
    this.setState({ sharedMessage: msg });
  }
  shouldComponentUpdate(nextProps, nextState) {
    return nextState.sharedMessage!== this.state.sharedMessage;
  }
  componentDidUpdate(prevProps, prevState) {
    if (prevState.sharedMessage!== this.state.sharedMessage) {
      console.log('Shared message has been updated');
    }
  }
  render() {
    return (
      <div>
        <Brother1 onSendMessage={(msg) => this.handleMessageFromBrother1(msg)} />
        <Brother2 message={this.state.sharedMessage} />
      </div>
    );
  }
}

export default Parent;

在上述代码中,Parent 组件通过 shouldComponentUpdate 方法确保只有当 sharedMessage 变化时才更新,componentDidUpdate 方法在 sharedMessage 更新后打印日志。

  1. 跨层级组件通信与生命周期
    • Context 变化引发的组件更新:当 Contextvalue 发生变化时,使用该 Context 的所有子孙组件都会更新。在这些组件的更新过程中,生命周期方法同样会按顺序执行。例如,在之前的 Context 示例中,当 Parent 组件更新 contextValue 时,GrandChild 组件会进入更新阶段。GrandChild 组件可以通过 shouldComponentUpdate 方法进行更新优化。
import React, { createContext, Component } from'react';

const MyContext = createContext();

class GrandChild extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    const nextValue = React.useContext(MyContext);
    const currentValue = React.useContext(MyContext);
    return nextValue!== currentValue;
  }
  render() {
    const value = React.useContext(MyContext);
    return <div>Value from context: {value}</div>;
  }
}

class Child extends Component {
  render() {
    return <GrandChild />;
  }
}

class Parent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      contextValue: 'Initial value'
    };
  }
  handleClick() {
    this.setState({ contextValue: 'New value' });
  }
  render() {
    return (
      <MyContext.Provider value={this.state.contextValue}>
        <Child />
        <button onClick={() => this.handleClick()}>Update context value</button>
      </MyContext.Provider>
    );
  }
}

export default Parent;

在上述代码中,GrandChild 组件的 shouldComponentUpdate 方法通过对比 Contextvalue 来决定是否更新,避免了不必要的渲染。

实际应用场景中的考量

  1. 性能优化方面
    • 在组件间频繁通信的场景下,合理使用生命周期方法进行更新控制至关重要。例如,在一个包含大量列表项的父子组件通信场景中,子组件列表项可能会因为父组件传递的某些数据频繁更新而导致不必要的渲染。通过在子组件的 shouldComponentUpdate 方法中精确判断 prop 的变化,可以显著提升性能。
import React, { Component } from'react';

class ListItem extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    return nextProps.item.id!== this.props.item.id;
  }
  render() {
    return <li>{this.props.item.name}</li>;
  }
}

class List extends Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [
        { id: 1, name: 'Item 1' },
        { id: 2, name: 'Item 2' }
      ]
    };
  }
  handleUpdate() {
    const newItems = [...this.state.items];
    newItems[0].name = 'Updated Item 1';
    this.setState({ items: newItems });
  }
  render() {
    return (
      <div>
        <ul>
          {this.state.items.map(item => (
            <ListItem key={item.id} item={item} />
          ))}
        </ul>
        <button onClick={() => this.handleUpdate()}>Update list item</button>
      </div>
    );
  }
}

export default List;

在上述代码中,ListItem 组件的 shouldComponentUpdate 方法只在 item.id 变化时才更新,即使 item.name 变化,如果 id 不变,也不会触发更新,从而优化了性能。 2. 数据一致性维护

  • 在使用 Context 进行跨层级通信时,由于多个组件可能依赖于相同的 Context 数据,需要注意数据的一致性。生命周期方法如 componentDidUpdate 可以用于在 Context 数据变化时进行相关的同步操作。例如,在一个多语言切换的应用中,Context 用于传递当前语言设置,多个组件依赖这个设置来显示不同语言的文本。当语言设置通过 Context 更新时,相关组件可以在 componentDidUpdate 中重新获取翻译后的文本,确保数据一致性。
import React, { createContext, Component } from'react';

const LanguageContext = createContext();

class TranslatedText extends Component {
  constructor(props) {
    super(props);
    this.state = {
      text: ''
    };
  }
  componentDidMount() {
    this.updateText();
  }
  componentDidUpdate(prevProps, prevState) {
    const language = React.useContext(LanguageContext);
    if (language!== prevProps.language) {
      this.updateText();
    }
  }
  updateText() {
    const language = React.useContext(LanguageContext);
    const translations = {
      en: { greeting: 'Hello' },
      fr: { greeting: 'Bonjour' }
    };
    this.setState({ text: translations[language].greeting });
  }
  render() {
    return <p>{this.state.text}</p>;
  }
}

class LanguageProvider extends Component {
  constructor(props) {
    super(props);
    this.state = {
      language: 'en'
    };
  }
  handleLanguageChange() {
    this.setState({ language: this.state.language === 'en'? 'fr' : 'en' });
  }
  render() {
    return (
      <LanguageContext.Provider value={this.state.language}>
        <TranslatedText />
        <button onClick={() => this.handleLanguageChange()}>
          Change language
        </button>
      </LanguageContext.Provider>
    );
  }
}

export default LanguageProvider;

在上述代码中,TranslatedText 组件在 componentDidMountcomponentDidUpdate 中根据 Context 中的语言设置更新显示的文本,确保了数据的一致性。 3. 复杂业务逻辑处理

  • 在处理复杂业务逻辑时,组件间通信与生命周期方法相互配合。例如,在一个电商购物车应用中,商品列表组件(父组件)与购物车组件(子组件)通过父子组件通信传递商品信息。当用户添加商品到购物车时,购物车组件可能需要在 componentDidUpdate 中计算总价、更新库存等复杂业务逻辑。同时,兄弟组件如优惠券组件与购物车组件之间通过父组件作为桥梁通信,当优惠券应用时,购物车组件同样需要在生命周期方法中处理价格调整等业务逻辑。
import React, { Component } from'react';

class ProductItem extends Component {
  handleAddToCart() {
    this.props.onAddToCart(this.props.product);
  }
  render() {
    return (
      <div>
        <p>{this.props.product.name}</p>
        <p>Price: {this.props.product.price}</p>
        <button onClick={() => this.handleAddToCart()}>Add to cart</button>
      </div>
    );
  }
}

class Cart extends Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [],
      total: 0
    };
  }
  componentDidUpdate(prevProps, prevState) {
    if (prevProps.items!== this.props.items) {
      const newTotal = this.props.items.reduce((acc, item) => acc + item.price, 0);
      this.setState({ total: newTotal });
    }
  }
  render() {
    return (
      <div>
        <p>Total: {this.state.total}</p>
        <ul>
          {this.props.items.map(item => (
            <li key={item.id}>{item.name} - {item.price}</li>
          ))}
        </ul>
      </div>
    );
  }
}

class Store extends Component {
  constructor(props) {
    super(props);
    this.state = {
      products: [
        { id: 1, name: 'Product 1', price: 10 },
        { id: 2, name: 'Product 2', price: 20 }
      ],
      cartItems: []
    };
  }
  handleAddToCart(product) {
    this.setState({ cartItems: [...this.state.cartItems, product] });
  }
  render() {
    return (
      <div>
        <h2>Products</h2>
        {this.state.products.map(product => (
          <ProductItem key={product.id} product={product} onAddToCart={(p) => this.handleAddToCart(p)} />
        ))}
        <h2>Cart</h2>
        <Cart items={this.state.cartItems} />
      </div>
    );
  }
}

export default Store;

在上述代码中,Cart 组件在 componentDidUpdate 中根据接收到的新的 items 更新总价,处理了复杂的业务逻辑。

通过深入理解 React 组件间通信与生命周期方法的关系,开发者可以更高效地构建复杂、高性能的 React 应用,确保应用在数据传递、更新控制以及业务逻辑处理等方面都能达到最优状态。在实际开发中,需要根据具体的业务需求和应用场景,灵活运用这些知识,以实现最佳的用户体验和应用性能。同时,随着 React 的不断发展,一些新的特性和方法可能会进一步优化组件间通信和生命周期管理,开发者需要持续关注并学习,以跟上技术的发展步伐。