【React】完整代码,多种方式实现跨组件通信和调用其他组件的函数


前言

公司来了一批新的实习生,有几位同学没有使用过React,在项目中使用React时遇到了跨组件通信的问题,在和新人传授经验的同时想着记录下来方便后面新人培训使用。


假设有A,B两个组件。模拟从A调用B的函数,进行操作或返回某个值

一、使用上下文(Context)和钩子(Hook)

借助 React 的上下文 API 和自定义钩子,能够实现跨层级的函数调用。
首先,创建一个上下文:

// context/FunctionContext.tsx
import React, { createContext, useContext, useState } from 'react';

type FunctionContextType = {
  bFunction: () => void;
  setBFunction: (fn: () => void) => void;
};

const FunctionContext = createContext<FunctionContextType | undefined>(undefined);

export const FunctionProvider = ({ children }: { children: React.ReactNode }) => {
  const [bFunction, setBFunction] = useState<() => void>(() => () => {});
  
  return (
    <FunctionContext.Provider value={{ bFunction, setBFunction }}>
      {children}
    </FunctionContext.Provider>
  );
};

export const useFunctionContext = () => {
  const context = useContext(FunctionContext);
  if (!context) {
    throw new Error('useFunctionContext must be used within a FunctionProvider');
  }
  return context;
};

接着,在 B 组件中注册函数:

// components/BComponent.tsx
import React, { useEffect } from 'react';
import { useFunctionContext } from '../context/FunctionContext';

const BComponent = () => {
  const { setBFunction } = useFunctionContext();

  const handleClick = () => {
    console.log('B组件的函数被调用了');
  };

  useEffect(() => {
    setBFunction(handleClick);
    return () => {
      // 清理函数
      setBFunction(() => () => {});
    };
  }, []);

  return <div>B组件</div>;
};

export default BComponent;

最后,在 A 组件中调用 B 组件的函数:

// components/AComponent.tsx
import React from 'react';
import { useFunctionContext } from '../context/FunctionContext';

const AComponent = () => {
  const { bFunction } = useFunctionContext();

  const handleCallBFunction = () => {
    bFunction();
  };

  return (
    <div>
      <button onClick={handleCallBFunction}>调用B组件的函数</button>
    </div>
  );
};

export default AComponent;

在页面中使用这些组件:

// pages/index.tsx
import React from 'react';
import { FunctionProvider } from '../context/FunctionContext';
import AComponent from '../components/AComponent';
import BComponent from '../components/BComponent';

const HomePage = () => {
  return (
    <FunctionProvider>
      <div>
        <AComponent />
        <BComponent />
      </div>
    </FunctionProvider>
  );
};

export default HomePage;

二、使用事件总线(Event Bus)

用过Vue的应该比较熟悉这个,不过event bus更适合用在函数调用,获取变量值推荐另两种方案。(也能获取变量值,比较麻烦需要注册两个事件)

创建一个全局事件总线,以此实现组件间的通信。

// utils/eventBus.ts
type EventHandler = (...args: any[]) => void;

class EventBus {
  private events: Record<string, EventHandler[]> = {};

  on(eventName: string, handler: EventHandler) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(handler);
  }

  off(eventName: string, handler: EventHandler) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(h => h !== handler);
    }
  }

  emit(eventName: string, ...args: any[]) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(handler => handler(...args));
    }
  }
}

export const eventBus = new EventBus();

B 组件监听事件:

// components/BComponent.tsx
import React, { useEffect } from 'react';
import { eventBus } from '../utils/eventBus';

const BComponent = () => {
  const handleFunctionCall = () => {
    console.log('B组件的函数被调用了');
  };

  useEffect(() => {
    eventBus.on('callBFunction', handleFunctionCall);
    return () => {
      eventBus.off('callBFunction', handleFunctionCall);
    };
  }, []);

  return <div>B组件</div>;
};

export default BComponent;

A 组件触发事件:

// components/AComponent.tsx
import React from 'react';
import { eventBus } from '../utils/eventBus';

const AComponent = () => {
  const handleCallBFunction = () => {
    eventBus.emit('callBFunction');
  };

  return (
    <div>
      <button onClick={handleCallBFunction}>调用B组件的函数</button>
    </div>
  );
};

export default AComponent;

三、使用状态管理库(如 Redux 或 Zustand)

以 Zustand 为例
创建Store

// store/functionStore.ts
import create from 'zustand';

type FunctionStore = {
  bFunction: () => void;
  setBFunction: (fn: () => void) => void;
};

export const useFunctionStore = create<FunctionStore>((set) => ({
  bFunction: () => {},
  setBFunction: (fn) => set({ bFunction: fn }),
}));

B 组件注册函数:

// components/BComponent.tsx
import React, { useEffect } from 'react';
import { useFunctionStore } from '../store/functionStore';

const BComponent = () => {
  const setBFunction = useFunctionStore((state) => state.setBFunction);

  const handleClick = () => {
    console.log('B组件的函数被调用了');
  };

  useEffect(() => {
    setBFunction(handleClick);
    return () => {
      setBFunction(() => () => {});
    };
  }, [setBFunction]);

  return <div>B组件</div>;
};

export default BComponent;

A 组件调用函数:

// components/AComponent.tsx
import React from 'react';
import { useFunctionStore } from '../store/functionStore';

const AComponent = () => {
  const bFunction = useFunctionStore((state) => state.bFunction);

  const handleCallBFunction = () => {
    bFunction();
  };

  return (
    <div>
      <button onClick={handleCallBFunction}>调用B组件的函数</button>
    </div>
  );
};

export default AComponent;

总结和方案选择

  • 当组件层级较深时,建议使用上下文(Context)。
  • 若组件关系灵活,事件总线更为合适。
  • 对于复杂的应用场景,状态管理库是不错的选择。

要避免在函数式组件中使用 refs,因为它不太适合这种场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值