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

React 父子组件间通过 Props 交互的方式

2022-08-113.2k 阅读

React 父子组件间通过 Props 交互的方式

在 React 应用开发中,组件间的通信是非常重要的一部分。其中,父子组件间通过 Props 进行交互是一种基本且常用的通信方式。理解这种交互方式对于构建可维护、可扩展的 React 应用至关重要。

Props 的基本概念

Props 即“properties”的缩写,它是 React 中父组件向子组件传递数据的一种方式。可以将 Props 看作是子组件的输入参数,父组件通过在子组件标签上以属性名 - 属性值对的形式传递数据,子组件则通过函数参数(对于函数式组件)或 this.props(对于类组件)来接收这些数据。

在 React 中,Props 是单向流动的,这意味着数据只能从父组件流向子组件,子组件不能直接修改接收到的 Props。这种单向数据流使得应用的状态管理更加可预测和易于调试。

向子组件传递基本数据类型

最常见的情况是向子组件传递基本数据类型,如字符串、数字、布尔值等。以下是一个简单的示例,展示如何传递字符串和数字类型的 Props。

函数式子组件示例

首先,创建一个函数式子组件 ChildComponent,它接收两个 Props:name(字符串类型)和 age(数字类型)。

import React from 'react';

const ChildComponent = (props) => {
  return (
    <div>
      <p>Name: {props.name}</p>
      <p>Age: {props.age}</p>
    </div>
  );
};

export default ChildComponent;

然后,在父组件 ParentComponent 中引入并使用 ChildComponent,并传递相应的 Props。

import React from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const name = 'John';
  const age = 30;
  return (
    <div>
      <ChildComponent name={name} age={age} />
    </div>
  );
};

export default ParentComponent;

在上述代码中,ParentComponent 定义了 nameage 变量,并将它们作为 Props 传递给 ChildComponentChildComponent 通过 props.nameprops.age 来获取这些数据并在页面上进行展示。

类子组件示例

同样的功能,我们也可以使用类组件来实现。首先创建类子组件 ClassChildComponent

import React, { Component } from 'react';

class ClassChildComponent extends Component {
  render() {
    return (
      <div>
        <p>Name: {this.props.name}</p>
        <p>Age: {this.props.age}</p>
      </div>
    );
  }
}

export default ClassChildComponent;

接着,在父组件 ClassParentComponent 中使用 ClassChildComponent 并传递 Props。

import React, { Component } from 'react';
import ClassChildComponent from './ClassChildComponent';

class ClassParentComponent extends Component {
  render() {
    const name = 'Jane';
    const age = 25;
    return (
      <div>
        <ClassChildComponent name={name} age={age} />
      </div>
    );
  }
}

export default ClassParentComponent;

这里,类子组件通过 this.props 来访问父组件传递过来的 Props。

传递对象和数组类型的 Props

除了基本数据类型,也可以向子组件传递对象和数组类型的数据。这在需要传递一组相关数据或列表数据时非常有用。

传递对象

假设我们有一个用户信息的对象,需要传递给子组件展示。首先创建接收对象 Props 的函数式子组件 ObjectChildComponent

import React from 'react';

const ObjectChildComponent = (props) => {
  return (
    <div>
      <p>Username: {props.user.username}</p>
      <p>Email: {props.user.email}</p>
    </div>
  );
};

export default ObjectChildComponent;

然后在父组件 ObjectParentComponent 中创建用户对象并传递给子组件。

import React from 'react';
import ObjectChildComponent from './ObjectChildComponent';

const ObjectParentComponent = () => {
  const user = {
    username: 'user1',
    email: 'user1@example.com'
  };
  return (
    <div>
      <ObjectChildComponent user={user} />
    </div>
  );
};

export default ObjectParentComponent;

在这个例子中,父组件将 user 对象作为 Props 传递给 ObjectChildComponent,子组件通过 props.user 来访问对象的属性。

传递数组

如果要传递一个列表数据,比如一个待办事项列表,可以这样实现。先创建接收数组 Props 的函数式子组件 ListChildComponent

import React from 'react';

const ListChildComponent = (props) => {
  return (
    <ul>
      {props.tasks.map((task, index) => (
        <li key={index}>{task}</li>
      ))}
    </ul>
  );
};

export default ListChildComponent;

然后在父组件 ListParentComponent 中创建任务列表并传递给子组件。

import React from 'react';
import ListChildComponent from './ListChildComponent';

const ListParentComponent = () => {
  const tasks = ['Task 1', 'Task 2', 'Task 3'];
  return (
    <div>
      <ListChildComponent tasks={tasks} />
    </div>
  );
};

export default ListParentComponent;

这里,父组件将 tasks 数组传递给 ListChildComponent,子组件使用 map 方法遍历数组并展示每个任务项。

传递函数作为 Props

在 React 中,传递函数作为 Props 是实现子组件与父组件交互的重要方式。因为子组件不能直接修改父组件的数据,但可以通过调用父组件传递过来的函数来触发父组件中的状态更新等操作。

简单的事件传递

假设我们有一个按钮在子组件中,点击按钮需要通知父组件并执行一些操作。首先创建子组件 ButtonChildComponent

import React from 'react';

const ButtonChildComponent = (props) => {
  return (
    <button onClick={props.handleClick}>Click me</button>
  );
};

export default ButtonChildComponent;

然后在父组件 ButtonParentComponent 中定义点击处理函数并传递给子组件。

import React, { useState } from'react';
import ButtonChildComponent from './ButtonChildComponent';

const ButtonParentComponent = () => {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <p>Count: {count}</p>
      <ButtonChildComponent handleClick={handleClick} />
    </div>
  );
};

export default ButtonParentComponent;

在这个例子中,父组件 ButtonParentComponent 定义了 handleClick 函数,该函数更新 count 状态。子组件 ButtonChildComponent 通过 props.handleClick 接收这个函数,并在按钮点击时调用它,从而间接更新父组件的状态。

复杂的回调场景

有时候,子组件需要传递一些数据给父组件作为回调函数的参数。例如,在一个输入框子组件中,当用户输入内容时,将输入值传递给父组件。创建输入框子组件 InputChildComponent

import React from'react';

const InputChildComponent = (props) => {
  const handleChange = (e) => {
    props.onInputChange(e.target.value);
  };
  return (
    <input type="text" onChange={handleChange} />
  );
};

export default InputChildComponent;

然后在父组件 InputParentComponent 中定义处理输入值的函数并传递给子组件。

import React, { useState } from'react';
import InputChildComponent from './InputChildComponent';

const InputParentComponent = () => {
  const [inputValue, setInputValue] = useState('');
  const handleInputChange = (value) => {
    setInputValue(value);
  };
  return (
    <div>
      <p>Input Value: {inputValue}</p>
      <InputChildComponent onInputChange={handleInputChange} />
    </div>
  );
};

export default InputParentComponent;

在这个场景中,子组件 InputChildComponent 在输入框内容变化时,调用 props.onInputChange 并传递输入值,父组件 InputParentComponent 通过 handleInputChange 函数接收这个值并更新自己的状态。

Props 的默认值设置

在 React 中,可以为子组件的 Props 设置默认值。这样当父组件没有传递某个 Props 时,子组件可以使用默认值。

函数式组件的 Props 默认值

对于函数式组件,可以使用 defaultProps 属性来设置 Props 的默认值。例如,修改之前的 ChildComponent 组件,为 nameage 设置默认值。

import React from'react';

const ChildComponent = (props) => {
  return (
    <div>
      <p>Name: {props.name}</p>
      <p>Age: {props.age}</p>
    </div>
  );
};

ChildComponent.defaultProps = {
  name: 'Guest',
  age: 18
};

export default ChildComponent;

现在,如果父组件没有传递 nameage Props,子组件将使用默认值进行展示。

类组件的 Props 默认值

对于类组件,在类定义后使用 defaultProps 来设置 Props 默认值。例如,修改 ClassChildComponent 组件。

import React, { Component } from'react';

class ClassChildComponent extends Component {
  render() {
    return (
      <div>
        <p>Name: {this.props.name}</p>
        <p>Age: {this.props.age}</p>
      </div>
    );
  }
}

ClassChildComponent.defaultProps = {
  name: 'Default Name',
  age: 20
};

export default ClassChildComponent;

这样,当父组件没有传递 nameage Props 给 ClassChildComponent 时,子组件将使用默认值。

Props 的验证

在开发过程中,为了确保传递给子组件的 Props 符合预期的类型和格式,可以使用 PropTypes 进行 Props 验证。虽然 React 15.5 之后官方推荐使用 TypeScript 进行类型检查,但 PropTypes 仍然是一种简单且轻量级的方式。

首先,需要安装 prop-types 库。

npm install prop-types --save

函数式组件的 Props 验证

对于 ChildComponent,可以这样进行 Props 验证。

import React from'react';
import PropTypes from 'prop-types';

const ChildComponent = (props) => {
  return (
    <div>
      <p>Name: {props.name}</p>
      <p>Age: {props.age}</p>
    </div>
  );
};

ChildComponent.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired
};

export default ChildComponent;

这里,PropTypes.string.isRequired 表示 name Props 必须是字符串类型且是必填的,PropTypes.number.isRequired 表示 age Props 必须是数字类型且是必填的。

类组件的 Props 验证

对于 ClassChildComponent,同样可以进行 Props 验证。

import React, { Component } from'react';
import PropTypes from 'prop-types';

class ClassChildComponent extends Component {
  render() {
    return (
      <div>
        <p>Name: {this.props.name}</p>
        <p>Age: {this.props.age}</p>
      </div>
    );
  }
}

ClassChildComponent.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired
};

export default ClassChildComponent;

如果父组件传递的 Props 不符合验证规则,在开发环境中 React 会在控制台打印警告信息,帮助开发者发现问题。

多层嵌套组件间的 Props 传递

在实际应用中,组件结构可能会比较复杂,存在多层嵌套的情况。这时,父组件需要将 Props 层层传递给深层的子组件。

假设有三个组件:GrandParentComponentParentComponentChildComponentGrandParentComponent 是最外层组件,ParentComponent 是中间层组件,ChildComponent 是最内层组件。GrandParentComponent 要将数据传递给 ChildComponent

首先创建 ChildComponent,它接收并展示 Props。

import React from'react';

const ChildComponent = (props) => {
  return (
    <div>
      <p>Value from GrandParent: {props.value}</p>
    </div>
  );
};

export default ChildComponent;

然后创建 ParentComponent,它从 GrandParentComponent 接收 Props 并传递给 ChildComponent

import React from'react';
import ChildComponent from './ChildComponent';

const ParentComponent = (props) => {
  return (
    <div>
      <ChildComponent value={props.value} />
    </div>
  );
};

export default ParentComponent;

最后创建 GrandParentComponent,它传递数据给 ParentComponent

import React from'react';
import ParentComponent from './ParentComponent';

const GrandParentComponent = () => {
  const value = 'Hello from GrandParent';
  return (
    <div>
      <ParentComponent value={value} />
    </div>
  );
};

export default GrandParentComponent;

在这个例子中,GrandParentComponent 通过 ParentComponentvalue Props 传递给了 ChildComponent。这种层层传递的方式虽然可以实现数据传递,但如果嵌套层次过深,会使得代码变得繁琐且难以维护。在这种情况下,可以考虑使用 React 的 Context 来解决跨层级传递数据的问题。

使用 React.memo 优化 Props 传递

在 React 中,当组件的 Props 发生变化时,组件会重新渲染。对于一些纯展示型组件,如果 Props 没有变化,重新渲染是不必要的性能开销。React.memo 可以帮助我们优化这种情况。

React.memo 是一个高阶组件,它可以包裹函数式组件。只有当组件的 Props 发生变化时,才会重新渲染该组件。

例如,对于 ChildComponent,可以使用 React.memo 进行优化。

import React from'react';

const ChildComponent = (props) => {
  return (
    <div>
      <p>Name: {props.name}</p>
      <p>Age: {props.age}</p>
    </div>
  );
};

export default React.memo(ChildComponent);

这样,当 ChildComponentnameage Props 没有变化时,即使父组件重新渲染,ChildComponent 也不会重新渲染,从而提高应用的性能。

需要注意的是,React.memo 只对函数式组件有效,并且默认情况下它只会进行浅比较(shallow comparison)。如果 Props 是复杂对象或数组,浅比较可能无法准确判断 Props 是否真的发生了变化。在这种情况下,可以通过传递自定义的比较函数来进行深度比较。

import React from'react';

const ChildComponent = (props) => {
  return (
    <div>
      <p>Data: {props.data}</p>
    </div>
  );
};

const arePropsEqual = (prevProps, nextProps) => {
  return JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data);
};

export default React.memo(ChildComponent, arePropsEqual);

在上述代码中,arePropsEqual 函数通过将 data 属性转换为 JSON 字符串并进行比较,实现了深度比较,只有当 data 真正发生变化时,组件才会重新渲染。

总结 Props 传递中的常见问题及解决方法

  1. Props 未更新问题:有时候会遇到子组件接收的 Props 没有更新的情况。这可能是因为父组件没有正确地触发重新渲染。确保父组件的状态或属性发生变化时,能够正确地触发重新渲染。例如,在使用 useStatesetState 时,要保证新的值与旧值不同,否则 React 可能不会认为状态发生了变化。
  2. Props 类型错误:传递错误类型的 Props 可能导致应用出现运行时错误。通过 Props 验证(如使用 PropTypes 或 TypeScript)可以在开发过程中发现这类问题。同时,在传递复杂对象或数组 Props 时,要确保子组件对这些数据的处理是符合预期的。
  3. Props 传递过多导致代码臃肿:在多层嵌套组件中,Props 层层传递可能使代码变得冗长和难以维护。可以考虑使用 React Context 来解决跨层级传递数据的问题,或者通过状态提升等方式来优化组件结构,减少不必要的 Props 传递。

通过深入理解 React 父子组件间通过 Props 交互的方式,包括基本数据类型、对象和数组、函数传递、默认值设置、验证以及在复杂场景下的应用和优化,开发者能够构建出更加高效、可维护的 React 应用。在实际开发中,根据具体的业务需求和组件结构,灵活运用这些知识,将有助于提高开发效率和应用的质量。同时,随着 React 技术的不断发展,也需要关注新的特性和最佳实践,以更好地适应不同的项目需求。