React---组件通信(发布订阅模式)props、Context

91 阅读7分钟

组件通信的方式有哪些

  • ⽗组件向⼦组件通讯: ⽗组件可以向⼦组件通过传 props 的⽅式,向⼦组件进⾏通讯

  • ⼦组件向⽗组件通讯: props+回调的⽅式,⽗组件向⼦组件传递props进⾏通讯,此props为作⽤域为⽗组件⾃身的函 数,⼦组件调⽤该函数,将⼦组件想要传递的信息,作为参数,传递到⽗组件的作⽤域中

  • 兄弟组件通信: 找到这两个兄弟节点共同的⽗节点,结合上⾯两种⽅式由⽗节点转发信息进⾏通信

  • 跨层级通信: Context 设计⽬的是为了共享那些对于⼀个组件树⽽⾔是“全局”的数据,例如当前认证的⽤户、主题或⾸选语⾔,对于跨越多层的全局数据通过 Context 通信再适合不过

  • 发布订阅模式: 发布者发布事件,订阅者监听事件并做出反应,我们可以通过引⼊event模块进⾏通信

  • 全局状态管理⼯具: 借助Redux或者Mobx等全局状态管理⼯具进⾏通信,这种⼯具会维护⼀个全局状态中⼼Store,并根据不同的事件产⽣新的状态

父子组件通信

父组件向子组件通信:父组件传递数据->子组件标签上绑定属性,子组件接收数据->props的参数

  • 父组件提供要传递的数据
  • 给子组件标签添加属性,属性值为我们要传送的数据
  • 子组件通过props接收父组件中传递的数据
// 父组件
function Father(){
const name = 'test'
 return(
  <div>
   <Child name={name}/>
  </div>
 )
}


// 子组件
function Child(props){  
  return (
    <div>
      {props.name}
    </div>
  )
}

子组件向父组件通信:在子组件中调用父组件中的函数并传递参数,【利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数】

  • 子组件去传值,通过触发父组件的方法,父组件的方法通过props传递到子组件
  • 父组件自定义子组件中的方法
  • 子组件调用父组件中的自定义方法
// 父组件

function Father(){
 const [msg,setMsg] = useState('')
 const getMsg = (msg)=>{
   console.log(msg)
   setMsg(msg)
 }
 return(
  <div>
  {msg}
   <Child onGetSonMsg={getMsg}/>
  </div>
 )
}


// 子组件
function Child({onGetSonMsg}){
  const sonMsg = 'son msg'
  return (
    <div>
     <button onClick={()=>onGetSonMsg(sonMsg)}></button>
    </div>
  )
}

兄弟组件通信:

1、利用共同父组件通信1:通过子传父(A-> Father)2:通过父传子(Father->B)

// 父组件
import React, { useState } from 'react';
import BrotherOne from './BrotherOne';
import BrotherTwo from './BrotherTwo';

const Parent = () => {
  const [sharedData, setSharedData] = useState("");

  const handleData = (data) => {
    setSharedData(data);
  };

  return (
    <div>
      <h1>Parent Component</h1>
      <BrotherOne sendData={handleData} />
      <BrotherTwo receivedData={sharedData} />
    </div>
  );
};

export default Parent;

// 兄弟组件One
import React from 'react';

const BrotherOne = ({ sendData }) => {
  const sendMessage = () => {
    sendData("Hello from Brother One");
  };

  return (
    <div>
      <h2>Brother One</h2>
      <button onClick={sendMessage}>Send Message to Brother Two</button>
    </div>
  );
};

export default BrotherOne;

// 兄弟组件Two
import React from 'react';

const BrotherTwo = ({ receivedData }) => {
  return (
    <div>
      <h2>Brother Two</h2>
      <p>Received: {receivedData}</p>
    </div>
  );
};

export default BrotherTwo;

2、使用Event Bus通信

使用事件总线(Event Bus)来进行兄弟组件间的通信。事件总线可以是一个简单的发布/订阅模式实现,允许组件之间通过事件来传递数据。

// EventBus.js
import { EventEmitter } from 'events';
const eventBus = new EventEmitter();
export default eventBus;

// 兄弟组件One
import React from 'react';
import eventBus from './EventBus';

const BrotherOne = () => {
  const sendMessage = () => {
    eventBus.emit('message', 'Hello from Brother One');
  };

  return (
    <div>
      <h2>Brother One</h2>
      <button onClick={sendMessage}>Send Message</button>
    </div>
  );
};

export default BrotherOne;

// 兄弟组件Two
import React, { useEffect, useState } from 'react';
import eventBus from './EventBus';

const BrotherTwo = () => {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const handleMessage = (msg) => {
      setMessage(msg);
    };

    eventBus.on('message', handleMessage);

    return () => {
      eventBus.off('message', handleMessage);
    };
  }, []);

  return (
    <div>
      <h2>Brother Two</h2>
      <p>Received Message: {message}</p>
    </div>
  );
};

export default BrotherTwo;

解析:

通过引入一个事件总线EventBusBrotherOne可以通过eventBus.emit发布消息,BrotherTwo通过eventBus.on监听消息,实现了无需共同父组件的兄弟组件间通信。这种方法适用于组件层级较深或彼此关系较远的场景。

跨级组件通信:Father->A->B

1、Context API

// ThemeContext.js
import React from 'react';

const ThemeContext = React.createContext('light');

export default ThemeContext;

// 父组件
import React, { useState } from 'react';
import ThemeContext from './ThemeContext';
import DeepChild from './DeepChild';

const Parent = () => {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={theme}>
      <div>
        <h1>Parent Component</h1>
        <button onClick={toggleTheme}>Toggle Theme</button>
        <DeepChild />
      </div>
    </ThemeContext.Provider>
  );
};

export default Parent;

// 深层子组件
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';

const DeepChild = () => {
  const theme = useContext(ThemeContext);
  
  return (
    <div>
      <h2>Deep Child Component</h2>
      <p>Current Theme: {theme}</p>
    </div>
  );
};

export default DeepChild;

通过ThemeContext.Providertheme值提供给整个组件树,DeepChild组件通过useContext钩子直接获取到theme值,无需通过中间层级组件传递props,实现了跨层级的通信。

2、Redux状态管理

// store.js
import { createStore } from 'redux';

const initialState = {
  message: ''
};

const reducer = (state = initialState, action) => {
  switch(action.type) {
    case 'SET_MESSAGE':
      return { ...state, message: action.payload };
    default:
      return state;
  }
};

const store = createStore(reducer);

export default store;

// 父组件
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import BrotherOne from './BrotherOne';
import BrotherTwo from './BrotherTwo';

const Parent = () => {
  return (
    <Provider store={store}>
      <div>
        <h1>Parent Component</h1>
        <BrotherOne />
        <BrotherTwo />
      </div>
    </Provider>
  );
};

export default Parent;

// 兄弟组件One
import React from 'react';
import { useDispatch } from 'react-redux';

const BrotherOne = () => {
  const dispatch = useDispatch();

  const sendMessage = () => {
    dispatch({ type: 'SET_MESSAGE', payload: 'Hello from Brother One' });
  };

  return (
    <div>
      <h2>Brother One</h2>
      <button onClick={sendMessage}>Send Message</button>
    </div>
  );
};

export default BrotherOne;

// 兄弟组件Two
import React from 'react';
import { useSelector } from 'react-redux';

const BrotherTwo = () => {
  const message = useSelector(state => state.message);

  return (
    <div>
      <h2>Brother Two</h2>
      <p>Received Message: {message}</p>
    </div>
  );
};

export default BrotherTwo;

通过Redux的Provider将Store提供给整个应用,BrotherOne组件通过useDispatch发送动作来更新Store,BrotherTwo组件通过useSelector获取Store中的数据,实现了全局状态的共享和更新。

3、使用React hooks

// GlobalState.js
import React, { useReducer, createContext } from 'react';

const initialState = { message: '' };
const GlobalStateContext = createContext(initialState);
const GlobalDispatchContext = createContext(() => {});

const reducer = (state, action) => {
  switch(action.type) {
    case 'SET_MESSAGE':
      return { ...state, message: action.payload };
    default:
      return state;
  }
};

const GlobalProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <GlobalDispatchContext.Provider value={dispatch}>
      <GlobalStateContext.Provider value={state}>
        {children}
      </GlobalStateContext.Provider>
    </GlobalDispatchContext.Provider>
  );
};

export { GlobalProvider, GlobalStateContext, GlobalDispatchContext };

// 父组件
import React from 'react';
import { GlobalProvider } from './GlobalState';
import BrotherOne from './BrotherOne';
import BrotherTwo from './BrotherTwo';

const Parent = () => {
  return (
    <GlobalProvider>
      <div>
        <h1>Parent Component</h1>
        <BrotherOne />
        <BrotherTwo />
      </div>
    </GlobalProvider>
  );
};

export default Parent;

// 兄弟组件One
import React, { useContext } from 'react';
import { GlobalDispatchContext } from './GlobalState';

const BrotherOne = () => {
  const dispatch = useContext(GlobalDispatchContext);

  const sendMessage = () => {
    dispatch({ type: 'SET_MESSAGE', payload: 'Hello from Brother One' });
  };

  return (
    <div>
      <h2>Brother One</h2>
      <button onClick={sendMessage}>Send Message</button>
    </div>
  );
};

export default BrotherOne;

// 兄弟组件Two
import React, { useContext } from 'react';
import { GlobalStateContext } from './GlobalState';

const BrotherTwo = () => {
  const { message } = useContext(GlobalStateContext);

  return (
    <div>
      <h2>Brother Two</h2>
      <p>Received Message: {message}</p>
    </div>
  );
};

export default BrotherTwo;

通过组合useReduceruseContext, GlobalProvider提供了一个全局状态和调度函数。BrotherOne通过useContext获取到dispatch来更新状态,BrotherTwo通过useContext获取到全局状态,实现了跨层级组件的通信。这种方式较为灵活,适合中小型项目。

解决props层级过深的问题

  • 使用Context API:提供一种组件之间的状态共享,而不必通过显式组件树逐层传递props;
  • 使用Redux等状态库。

Props的不可变性

props 不可变指的是,一旦父组件把 props 传递给子组件,子组件就不能直接去改变这些 props

为什么React要遵循Props的不可变性?

  • 单向数据流:React采用单向数据流架构,数据的流动是单向且可预测的。
  • 数据一致性:不可变的props保证数据在不同组件之间的一致性。所有使用相同props的组件都会看到相同的数据,不会因为某个组件修改props导致其他组件的数据不一致
  • 浅比较优化:React通过比较新旧props决定是否需要重新渲染组件。如果 props 是不可变的,React 可以使用浅比较(只比较对象的引用)来快速判断 props 是否发生了变化。如果 props 没有变化,React 可以跳过组件的重新渲染,从而提高性能。
  • 状态管理库:许多React状态管理库都依赖于数据的不可变性。

React如何保证props的不可变性?

  • 不直接修改传入props对象,把props当作只读数据使用
  • 当props是对象或者数组时,若要更新数据,不直接修改原始对象或数组,而是创建新的对象或数组。

若需要修改props的值,怎么解决?

  • 第一种:让父组件来管理状态,子组件通过回调函数通知父组件更新状态。
import React, { useState } from 'react';

// 子组件,接收 value 和 onChange 作为 props
const ChildComponent = (props) => {
    // 定义一个处理点击事件的函数
    const handleClick = () => {
        // 调用父组件传递过来的 onChange 函数,通知父组件更新值
        props.onChange(props.value + 1); 
    };

    return (
        <div>
            {/* 渲染 props 的值 */}
            <p>{props.value}</p> 
            {/* 点击按钮触发 handleClick 函数 */}
            <button onClick={handleClick}>增加</button> 
        </div>
    );
};

// 父组件,负责管理状态
const ParentComponent = () => {
    // 使用 useState 钩子来管理状态
    const [value, setValue] = useState(0); 

    // 定义一个更新值的函数
    const updateValue = (newValue) => {
        // 调用 setValue 函数更新状态
        setValue(newValue); 
    };

    return (
        <div>
            {/* 将 value 和 updateValue 作为 props 传递给 ChildComponent */}
            <ChildComponent value={value} onChange={updateValue} /> 
        </div>
    );
};

export default ParentComponent;

父组件 ParentComponent 用 useState 钩子来管理状态。子组件 ChildComponent 接收 value 和 onChange 作为 props