React 19 Props 和 react-icons 和 事件处理函数

  • Props 是从父组件向子组件传递数据的方式。你可以将它们想象成 HTML 标签的属性,但它们可以传递更丰富的数据类型(不仅仅是字符串)。
  • 它们是组件接收的参数,用于配置组件的外观和行为。
  • "Props" 是 "Properties" 的缩写。
  • Props 是只读 (Read-Only) 的。子组件永远不应该尝试直接修改它接收到的 props。

为什么需要 Props?

  1. 组件复用 (Reusability): Props 允许你创建通用的、可复用的组件。同一个组件可以通过接收不同的 props 来显示不同的内容或具有不同的行为。例如,一个 Button 组件可以通过 props 接收不同的文本 (label)、颜色 (color) 和点击事件处理函数 (onClick)。
  2. 数据流 (Data Flow): React 遵循单向数据流(从父到子)。Props 是实现这种数据流的主要方式,使得应用程序的状态变化更易于追踪和理解。
  3. 组件配置 (Configuration): 父组件使用 props 来配置子组件应该如何渲染和工作。

如何使用 Props?

使用 Props 分为两个主要步骤:

1. 在父组件中传递 Props:

  • 当你渲染一个子组件时,可以在 JSX 标签上像添加 HTML 属性一样添加 props。

  • 传递字符串: 使用引号 ""。

    js 复制代码
    <Greeting message="Hello World!" />
  • 传递 JavaScript 表达式 (变量、数字、布尔值、对象、数组、函数等): 使用花括号 {}。

    js 复制代码
    const userName = "Alice";
    const userAge = 30;
    const isLoggedIn = true;
    const userProfile = { avatar: '/path/to/avatar.jpg' };
    const items = ['apple', 'banana'];
    const handleClick = () => alert('Button clicked!');
    
    <UserProfile
      name={userName}
      age={userAge}
      loggedIn={isLoggedIn}
      profileData={userProfile}
      itemList={items}
      onButtonClick={handleClick} // 传递函数引用
      isEnabled // 属性存在即为 true (等同于 isEnabled={true})
    />
        
  • 传递布尔值 true 的简写: 只写属性名,不写值,相当于传递了 true (如上例的 isEnabled)。

  • 使用展开运算符传递 Props (...): 如果你有一个对象,其属性与子组件期望的 props 名称相匹配,你可以使用展开运算符一次性传递它们。

    js 复制代码
      const buttonProps = {
      label: "Submit",
      onClick: handleSubmit,
      disabled: false,
      theme: "primary"
    };
    
    <Button {...buttonProps} />
    // 等同于:
    // <Button label={buttonProps.label} onClick={buttonProps.onClick} disabled={buttonProps.disabled} theme={buttonProps.theme} />
        

2. 在子组件中接收和使用 Props:

  • 函数组件 (推荐方式): Props 对象会作为第一个参数传递给函数组件。

    • 直接访问 props 对象:

      js 复制代码
      function Greeting(props) {
        // props 是一个对象: { message: "Hello World!" }
        return <h1>{props.message}</h1>;
      }
      
      function UserProfile(props) {
        // props: { name: "Alice", age: 30, loggedIn: true, ... }
        return (
          <div>
            <h2>{props.name} ({props.age})</h2>
            {props.loggedIn && <p>Status: Logged In</p>}
            <button onClick={props.onButtonClick}>Click Me</button>
          </div>
        );
      }
          
    • 使用参数解构 (更常用): 在函数参数位置直接解构 props 对象,使代码更简洁,更清晰地表明组件依赖哪些 props。

      js 复制代码
      // 直接解构 props 参数
      function Greeting({ message }) {
        return <h1>{message}</h1>;
      }
      
      function UserProfile({ name, age, loggedIn, onButtonClick }) {
        return (
          <div>
            <h2>{name} ({age})</h2>
            {loggedIn && <p>Status: Logged In</p>}
            <button onClick={onButtonClick}>Click Me</button>
          </div>
        );
      }
          
  • 类组件 (较少用于新项目, 我们在以后不会使用,了解即可): Props 可以通过 this.props 访问。

    js 复制代码
    class GreetingClass extends React.Component {
      render() {
        // 通过 this.props 访问
        return <h1>{this.props.message}</h1>;
      }
    }
        

Props 的核心特性:只读性 (Read-Only)

这是一个极其重要 的规则:组件永远不应该修改它自己的 props。

  • 无论是函数组件还是类组件,都应将接收到的 props 视为只读的。
  • 如果组件需要根据 props 的初始值来改变某些数据(例如,用户输入、计时器更新),它应该将 props 的值用作内部状态 (state) 的初始值。
js 复制代码
import  { useState } from 'react';

function Counter({ initialCount = 0 }) { // 使用默认 props 值
  // 使用 props.initialCount 初始化 state
  // count 是组件自己的状态,可以修改
  const [count, setCount] = useState(initialCount);

  // 错误!不能直接修改 props
  // props.initialCount = 10; // <--- DON'T DO THIS!

  const increment = () => {
    setCount(prevCount => prevCount + 1); // 修改自己的 state
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      {/* 可以显示 props,但不能修改它 */}
      <p><small>Initial count was: {initialCount}</small></p>
    </div>
  );
}

function App() {
  return <Counter initialCount={5} />;
}

export default App;
    

特殊 Prop: children

当你像使用普通 HTML 标签一样,在组件的开始和结束标签之间放置内容时,这些内容会通过一个名为 children 的特殊 prop 传递给该组件。

js 复制代码
// 父组件
function App() {
  return (
    <Card title="Welcome Card">
      {/* 这部分内容会作为 children prop 传递给 Card */}
      <h1>Hello!</h1>
      <p>This content is inside the Card component.</p>
      <button>Click Me</button>
    </Card>
  );
}

// 子组件 (Card)
function Card({ title, children }) { // 解构出 children
  return (
    <div style={{ border: '1px solid #ccc', padding: '15px', margin: '10px' }}>
      {title && <h2>{title}</h2>}
      {/* 在这里渲染 children prop */}
      {children}
    </div>
  );
}

export default App;
    

children prop 非常适合创建布局组件、容器组件或任何需要包裹其他内容的通用组件。

总结:

  • Props 是 React 组件间传递数据的主要方式(父 -> 子)。
  • 它们使得组件可配置、可复用。
  • 在父组件中通过类 HTML 属性语法传递。
  • 在子组件(函数组件)中通过函数参数 props (或解构) 接收。
  • Props 是只读的,子组件不应修改它们。
  • children 是一个特殊 prop,代表组件标签之间的内容。

react-icons 是一个非常流行且方便的库,它将许多流行的图标库(如 Font Awesome, Material Design Icons, Bootstrap Icons 等)打包成了易于在 React 项目中使用的 SVG 图标组件。

1. 安装 react-icons

首先,你需要将 react-icons 添加到你的项目依赖中。打开你的项目终端,运行以下命令之一(根据你使用的包管理器):

bash 复制代码
# 使用 npm
npm install react-icons --save

# 使用 yarn
yarn add react-icons

# 使用 pnpm
pnpm add react-icons
    

2. 查找你需要的图标

react-icons 包含来自多个图标集的数千个图标。最好的查找方式是访问官方的 react-icons 网站(或其 GitHub 页面上的搜索功能):

在这个网站上,你可以搜索图标名称,它会告诉你该图标属于哪个图标集,以及如何导入它。

注意每个图标集的两字母前缀 ,这在导入时非常重要(例如 Fa 代表 Font Awesome, Md 代表 Material Design, Bs 代表 Bootstrap Icons, Ai 代表 Ant Design Icons 等)。

3. 在你的组件中导入图标

react-icons 最棒的一点是它支持 tree-shaking。这意味着你只需要导入你实际使用的图标,最终打包构建时只会包含这些图标的代码,而不会包含整个图标库,从而减小了包的大小。

导入的语法是:

js 复制代码
  import { IconName } from 'react-icons/xx';
    
  • IconName: 你在网站上找到的具体图标名称(通常是驼峰命名法,首字母大写)。
  • xx: 图标集对应的小写两字母前缀。

示例:

js 复制代码
// 从 Font Awesome 导入啤酒图标
import { FaBeer } from 'react-icons/fa';

// 从 Material Design 导入设置图标
import { MdSettings } from 'react-icons/md';

// 从 Bootstrap Icons 导入检查图标
import { BsCheckCircleFill } from 'react-icons/bs';

// 从 Ant Design Icons 导入用户图标
import { AiOutlineUser } from 'react-icons/ai';

function MyComponent() {
  return (
    <div>
      <h2>Here are some icons:</h2>
      <p>
        A beer icon from Font Awesome: <FaBeer />
      </p>
      <p>
        A settings icon from Material Design: <MdSettings />
      </p>
      <p>
        A check icon from Bootstrap Icons: <BsCheckCircleFill />
      </p>
      <p>
        A user icon from Ant Design Icons: <AiOutlineUser />
      </p>
    </div>
  );
}

export default MyComponent;
    

4. 使用图标组件

导入后,你可以像使用其他任何 React 组件一样,直接在你的 JSX 中使用这些图标组件。

js 复制代码
<FaBeer />
<MdSettings />
<BsCheckCircleFill />
    

5. 自定义图标样式 (大小、颜色等)

由于 react-icons 渲染的是 SVG 元素,你可以通过以下几种方式来自定义它们的样式:

  • CSS 类 (className) : 给图标组件添加 className 属性,然后在你的 CSS 文件中定义样式。这是最推荐的方式之一。

    js 复制代码
    // 在 JSX 中
    <FaBeer className="my-beer-icon" />
    
    // 在你的 CSS 文件 (e.g., App.css or a CSS module)
    .my-beer-icon {
      color: orange;
      font-size: 2rem; /* 使用 font-size 控制 SVG 大小很常见 */
      vertical-align: middle; /* 调整垂直对齐 */
      margin-right: 8px;
    }
        
  • 内联样式 (style) : 直接使用 style prop 传递样式对象。

    js 复制代码
     <MdSettings style={{ color: 'grey', fontSize: '24px' }} />   
  • IconContext.Provider (全局或局部设置) : react-icons 提供了一个 IconContext,允许你为嵌套在 Provider 内的所有图标设置通用的 props (如 color, size, className, style, attr)。这对于在某个区域内统一图标样式非常有用。

    js 复制代码
    import { IconContext } from "react-icons";
    import { FaFolder, FaRegFileAlt } from 'react-icons/fa';
    
    function FileBrowser() {
      return (
        // 使用 Provider 为内部所有图标设置默认颜色和大小
        <IconContext.Provider value={{ color: "blue", size: "1.5em", className: "global-class-name" }}>
          <div>
            <h3>Files</h3>
            <p>
              <FaFolder /> <span>Documents</span>
            </p>
            <p>
              {/* 可以覆盖 Provider 的设置 */}
              <FaRegFileAlt style={{ color: 'red' }} /> <span>Report.pdf</span>
            </p>
            <p>
               {/* 继承 Provider 的设置 */}
              <FaRegFileAlt /> <span>Notes.txt</span>
            </p>
          </div>
        </IconContext.Provider>
      );
    }
    
    export default FileBrowser;
        

    IconContext.Provider 的 value 对象可以接受以下属性:

    • color: CSS 颜色值。
    • size: CSS 字体大小值 (e.g., '2em', '20px')。
    • className: 添加到所有图标的 CSS 类名。
    • style: 合并到所有图标的内联样式对象。
    • attr: 添加到 SVG 元素的额外属性对象 (e.g., { fill: 'currentColor' })。

总结:

  1. 安装 react-icons。
  2. 访问官网查找所需图标及其导入路径 (react-icons/xx)。
  3. 从特定图标集的子目录导入你需要的图标组件 (import { IconName } from 'react-icons/xx';)。
  4. 像普通 React 组件一样在 JSX 中使用 。
  5. 使用 className, style, 或 IconContext.Provider 来自定义图标的大小、颜色和其他样式。

react-icons 是一个非常强大且易于使用的库,可以极大地丰富你的 React 应用界面。

React 中常用的事件处理函数 (Event Handlers)。

React 的事件处理方式与原生 DOM 事件处理非常相似,但有一些关键的区别和约定:

  1. 命名约定 (Camel Case): React 事件名称采用小驼峰式命名 (camelCase),而不是原生 HTML 的全小写。例如,HTML 的 onclick 在 React 中是 onClick,onchange 是 onChange。

  2. 传递函数: 你传递一个函数引用作为事件处理程序,而不是像 HTML 那样传递一个字符串。

    html 复制代码
     // HTML
    // <button onclick="handleClick()">Click Me</button>
    
    // React (函数引用)
    <button onClick={handleClick}>Click Me</button>
        
  3. 阻止默认行为: 在 React 中,你不能通过返回 false 来阻止事件的默认行为。必须显式地调用事件对象的 preventDefault() 方法。

  4. 合成事件 (SyntheticEvent): React 实现了一个跨浏览器的合成事件系统。当你传递事件处理函数时,它接收到的参数是一个 SyntheticEvent 实例,它包装了浏览器的原生事件对象,并提供了一致的 API (例如 e.preventDefault(), e.stopPropagation(), e.target 等)。

常用的事件处理函数及其用途:

  1. onClick

    • 触发时机: 用户点击元素时(鼠标左键单击,或触摸屏点击)。

    • 常用元素: <button>, <a>, <div>, 几乎任何可交互元素。

    • 示例:

      js 复制代码
      import { useState } from 'react';
      
      function ClickCounter() {
        const [count, setCount] = useState(0);
      
        const handleClick = (event) => {
          console.log('Button clicked!', event); // event 是 SyntheticEvent
          setCount(prevCount => prevCount + 1);
        };
      
        return (
          <div>
            <p>Count: {count}</p>
            <button onClick={handleClick}>Click Me</button>
          </div>
        );
      }
          
  2. onChange

    • 触发时机: 表单元素(如 <input>, <textarea>, <select>)的值发生改变 时。这是实现受控组件 (Controlled Components) 的关键事件。

    • 常用元素: <input>, <textarea>, <select>, <input type="checkbox">, <input type="radio">

    • 示例 (Input):

      js 复制代码
      import { useState } from 'react';
      
      function NameForm() {
        const [name, setName] = useState('');
      
        const handleChange = (event) => {
          // 通过 event.target.value 获取输入框的当前值
          setName(event.target.value);
        };
      
        return (
          <form>
            <label>
              Name:
              <input type="text" value={name} onChange={handleChange} />
            </label>
            <p>You typed: {name}</p>
          </form>
        );
      }
          
    • 示例 (Checkbox):

      js 复制代码
      function CheckboxExample() {
          const [isChecked, setIsChecked] = useState(false);
          const handleCheckChange = (event) => {
              // 对于 checkbox, 状态在 event.target.checked
              setIsChecked(event.target.checked);
          }
          return (
               <label>
                  <input type="checkbox" checked={isChecked} onChange={handleCheckChange} />
                  Is active? {isChecked ? 'Yes' : 'No'}
               </label>
          );
      }
          
  3. onSubmit

    • 触发时机:<form> 元素被提交时(通常是通过点击类型为 submit 的按钮或在表单输入框中按 Enter 键)。

    • 常用元素: <form>注意: onSubmit 是加在 <form> 标签上,而不是提交按钮上。

    • 重要: 通常需要调用 event.preventDefault() 来阻止浏览器默认的表单提交(页面刷新)行为。

    • 示例:

      js 复制代码
      import  { useState } from 'react';
      
      function LoginForm() {
        const [username, setUsername] = useState('');
      
        const handleUsernameChange = (e) => setUsername(e.target.value);
      
        const handleSubmit = (event) => {
          event.preventDefault(); // 阻止页面刷新
          alert(`Submitting username: ${username}`);
          // 在这里可以执行 API 调用等操作
        };
      
        return (
          <form onSubmit={handleSubmit}> {/* onSubmit 在 form 标签上 */}
            <label>
              Username:
              <input type="text" value={username} onChange={handleUsernameChange} />
            </label>
            <button type="submit">Login</button> {/* 按钮类型为 submit */}
          </form>
        );
      }
          
  4. onMouseEnter / onMouseLeave

    • 触发时机:

      • onMouseEnter: 鼠标指针进入元素的边界时触发。
      • onMouseLeave: 鼠标指针离开元素的边界时触发。
    • 与 onMouseOver/onMouseOut 的区别: onMouseEnter/onMouseLeave 不会因为鼠标在子元素之间移动而冒泡触发,通常用于处理悬停效果更方便。

    • 常用元素: 任何需要响应鼠标悬停的元素。

    • 示例:

      js 复制代码
      import { useState } from 'react';
      
      function HoverBox() {
        const [isHovering, setIsHovering] = useState(false);
      
        const handleMouseEnter = () => setIsHovering(true);
        const handleMouseLeave = () => setIsHovering(false);
      
        const style = {
          width: '100px',
          height: '100px',
          backgroundColor: isHovering ? 'lightblue' : 'lightgray',
          border: '1px solid black',
          textAlign: 'center',
          lineHeight: '100px',
          cursor: 'pointer'
        };
      
        return (
          <div
            style={style}
            onMouseEnter={handleMouseEnter}
            onMouseLeave={handleMouseLeave}
          >
            {isHovering ? 'Hovering!' : 'Hover me'}
          </div>
        );
      }
          
  5. onFocus / onBlur

    • 触发时机:

      • onFocus: 元素获得焦点时(例如,点击输入框或使用 Tab 键导航到它)。
      • onBlur: 元素失去焦点时(例如,点击到其他地方或 Tab 到下一个元素)。
    • 常用元素: <input>, <textarea>, <select>, <button>, <a> 等可聚焦元素。

    • 示例:

      js 复制代码
      import  { useState } from 'react';
      
      function FocusInput() {
        const [isFocused, setIsFocused] = useState(false);
        const [value, setValue] = useState('');
      
        const handleFocus = () => setIsFocused(true);
        const handleBlur = () => setIsFocused(false);
        const handleChange = (e) => setValue(e.target.value);
      
        const inputStyle = {
          borderColor: isFocused ? 'blue' : 'gray',
          outline: 'none',
          padding: '5px',
          margin: '5px'
        };
      
        return (
          <div>
            <input
              type="text"
              value={value}
              style={inputStyle}
              onFocus={handleFocus}
              onBlur={handleBlur}
              onChange={handleChange}
              placeholder={isFocused ? "Typing..." : "Click to focus"}
            />
            {isFocused && <p style={{ color: 'blue', fontSize: 'small' }}>Input is focused!</p>}
          </div>
        );
      }
          
  6. onKeyDown / onKeyUp / onKeyPress (较少用)

    • 触发时机:

      • onKeyDown: 按下键盘按键时。
      • onKeyUp: 释放键盘按键时。
      • onKeyPress: 按下产生字符值的键时(如字母、数字;像 Shift, Ctrl, Alt 通常不会触发 onKeyPress)。onKeyPress 正在被废弃,推荐使用 onKeyDown。
    • 用途: 监听特定的键盘输入,例如实现快捷键、在输入框按 Enter 提交等。

    • 事件对象属性: event.key (推荐, 如 'Enter', 'a'), event.keyCode (数字码, 不推荐), event.shiftKey, event.ctrlKey, event.altKey。

    • 示例 (Enter 键提交):

      js 复制代码
      function EnterSubmitInput() {
          const [text, setText] = useState('');
      
          const handleChange = (e) => setText(e.target.value);
      
          const handleKeyDown = (event) => {
              if (event.key === 'Enter') {
                  event.preventDefault(); // 可能需要阻止默认行为(如换行)
                  console.log('Submitting on Enter:', text);
                  // 这里可以调用提交函数
                  setText(''); // 清空输入框
              }
          }
      
          return (
              <input
                  type="text"
                  value={text}
                  onChange={handleChange}
                  onKeyDown={handleKeyDown}
                  placeholder="Type and press Enter"
              />
          );
      }
          
  7. onScroll

    • 触发时机: 当元素的滚动条滚动时。

    • 常用元素: 具有滚动条的 <div>, <textarea>, 或者 window 对象。

    • 注意: 滚动事件会非常频繁 地触发,在处理函数中执行复杂操作可能导致性能问题。通常需要结合防抖 (Debounce)节流 (Throttle) 技术来优化。

    • 示例 (监听窗口滚动):

      js 复制代码
      import { useState, useEffect } from 'react';
      
      function ScrollTracker() {
        const [scrollY, setScrollY] = useState(window.scrollY);
      
        const handleScroll = () => {
          console.log('Scroll event fired'); // 会频繁打印
          setScrollY(window.scrollY);
        };
      
        useEffect(() => {
          // 组件挂载时添加监听器
          window.addEventListener('scroll', handleScroll);
      
          // 组件卸载时移除监听器 (重要!)
          return () => {
            window.removeEventListener('scroll', handleScroll);
          };
        }, []); // 空依赖数组,确保只添加/移除一次
      
        return (
          <div style={{ position: 'fixed', top: 0, left: 0, background: 'lightyellow', padding: '5px' }}>
            Window Scroll Y: {scrollY}px
          </div>
        );
      }
          

关键点总结:

  • 使用小驼峰命名 (onClick, onChange)。
  • 传递函数引用给事件处理器。
  • 在需要时使用 event.preventDefault() 阻止默认行为。
  • onChange 对于处理表单输入至关重要(受控组件)。
  • onSubmit 用于处理表单提交,加在 上。
  • 理解合成事件 SyntheticEvent 对象及其常用属性 (target, key等) 和方法 (preventDefault, stopPropagation)。
  • 对于高频事件(如 onScroll, onMouseMove),考虑性能优化(防抖/节流)。
  • 在 useEffect 中添加/移除全局事件监听器(如 window.addEventListener)时,务必在返回的清理函数中移除监听器,防止内存泄漏。
相关推荐
泯泷13 分钟前
「译」解析 JavaScript 中的循环依赖
前端·javascript·架构
抹茶san16 分钟前
前端实战:从 0 开始搭建 pnpm 单一仓库(1)
前端·架构
Senar43 分钟前
Web端选择本地文件的几种方式
前端·javascript·html
烛阴1 小时前
UV Coordinates & Uniforms -- OpenGL UV坐标和Uniform变量
前端·webgl
姑苏洛言1 小时前
扫码小程序实现仓库进销存管理中遇到的问题 setStorageSync 存储大小限制错误解决方案
前端·后端
烛阴1 小时前
JavaScript 的 8 大“阴间陷阱”,你绝对踩过!99% 程序员崩溃瞬间
前端·javascript·面试
lh_12542 小时前
ECharts 地图开发入门
前端·javascript·echarts
jjw_zyfx2 小时前
成熟的前端vue vite websocket,Django后端实现方案包含主动断开websocket连接的实现
前端·vue.js·websocket
Mikey_n2 小时前
前台调用接口的方式及速率对比
前端