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 实战(开发必备)
-
useState:管理组件内部状态最基础的Hooks,用于定义组件内部的状态,返回"状态值 + 更新状态的方法",前面已实战,核心注意事项:异步更新、不可直接修改状态。
-
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>;
};`
- 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>
);
};`
- 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>
);
};`
- 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 使用规则(面试避坑,必背)
-
只能在函数组件的顶层调用Hooks(不能在if、for、循环、条件判断、嵌套函数中调用);
-
只能在函数组件或自定义Hooks中调用Hooks(不能在类组件、普通JavaScript函数中调用);
-
Hooks调用顺序必须固定(每次组件渲染时,Hooks的调用顺序不能变);
-
自定义Hooks必须以"use"开头(如useRequest、useTheme),便于React识别和检查规则;
-
useEffect的清理函数必须清理副作用(如定时器、请求、事件监听),避免内存泄漏。
-
2.5 组件通信(开发高频,面试必问)
React 组件通信是开发中最常用的场景,面试中常考"不同组件关系(父子、跨层级、兄弟)的通信方式",以下是按使用频率排序的5种通信方式,附实战代码和适用场景。
- **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>
);
};`
-
**useContext(跨层级组件通信,常用)**用于解决"props drilling"问题,跨层级(如祖父→孙子)组件通信,无需手动传递props,前面已实战,适用场景:中小型项目、跨层级组件通信不复杂的场景。
-
**状态管理工具(Redux/RTK/Zustand,中大型项目常用)**用于全局状态管理,适用于多组件共享状态(如用户信息、主题配置),后续生态章节详细讲解,面试中常考Redux的核心原理与使用。
-
**事件总线(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>;
};
- **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误判,影响性能和状态)。