React初识
简介
React 是 Facebook 开源的、用于构建用户界面的 JavaScript 库,核心思想是"组件化 + 声明式 + 单向数据流"。
2013 年 Facebook 开源,为了解决大型 Web 应用里 DOM 操作繁琐、状态难以维护的问题而诞生
React三大核心
- 组件 (Component) :把 UI 拆成可复用、可嵌套的小积木
- 声明式 (Declarative):用数据描述"界面应该长什么样",React 负责把差异同步到真实 DOM
- 单向数据流:数据只能从父组件通过 props 流向子组件,子组件想改只能回调
React特色
- Virtual DOM:用轻量 JS 对象描述真实 DOM,每次数据变化先算"最小补丁",再批量更新,性能高。 2.** JSX**:在 JS 里写 HTML-like 语法,编译成 React.createElement() ,让组件结构一眼可见。
- Hooks (函数组件):用
useState
、useEffect
等函数"钩"入状态与生命周期,告别class
组件的this
迷宫。
JSX
什么是JSX
JSX 是一种语法扩展,全称是 JavaScript XML。它由 Facebook(现为 Meta)开发,主要用于 React 框架中,用来描述用户界面的结构。
简单来说: JSX 让你在 JavaScript 代码中写类似 HTML 的语法,从而更直观地构建 UI 组件。
举例:
jsx
const element = React.createElement('h1', null, 'Hello, world!');
这行代码看起来像是 HTML,但它其实是 JavaScript。它会被编译成:
javascript
const element = React.createElement('h1', null, 'Hello, world!');
为什么要用 JSX?
- 直观:UI 和逻辑写在同一个地方,结构清晰。
- 可读性强:HTML-like 的语法更容易理解。
- React 推荐方式:虽然 React 可以不使用 JSX,但官方推荐使用。
注意事项
- JSX 不是合法的 JavaScript,必须通过工具(如 Babel)编译成标准 JavaScript。
- 在 JSX 中,可以使用 JavaScript 表达式(用 {} 包裹):
总结
JSX 是一种让你在 JavaScript 中写 HTML 风格代码的语法糖,是 React 的核心特性之一。
如果你用过 Vue,可以把它类比成 Vue 的模板语法,只不过 JSX 更贴近 JavaScript。
React组件(Component)
React 组件(Component)是 构建 React 应用的最小 UI 单元。 它把"界面 + 逻辑 + 状态"封装成一个可复用、可嵌套的"自定义标签",最终像搭积木一样拼出整个应用。
组件的本质
一个接收数据(props)并返回 React 元素(JSX)的函数(或类)。
组件的两种方式
- 函数组件
jsx
import React from 'react'
export default function demo() {
return (
<div>
这是一个函数组件
</div>
)
}
- 类组件
jsx
import React, { Component } from 'react'
export default class index extends Component {
render() {
return (
<div>
这是一个类式组件
</div>
)
}
}
特点:
- 函数组件:简洁、默认支持Hooks
- 类组件:this和生命周期使用方便
新代码99%已经开始使用函数组件+Hooks的方式;类组件只有在旧代码中才会出现。
state
在 React 里,state 就是"组件的私人记忆"。
它让函数组件也能记住数据,并在数据变化时自动触发界面更新。
state 是组件内部可变、驱动 UI 重新渲染的普通 JavaScript 变量。
为什么需要state
props
是只读的,父组件传什么就只能用什么。- 组件自己要想"计数器加 1""开关切换""输入框打字",就必须有能改、能记住的东西 → 这就是
state
。
如何使用state
函数组件使用 useState Hook(推荐)
- useState(初始值) 返回一个数组:
[当前值, 修改函数]
,用解构赋值写成[n, setN]
。 - 调用1setN(newValue)1后,React 会把新值存起来并重新执行整个组件函数,拿到最新
n
生成新 UI。
jsx
import { useState } from 'react';
function Counter() {
// 声明一个 state 变量 n,初始值 0
const [n, setN] = useState(0);
return (
<button onClick={() => setN(n + 1)}>
点击了 {n} 次
</button>
);
}
类组件使用(旧代码会体现)
- 通过
state
关键字定义 - 借助
setState
方法进行更新
jsx
import React, { Component } from 'react'
export default class demo extends Component {
state = {
n: 0
}
setN = () => {
this.setState({
n: this.state.n + 1
})
}
render() {
const { n } = this.state
return (
<button onClick={() => this.setN(n + 1)}>
点击了 {n} 次
</button>
)
}
}
props
React 的 props(properties 的缩写)是组件对外的唯一接口,也是父组件向子组件传递数据的单向通道。
props 是父组件给子组件的"只读参数",子组件可以读,但不能改。
如何使用Props
函数组件中的props
- 父组件通过属性把数据传进去。
- 子组件函数参数解构拿到
src
、size
。 - 没有传
size
时,默认值64
生效。
jsx
function Avatar({ src, size = 64 }) {
return <img src={src} width={size} height={size} />;
}
// 父组件
<Avatar src="/tom.jpg" size={100} />
类组件中的props
jsx
class Avatar extends React.Component {
render() {
const { src, size } = this.props;
return <img src={src} width={size} />;
}
}
// 父组件
<Avatar src="/tom.jpg" size={100} />
注意:所有 React 组件都必须像纯函数一样保护 props:不修改、不重写。
prop传递的类型
支持 字符串、数字、布尔、数组、对象、函数、JSX、甚至另一个组件:
jsx
<User
name="Alice"
age={18}
hobbies={['⚽', '🎮']}
onLogout={() => console.log('bye')}
header={<h2>用户中心</h2>}
/>
特殊的props:children
标签之间的内容会被收集到props.children
,实现"插槽"效果:
jsx
function Card({ title, children }) {
return (
<div className="card">
<h3>{title}</h3>
{children}
</div>
);
}
<Card title="登录">
<input placeholder="账号" />
<button>提交</button>
</Card>
props参数快速批量传递
jsx
const btnProps = { type: 'primary', size: 'small', disabled: false };
<Button {...btnProps}>保存</Button>
// 等价于
<Button type="primary" size="small" disabled={false}>保存</Button>
子组件修改props数据
- 父组件通过函数的形式传递
jsx
function Counter({ count, onAdd }) {
return <button onClick={onAdd}>{count}</button>;
}
function Parent() {
const [c, setC] = useState(0);
return <Counter count={c} onAdd={() => setC(c + 1)} />;
}
- 状态提升:数据始终放在公共父级,子组件只负责展示或触发回调。
ref
React 的 ref(reference)是绕过"props+state"机制、直接访问 DOM 节点或自定义组件实例的"应急通道"。
官方定位:能不用就不用,用时别滥用。
ref 是 React 提供的"挂钩",让你拿到真实的 DOM 元素或类组件实例,从而执行命令式操作(聚焦、滚动、动画、第三方库集成等)。
Ref创建方式
React 16.3+:
- 函数组件使用:
const ref = useRef(initial)
- 类组件使用:
createRef()
函数组件:useRef
useRef
返回一个可变的普通对象:{ current: ... }
- 把
ref={xxx}
挂在 JSX 上,React 会在挂载完成后把真实 DOM 塞进xxx.current
。 - 更新
current
不会触发重新渲染 → 适合放定时器 ID、实例对象等"隐形"数据。
jsx
import { useRef } from 'react';
function TextFocus() {
const inputRef = useRef(null); // 创建 ref 容器
const focus = () => {
inputRef.current.focus(); // 直接调原生 DOM 方法
};
return (
<>
<input ref={inputRef} defaultValue="hello" />
<button onClick={focus}>聚焦</button>
</>
);
}
类组件:createRef
jsx
class MyComp extends React.Component {
inputRef = React.createRef(); // 实例字段
focus = () => this.inputRef.current.focus();
render() {
return <input ref={this.inputRef} />;
}
}
旧版本
回调的ref方式:(node) => { ... }
jsx
//少见,但灵活,适合动态切换挂载/卸载时执行额外逻辑
<input ref={node => {
console.log('DOM 节点:', node);
if (node) node.focus();
}} />
使用场景
- 自动聚焦:
inputRef.current.focus()
- 滚动容器:
scrollRef.current.scrollTop = 0
- 选中文本:
inputRef.current.select()
- 拿到 canvas 上下文:
canvasRef.current.getContext('2d')
- 集成第三方库:把 DOM 交给 D3、Swiper、echarts 等
- 保存定时器/实例:
const timerRef = useRef(null)
forwardRef
函数组件默认封闭,父组件无法直接拿到子组件内部 DOM。
用forwardRef
显式"透传":
jsx
const FancyInput = React.forwardRef((props, ref) => (
<input ref={ref} className="fancy" />
));
// 父组件
const parentRef = useRef();
<FancyInput ref={parentRef} />
现在parentRef.current
就是子组件里的<input>
。
useImperativeHandle
配合forwardRef
,让子组件自定义父组件能拿到的方法:
jsx
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
select: () => inputRef.current.select(),
}));
return <input ref={inputRef} />;
});
父组件无法拿到整个 DOM,只能调用你暴露的focus()
/select()
→ 封装与安全。
函数组件vs类组件ref区别
- 函数组件没有实例,所以
ref
只能指向DOM 或forwardRef + useImperativeHandle
自定义对象。 - 类组件的
ref
直接拿到组件实例,可随意调用其任意方法(不推荐,破坏封装)。
注意事项&最佳实践
- 不要过度使用 ------ 优先用 props/state 描述式写法。
- 避免在渲染期间读写 ref(不确定性)。放到事件或
useEffect
里。 - ref 变化不会通知 → 若需响应式,用
useState / useReducer
或useCallback(ref, [])
。 - 清理工作 ------ 如果 ref 里创建了全局监听/定时器,在
useEffect
返回函数里清理。