React学习笔记(一)

React初识

简介

React 是 Facebook 开源的、用于构建用户界面的 JavaScript 库,核心思想是"组件化 + 声明式 + 单向数据流"。

2013 年 Facebook 开源,为了解决大型 Web 应用里 DOM 操作繁琐、状态难以维护的问题而诞生

React三大核心

  1. 组件 (Component) :把 UI 拆成可复用、可嵌套的小积木
  2. 声明式 (Declarative):用数据描述"界面应该长什么样",React 负责把差异同步到真实 DOM
  3. 单向数据流:数据只能从父组件通过 props 流向子组件,子组件想改只能回调

React特色

  1. Virtual DOM:用轻量 JS 对象描述真实 DOM,每次数据变化先算"最小补丁",再批量更新,性能高。 2.** JSX**:在 JS 里写 HTML-like 语法,编译成 React.createElement() ,让组件结构一眼可见。
  2. Hooks (函数组件):用useStateuseEffect等函数"钩"入状态与生命周期,告别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?

  1. 直观:UI 和逻辑写在同一个地方,结构清晰。
  2. 可读性强:HTML-like 的语法更容易理解。
  3. React 推荐方式:虽然 React 可以不使用 JSX,但官方推荐使用。

注意事项

  1. JSX 不是合法的 JavaScript,必须通过工具(如 Babel)编译成标准 JavaScript。
  2. 在 JSX 中,可以使用 JavaScript 表达式(用 {} 包裹):

总结

JSX 是一种让你在 JavaScript 中写 HTML 风格代码的语法糖,是 React 的核心特性之一。

如果你用过 Vue,可以把它类比成 Vue 的模板语法,只不过 JSX 更贴近 JavaScript。

React组件(Component)

React 组件(Component)是 构建 React 应用的最小 UI 单元。 它把"界面 + 逻辑 + 状态"封装成一个可复用、可嵌套的"自定义标签",最终像搭积木一样拼出整个应用。

组件的本质

一个接收数据(props)并返回 React 元素(JSX)的函数(或类)。

组件的两种方式

  1. 函数组件
jsx 复制代码
import React from 'react'

export default function demo() {
  return (
    <div>
      这是一个函数组件
    </div>
  )
}
  1. 类组件
jsx 复制代码
import React, { Component } from 'react'

export default class index extends Component {
  render() {
    return (
      <div>
        这是一个类式组件
      </div>
    )
  }
}

特点:

  1. 函数组件:简洁、默认支持Hooks
  2. 类组件:this和生命周期使用方便

新代码99%已经开始使用函数组件+Hooks的方式;类组件只有在旧代码中才会出现。

state

在 React 里,state 就是"组件的私人记忆"。

它让函数组件也能记住数据,并在数据变化时自动触发界面更新。

state 是组件内部可变、驱动 UI 重新渲染的普通 JavaScript 变量。

为什么需要state

  1. props是只读的,父组件传什么就只能用什么。
  2. 组件自己要想"计数器加 1""开关切换""输入框打字",就必须有能改、能记住的东西 → 这就是state

如何使用state

函数组件使用 useState Hook(推荐)

  1. useState(初始值) 返回一个数组:[当前值, 修改函数],用解构赋值写成[n, setN]
  2. 调用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>
  );
}

类组件使用(旧代码会体现)

  1. 通过state关键字定义
  2. 借助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

  1. 父组件通过属性把数据传进去。
  2. 子组件函数参数解构拿到srcsize
  3. 没有传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数据

  1. 父组件通过函数的形式传递
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)} />;
}
  1. 状态提升:数据始终放在公共父级,子组件只负责展示或触发回调。

ref

React 的 ref(reference)是绕过"props+state"机制、直接访问 DOM 节点或自定义组件实例的"应急通道"。

官方定位:能不用就不用,用时别滥用。

ref 是 React 提供的"挂钩",让你拿到真实的 DOM 元素或类组件实例,从而执行命令式操作(聚焦、滚动、动画、第三方库集成等)。

Ref创建方式

React 16.3+:

  1. 函数组件使用:const ref = useRef(initial)
  2. 类组件使用: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区别

  1. 函数组件没有实例,所以ref只能指向DOM 或forwardRef + useImperativeHandle自定义对象。
  2. 类组件的ref直接拿到组件实例,可随意调用其任意方法(不推荐,破坏封装)。

注意事项&最佳实践

  1. 不要过度使用 ------ 优先用 props/state 描述式写法。
  2. 避免在渲染期间读写 ref(不确定性)。放到事件或useEffect里。
  3. ref 变化不会通知 → 若需响应式,用useState / useReduceruseCallback(ref, [])
  4. 清理工作 ------ 如果 ref 里创建了全局监听/定时器,在useEffect返回函数里清理。
相关推荐
晨米酱1 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
拜无忧1 小时前
【教程】Nuxt v4 入门指南与实践 (vue前端角度开发)
前端·nuxt.js
云枫晖2 小时前
手写Promise-什么是Promise
前端·javascript
拜无忧2 小时前
html,svg,花海扩散效果
前端·css·svg
DevUI团队2 小时前
🚀 MateChat V1.8.0 震撼发布!对话卡片可视化升级,对话体验全面进化~
前端·vue.js·人工智能
RoyLin2 小时前
TypeScript设计模式:责任链模式
前端·后端·typescript
一枚前端小能手2 小时前
📋 前端复制那点事 - 5个实用技巧让你的复制功能更完美
前端·javascript
三小河2 小时前
解决vite环境下调用获取二进制文件流 部分文件报错 (failed)net::ERR_INVALID_HTTP_RESPONSE)
前端
好好好明天会更好2 小时前
pinia从定义到运用
前端·vue.js