react入门(上)

react是什么?

React 是一个开源的 JavaScript 库,由Facebook开发,主要用于构建用户界面,特别是复杂的单页应用程序(SPA)。它专注于视图层,即用户界面的开发,可以与多种库或框架结合使用,以管理应用程序的状态和数据流动。

React 的特点:

  1. 虚拟 DOM

    React 引入了虚拟 DOM 的概念,这是一种内存中的轻量级数据结构,表示实际 DOM 的抽象。当应用状态改变时,React 会高效地计算出虚拟 DOM 的最小变更量,并将这些变更应用到实际 DOM 上,从而大幅提升了性能。

  2. 组件化开发

    React 鼓励采用组件化的方式来构建 UI,即将 UI 分解为可复用的独立组件。每个组件负责管理自己的状态(state)和属性(props),这样可以提高代码的模块化程度,易于维护和重用。

  3. 单向数据流

    React 推崇一种单向数据流的架构,数据通常自上而下地传递给组件树,这简化了状态管理和问题追踪,减少了数据流动的复杂性。

  4. JSX

    JSX 是一种语法扩展,允许在 JavaScript 中混写 HTML 样式的代码。这使得描述 UI 结构变得更加直观和简洁,同时也方便静态代码分析工具进行检查。

  5. 服务器端渲染

    React 支持服务器端渲染(SSR),这对于改善首屏加载速度和搜索引擎优化(SEO)非常有利。

React 的优势:

  • 性能优化:通过虚拟 DOM 减少了直接操作实际 DOM 的次数,提升了应用运行效率。
  • 易学易用:React 的学习曲线相对较平缓,开发者可以快速上手并开始开发。
  • 强大的生态系统:React 拥有庞大的社区支持和丰富的第三方库,如 Redux、React Router 等,可以满足各种开发需求。
  • 代码复用与模块化:组件化的开发方式促进了代码的复用,使得开发大型项目更加高效。
  • 兼容性 和灵活性:React 不限定于特定的技术栈,可以与多种后端技术和前端库集成,提供了高度的灵活性。
  • React Native:React 的理念还延伸到了原生移动应用开发,React Native 允许使用相同的开发模式来构建原生移动应用,实现了跨平台开发能力。

这些特点和优势使得 React 成为了现代Web开发中极为流行的工具之一。

初始化一个react项目

这里以CRA为例,当然你可以选择任何你喜欢的工具来搭建,Vite或是Rsbuild

输入以下命令来创建一个新的React项目,其中react-demo替换为你想要的项目名称:

bash 复制代码
npx creact-react-app react-demo

使用 npx 命令会自动安装React及相关依赖,并设置好项目的基本结构

cd 切换到创建的文件夹

使用 npm starrt 启动项目

项目的目录结构

yml 复制代码
react-demo
    ├─ node_modules
    ├─ public
    ├─ favicon.ico
    ├─ index.html
    ├─ logo192.png
    ├─ logo512.png
        ├─ manifest.json
        ├─ robots.txt
    ├─ src
        ├─ App.css
        ├─ App.js
    ├─ App.test.js
    ├─ index.css
    ├─ index.js
    ├─ logo.svg
    ├─ reportWebVitals.js
        ├─ setupTests.js
    ├─ package.json   

下边我们分别来说一下每个文件(夹)的作用:

Node_modules

node的包目录,项目所依赖到的所有第三方包,没啥可说的

Public

public用来存放首页模板及静态资源,该目录中除了index.html都可以删除

  • index.html 首页模板(不能删)
  • favicon.ico 收藏夹图标(可以删,开发中会替换为自己的图标)
  • logoxxx.png React的Logo(删)
  • manifest.json(PWA的配置文件,大部分情况没啥用,删)
  • robots.txt(搜索引擎配置文件,可删)

Src

源码目录,我们写代码就是在这里

index.js

项目入口文件,不能删。

index.css

index.js的样式表,可改可删

App.js

主组件,可改可删

App.css

App.js的样式表,可改可删

xxx.test.js

带有test的都是单元测试的文件,可删

reportWebVitals.js

应用性能统计相关的代码,简单说用户在使用应用时,该模块可以统计用户加载应用所花费的时间,并且根据需要发送给统计服务器,如果没有相关需求可删。

编写我们的第一个react应用,浅尝一下

tsx 复制代码
// app.tsx
function App() {
  return <div className="App">你好,这是我的第一个react项目!</div>;
}

export default App;
tsx 复制代码
import { createRoot } from "react-dom/client"; // react18 的新方法, 创建根节点,替代原来的 createDOM

import App from "./App.tsx";  // 导入 app 组件,也是入口文件和顶级组件
/**
createRoot 接收一个 DOM 元素作为参数,返回一个根节点,在后面继续链式调用 render 方法渲染根组件到获取的 dom 元素中
*/
createRoot(document.querySelector("#root")).render(<App />);

createRoot()

createRoot(domNode, options?)

domNode:一个 ,DOM 元素React 将为这个 DOM 元素创建一个根节点然后允许你在这个根节点上调用函数

options?:可选的根节点配置对象

返回值

createRoot 返回一个带有两个方法的的对象,这两个方法是:renderunmount

unmount()

调用 unmount 以销毁 React 根节点中的一个已经渲染的树

返回值

返回 undefined

render()

用来将React元素渲染到根元素中

首次调用 root.render 时,React 会先清空根节点中所有已经存在的 HTML,然后才会渲染 React 组件

重复调用会在内部进行diff算法,将两次渲染结果进行比较,只修改发生变化的部分,避免没必要的更新,提高性能

tsx 复制代码
react17 写法,不建议使用,后续可能会不再维护
import React from "react"; // react 核心包,在 17 之前不手动导入会报错,在 17 之后可选不用手动导入
import ReactDOM from "react-dom"; 
import App from "./App.jsx";  // 导入App组件,作为应用的入口

// 使用ReactDOM.render方法,将<App />组件渲染到id为"root"的DOM元素中
ReactDOM.render(<App />, document.querySelector("#root")); // 接收两个参数要渲染的结构和目标 DOM

在react的核心包中createRoot方法创建根节点,还有一个方法createElement创建react元素,且创建后无法修改,只能覆盖

tsx 复制代码
import { createElement } from 'react-dom/client'
createElemnet('div',{id:'box',type:'div',className:'btn',onClick:()=>{alert('哎呀!你干嘛.')}},'你好!我是蔡徐坤') 

接收三个参数,第一个是创建元素的类型,第二个是元素对应的一些样式的配置对象,第三个参数是要填充的内容

元素名称必须为html格式的小写

标签中的属性class属性使用className,设置事件属性名采用驼峰命名

元素的内容,子元素,直接在后面使用逗号隔开添加

jsx概念

JSX是JavaScript XML的缩写,它是一种用于在React中编写UI组件的语法扩展。JSX允许开发者在JavaScript代码中编写类似HTML的结构,使得编写和阅读React组件更加直观和简洁。虽然它看起来像是在JavaScript中直接写HTML,但实际上,JSX被编译成普通的JavaScript函数调用,这些函数调用会创建虚拟DOM元素树,最终渲染为真实的DOM元素。

jsx本质

JSX并不是标准的JS语法,它是JS的语法扩展,浏览器本身不能识别,需要通过解析工具做解析之后才能在浏览器中运行

jsx是声明式编程,简单理解就是以结果为导向,就像是你的老板,我不管你的过程我只要结果

tsx 复制代码
// 命令式编程
// 使用createElement创建一个react元素,就相当于我告诉你,我给你10块钱,出门右转,到第二个红路灯路口右转,到旁边的商店买一包盐,再怎么怎么回来
const button = createElement('button',{},'按钮')

// 使用jsx 面向结果声明式编程,以结果为导向
//  可以简单理解为,我告诉你家里炒菜现在没盐了,想办法整一包来,我不管你是使用什么方法 
const button = <button>我是按钮</button>

jsx中使用js表达式

在jsx中可以通过 大括号语法{ }识别JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等

tsx 复制代码
<script setup>
 const app = <div calssName="app">
          // 使用引号传递字符串
          {'php是世界上最好的语言'}
          // 识别变量
          const name = tom
          { name }
          // 函数调用
          const getUserInfo = () => {
              return { name: Tom , age: 18 }
          }
          { getUserInfo() }
          // 方法调用
          { New Date().getDate() }
          //使用js对象
          <div style={{color:'red'}}>Hello World</div> // 外层{ }识别表达式语法,内层{ }为对象
      </div>
</script>

注意:

  • jsx不是字符串,不要加引号

  • jsx中的html标签小写开头,React组件为大写开头

  • jsx有且只有一个根标签,必须正确结束

  • 布尔类型、Null 、直接写对象的形式以及 Undefined 将会忽略

  • if语句、Switch语句、变量声明属于语句,不是表达式,不能出现在{ }中

jsx只允许只有一个根标签,我们也可以使用 的虚拟根标签来包裹内容,最终是不会渲染出来的,可以理解为vue中的template标签

tsx 复制代码
import { Fragment } from "react/jsx-runtime";

export default function App() {
  return (
    <Fragment>
      <h1>Hello, world!</h1>
    </Fragment>
  )
}

简写形式
export default function App() {
  return (
    <>
      <h1>Hello, world!</h1>
    </>
  )
}

上面提到了一些像布尔类型,null和undefined是不会显示的,那如果我们在调试的时候需要显示那该怎么办呢?

通过值 加上空字符串的形式来解决,对象我们可以使用JSON.stringify()转成字符串的形式来显示

tsx 复制代码
export default function App() {
  return (
    <>
     <div>
      { false + ''}
      <br />
      { true + ''}
      <br />
      { undefined + ''}
      <br />
      {JSON.stringify({name: 'tom', age: 30})}
      <br />
     </div>
    </>
  );
}

列表渲染

tsx 复制代码
const heroList = [
  {
    id: 1,
    name: '锐雯',
    lane: '上单',
  },

  {
    id: 2,
    name: '泰拉米尔',
    lane: '上单',
  },

  {
    id: 3,
    name: '奎因',
    lane: '上单',
  },

  {
    id: 4,
    name: '奥恩',
    lane: '上单',
  },

  {
    id: 5,
    name: '亚索',
    lane: '中单',
  },

  {
    id: 6,
    name: '李青',
    lane: '打野',
  },

  {
    id: 7,
    name: '努努',
    lane: '打野',
  },

  {
    id: 8,
    name: '艾希',
    lane: 'ADC',
  },

  {
    id: 9,
    name: '薇恩',
    lane: 'ADC',
  },

  {
    id: 10,
    name: '卡特琳',
    lane: '辅助',
  },

  {
    id: 11,
    name: '莫德凯撒',
    lane: '辅助',
  },
]

function getHeroList() {
  return (
    <ul>
      {heroList.map(hero => (
        <li key={hero.id}>{hero.name}  分路:{hero.lane}</li>
      ))}
    </ul>
  )
}

export default getHeroList;

条件渲染

tsx 复制代码
export const isLogin = false

export selectedValue = 1

export const getOptions = (val)=>{
if (val === 1) {
  return <div>选项1</div>
}else if (val === 2) {
  return <div>选项2</div>
}else {
  return <div>选项3</div>
 }
}

function App() {
    return (
        // 需要判断两个内容的显示隐藏的时候 可以使用 if else 或是使用简短的三元
    <div>{ isLogin ? '登录成功' : '未登录' }</div> // 未登录
        // 只需判断一个内容显示隐藏,无需使用 else 的时候,可以选择逻辑或 && 来判断,当值为真时,显示,否则反之
    <div>{ isLogin && '登录成功!' }</div>
        // 判断多组内容
        //无法在 jsx 内书写 if else 语句 我们通过调用函数来判断,在函数内进行 if else 判断
     <div>{ getOptions }</div>
    )
}

响应事件

tsx 复制代码
function myButton () {
    const handleClick = () => alert('喜中500万,请到缅北kk园区兑换!')
    return (
    <button onClick={ handleClick }>开奖</button>
    )
}

onClick={handleClick} 的结尾没有小括号!不要调用事件处理函数:你只需把函数传递给事件即可。当用户点击按钮时 React 会调用你传递的事件处理函数。

传递参数

通过一个箭头函数的回调来传值

tsx 复制代码
export default function App() {
  const handleClick = (e: any, value: object) => {
    console.log(e, value);
  };

  return (
    <>
      <button onClick={(e) => handleClick(e, {name: '123', age: 123})}>点击按钮</button>
    </>
  );
}

组件点标记写法

有两种方式,一种是对象的方式,一种是函数的形式

函数的形式

tsx 复制代码
const App = () => {
 return (
  <>
  <div>hello</div>
  <User/>
  </>
 )
}

const User = () => {
  return (
    <div>Welcome</div>
  )
}

export default App;

对象的形式

tsx 复制代码
const App = () => {
 return (
  <>
  <div>hello</div>
  <Abc.User/>
  <Abc.Info/>
  </>
 )
}

 const Abc = {
  User() {
    return <div>Welcome</div>
  },
  Info() {
    return <div>Info</div>
  },
}

这种好处就是我们可以在一个单独的模块下维护多个属于这一个类型的组件,在使用时就知道这个组件是属于哪一个大模块下的,方便维护

同样你也可以选择使用解构,如果你愿意的话

class和style属性绑定

class类名使用className进行属性绑定

不要使用class,为保留关键字

多类名属性绑定,需要使用到 classNames

bash 复制代码
npm i classNames

基本使用

tsx 复制代码
import React from 'react';
import classNames from 'classnames';

const MyComponent = () => {
  const classes = classNames('btn', 'btn-primary');
  return <button className={classes}>Click me</button>;
};

export default MyComponent;

在这个例子中,classNames 函数将 'btn''btn-primary' 两个类名组合在一起,然后将组合后的类名应用到按钮上

依据条件添加类名

tsx 复制代码
import React, { useState } from 'react';
import classNames from 'classnames';

const MyComponent = () => {
  const [isActive, setIsActive] = useState(false);

  const toggleActive = () => {
    setIsActive(!isActive);
  };

  const classes = classNames('btn', {
    'btn-active': isActive,
  });

  return (
    <button className={classes} onClick={toggleActive}>
      {isActive ? 'Active' : 'Inactive'}
    </button>
  );
};

export default MyComponent;

这里,classNames 函数接收一个对象作为参数,对象的键是类名,值是一个布尔值。如果布尔值为 true,则对应的类名会被添加到最终的类名列表中;如果为 false,则不会添加

组合多个条件和类名

tsx 复制代码
import React, { useState } from 'react';
import classNames from 'classnames';

const MyComponent = () => {
  const [isActive, setIsActive] = useState(false);
  const [isDisabled, setIsDisabled] = useState(false);

  const toggleActive = () => {
    setIsActive(!isActive);
  };

  const toggleDisabled = () => {
    setIsDisabled(!isDisabled);
  };

  const classes = classNames('btn', {
    'btn-active': isActive,
    'btn-disabled': isDisabled,
  });

  return (
    <div>
      <button className={classes} onClick={toggleActive}>
        {isActive ? 'Active' : 'Inactive'}
      </button>
      <button onClick={toggleDisabled}>
        {isDisabled ? 'Enable' : 'Disable'}
      </button>
    </div>
  );
};

export default MyComponent;

在这个例子中,classNames 函数根据 isActiveisDisabled 两个状态变量的值来动态地组合类名

sttyle属性绑定

使用jsx语法,接受对象,连词使用大写方案

css 复制代码
 <h1 className='title' style={{color: 'red', fontSize: '30px'}}>Vite + React</h1>

这个一般很少写,都是独立放在一个×××.module.css 文件里面,再进行引入

css_in_js

styled-components

css 复制代码
npm i styled-components

基本使用

tsx 复制代码
// app.tsx
import styled from 'styled-components'

const AppWrapper = styled.div`
  .title {
    font-size: 34px;
    color: red;
  }

  .footer {
    font-size: 14px;
    color: #999;
    text-align: center;
    margin-top: 20px;
  }
`

export default AppWrapper
tsx 复制代码
import AppWrapper from './style';

function App() {

  return (
    <>
    <AppWrapper>
      <div className="title">hello React</div>
      <div className="footer">I am a footer</div>
    </AppWrapper>
    </>
  )
}

export default App

它会返回一个组件,可以理解为是一个容器组件,在里面进行内容布局

按照像 scss 和 less 那样实现嵌套

div后面直接写`` 为ES6的标签模板字符串

ts 复制代码
const name = 'Tom'
const age = 18

function printInfo (...arg: any[]) {
  console.log(arg)
  }
  
  printInfo`hello my name is ${name}, age is ${age}` 

可以理解为通过函数的形式处理模板字符串,实际上就是对函数的调用,接收处理后的模板内容作为参数

第一个参数是静态的模板内容文字分割后的数组

后续参数依次为模板字符串中每个插值表达式 (如 ${name}${age})的计算结果

Css Module方案

css 复制代码
/* styles.module.css */
.container {
  background-color: lightblue;
  padding: 20px;
}

.title {
  color: darkblue;
  font-size: 24px;
}
tsx 复制代码
import React from 'react';
import styles from './styles.module.css';

const MyComponent = () => {
  return (
    <div className={styles.container}>
      <h1 className={styles.title}>Hello, CSS Modules!</h1>
    </div>
  );
};

export default MyComponent;

组件

组件化是一种分而治之的思想:

如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展

但如果,我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了

React组件相较于Vue更加的灵活和多样,可以按照不同的方式划分成很多类组件

函数式组件

定义

  • 使用普通JavaScript函数定义组件。
  • 早期仅用于无状态UI展示(通过props接收数据)。
  • 自React 16.8引入Hooks后,函数式组件可以管理状态和生命周期。

无状态

ts 复制代码
function Greeting(props) {
   return <h1>Hello, {props.name}!</h1>; // 仅依赖props
}

有状态

tsx 复制代码
import { useState, useEffect } from 'react';

function Counter() {
  const [count, setCount] = useState(0); // 使用useState管理状态

  useEffect(() => {
    console.log("组件已挂载"); // 使用useEffect模拟生命周期
  }, []);

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

特点

  • 简洁性:代码更短,逻辑更清晰。
  • Hooks 支持 :通过useStateuseEffect等管理状态和生命周期。
  • React 推荐:现代React开发中优先使用函数式组件。

class类组件

定义

  • 使用ES6的class语法,继承自React.Component
  • 必须包含render()方法,返回JSX元素。
  • 可以访问生命周期方法(如componentDidMountcomponentDidUpdate)和内部状态(this.state
tsx 复制代码
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 }; // 内部状态
  }

  componentDidMount() {
    console.log("组件已挂载"); // 生命周期方法
  }

  render() {
    return (
      <div>
        <p>计数:{this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          增加
        </button>
      </div>
    );
  }
}

特点

  • 有状态 :通过this.state管理状态。
  • 生命周期:可以控制组件的挂载、更新、卸载等阶段。

生命周期

React 的生命周期指的是组件从创建到销毁的整个过程中,在不同阶段自动触发的特定方法。这些方法允许你在组件不同阶段执行逻辑(如数据初始化、状态更新、DOM 操作、资源清理等)

生命周期主要针对类组件 ,但在函数式组件中可以通过 Hooks (如 useEffect)模拟类似行为。

类组件生命周期

类组件的生命周期分为三个阶段:挂载(Mounting)更新(Updating)卸载(Unmounting)

  1. 挂载阶段(Mounting)

组件首次被创建并插入 DOM 时触发。

  1. constructor()

    1. 作用 :初始化状态(this.state)和绑定事件方法(如 this.handleClick = this.handleClick.bind(this))。
    2. 注意 :必须调用 super(props),否则 this.props 将未定义。
tsx 复制代码
constructor(props) {
  super(props);
  this.state = { count: 0 };
  this.handleClick = this.handleClick.bind(this);
}

static getDerivedStateFromProps(props, state)(不常用)

  • 作用 :在渲染前根据新的 props 更新 state(需要返回新的 state 对象或 null)。
  • 注意 :适用于 props 变化时需要同步更新 state 的特殊场景。

render()

  • 作用:返回 JSX 元素,描述组件的 UI。
  • 规则 :必须为纯函数(不修改 state,不直接操作 DOM)。

componentDidMount()

  • 作用:组件挂载到 DOM 后触发,适合执行副作用操作(如网络请求、订阅事件、操作 DOM)。
tsx 复制代码
componentDidMount() {
  fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => this.setState({ data }));
}

更新阶段(Updating)

组件因 propsstate 变化而重新渲染时触发。

static getDerivedStateFromProps(props, state)

  • 同挂载阶段,但发生在更新阶段。

shouldComponentUpdate(nextProps, nextState)

  • 作用 :决定组件是否需要重新渲染(返回 truefalse)。
  • 用途:性能优化,避免不必要的渲染。
tsx 复制代码
shouldComponentUpdate(nextProps, nextState) {
  return nextState.count !== this.state.count; // 仅当 count 变化时重新渲染
}

render()

  • 重新渲染 UI。

getSnapshotBeforeUpdate(prevProps, prevState)(不常用)

  • 作用:在 DOM 更新前捕获一些信息(如滚动位置)。
  • 返回值 :传递给 componentDidUpdate 的第三个参数。
ts 复制代码
getSnapshotBeforeUpdate() {
  return this.listRef.scrollHeight; // 返回滚动高度
}
  1. 作用:DOM 更新后触发,适合执行依赖新 DOM 的操作(如更新第三方库)。
ts 复制代码
componentDidUpdate(prevProps) {
  if (this.props.id !== prevProps.id) {
    this.fetchData(this.props.id); // props.id 变化时重新获取数据
  }
}

卸载阶段(Unmounting)

组件从 DOM 中移除时触发。

componentWillUnmount()

  • 作用:清理资源(如取消网络请求、移除事件监听、清除定时器)。
ts 复制代码
componentWillUnmount() {
clearInterval(this.timerID); // 清除定时器
  window.removeEventListener('resize', this.handleResize);
  }

函数式组件生命周期

函数式组件通过 Hooks 模拟生命周期行为,核心是 useEffect

1. useEffect 的三种模式

  • 挂载阶段 :依赖项数组为空 [],仅在组件挂载时执行。
  • 更新阶段 :依赖项数组包含变量(如 [count]),变量变化时触发。
  • 卸载阶段 :通过返回一个清理函数(cleanup function)模拟 componentWillUnmount
ts 复制代码
import { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // 模拟 componentDidMount + componentDidUpdate
  useEffect(() => {
    console.log('组件挂载或 count 更新');
    return () => {
      console.log('清理操作(如卸载时)');
    };
  }, [count]); // 依赖项数组控制触发时机

  return <button onClick={() => setCount(count + 1)}>点击 {count}</button>;
}

常见场景对照

类组件生命周期 函数式组件等效写法
componentDidMount useEffect(() => {}, \[\])
componentDidUpdate useEffect(() => {}, dep)
componentWillUnmount useEffect(() => { return cleanup }, \[\])
shouldComponentUpdate React.memo 或 useMemo

组件通讯

父向子通讯

react中不支持子向父传值

传递给子组件的数据是只读的,不允许进行修改

传递字符串,变量

tsx 复制代码
const App = () => {
  const text = '你好 React!'
  return (
    <>
      <div>
        父组件
        <User
          name='张三'
          text={text}
        /> // 在父组件中使用属性向子组件传值
      </div>
    </>
  )
}

在子组件中可以使用 porps 进行接收或是对其进行解构渲染,结果是以对象的形式进行包裹

tsx 复制代码
const User = ({ name, text }) => {
  return (
    <>
      <div>子组件:user界面</div>
      <div>{name}</div>
      <div>{text}</div>
    </>
  )
}
export default App

传递函数,事件

事件

tsx 复制代码
const App = () => {
  const getMsg = () => {
    alert('调用了父组件的方法')
  }
  return (
    <div>
      <h1>父组件</h1>
      <Child childClick={getMsg} />  // 这里的 childClick 相当于就是一个属性用来传递数据的,所以在点击时并不会触发点击事件
    </div>
  )
}
// 使用解构接收这个事件,并绑定在子组件的 button 按钮上
 const Child = ({ childClick }) => {
  return (
    <div>
      <h1>子组件</h1>
      <button onClick={childClick }>点击</button>
    </div>
  )
}

export default App

自定义函数

tsx 复制代码
const App = () => {
  const getData = data => { // 接收子组件传递来的数据并打印
    console.log(data)
  }
  return (
    <div>
      <h1>父组件</h1>
      <Child childData={getData} />  // 向子组件传递了一个自定义函数
    </div>
  )
}
// 使用解构接收这个函数
const Child = ({ childData }) => {
  const text = '来自遥远的子组件的数据'
  return (
    <div>
      <h1>子组件</h1>
      <button onClick={childData(text)}>点击</button> // 在子组件中点击触发这个自定义函数,并传递数据
    </div>
  )
}

export default App

使用扩展运算符批量传递数据

tsx 复制代码
const App = () => {
  const data = {
    name: '张三',
    age: 18,
    gender: '男',
    address: '北京市',
    hobby: ['篮球', '足球', '游泳'],
  }
  return (
    <div>
      <h1>父组件</h1>
      <Child {...data} />
    </div>
  )
}

const Child = ({ name, age, gender, address, hobby }) => {
  return (
    <div>
      <h1>子组件</h1>
      <h3>{name}</h3>
      <h3>{age}</h3>
      <h3>{gender}</h3>
      <h3>{address}</h3>
      <ul>
        {hobby.map((item, index) => {
          return <li key={index}>{item}</li>
        })}
      </ul>
    </div>
  )
}

export default App

特殊的children属性

当内容嵌套在子组件中的标签中时,父组件会在children属性中进行接收

tsx 复制代码
const Son = ({children}) => {
  return(
    <div>{children}</div>
  )
}

const App = () => {
  return (
    <>
      <Son>
        <div>子组件中的数据</div>
      </Son>
    </>
  )
}

export default App

子向父通讯

父组件

tsx 复制代码
import { useState } from 'react'
import Son from './Son'

const App = () => {
  const [msg, setMsg] = useState('hello')
  const getMsg = msg => {
    setMsg(msg)
  }
  return (
    <>
      <div>
        <span>{msg}</span>
        <Son onGetMsg={getMsg} />
      </div>
    </>
  )
}

export default App

子组件

tsx 复制代码
 // 在子组件中调用父组件的函数并传递实参
 const Son = ({onGetMsg}) => {
  const msgText: String = 'hello world'
  return (
    <>
      <div>
        this is Son:
        <button onClick={()=>onGetMsg(msgText)}>send</button>
      </div>
    </>
  )
}

export default Son

兄弟组件通讯(也就是下面案例中的组件共享数据)

借助状态提升,通过数据下放,实现兄弟组件之间的数据通讯

在a页面点击发送数据到父组件,再由父组件传递数据给b页面实现数据传递

父组件

tsx 复制代码
import { useState } from 'react'
import A from './A'
import B from './B'

const App = () => {
  const [msg, setMsg] = useState("")

  const getMsg = (text)=> {
    setMsg(text)
  }

  return (
    <>
    <A onGetMsg={getMsg} />
    <B msg={msg}/>
    </>
  )
}

export default App

a页面

tsx 复制代码
const A  = ({onGetMsg})=> {
  const aText = 'this is a text'
  return (
    <>
    <div>page a</div>
    <button onClick={()=> onGetMsg(aText)}>send</button>
    </>
  )
}

export default A; // export default A;

b 页面

tsx 复制代码
const B  = ({msg})=> {
  return (
    <>
    <div>page b</div>
    <div>a 页面的数据:{msg}</div>
    </>
  )
}

export default B; // export default B;

传递设置默认值

tsx 复制代码
const App = () => {
  const number = 100
  const data = 'App组件数据内容'
  return (
    <div>
      <h1>父组件</h1>
      <Child
        number={number}
        data={data}
      />
    </div>
  )
}

方式一:使用 es6 添加默认值的方式,直接在后面赋值

tsx 复制代码
const Child = ({ number = 0, data = '默认显示的数据' }) => {
  return (
    <div>
      <h2>子组件</h2>
      {number}
      <hr />
      {data}
    </div>
  )
}

const Child = ({ number, data }) => {
  return (
    <div>
      <h2>子组件</h2>
      {number}
      <hr />
      {data}
    </div>
  )
}

方式二: 使用 react 提供的defaultProps 属性,在组件中添加默认值

tsx 复制代码
Child.defaultProps = {
  number: 0,
  data: '默认显示的数据',
}
export default App

复杂组件之间的数据通讯

tsx 复制代码
import { createContext, useContext } from 'react'
//  1. 使用 createContext 创建一个上下文对象
//  2. 定义一个 Provider 组件,用于提供数据,在顶层组件使用
//  3. 在需要使用的地方通过 useContext 获取上下文中提供的对象,在需要使用数据的地方使用
const Context = createContext('')

const App = () => {
  const msg = '父组件的数据'
  return (
    <div>
      <Context.Provider value={ msg }> // 顶层组件提供数据
        父组件
        <Son />
      </Context.Provider>
    </div>
  )
}

const Son = () => {
  return (
    <div>
      子组件
      <Grandson />
    </div>
  )
}

const Grandson = () => {
  return (
    <div>
      孙子组件
      <Lower />
    </div>
  )
}

const Lower = () => {
  const value = useContext(Context)  // 使用 useContext 获取上下文中的数据
  return (
    <div>
      底层组件
      {value}
    </div>
  )
}
export default App

常用的 react hook

useState

vue3 中我们使用 refreactive 来声明响应式数据

而在 react 中我们使用 useState 钩子,来创建一个响应式数据,通过 count 来访,通过 setCount 来修改数据

tsx 复制代码
import { useState } from 'react'

function App() {
  const [count, setCount] = useState(0)
// 你将从 useState 中获得两样东西:当前的 state(count),以及用于更新它的函数(setCount)。你可以给它们起任何名字,但按照惯例会像 [something, setSomething] 这样为它们命名
  return (
      <div>
        <div>{count}</div>
        <button onClick={() => setCount(count + 1)}>按钮</button>
      </div>
  )
}

useMemo

tsx 复制代码
import { useMemo, useState } from 'react'
import './App.css'

const App = () => {
  const [count, setCount] = useState(1)
  // useMemo 有两个参数,
  // 第一个参数是一个计算函数,返回一个经过计算缓存之后的值
  // 第二个参数是一个依赖数组,当依赖数组中的值发生变化时,才会重新计算缓存值

  const double = useMemo(() => {
    return count * 2
  }, [count])
  return (
    <>
      <button onClick={() => setCount(count + 1)}>double</button>
      <div className='fs20'>{double}</div>
    </>
  )
}

export default App

useEffect

useEffect 在函数组件中执行副作用操作,如数据获取、订阅或手动更改 DOM。它可以看作是 componentDidMount, componentDidUpdate, 和 componentWillUnmount 的组合

简单理解就是 vue 中的 onMounted 周期

tsx 复制代码
useEffect(()=>{
// 需要执行的操作
},[])
  • 参数一是一个函数,可以把它叫做副作用函数,在里面放入需要执行的操作
  • 参数二是一个依赖项数组,可选,不同的依赖项会影响第一个参数函数的执行
依赖项 函数执行时机
不传 组件初始化渲染,组件更新时会再次执行
空数组 只会在初始化渲染执行一次
添加特定依赖项 组件初始化渲染,特定依赖项更新时执行

useEffect清除副作用

在useEffect中编写的由渲染本身引起的 对接组件外部的操作,社区也经常把它叫做副作用操作,比如在useEffect中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉,这个过程就是清理副作用

可以理解为 vue 生命周期中的 beforedestory

只需要在实现副作用逻辑的操作里面后面 return 一个函数里面写上清除副作用的逻辑就行

tsx 复制代码
useEffect(()=>{
// 实现副作用的逻辑操作
return ()=> {
// 清除副作用逻辑
}
},[])
tsx 复制代码
import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // 启动一个定时器
    const timerID = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);

    // 返回一个清理函数,用于清除定时器
    return () => {
      clearInterval(timerID);
    };
  }, []); // 空数组作为依赖项,意味着该 effect 只在组件挂载和卸载时执行

  return (
    <div>
      <h1>{count}</h1>
    </div>
  );
}

export default MyComponent;

useReducer

和useState类似用于管理相对复杂的状态数据

  1. 定义 reducer 函数,依据 action 对象的不同状态返回对应的 state
  2. 调用 useReducer hook函数,传入定义好的 reducer 函数和默认初始状态
  3. 使用时调用dispatch函数传入一个 action 对象,对象中包含一个 type 属性,reducer 函数会依据这个 type 属性进行不同的操作
tsx 复制代码
  import { useReducer } from 'react'

  function reducer(state,{type}) {
    if(type === 'inc') {
      return state + 1
    } else if(type === 'dec') {
      return state - 1
    } else {
      return state
    }
   }

function App() {
  const [state,dispatch] = useReducer(reducer,0)

  return (
    <>
      <button onClick={() => dispatch({type: 'dec'})}>-</button>
      &nbsp;
      <span>{state}</span>
      &nbsp;  
      <button onClick={() => dispatch({type: 'inc'})}>+</button>
    </>
  )
}
export default App

useCallback

useCallback 返回一个 memoized 回调函数,避免在每次渲染时重新创建函数,适用于传递给子组件的回调函数,以优化性能

  • 在需要缓存函数的地方调用useCallBack ,传入需要缓存的函数及依赖项数组
  • 当数组中的值变化时,重新才会创建回调函数
tsx 复制代码
import React, { useState, useCallback } from 'react';

function Parent() {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return <Child onClick={increment} />;
}

function Child({ onClick }) {
  return <button onClick={onClick}>增加</button>;
}

export default Parent;

useRef

用于绑定dom对象,访问组件的实例,类似于vue中的ref

使用时使用 .current 属性进行访问

tsx 复制代码
import React, { useRef } from 'react';

function TextInputWithFocusButton() {
  const inputEl = useRef(null);

  const onButtonClick = () => {
    // 直接访问 DOM 元素
    inputEl.current.focus();
  };

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

export default TextInputWithFocusButton;

forwardRef

使用 ref 暴露 dom 节点给父组件

在vue3中是需要使用defineexpose来暴露节点,然后通过组件的ref来获取想要的信息

父组件

tsx 复制代码
function App() {
 const inputRef = useRef(null)
 return (
  <>
  <Son ref={inputRef} />
  <button onClick={()=>inputRef.current.focus()}>获取焦点</button> //通过.current获取到组件实例来进行相应的操作
  </>
 )
}

export default App

子组件

tsx 复制代码
 // forwardRef 返回的还是一个组件
const Son = forwardRef((props,ref)=> {
  return <input ref={ref} />
})

uselmperativeHandle

通过ref暴露子组件的方法

父组件

tsx 复制代码
function App() {
 const inputRef = useRef(null)
 // 调用子组件的方法获取焦点
 const focusHandler = () => inputRef.current.focusIpt()
 return (
  <>
  <Son ref={inputRef} />
  <button onClick={focusHandler}>获取焦点</button>
  </>
 )
}

export default App

子组件

tsx 复制代码
 // forwardRef 返回的还是一个组件
const Son = forwardRef((props,ref)=> {
// 获取dom实现聚焦
const iptRef = useRef(null)
const focusIpt = () => iptRef.current.focus()
// 把聚焦方法暴露出去
useImperativeHandle(ref,()=> {
  return {
    focusIpt
  }
})
  return <input ref={iptRef} />
})

数据不可变性

在 React 中,保持 State 的不可变性(Immutability) 是指修改状态时,不直接改变原始状态对象或数组,而是创建一个新的副本,并在副本上进行修改。

一、什么是不可变性?

  • 不可变对象:一旦创建,就不能被直接修改(如修改属性、增删元素等)。
  • 修改方式 :通过创建对象的新副本,并在副本上修改数据,而不是直接修改原对象。
tsx 复制代码
// ❌ 错误:直接修改原状态
state.user.name = 'Alice';

// ✅ 正确:创建新对象并替换原状态
this.setState({ user: { ...state.user, name: 'Alice' } });

二、为什么必须保持不可变性?

1. React 的更新机制依赖不可变性

  • React 使用浅比较(Shallow Comparison) 判断状态是否变化:

    • 如果直接修改原对象,对象的引用地址不变,React 会认为状态未变化,导致组件不更新。
    • 只有创建新对象,才会触发重新渲染。
tsx 复制代码
// ❌ 错误:直接修改数组
const newTodos = todos;
newTodos.push({ id: 4, text: 'Learn React' });
setTodos(newTodos); // React 检测不到变化,不会更新!

// ✅ 正确:创建新数组
setTodos([...todos, { id: 4, text: 'Learn React' }]);

2. 避免副作用(Side Effects)

  • 直接修改原状态可能导致代码中其他依赖该状态的地方出现意外行为。
  • 不可变性确保状态的每次变化都是可预测的。

3. 支持时间旅行(Time Travel)

  • 在 Redux 或 React 状态管理工具中,不可变性是实现撤销/重做(Undo/Redo)等功能的基石。
  • 每次状态变更都保留完整的历史副本,便于回溯。

4. 性能优化

  • 不可变数据可以快速比较引用,优化 shouldComponentUpdateReact.memouseMemo 的性能判断。
  • 对大型数据结构的深比较(Deep Comparison)成本高,不可变性通过浅比较提升性能。

三、如何保持不可变性?

1. 修改对象

  • 使用 Object.assign 或扩展运算符(...)创建新对象:
tsx 复制代码
// ✅ 正确:合并对象
const newState = { ...state, age: 25 };

// ✅ 正确:修改嵌套属性
const newUser = { 
  ...state.user, 
  profile: { ...state.user.profile, name: 'Bob' } 
};

2. 修改数组

  • 使用 mapfilterconcatslice 或扩展运算符返回新数组:
tsx 复制代码
// ✅ 添加元素
const newArray = [...array, newItem];

// ✅ 删除元素(过滤)
const newArray = array.filter(item => item.id !== idToRemove);

// ✅ 修改元素
const newArray = array.map(item => 
  item.id === id ? { ...item, value: newValue } : item
);

受控组件/非受控组件

受控组件

受控组件是表单元素的值由 React 组件的状态控制的组件。也就是说,表单元素(如<input><textarea><select>等)的值是从 React 组件的状态中获取,并且其值的变化通过事件处理函数更新状态。受控组件使得数据的流向是单向的,即数据从状态流向表单元素,而表单元素的变化又会更新状态。

tsx 复制代码
import React, { useState } from 'react';

function ControlledInput() {
  const [value, setValue] = useState('');

  const handleChange = (event) => {
    setValue(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('提交的值:', value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={value}
        onChange={handleChange}
      />
      <button type="submit">提交</button>
    </form>
  );
}

export default ControlledInput;

在上述代码中,input 元素的值由 value 状态控制,每次输入框内容发生变化时,handleChange 函数会更新 value 状态。这样,输入框的值始终与 value 状态保持一致。

非受控组件

非受控组件是表单元素的值由 DOM 自身控制的组件。在非受控组件中,你可以使用 ref 来获取表单元素的值,而不是通过 React 组件的状态来控制。非受控组件通常在表单元素的值只在提交表单时才需要使用的情况下使用。

tsx 复制代码
import React, { useRef } from 'react';

function UncontrolledInput() {
  const inputRef = useRef(null);

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('提交的值:', inputRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        ref={inputRef}
      />
      <button type="submit">提交</button>
    </form>
  );
}

export default UncontrolledInput;

在上述代码中,使用 useRef 创建了一个 ref 对象 inputRef,并将其传递给 input 元素的 ref 属性。在提交表单时,通过 inputRef.current.value 获取输入框的值

受控组件和非受控组件的对比

  • 数据流向:受控组件的数据流向是单向的,由状态控制表单元素的值;非受控组件的数据由 DOM 自身控制。
  • 状态管理:受控组件需要在 React 组件中管理状态,每次表单元素的值变化时都要更新状态;非受控组件不需要在 React 组件中管理状态,通过 ref 直接获取 DOM 元素的值。
  • 使用场景:受控组件适用于需要实时获取表单元素的值、对表单元素的值进行验证和处理的场景;非受控组件适用于表单元素的值只在提交表单时才需要使用的场景。

高阶组件

在 React 里,高阶组件(Higher-Order Component,简称 HOC)是一种函数,它接收一个组件作为参数,并且返回一个新的组件。高阶组件并非 React API 的一部分,而是从 React 的组合特性里衍生出的一种设计模式。其核心作用在于代码复用、状态抽象与管理、渲染劫持等。

以下是一个简单的登录鉴权高阶组件示例,此高阶组件会对用户的登录状态进行检查,若未登录就会重定向到登录页面,反之则渲染原组件

tsx 复制代码
import React from 'react';
import { useNavigate } from 'react-router-dom';

// 模拟检查用户是否登录的函数
const isAuthenticated = () => {
    // 这里可以根据实际情况修改,比如从 localStorage 中获取 token 等
    return localStorage.getItem('isLoggedIn') === 'true';
};

const withAuth = (WrappedComponent) => {
    return function AuthComponent(props) {
        const navigate = useNavigate();

        if (!isAuthenticated()) {
            // 如果未登录,重定向到登录页面
            navigate('/login');
            return null;
        }

        // 如果已登录,渲染原组件
        return <WrappedComponent {...props} />;
    };
};

export default withAuth;
    

使用示例

假设你有一个受保护的组件 Dashboard,你可以使用这个高阶组件来保护它:

tsx 复制代码
import React from 'react';
import withAuth from './login_auth_hoc';

const Dashboard = () => {
    return (
        <div>
            <h1>欢迎来到首页</h1>
        </div>
    );
};

export default withAuth(Dashboard);

组件间共享数据

tsx 复制代码
import { useState } from 'react';

export default function MyApp() { 
  // 在 MyApp 组件中定义 count 状态和 handleClick 事件处理函数
  const [count, setCount] = useState(0);

  function handleClick() {
    // 当按钮被点击时,更新 count 状态
    setCount(count + 1);
  }

  return (
    <div>
      <h1>数据更新</h1>
      {/* 将 handleClick 函数和 count 状态通过 props 传递给 MyButton 组件 */}
      <MyButton onClick={ handleClick } count={ count }/>
      <br/>
      <MyButton onClick={ handleClick } count={ count }/>
    </div>
  );
}

// MyButton 组件接收 onClick 和 count 作为 props
function MyButton({ onClick, count }) {
  // 当按钮被点击时,调用父组件传递过来的 onClick 函数
  return (
    <button onClick={ onClick }>
      {/* 显示从父组件传递来的 count 值 */}
      当前数字为:{ count }
    </button>
  );
}
  1. 我们将 count 状态和 handleClick 事件处理函数从 MyButton 组件移动到了 MyApp 组件中。这种做法称为"状态提升",可以将共享的状态和行为放在更高层级的组件中。
  2. MyApp 组件中,我们通过 props 将 handleClick 函数和 count 状态传递给每个 MyButton 组件。这样,每个按钮都可以访问相同的状态和行为。
  3. MyButton 组件中,我们接收 onClickcount 作为 props,并将 onClick 函数绑定到按钮的点击事件上。每个按钮显示的数字都是从父组件传递过来的 count 值。

组件的排列组合

一个父组件里面套了一个子组件,那如果我想在子组件里面接着嵌套其他的功能模块的时候是把它放到子组件的内容里面吗?

我们来 try 一 try

tsx 复制代码
const App = () => {
  return (
    <div>
      <h1>父组件</h1>
      <Child />
    </div>
  )
}

const Child = () => {
  return (
    <div>
      <h2>子组件</h2>
      <Other />
      <List />
    </div>
  )
}

const Other = () => {
  return (
    <div>
      <h3>其他组件</h3>
    </div>
  )
}

const List = () => {
  return (
    <div>
      <h3>列表组件</h3>
    </div>
  )
}

export default App

可以实现效果

这样就会出现一个问题,就是我子组件里面的组件想要去拿取数据只能是在相对于它自己的父级,涉及到了一个作用域的问题

tsx 复制代码
const App = () => {
  const appData = 'App组件数据内容'
  return (
    <div>
      <h1>父组件</h1>
      <Child />
    </div>
  )
}

const Child = () => {
  const childData = 'Child组件数据内容'
  return (
    <div>
      <h2>子组件</h2>
      <Other data={childData} />
    </div>
  )
}

const Other = ({ data }) => {
  return (
    <div>
      <h3>其他组件 :{data}</h3>
    </div>
  )
}

export default App

所以在实际开发中一般使用 props 里面的另外一个属性 children 属性来渲染组件内的任何子元素

注意是小写

tsx 复制代码
const App = () => {
  const data = '父组件数据'
  return (
    <div>
      <h1>父组件</h1>
      <Child>
        <Other data={data} />
      </Child>
    </div>
  )
}

const Child = ({ children }) => {
  return (
    <div>
      <h2>子组件</h2>
      {children}
    </div>
  )
}

const Other = ({ data }) => {
  return (
    <div>
      <h3>其他组件{data}</h3>
    </div>
  )
}

export default App

这样就实现了跨层级之间的数据传递

但是如果我想要传递多个内容并把它放在指定不同的位置,该如何实现呢?

这个和 vue 中的插槽的功能有一些相像,都是向组件内分发内容,不过 vue 是可以通过具名插槽来进行指定的分发和接收

vue 的实现方式

tsx 复制代码
// app.vue
<script setup lang="ts">
  import son from './components/son.vue'
</script>

<template>
  <son>
    <template #header>
      <h1>我是标题</h1>
    </template>

    <template #default>
      <p>我是内容</p>
    </template>

    <template #footer>
      <p>我是底部</p>
    </template>
  </son>
</template>

// son.vue
<template>
  <div class="root">
    <slot name="header"></slot>
    <slot name="default"></slot>
    <slot name="footer"></slot>
  </div>
</template>

react的实现方式

在传递数据的时候是可以传递 jsx 的,我们在传递的时候就直接传递不同的内容,对应进行接收使用

tsx 复制代码
const App = () => {
  return (
    <div>
      <h1>父组件</h1>
      <Child header={<h4>头部标题</h4>} body={<p>内容</p>} />
    </div>
  )
}

const Child = ({ header,body }) => {
  return (
    <div>
      <h2>子组件</h2>
      {header}
      <hr />
      {body}
    </div>
  )
}

export default App

一个组件中与多个相同的组件,数据之间是相互隔离的

tsx 复制代码
import { useState } from 'react';

export default function MyApp() {
  return (
    <div>
      <h1>数据更新</h1>
      <MyButton />
      <MyButton />
    </div>
  );
}

function MyButton() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      当前数据: {count}
    </button>
  );
}

本文为个人学习记录,内容会根据学习进度不定期补充和更新,难免存在一定的疏漏或错误。若发现任何问题,恳请指正与建议

相关推荐
To_OC5 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic6 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy6 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志6 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble7 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯8 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜9 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司9 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
江华森10 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http
IT_陈寒10 小时前
Redis的KEYS命令把我搞崩溃了,改用SCAN才活过来
前端·人工智能·后端