数据传递和方法调用

组件之间经常需要互相进行传递数据和方法调用,这种情况大部分都出现在父子组件中,一般会用到这样几种传递方式:

  1. 父组件传递数据到子组件
  2. 子组件传递数据到父组件
  3. 子组件调用父组件的方法
  4. 父组件调用子组件的方法

父组件传递数据到子组件比较简单,我们直接使用props传递数据即可;而子组件调用父组件的方法其实也不复杂,我们通过props将父组件中的回调函数传递给子组件,就间接实现子组件调用父组件的方法;子组件向父组件传递数据,这通过子组件调用父组件的方法就可以实现。至于父组件调用子组件的场景在实际开发中相对少见,一般开发中都是通过props传递一些状态变量来驱动子组件变化的。

父组件传递数据到子组件

父组件传递数据到子组件非常简单,我们直接用props属性传递数据即可。

ParentComponent.jsx

import ChildComponent from "./ChildComponent";

const ParentComponent = () => {
  return <ChildComponent text="hello"></ChildComponent>;
};

export default ParentComponent;

ChildComponent.jsx

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

export default ChildComponent;

子组件调用父组件的方法

子组件调用父组件的方法和子组件向父组件传递数据是等同的,父组件通过props将回调函数传递给子组件即可,通过回调函数参数即可实现数据传递,下面是一个例子。

ParentComponent.jsx

import ChildComponent from "./ChildComponent";

const ParentComponent = () => {
  const callbackFunc = (data) => {
    console.log(data);
  };

  return <ChildComponent callbackFunc={callbackFunc}></ChildComponent>;
};

export default ParentComponent;

ChildComponent.jsx

const ChildComponent = (props) => {
  return (
    <div>
      <button
        onClick={() => {
          props.callbackFunc("hello");
        }}
      ></button>
    </div>
  );
};

export default ChildComponent;

代码中父组件在子组件的HTML标签上添加了callbackFunc属性,子组件中使用props.callbackFunc接受该函数并调用,此时便可实现调用父组件函数并传递数据了。

父组件调用子组件方法

父组件调用子组件需要用到useRef()获取子组件的引用。

ParentComponent.jsx

import { useRef } from "react";
import ChildComponent from "./ChildComponent";

const ParentComponent = () => {
  const childRef = useRef();

  return (
    <>
      <ChildComponent ref={childRef} />
      <button
        onClick={() => {
          childRef.current.func();
        }}
      >
        Click Me
      </button>
    </>
  );
};

export default ParentComponent;

子组件中用到了React.forwardRef(),该API用于在函数式组件中支持ref的传递;此外我们还用到了useImperativeHandle,它用于在函数式组件中暴露特定的值或方法给父组件。

ChildComponent.jsx

import { forwardRef, useImperativeHandle } from "react";

const ChildComponent = forwardRef((props, ref) => {
  const func = () => {
    console.log("func called");
  };

  useImperativeHandle(ref, () => ({
    func,
  }));

  return <div></div>;
});

export default ChildComponent;

不具有父子关系的组件之间传递数据

React提供了ContextAPI能够实现全局的状态数据,通过全局数据就能够实现任意两个或多个组件之间的数据传递了。在React早期版本中使用Context.ProviderContext.Consumer来设置和读取全局数据,新版本提供了useContext()这个Hooks函数,使用更加方便。

ComponentA.jsx

import React, { useState } from "react";
import ComponentB from "./ComponentB";

export const UserContext = React.createContext({});

const ComponentA = () => {
  const [user, setUser] = useState({
    username: "tom",
    password: "abc123",
  });

  return (
    <UserContext.Provider
      value={{
        user,
        setUser,
      }}
    >
      <ComponentB></ComponentB>
    </UserContext.Provider>
  );
};

export default ComponentA;

ComponentB.jsx

import ComponentC from "./ComponentC";

const ComponentB = () => {
  return (
    <>
      <ComponentC></ComponentC>
    </>
  );
};

export default ComponentB;

ComponentC.jsx

import { useContext } from "react";
import { UserContext } from "./ComponentA";

const ComponentC = () => {
  const { user } = useContext(UserContext);

  return <div>{JSON.stringify(user)}</div>;
};

export default ComponentC;

上面组件代码的嵌套关系如下:

在组件A中我们使用了React.createContext()创建了名为UserContext的对象,<UserContext.Provider>包裹的组件都可以获取它的值。注意这里我们实际上是将组件A的状态数据usersetUser都传递给了Context,实际上Context可以传递任意数据,代码中的写法是为了子组件可以调用user获取数据,调用setUser()修改数据;如果子组件只需要读取数据,那么不传setUser也是完全可以的。

在组件C中我们使用useContext()获取了Context中的数据,并将其JSON形式显示在了页面中。

使用全局的状态参数要小心,如果设计的不合理,很可能使得应用的数据流变得混乱。对于数据流很复杂的应用,应该考虑使用Redux,有关Redux的内容将在后续章节介绍。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。