React 的基本概念介绍

React 的简单介绍

  • 特色 - 组件化:像搭乐高一样,把页面拆分成一个个独立、可复用的"零件"(组件),然后组合起来。
  • 特色 - 虚拟DOM:虚拟DOM带来两个好处:
  1. 性能卓越: 每次数据变更,只会更新真正产生变更的地方
  2. 跨平台:虚拟DOM把HTML的DOM节点抽象成了一个JS的对象,所以React理论可以运行在所有支持js的环境中,如:nodejs(nextjs)、移动APP(React Native)、PC平台(electron)

a. 使用在线编辑器:访问 codesandbox.io ,选择 "React" 模板

b. 本地开发(推荐最终方式):使用 Create React App

  • 这是React官方推荐的脚手架工具,能一键生成项目环境。

  • 只需要一行命令 (确保已安装Node.js):

    bash 复制代码
    npx create-react-app react-demo
    cd react-demo
    npm start
  • 执行后,一个React开发环境就在 http://localhost:3000 运行起来了!


1. JSX (JavaScript XML) - 在JS里写"HTML"

1.1 什么是JSX?

JavaScript的语法扩展,允许我们在JavaScript代码中直接编写类似HTML的结构。在我的理解中JSX就是一个描述UI结构的字符串。

  • 示例

    js 复制代码
    // js生成HTML片段版本
    const name = 'World'
    const time = '2025-09-01'
    const element = '<div>'+
                      '<h1>Hello, ' + name + '!</h1>' +
                      '<p>当前时间:' + time + '</p>' +
                    '</div>';
    js 复制代码
    // 使用ES6语法优化版本
    const name = 'World'
    const time = '2025-09-01'
    const element = `
      <div>
        <h1>Hello, ${ name }!</h1>
        <p>当前时间:${ time }</p>
      </div>
    `;
    jsx 复制代码
    // React jsx 版本
    const [ name, nameSet ] = useState('World');
    const [ time, timeSet ] = useState('2025-09-01');
    const element = (
      <div>
        <h1>Hello, { name }!</h1>
        <input onChange={ change }/>
        <p>当前时间:{ time }</p>
      </div>
    )

1.2 在JSX中嵌入表达式

使用花括号 {} 可以嵌入任何有效的JavaScript表达式,如:if 语句、 for 循环或者三目运算等

jsx 复制代码
const [ logged, loggedSet ] = useState(false);
const [ name, nameSet ] = useState('World');
const [ time, timeSet ] = useState('2025-09-01');
const [ devices, devicesSet ] = useState([
  { time: '2025-01-01', device: 'Android', address: '北京市朝阳区' },
  { time: '2025-05-01', device: 'IPhone 16 Pro', address: '深圳市福田区' },
  { time: '2025-09-01', device: 'Chrome 139.0.0.0', address: '深圳市福田区' },
]);

const list = (
  {/*
    JSX表达式必须有一个父元素包裹,通常使用 `<div>` 或 React Fragment `<></>`, 在React Native中一般是View; 使用React Fragment`<></>`,不会产生额外的DOM元素
  */}
  <>
    <h3>登录设备:</h3>
    {
      devices.map(item => (
        <div>
          <div>{ item.time }</div> 
          <div>{ item.device }</div> 
          <div>{ item.address }</div> 
        </div>
      ))
    }
  </>
)

const element = (
  {/* 使用 `className` 而不是 `class`(因为class是JS的保留字)。 */}
  <div className={ logged ? '' : 'disabled' }>
    <h1>Hello, { name }!</h1>

    {/* 所有标签都必须闭合,如 `<img />` 或 `<input />`。 */}
    <img src={ user.avatarUrl }></img>
    <input onChange={ change }/>

    <p>当前时间:{ time }</p>
    { list }
  </div>
)

2. 组件 - 应用的基石

使用组件可以将UI拆分为独立、可复用的代码片段。在React中组件有两种书写方式:函数组件和类组件。

2.1 函数组件 (推荐) 最简单的定义组件的方式是编写一个JavaScript函数。

jsx 复制代码
// 定义一个组件,这里注意有两个关键元素:
// 1. 组件名称: Welcome (函数名必须大写)
// 2. 外部状态: props (必须是一个对象)
function Welcome(props) {
  // 定义组件内部状态, 这里的name对应java中数据的get;nameSet对应数据的set(Model)
  const [ name, nameSet ] = useState('World');

  // 定义组件内部行为 (Control)
  const change = (event) => {
    const value = event.target.value;
    // 更新state,组件会重新渲染
    nameSet(value);
  }

  // 另一个useEffect,模拟componentDidMount
  useEffect(() => {
    console.log('仅挂载时执行一次');

    // 相当于 componentWillUnmount
    return () => {
      console.log('组件即将卸载');
    };
  }, []); // 空依赖数组表示只在挂载和卸载时执行

  // 相当于 componentDidMount 和 componentDidUpdate
  useEffect(() => {
    console.log('组件已挂载或count已经更新更新');
  }, [name]); // 依赖数组,只有count变化时才执行

  // 这个组件返回一段JSX(View)
  return (
    <div>
      <h1>Hello, { name }!</h1>
      <input onChange={ change }/>
      <p>当前时间:{ props.time }</p>
    </div>
  );
}

// 3. 像使用HTML标签一样使用它
function App() {
  return (
    <div>
      <Welcome time="2025-09-01"/> {/* 这就是我们自定义的组件 */}
    </div>
  );
}

2.2 类组件 (Class Components) ES6的Class也可以用来定义组件,但现在主要用于需要生命周期函数的旧项目。

jsx 复制代码
class Welcome extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: ''
    };
  }

  componentDidMount() {
    console.log('类组件: 组件已挂载');
  }

  componentDidUpdate(prevProps, prevState) {
    console.log('类组件: 组件已更新');
  }

  componentWillUnmount() {
    console.log('类组件: 组件即将卸载');
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('类组件: 判断是否应该更新');
    // 只有当count变化时才更新
    return nextState.name !== this.state.name
  }

  change(event) {
    this.state.name = event.target.value
  }

  render() {
    const { name } = this.state;
    const { time } = this.props;

    return (
      <div>
        <h1>Hello, { name }!</h1>
        <input onChange={ this.change }/>
        <p>当前时间:{ time }</p>
      </div>
    );
  }
}

3. 组件通信

React 的数据流是"单向"的,自上而下通过 props 传递,但根据通信方向与距离的不同,我们采用不同的方式。

3.1 父组件向子组件传递信息:Props

这是最基础、最常见的通信方式。父组件通过子组件的属性(props) 将数据传递下去。

  • 方式:在子组件标签上写入属性。
  • 子组件接收 :通过函数的参数 props 或解构赋值获取。

示例

tsx 复制代码
// 父组件 Parent.js
import Child from './Child';

function Parent() {
  const parentData = "Data from Parent";
  const sayHello = () => { alert('Hello from Parent!'); };

  return (
    <div>
      {/* 传递字符串和数据函数 */}
      <Child 
        message={parentData} 
        onSayHello={sayHello} 
        extraContent={<span>This is JSX from Parent</span>}
      />
    </div>
  );
}

// 子组件 Child.js
interface IChildProps {
  message: string;
  extraContent: string;
  onSayHello?: () => void;
}
function Child(props: IChildProps) {
  // 也可以使用解构: function Child({ message, onSayHello, extraContent }) { ... }
  return (
    <div>
      <p>收到来自父组件的信息:{props.message}</p>
      <button onClick={props.onSayHello}>点击我</button>
      {props.extraContent}
    </div>
  );
}

export default Child;

3.2 子组件向父组件传递信息:回调函数

子组件不能直接修改父组件的 props。如果需要通知父组件某些事件(如表单提交、按钮点击)或传递数据,父组件可以通过 props 传递一个回调函数给子组件,子组件在适当时机调用此函数。

  • 方式:父组件定义函数,通过 prop 传递给子组件 -> 子组件调用该函数并传入参数。

示例

jsx 复制代码
// 父组件 Parent.js
import { useState } from 'react';
import Child from './Child';

function Parent() {
  const [messageFromChild, setMessageFromChild] = useState('');

  // 定义回调函数,用于接收子组件的数据
  const handleChange = (data) => {
    console.log("父组件收到用户的输入数据:", data);
  };

  // 定义回调函数,用于接收子组件的数据
  const handleDataFromChild = (data) => {
    setMessageFromChild(data);
    console.log("父组件收到:", data);
  };

  return (
    <div>
      <p>子组件对我说:{messageFromChild}</p>
      {/* 将回调函数传递给子组件 */}
      <Child onSendData={handleDataFromChild} onChange={handleChange}/>
    </div>
  );
}

// 子组件 Child.js
function Child({ onChange, onSendData }) {
  const [count, countSet] = useState(0);

  const handleChange = (event) => {
    const value = event.target.value;
    countSet(value)
    // 调用父组件传来的函数,并传递数据
    onChange(value)
  };

  const handleClick = () => {
    // 调用父组件传来的函数,并传递数据
    onSendData("Hello Parent! - From your child");
  };

  return (
    <>
      <input onChange={handleChange}/>
      <button onClick={handleClick}>发送信息给父组件</button>
    <>
  );
}

export default Child;

3.3 多级组件跨越式传递信息:

当需要在远房组件(如孙组件、曾孙组件)之间传递数据,层层手动传递 props(称为"Prop Drilling")会非常繁琐。此时有两种主流解决方案:

3.3.1 上下文 (Context) - (React 官方推荐)

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。它创建了一个"全局"的数据域,其下的任意组件都能订阅这个数据。

  • 适用场景:React组件中的多级组件之间的通信,如果涉及组件和普通js函数之间的通信用这种方式就不适合

示例

jsx 复制代码
// 1. 创建 Context (例如:ThemeContext.js)
import { createContext } from 'react';
export const ThemeContext = createContext('light'); // 'light' 为默认值

// 2. 在顶层组件提供数据 (App.js)
import { ThemeContext } from './ThemeContext';
import Toolbar from './Toolbar';

function App() {
  const [theme, setTheme] = useState('dark');

  return (
    // 使用 Provider 提供 value,包裹需要接收数据的子组件树
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Toolbar /> {/* Toolbar 及其所有子组件都能订阅这个 Context */}
    </ThemeContext.Provider>
  );
}

// 3. 在深层子组件中消费数据 (ThemedButton.js)
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function ThemedButton() {
  // 使用 useContext Hook 来订阅 Context 的变化
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button 
      style={{ 
        background: theme === 'dark' ? '#333' : '#CCC',
        color: theme === 'dark' ? 'white' : 'black'
      }}
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
    >
      My theme is {theme}
    </button>
  );
}
3.3.2 全局事件中心 (Event Bus / PubSub)

这是一种传统的设计模式,创建一个全局的中央事件管理器。组件可以"发布(Publish)"事件到中心,也可以"订阅(Subscribe)"中心的事件。彼此之间不直接通信,而是通过事件中心间接联系。

  • 适用场景:非父子组件、关系非常遥远的组件间通信。在现代React开发中,通常优先考虑 Context API 或状态管理库(如 Redux),但在某些特定场景或简单应用中仍可使用。

概念性示例

javascript 复制代码
// eventBus.js (一个简单的实现)
const events = {};

const eventBus = {
  // 订阅事件
  on(eventName, callback) {
    if (!events[eventName]) {
      events[eventName] = [];
    }
    events[eventName].push(callback);
  },
  // 取消订阅
  off(eventName, callback) {
    if (!events[eventName]) return;
    events[eventName] = events[eventName].filter(cb => cb !== callback);
  },
  // 发布事件
  emit(eventName, data) {
    if (!events[eventName]) return;
    events[eventName].forEach(callback => {
      callback(data);
    });
  }
};

export default eventBus;
jsx 复制代码
// Component.js (订阅者,可以是任意位置的组件)
import { useEffect } from 'react';
import eventBus from './eventBus';

function Component() {
  useEffect(() => {
    // 组件挂载时订阅事件
    const handleEvent = (data) => {
      console.log('收到事件和数据:', data);
    };
    eventBus.on('myEvent', handleEvent);

    // 组件卸载时取消订阅,防止内存泄漏
    return () => {
      eventBus.off('myEvent', handleEvent);
    };
  }, []);

  return <div>Listening for events...</div>;
}

// Socket.js
function socket() {
  const msg = {sub: "ticker", id: "123456", type:"add"}
  const socket = new WebSocket('wss://somesocket.com');

  socket.onmessage = (e: MessageEvent) => {
    eventBus.emit('myEvent', { data: e.data });
  };

  socket.send(msg)
}

通信方式总结

通信方向 使用方式 适用场景
父 -> 子 Props 最常用,直接的父子关系
子 -> 父 回调函数 (通过 Props) 子组件通知父组件,传递数据
任意组件 Context API 跨多级组件传递"全局"数据(React 首选
任意组件、函数 全局事件中心 (Event Bus) 非父子组件、极度松散的通信
任意组件 状态管理库 (Redux, Zustand) 复杂的应用状态管理,可视为Context的增强版(我们项目中没有用到)

4. Hooks

Hooks 是 React 16.8 版本引入的一项革命性特性。它允许你在函数组件中使用 state 以及其他 React 特性(如生命周期),从而摆脱了必须使用 class 组件的限制。

核心优势:

  1. 逻辑复用:解决了 Class 组件中高阶组件(HOC)和渲染属性(Render Props)带来的"嵌套地狱"问题(后续第五部分会单独讲讲),使状态逻辑的复用变得非常简单。
  2. 代码组织:允许将组件中相互关联的逻辑拆分成更小的函数(自定义 Hook)。
  3. 易于理解 :函数组件更简洁,没有复杂的 this 绑定问题。

4.1 内置 Hooks

a. useState - 状态钩子

用于在函数组件中添加和管理局部状态。

  • 用法const [state, setState] = useState(initialState);

  • 参数initialState 是状态的初始值(可以是任意类型,函数也行)。

  • 返回值: 一个包含两个元素的数组:

    • state: 当前的状态值。
    • setState: 用于更新状态的函数,调用它会触发组件重新渲染。
  • 示例

    jsx 复制代码
    import React, { useState } from 'react';
    
    function Counter() {
      const [count, setCount] = useState(0); // 初始值为 0
    
      return (
        <div>
          <p>你点击了 {count} 次</p>
          <button onClick={() => setCount(count + 1)}>
            点击我
          </button>
          {/* 函数式更新,解决异步更新依赖问题 */}
          <button onClick={() => setCount(prevCount => prevCount - 1)}>
            减少
          </button>
        </div>
      );
    }
b. useEffect - 副作用钩子

用于在函数组件中执行副作用操作(数据获取、订阅、手动修改 DOM 等)。它可以看作是 componentDidMount, componentDidUpdate, 和 componentWillUnmount 的组合。

  • 用法useEffect(effectFunction, dependencyArray?)

  • 参数

    • effectFunction: 包含副作用逻辑的函数。此函数可以返回一个清理函数(cleanup),用于在组件卸载或执行下一次 effect 前清除副作用(如取消订阅、清除定时器)。
    • dependencyArray (可选): 依赖项数组。React 会根据这个数组来决定是否重新执行 effect。
  • 执行时机

    1. 无依赖数组 (useEffect(effect)): 每次组件渲染后都会执行。
    2. 空依赖数组 (useEffect(effect, [])): 只在组件首次挂载后执行一次(类似于 componentDidMount)。
    3. 有依赖项 (useEffect(effect, [state1, state2])): 只在依赖项(state1, state2)发生变化时执行。
  • 示例

    jsx 复制代码
    import React, { useState, useEffect } from 'react';
    
    function UserProfile({ userId }) {
      const [user, setUser] = useState(null);
    
      //  effect 依赖于 userId prop
      useEffect(() => {
        // 异步获取数据
        fetch(`/api/user/${userId}`)
          .then(response => response.json())
          .then(data => setUser(data));
    
        // 返回清理函数(可选)
        return () => {
          // 这里可以取消未完成的请求(例如使用 AbortController)
          console.log('Cleanup for userId:', userId);
        };
      }, [userId]); // 只有当 userId 变化时,才会重新执行
    
      return <div>{user ? user.name : 'Loading...'}</div>;
    }
c. useContext - 上下文钩子

用于接收一个 Context 对象(由 React.createContext 创建)并返回该 Context 的当前值。让你无需组件嵌套即可订阅 React 的 Context。

  • 用法const value = useContext(MyContext);

  • 示例

    jsx 复制代码
    // 1. 创建 Context
    const ThemeContext = React.createContext('light');
    
    function App() {
      // 2. 使用 Provider 提供值
      return (
        <ThemeContext.Provider value="dark">
          <Toolbar />
        </ThemeContext.Provider>
      );
    }
    
    function Toolbar() {
      // 中间的组件无需再传递 theme prop
      return <ThemedButton />;
    }
    
    function ThemedButton() {
      // 3. 在子组件中使用 useContext 获取值
      const theme = useContext(ThemeContext);
      return <button className={theme}>我是 {theme} 主题的按钮</button>;
    }
d. useCallback & useMemo - 性能优化钩子

用于避免不必要的重复渲染和计算,优化性能。

  • useCallback: 缓存一个函数本身。

    • 问题: 父组件重新渲染时,其内部定义的函数会重新创建,导致接收该函数作为 prop 的子组件不必要的重渲染。
    • 解决useCallback(fn, deps) 返回一个记忆化的回调函数,只在依赖项 deps 变化时才会更新。
    jsx 复制代码
    const memoizedCallback = useCallback(() => {
      doSomething(a, b);
    }, [a, b]); // 只有当 a 或 b 变化时,函数才会重新创建
  • useMemo: 缓存一个计算值。

    • 问题: 每次渲染时都要进行昂贵的计算(如过滤大型数组)。
    • 解决useMemo(() => computeExpensiveValue(a, b), [a, b]) 返回一个记忆化的值,只在依赖项变化时重新计算。
    jsx 复制代码
    const expensiveValue = useMemo(() => {
      return someExpensiveCalculation(a, b);
    }, [a, b]); // 只有当 a 或 b 变化时,计算才会重新执行
e. useRef 用于储存一个不会随着函数组件刷新而重新定义、赋值的数据

4.2 自定义 Hooks

自定义 Hook 是一个以 use 开头的 JavaScript 函数,它内部可以调用其他的 Hook。它的目的是将组件逻辑提取到可重用的函数中,所以当你发现一些逻辑在多个组件中重复出现时,就可以考虑将其提取为自定义 Hook。

核心规则:

  • 函数名必须以 use 开头。
  • 自定义 Hook 内部可以调用其他 Hook。
  • 两个不同的组件使用相同的自定义 Hook 不会共享 state。每次调用都有自己的独立状态。

示例 :我们去看下React官方的例子:zh-hans.react.dev/learn/reusi...

5. 拓展

这部分咱们主要说说为什么推荐函数组件,而不是类组件,主要有以下几个原因:

5.1 更好的逻辑复用

5.2 无需理解 JavaScript 的 this 机制

在JS中this和其他语言有很大的不同:this 的值取决于函数被调用的方式,而不是定义的方式. 而类组件严重依赖this,这就带来了几个经典难题:

  • a. 事件处理函数的绑定问题
  • b. this 在生命周期方法中的不一致性
jsx 复制代码
// 示例地址: https://codesandbox.io/p/sandbox/stoic-grass-8c9sfm?file=%2Fsrc%2FApp.js%3A6%2C25-7%2C22
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };

    // 解决: 在构造函数中手动绑定
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // 如果不绑定,这里的 `this` 将是 undefined,导致 Cannot read properties of undefined (reading 'setState') 的错误;
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
  }

  componentDidMount() {
    // 这里的 `this`指向组件实例,
    console.log('this', this);
    
    setTimeout(function() {
      console.log('setTimeout this', this);
    }, 1000)
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Count: {this.state.count}
      </button>
    );
  }
}

而在函数组件中根本就没有this所以天然就不会有上面说的问题

jsx 复制代码
function MyComponent() {
  const [count, setCount] = useState(0);

  // 直接声明一个函数。没有 `this`,没有绑定。
  const handleClick = () => {
    // 直接使用 `setCount`,它来自闭包,永远指向正确的函数
    setCount(prevCount => prevCount + 1);
  };

  return (
    <button onClick={handleClick}>
      Count: {count}
    </button>
  );
}
相关推荐
古夕5 小时前
Vue 3 复杂表单父子组件双向绑定的最佳实践
前端·javascript·vue.js
烛阴5 小时前
TypeScript 进阶必修课:解锁强大的内置工具类型(一)
前端·javascript·typescript
Zayn6 小时前
前端路径别名跳转和提示失效?一文搞懂解决方案
前端·javascript·visual studio code
YuspTLstar6 小时前
从工作原理入手理解React一:React核心内容和基本使用
react.js
__M__6 小时前
Zalo Mini App 初体验
前端·react.js
程序员小续7 小时前
告别重复造轮子!看 ahooks 如何改变你的代码结构
前端·javascript·react.js
大力yy7 小时前
从零到一:VS Code 扩展开发全流程简介(含 Webview 与 React 集成)
前端·javascript·react.js
猪哥帅过吴彦祖7 小时前
JavaScript Set 和 Map:现代 JavaScript 的数据结构双雄
前端·javascript·面试
ldj20208 小时前
下拉默认全选,选择展示对象的字段list
前端·javascript