React 事件、组件通信

一、React 事件系统特点

  1. 合成事件(SyntheticEvent)

    • React 封装了原生事件,提供跨浏览器一致的事件接口。

    • 事件对象会被复用(需通过 e.persist() 保留引用)。

  2. 驼峰命名法
    事件名使用 onClick 而非 onclick

  3. 事件委托
    React 默认在根节点委托事件,通过冒泡机制处理。


二、事件绑定方式

1. JSX 内联绑定

直接在 JSX 中绑定函数(推荐用于简单场景):

function Button() {
  const handleClick = () => {
    console.log("按钮被点击");
  };

  return <button onClick={handleClick}>点击我</button>;
}

2. 传递参数

通过箭头函数或 bind 传递参数:

function List() {
  const handleItemClick = (id, e) => {
    console.log("点击项 ID:", id, "事件对象:", e);
  };

  return (
    <ul>
      {[1, 2, 3].map((item) => (
        <li
          key={item}
          onClick={(e) => handleItemClick(item, e)} // 箭头函数传参
        >
          项目 {item}
        </li>
      ))}
    </ul>
  );
}

三、事件对象(SyntheticEvent)

React 事件对象提供与原生事件一致的属性和方法,常用 API:

  • e.target:触发事件的 DOM 元素。

  • e.currentTarget:绑定事件的 DOM 元素。

  • e.preventDefault():阻止默认行为。

  • e.stopPropagation():阻止冒泡。

访问原生事件

通过 e.nativeEvent 访问底层原生事件:

function HandleNativeEvent() {
  const handleClick = (e) => {
    console.log("原生事件类型:", e.nativeEvent.type);
  };

  return <button onClick={handleClick}>点击查看原生事件</button>;
}

四、常见事件类型与示例

1. 表单事件

function Form() {
  const [inputValue, setInputValue] = useState("");

  const handleChange = (e) => {
    setInputValue(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log("表单提交值:", inputValue);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" value={inputValue} onChange={handleChange} />
      <button type="submit">提交</button>
    </form>
  );
}

2. 鼠标事件

function MouseTracker() {
  const handleMouseMove = (e) => {
    console.log("鼠标位置:", e.clientX, e.clientY);
  };

  return <div onMouseMove={handleMouseMove}>移动鼠标查看坐标</div>;
}

3. 键盘事件

function KeyboardInput() {
  const handleKeyDown = (e) => {
    if (e.key === "Enter") {
      console.log("按下了回车键");
    }
  };

  return <input type="text" onKeyDown={handleKeyDown} />;
}

五、性能优化与最佳实践

1. 避免在 render 中创建新函数

错误示例(导致子组件不必要的重新渲染):

// ❌ 每次渲染都会创建新函数
<button onClick={() => console.log("点击")}>按钮</button>

优化方案:

// ✅ 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
  console.log("点击");
}, []);

return <button onClick={handleClick}>按钮</button>;

2. 阻止默认行为与冒泡

function PreventDemo() {
  const handleLinkClick = (e) => {
    e.preventDefault(); // 阻止链接跳转
    e.stopPropagation(); // 阻止事件冒泡
    console.log("链接点击被阻止");
  };

  return (
    <a href="https://react.dev" onClick={handleLinkClick}>
      阻止跳转的链接
    </a>
  );
}

3. 事件委托优化

利用 e.target 处理动态列表:

function List() {
  const handleListClick = (e) => {
    if (e.target.tagName === "LI") {
      console.log("点击项内容:", e.target.textContent);
    }
  };

  return (
    <ul onClick={handleListClick}>
      <li>项目 1</li>
      <li>项目 2</li>
      <li>项目 3</li>
    </ul>
  );
}

六、常见问题与解决方案

1. 事件处理函数中的 this 为 undefined

  • 原因:类组件中未正确绑定 this

  • 解决:使用箭头函数或在构造函数中绑定。

2. 事件对象属性被重置

  • 现象:异步代码中访问 e.target 为 null

  • 解决:调用 e.persist() 保留事件对象:

    const handleClick = (e) => {
      e.persist(); // 保留事件对象
      setTimeout(() => {
        console.log("异步访问:", e.target);
      }, 1000);
    };

3. React 事件与原生事件混用

  • 注意:React 事件通过 document 委托,原生事件可能先于 React 事件触发。

  • 建议:优先使用 React 事件系统。


  

React 常用通信方式


一、父子组件通信

1. 父传子:Props

  • 场景:父组件向子组件传递数据。

  • 实现

    // 父组件
    function Parent() {
      const data = "Hello from Parent";
      return <Child message={data} />;
    }
    
    // 子组件
    function Child({ message }) {
      return <div>{message}</div>;
    }

2. 子传父:回调函数

  • 场景:子组件通知父组件事件或传递数据。

  • 实现

    // 父组件
    function Parent() {
      const handleChildEvent = (data) => {
        console.log("子组件传递的数据:", data);
      };
      return <Child onEvent={handleChildEvent} />;
    }
    
    // 子组件
    function Child({ onEvent }) {
      return <button onClick={() => onEvent("Child Data")}>触发事件</button>;
    }

二、兄弟组件通信

3. 状态提升(Lifting State Up)

  • 场景:兄弟组件共享同一状态,将状态提升到共同父组件。

  • 实现

    // 父组件
    function Parent() {
      const [sharedState, setSharedState] = useState("");
    
      return (
        <div>
          <ChildA onUpdate={setSharedState} />
          <ChildB state={sharedState} />
        </div>
      );
    }
    
    // 子组件A(更新状态)
    function ChildA({ onUpdate }) {
      return <input onChange={(e) => onUpdate(e.target.value)} />;
    }
    
    // 子组件B(显示状态)
    function ChildB({ state }) {
      return <div>当前值: {state}</div>;
    }

三、跨层级组件通信

4. Context API

  • 场景:跨多层组件传递数据,避免逐层传递 Props。

  • 实现

    // 1. 创建 Context
    const ThemeContext = React.createContext("light");
    
    // 2. 父组件提供数据
    function App() {
      return (
        <ThemeContext.Provider value="dark">
          <Toolbar />
        </ThemeContext.Provider>
      );
    }
    
    // 3. 深层子组件消费数据
    function Toolbar() {
      return <ThemedButton />;
    }
    
    function ThemedButton() {
      const theme = useContext(ThemeContext); // 直接获取 Context 值
      return <button>当前主题: {theme}</button>;
    }

四、全局状态管理

5. Redux 或 Zustand

  • 场景:复杂应用中多个组件共享全局状态。

  • 实现(Redux 示例)

    // 1. 定义 Store 和 Reducer
    const store = createStore((state = { count: 0 }, action) => {
      switch (action.type) {
        case "INCREMENT":
          return { count: state.count + 1 };
        default:
          return state;
      }
    });
    
    // 2. 父组件包裹 Provider
    function App() {
      return (
        <Provider store={store}>
          <Counter />
        </Provider>
      );
    }
    
    // 3. 子组件连接 Store
    function Counter() {
      const count = useSelector((state) => state.count);
      const dispatch = useDispatch();
      return (
        <div>
          <button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button>
          <p>Count: {count}</p>
        </div>
      );
    }

五、非父子组件通信

6. Event Bus / 发布订阅模式

  • 场景:任意组件间通信(慎用,可能破坏 React 数据流)。

  • 实现

    // 1. 创建事件总线
    const eventBus = new EventEmitter();
    
    // 2. 组件A发布事件
    function ComponentA() {
      return (
        <button onClick={() => eventBus.emit("event", "Data from A")}>
          发送事件
        </button>
      );
    }
    
    // 3. 组件B订阅事件
    function ComponentB() {
      const [data, setData] = useState("");
    
      useEffect(() => {
        eventBus.on("event", setData);
        return () => eventBus.off("event", setData); // 清理订阅
      }, []);
    
      return <div>接收数据: {data}</div>;
    }

六、其他通信方式

7. Ref 传递(forwardRef + useImperativeHandle)

  • 场景:父组件直接调用子组件方法或操作 DOM。

  • 实现

    // 子组件
    const Child = forwardRef((props, ref) => {
      useImperativeHandle(ref, () => ({
        childMethod: () => {
          console.log("子组件方法被调用");
        },
      }));
    
      return <div>子组件</div>;
    });
    
    // 父组件
    function Parent() {
      const childRef = useRef();
    
      return (
        <div>
          <Child ref={childRef} />
          <button onClick={() => childRef.current.childMethod()}>
            调用子组件方法
          </button>
        </div>
      );
    }

七、通信方式对比与选择

方式适用场景优点缺点
Props父子组件简单数据传递简单直观深层嵌套时需逐层传递
Context API跨层级组件共享主题、用户信息等避免 Props 逐层传递不适合频繁更新的数据
Redux/Zustand复杂全局状态管理状态集中管理,易于调试增加项目复杂度
Event Bus非父子组件解耦通信高度灵活破坏 React 单向数据流
Ref 传递父组件操作子组件 DOM 或方法直接访问子组件实例违反组件封装原则,慎用

八、最佳实践

  1. 优先使用 Props 和 Context:保持数据流清晰。

  2. 避免过度使用 Redux:仅在全局状态复杂时引入。

  3. 慎用 Event Bus:容易导致代码难以维护。

  4. 优化性能:结合 React.memouseMemouseCallback 减少不必要的渲染。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值