React核心语法:组件化与声明式编程

React 的核心语法围绕"组件化""声明式编程"展开,从最初的类组件,到如今的函数组件+Hooks,开发体验不断优化。以下是开发和面试中最常用的核心语法,附实战代码、考点解析和避坑指南,确保拿来就用、记了就会。

2.1 核心基础:JSX 语法(React 独有,面试必问)

JSX(JavaScript XML)是React的核心语法,允许在JavaScript中编写HTML-like代码,本质是React.createElement()方法的语法糖,浏览器无法直接解析JSX,需通过Babel编译为JavaScript代码后执行。

2.1.1 JSX 基础语法规则
jsx 复制代码
// 1. 基本使用:HTML标签直接嵌入JSX
const App = () => {
  return (
    <div className="app">
      <h1>React 基础语法</h1>
      <p>JSX 是 React 独有的语法糖</p>
    </div>
  );
};

// 2. 嵌入JS表达式:用 {} 包裹
const name = "React";
const age = 10;
const App = () => {
  return (
    <div>
      <p>框架:{name}</p>
      <p>诞生至今:{age + "年"}</p>
      <p>是否主流:{age > 5 ? "是" : "否"}</p>
    </div>
  );
};

// 3. 注意事项(面试避坑)
// ① class 需改为 className(避免与JS关键字冲突)
// ② style 需传入对象,key为驼峰命名(如fontSize,而非font-size)
// ③ 标签必须闭合(单标签需加 /,如 <img src="" />)
// ④ 只能有一个根节点(或用 Fragment <></> 无包裹节点)
const App = () => {
  return (
    <>
      <div className="box" style={{ fontSize: "16px", color: "#333" }}>
        正确写法
      </div>
      <img src="react-logo.png" alt="React图标" />
    </>
  );
};
2.1.2 面试考点(必背)

问题:JSX 是什么?它和 HTML、JavaScript 的关系是什么?

标准答案:1. JSX 是 React 独有的语法糖,允许在 JavaScript 中编写 HTML-like 代码,简化 React 组件的编写;2. JSX 本质是 React.createElement() 方法的语法糖,Babel 会将 JSX 编译为 React.createElement(type, props, children) 调用,最终生成虚拟 DOM 对象;3. JSX 不是 HTML,它支持嵌入 JS 表达式、自定义属性(如 className),且有严格的语法规则(如标签闭合、驼峰命名);4. JSX 也不是纯 JavaScript,需通过编译才能被浏览器解析执行。

2.2 组件定义:函数组件 vs 类组件(面试高频对比)

React 组件分为两种:函数组件(React 16.8+ 推荐)和类组件(传统方式,逐步被函数组件替代),面试中常考两者的区别、适用场景,以及为什么推荐函数组件。

2.2.1 函数组件(推荐,结合 Hooks 使用)

函数组件是简单的JavaScript函数,接收Props参数,返回JSX,React 16.8引入Hooks后,函数组件可以拥有状态(State)和生命周期,成为开发首选。

jsx 复制代码
// 基础函数组件(无状态)
const Greeting = (props) => {
  // 接收Props参数
  const { name } = props;
  return <h1>Hello, {name}!</h1>;
};

// 带状态的函数组件(结合Hooks)
import { useState } from "react";
const Counter = () => {
  // 用useState定义状态,count为状态值,setCount为更新状态的方法
  const [count, setCount] = useState(0);

  // 事件处理
  const increment = () => {
    setCount(count + 1); // 不可直接修改count,需通过setCount更新
  };

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={increment}>增加</button>
    </div>
  );
};
2.2.2 类组件(传统方式,了解即可)

类组件继承自React.Component,通过state定义状态,通过生命周期方法管理组件生命周期,语法繁琐,目前仅在旧项目中可见。

jsx 复制代码
import React from "react";

class Counter extends React.Component {
  // 定义状态
  state = { count: 0 };

  // 事件处理
  increment = () => {
    // 不可直接修改this.state,需通过this.setState()更新
    this.setState({ count: this.state.count + 1 });
  };

  // 渲染方法(必须有,返回JSX)
  render() {
    return (
      <div>
        <p>计数:{this.state.count}</p>
        <button onClick={this.increment}>增加</button>
      </div>
    );
  }
}
2.2.3 面试考点:函数组件 vs 类组件(必背)
对比维度 函数组件 类组件 面试重点
语法复杂度 简单,JavaScript函数,代码简洁 繁琐,需继承React.Component,重写render方法 为什么推荐函数组件?1. 语法简洁,开发效率高;2. 结合Hooks,可灵活管理状态和生命周期,避免类组件的this指向问题;3. 性能更优(无类实例创建的开销);4. 更易适配TypeScript,类型推导更简单;5. 符合React未来的发展趋势(React官方重点优化函数组件)。
状态管理 通过Hooks(useState、useReducer)管理 通过this.state和this.setState()管理
生命周期 通过Hooks(useEffect、useLayoutEffect)模拟 通过生命周期方法(componentDidMount等)管理
this指向 无this,避免this指向混乱 有this,需注意绑定(箭头函数、bind),易出错
适用场景 所有场景(推荐),尤其是复杂交互、状态管理场景 旧项目维护,简单无状态组件(不推荐新开发使用)
2.3 核心API:Props 与 State(组件通信与状态管理基础)

Props 和 State 是 React 组件的核心概念,两者都用于存储数据,但用途不同,面试中常考两者的区别、使用场景及注意事项。

2.3.1 Props(父传子,只读不可改)

Props(Properties)是父组件传递给子组件的数据,子组件接收后只读不可修改(单向数据流),用于组件间通信(父传子),可设置默认值、类型校验。

jsx 复制代码
// 父组件:传递Props
import { useState } from "react";
import Child from "./Child";

const Parent = () => {
  const [name] = useState("React");
  return (
    <div>
      <Child name={name} age={10} isPopular={true} />
    </div>
  );
};

// 子组件:接收Props,设置默认值和类型校验
import PropTypes from "prop-types";

const Child = (props) => {
  // 解构Props
  const { name, age, isPopular, gender = "未知" } = props; // gender设置默认值
  return (
    <div>
      <p>框架:{name}</p>
      <p>年龄:{age}</p>
      <p>是否主流:{isPopular ? "是" : "否"}</p>
      <p>性别:{gender}</p>
    </div>
  );
};

// 类型校验(面试考点,提升代码健壮性)
Child.propTypes = {
  name: PropTypes.string.isRequired, // 字符串类型,必填
  age: PropTypes.number.isRequired, // 数字类型,必填
  isPopular: PropTypes.bool, // 布尔类型,可选
  gender: PropTypes.string, // 字符串类型,可选
};

export default Child;
2.3.2 State(组件内部状态,可修改)

State 是组件内部的状态数据,用于管理组件自身的动态变化(如输入框内容、计数器值),可通过setState(类组件)或useState(函数组件)修改,修改后组件会重新渲染。

核心注意事项(面试避坑):

  • State 不可直接修改(如 count = count + 1 错误,需用 setCount(count + 1));

  • State 更新是异步的(类组件this.setState、函数组件setState都是异步),如需依赖上一次的State,需传入函数(如 setCount(prev => prev + 1));

  • State 是局部的,仅作用于当前组件,子组件无法直接访问,需通过Props传递。

jsx 复制代码
import { useState } from "react";

const InputDemo = () => {
  // 定义输入框状态
  const [inputValue, setInputValue] = useState("");

  // 处理输入变化(依赖上一次状态,用函数形式)
  const handleInputChange = (e) => {
    setInputValue(e.target.value); // 普通更新
  };

  // 重置输入框(依赖上一次状态,用函数形式)
  const resetInput = () => {
    setInputValue(prev => ""); // 传入函数,确保拿到最新的prev值
  };

  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={handleInputChange}
        placeholder="请输入内容"
      />
      <button onClick={resetInput}>重置</button>
      <p>输入内容:{inputValue}</p>
    </div>
  );
};
2.3.3 面试考点:Props vs State(必背)

问题:Props 和 State 的区别是什么?

复制代码
  标准答案:1. 来源不同:Props 是父组件传递的,State 是组件内部定义的;2. 可修改性不同:Props 只读不可修改,State 可通过setState/useState修改;3. 作用范围不同:Props 用于组件间通信(父传子),State 用于管理组件内部动态状态;4. 更新影响不同:Props 变化会触发子组件重新渲染,State 变化会触发当前组件及子组件重新渲染;5. 默认值:Props 可设置默认值,State 可设置初始值。
2.4 核心API:Hooks(React 16.8+ 重点,面试必考)

Hooks 是 React 16.8 引入的新特性,核心作用是"让函数组件拥有状态和生命周期",解决类组件语法繁琐、this指向混乱、逻辑复用困难的问题。常用 Hooks 及实战的是面试高频考点,必须熟练掌握。

2.4.1 常用 Hooks 实战(开发必备)
  1. useState:管理组件内部状态最基础的Hooks,用于定义组件内部的状态,返回"状态值 + 更新状态的方法",前面已实战,核心注意事项:异步更新、不可直接修改状态。

  2. useEffect:模拟生命周期,处理副作用副作用:组件渲染后执行的操作(如请求数据、操作DOM、设置定时器),useEffect 可模拟类组件的 componentDidMount、componentDidUpdate、componentWillUnmount 三个生命周期。

bash 复制代码
`import { useState, useEffect } from "react";

const EffectDemo = () => {
  const [count, setCount] = useState(0);

  // 1. 模拟 componentDidMount(只执行一次,依赖项为空数组)
  useEffect(() => {
    console.log("组件挂载完成,请求数据");
    // 模拟请求数据
    fetch("/api/data")
      .then(res => res.json())
      .then(data => console.log("请求到的数据:", data));

    // 模拟设置定时器
    const timer = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
    
    // 清理函数:模拟 componentWillUnmount(组件卸载时执行)
    return () => {
      console.log("组件卸载,清理定时器和请求");
      clearInterval(timer);
      // 取消请求(需结合AbortController)
    };

  }, []); // 依赖项为空数组,只执行一次

  // 2. 模拟 componentDidUpdate(count变化时执行)
  useEffect(() => {
    console.log("count变化了:", count);
  }, [count]); // 依赖项为count,count变化时执行

  return <p>count:{count}</p>;
};`
  1. useContext:跨层级组件通信用于解决"props drilling"(props层层传递)问题,实现跨层级组件通信,无需手动传递props。
bash 复制代码
`import { createContext, useContext, useState } from "react";

// 1. 创建Context
const ThemeContext = createContext();

// 2. 父组件:提供Context值
const Parent = () => {
  const [theme, setTheme] = useState("light");

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

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      <Child />
    </ThemeContext.Provider>
  );
};

// 3. 子组件:接收Context值(无需通过props传递)
const Child = () => {
  const { theme, toggleTheme } = useContext(ThemeContext);
  return (
    <div style={{ background: theme === "light" ? "#fff" : "#333", color: theme === "light" ? "#333" : "#fff" }}>
      <p>当前主题:{theme}</p>
      <button onClick={toggleTheme}>切换主题</button>
    </div>
  );
};`
  1. useReducer:复杂状态管理用于管理复杂状态(如对象、数组),类似Redux的思想,将状态更新逻辑抽离,使代码更易维护。
bash 复制代码
`import { useReducer } from "react";

// 1. 定义reducer函数(接收state和action,返回新state)
const reducer = (state, action) => {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, count: state.count + 1 };
    case "DECREMENT":
      return { ...state, count: state.count - 1 };
    case "RESET":
      return { ...state, count: 0 };
    default:
      return state;
  }
};

const ReducerDemo = () => {
  // 2. 使用useReducer,接收reducer和初始state
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <p>计数:{state.count}</p>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>增加</button>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>减少</button>
      <button onClick={() => dispatch({ type: "RESET" })}>重置</button>
    </div>
  );
};`
  1. useRef:获取DOM元素/保存持久化值有两个核心用途:1. 获取DOM元素(如输入框焦点);2. 保存持久化值(组件重新渲染时,值不会重置,且修改不会触发组件重新渲染)。
bash 复制代码
`import { useRef, useEffect } from "react";

const RefDemo = () => {
  // 1. 获取DOM元素
  const inputRef = useRef(null);

  // 2. 保存持久化值(组件重新渲染时,值不会重置)
  const countRef = useRef(0);

  useEffect(() => {
    // 组件挂载后,让输入框获取焦点
    inputRef.current.focus();

    // 每渲染一次,countRef的值加1(不会触发组件重新渲染)
    countRef.current += 1;
    console.log("组件渲染次数:", countRef.current);

  }, []);

  return (
    <div>
      <input ref={inputRef} type="text" placeholder="自动获取焦点" />
    </div>
  );
};`

6. **useMemo & useCallback:性能优化**用于优化组件渲染性能,避免不必要的重新渲染:`import { useState, useMemo, useCallback } from "react";
   import Child from "./Child";

const MemoDemo = () => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("React");

  // useMemo:缓存计算结果,只有count变化时才重新计算
  const doubleCount = useMemo(() => {
    console.log("重新计算doubleCount");
    return count * 2;
  }, [count]);

  // useCallback:缓存函数,只有name变化时才重新创建函数
  const handleClick = useCallback(() => {
    console.log("点击事件,name:", name);
  }, [name]);

  return (
    <div>
      <p>count:{count},doubleCount:{doubleCount}</p>
      <button onClick={() => setCount(prev => prev + 1)}>增加count</button>
      <button onClick={() => setName(prev => prev + "!")}>修改name</button>
      <Child onClick={handleClick} />
    </div>
  );
};`
复制代码
- useMemo:缓存计算结果,依赖项不变时,不会重新计算;

- useCallback:缓存函数,依赖项不变时,不会重新创建函数(避免子组件因函数引用变化而重新渲染)。
2.4.2 Hooks 使用规则(面试避坑,必背)
  1. 只能在函数组件的顶层调用Hooks(不能在if、for、循环、条件判断、嵌套函数中调用);

    1. 只能在函数组件或自定义Hooks中调用Hooks(不能在类组件、普通JavaScript函数中调用);

    2. Hooks调用顺序必须固定(每次组件渲染时,Hooks的调用顺序不能变);

    3. 自定义Hooks必须以"use"开头(如useRequest、useTheme),便于React识别和检查规则;

    4. useEffect的清理函数必须清理副作用(如定时器、请求、事件监听),避免内存泄漏。

2.5 组件通信(开发高频,面试必问)

React 组件通信是开发中最常用的场景,面试中常考"不同组件关系(父子、跨层级、兄弟)的通信方式",以下是按使用频率排序的5种通信方式,附实战代码和适用场景。

  1. **Props/回调函数(父子组件通信,最常用)**父传子用Props,子传父用"回调函数"(父组件传递一个函数给子组件,子组件调用该函数传递数据)。`// 父组件:传递Props和回调函数
bash 复制代码
   import { useState } from "react";
   import Child from "./Child";

const Parent = () => {
  const [parentMsg, setParentMsg] = useState("父组件消息");
  const [childMsg, setChildMsg] = useState("");

  // 父组件定义回调函数,接收子组件传递的数据
  const handleChildMsg = (msg) => {
    setChildMsg(msg);
  };

  return (
    <div>
      <p>子组件传来:{childMsg}</p>
      <Child msg={parentMsg} onSendMsg={handleChildMsg} />
    </div>
  );
};

// 子组件:接收Props,调用回调函数传递数据
const Child = (props) => {
  const { msg, onSendMsg } = props;

  const sendMsg = () => {
    // 调用父组件传递的回调函数,传递子组件数据
    onSendMsg("子组件消息");
  };

  return (
    <div>
      <p>父组件传来:{msg}</p>
      <button onClick={sendMsg}>向父组件发送消息</button>
    </div>
  );
};`
  1. **useContext(跨层级组件通信,常用)**用于解决"props drilling"问题,跨层级(如祖父→孙子)组件通信,无需手动传递props,前面已实战,适用场景:中小型项目、跨层级组件通信不复杂的场景。

  2. **状态管理工具(Redux/RTK/Zustand,中大型项目常用)**用于全局状态管理,适用于多组件共享状态(如用户信息、主题配置),后续生态章节详细讲解,面试中常考Redux的核心原理与使用。

  3. **事件总线(EventBus,兄弟组件通信,偶尔用)**通过自定义事件机制,实现非父子、非跨层级组件通信(如兄弟组件),需手动解绑事件,避免内存泄漏。

bash 复制代码
// 1. 定义事件总线(utils/eventBus.js)
   class EventBus {
     constructor() {
    this.events = {};
     }

  // 订阅事件
  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(callback);
  }

  // 发布事件
  emit(eventName, data) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(callback => callback(data));
    }
  }

  // 解绑事件
  off(eventName, callback) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
    }
  }
}

export default new EventBus();

// 2. 组件A(发布事件)
import eventBus from "../utils/eventBus";

const ComponentA = () => {
  const sendMsg = () => {
    eventBus.emit("brotherMsg", "组件A的消息");
  };

  return <button onClick={sendMsg}>向组件B发送消息</button>;
};

// 3. 组件B(订阅事件)
import { useEffect } from "react";
import eventBus from "../utils/eventBus";

const ComponentB = () => {
  useEffect(() => {
    // 订阅事件
    const callback = (data) => {
      console.log("收到组件A的消息:", data);
    };
    eventBus.on("brotherMsg", callback);

    // 解绑事件,避免内存泄漏
    return () => {
      eventBus.off("brotherMsg", callback);
    };

  }, []);

  return <p>组件B:等待接收消息</p>;
};
  1. **useRef + forwardRef(父子组件通信,获取子组件DOM/方法)**父组件通过useRef获取子组件的DOM元素或方法,需子组件用forwardRef包裹,适用于父组件需要操作子组件DOM的场景(如输入框焦点、滚动操作)。
bash 复制代码
import { useRef, forwardRef, useImperativeHandle } from "react";

// 子组件:用forwardRef包裹,暴露方法给父组件
const Child = forwardRef((props, ref) => {
  const inputRef = useRef(null);

  // 用useImperativeHandle暴露方法给父组件(可选,避免暴露整个子组件实例)
  useImperativeHandle(ref, () => ({
    focusInput: () => {
      inputRef.current.focus();
    },
    clearInput: () => {
      inputRef.current.value = "";
    }
  }));

  return <input ref={inputRef} type="text" />;
});

// 父组件:用useRef获取子组件暴露的方法
const Parent = () => {
  const childRef = useRef(null);

  const handleFocus = () => {
    // 调用子组件暴露的方法
    childRef.current.focusInput();
  };

  const handleClear = () => {
    childRef.current.clearInput();
  };

  return (
    <div>
      <Child ref={childRef} />
      <button onClick={handleFocus}>让子组件输入框获取焦点</button>
      <button onClick={handleClear}>清空子组件输入框</button>
    </div>
  );
};`
2.6 其他常用语法(开发必备)
  • 条件渲染:通过if-else、三元表达式、逻辑与(&&)实现,适用于根据状态展示不同UI。

  • 列表渲染:用map方法渲染列表,必须添加key(唯一标识,避免渲染错误,面试必问key的作用)。

  • 事件处理:React事件是合成事件(不是原生DOM事件),语法为onClick(驼峰命名),需注意绑定this(函数组件无需绑定)。

  • 组件懒加载:用React.lazy和Suspense实现,减少首屏加载体积,提升首屏加载速度。

jsx 复制代码
// 1. 条件渲染
const ConditionDemo = () => {
  const [isLogin, setIsLogin] = useState(false);
  return (
    <div>
      {isLogin ? <p>欢迎登录</p> : <button onClick={() => setIsLogin(true)}>登录</button>}
      {isLogin && <p>登录后可查看更多内容</p>}
    </div>
  );
};

// 2. 列表渲染(key必加)
const ListDemo = () => {
  const list = [
    { id: 1, name: "React" },
    { id: 2, name: "Vue" },
    { id: 3, name: "Angular" }
  ];
  return (
    <ul>
      {list.map(item => (
        <li key={item.id}>{item.name}</li> // key为唯一标识,推荐用后端返回的id
      ))}
    </ul>
  );
};

// 3. 组件懒加载
import { lazy, Suspense } from "react";

// 懒加载组件(按需加载,只有当组件被渲染时才加载)
const LazyComponent = lazy(() => import("./LazyComponent"));

const LazyDemo = () => {
  return (
    <Suspense fallback={<p>加载中...</p>}>
      <LazyComponent />
    </Suspense>
  );
};

面试考点:key 的作用是什么?

复制代码
  标准答案:1. key 是React用于识别列表中唯一的DOM元素,帮助React区分不同的列表项;2. 作用:减少不必要的DOM操作,提升列表渲染性能;避免列表项渲染混乱(如状态错位);3. 注意:key 必须是唯一的、稳定的,不能用index作为key(当列表项增删改时,index会变化,导致React误判,影响性能和状态)。
相关推荐
DJ斯特拉2 小时前
文件上传(UUID防止重名文件&&阿里云实现云端上传&&MultipartFile接收前端文件)
前端
Alan Lu Pop2 小时前
React 表单提交关键词意外触发刷新
前端·javascript·react.js
掘金安东尼2 小时前
企业级Claw落地避坑指南:70%项目失败的真实原因
前端·面试·github
这儿有一堆花2 小时前
从技术标准到营销概念:深度解析 HTML5 与 H5 的演变与区别
前端·html·html5
我命由我123452 小时前
React - 创建 React 项目、React 项目结构、React 简单案例、TodoList 案例
前端·javascript·react.js·前端框架·ecmascript·html5·js
SuperEugene2 小时前
Vue3 Pinia 状态管理规范:何时用 Pinia 何时用本地状态|状态管理与路由规范篇
开发语言·前端·javascript·vue.js·前端框架
Moment2 小时前
TypeScript 要换芯了,6.0 竟是旧编译器的最后一舞
前端·javascript·github
Cg136269159742 小时前
Element-入门
前端
萝卜白菜。2 小时前
TongWeb7.0配置tongweb-web.xml修改jsessionid名及其值的长度
xml·前端·word