技术分享
🗒️React18 核心与实战(一)React基础
00 分钟
2024-10-23
2024-11-3
type
status
date
slug
summary
tags
category
icon
password

React简单介绍

  1. React是由Meta公司研发,是一个用于构建Web和原生交互界面的库
  1. 优势
    1. 相较于传统基于DOM开发的优势
      • 组件化的开发方式:提高开发效率和组件复用性
      • 不错的性能
        • 采用虚拟dom,最大限度的减少与DOM的交互,速度快。
      相较于其它前端框架的优势
      • 丰富的生态
      • 跨平台支持
        • React Native和Expo可使用React构建Andrio、iOS等应用程序
      使用JSX,代码的可读性很好。
      可以与已有的库或者框架很好的配合。
      使用React构建组件使代码更加容易得到复用,可以很好的应用在大型项目的开发过程中。
      单向数据流减少了重复的代码,轻松实现数据绑定。

搭建开发环境

  1. create-react-app是一个快速创建React开发环境的工具,底层由Webpack构件,封装了配置细节,开箱即用.
      • npx :Node.js工具命令,查找并执行后续的包命令。
      • create-react-app :核心包(固定写法),用于创建React项目。
      • react-basic :React项目的名称(可以自定义)
      • 更多的创建方式
  1. cd +文件名,切换到文件夹下,使用 npm start 启动项目
  1. 项目文件
    1. package.json 项目的依赖配置管理文件(有点类似于 Maven 中 pom)
    2. src(核心源码)
      1. src(source 的缩写)目录是存放项目源代码的地方。在 React 项目中,这是开发的核心区域。在这里,你会编写 React 组件、样式文件、业务逻辑等代码。
      2. index.js
        1. 项目的入口
      3. App.js
        1. 项目的根组件
    3. node_modules
      1. node_modules目录是npm(Node Package Manager)在安装项目依赖时创建的目录。它用于存放项目所依赖的所有外部包(包括 JavaScript 库、工具等)。这些依赖包是根据package.json文件中的dependencies和devDependencies配置来安装的。 依赖包存储:当你在项目中运行npm install命令时,npm会从远程的npm仓库(如https://www.npmjs.com/)下载所需的依赖包,并将它们解压和安装到node_modules目录中。例如,如果你在package.json中有react作为依赖,npm会将react包及其相关的文件安装到node_modules/react目录下。这个目录下包含了react库的 JavaScript 代码、类型定义文件(如果有)等内容,这些代码可以被项目中的其他代码引用。 依赖树结构:node_modules目录具有复杂的依赖树结构。一个依赖包可能还依赖于其他的包,npm会自动解析这些依赖关系并安装所有必需的包。例如,react-dom依赖于react,当你安装react-dom时,npm会确保react也被正确安装,并且react-dom可以访问到react的代码。这种依赖树可能会很深,包含许多不同的包及其版本。不过,现代的构建工具和npm机制会尽量优化这个结构,以减少冗余和提高性能。
    4. public
      1. public目录主要用于存放项目的静态资源,这些资源在构建和部署后会原封不动地提供给用户访问。它是项目的公共资源目录,包括 HTML(index.html等) 文件、图标文件、字体文件等。

JSX基本语法

什么是jsx

JSX是JavaScript和XML(HTML)的缩写,表示在JS代码中编写HTML模板结构,它是React中编写UI模板的方式。
notion image
优势:
1、HTML的声明式模板写法
2、JS的可编程能力

jsx的本质

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

jsx基础-高频场景

JSX中使用JS表达式
在JSX中可以通过 大括号语法{} 识别JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等。
  1. 使用引号传递字符串
  1. 使用JavaScript变量
  1. 函数调用和方法调用
  1. 使用JavaScript对象
案例
上面创建js对象是通过字面量的方式创建的对象
对象字面量创建对象
优点:
  • 简单直观
    • 对象字面量是创建简单对象的最直接方式。例如,let person = { name: 'John', age: 30 };,代码非常简洁明了,很容易理解。可以直接看到对象的属性和对应的值,对于快速创建包含少量属性的对象非常方便。
  • 易于阅读和维护
    • 因为其语法简单,所以对于其他开发人员来说,阅读和理解代码的成本较低。不需要了解复杂的构造函数或类的概念,就能够明白对象的结构和内容。
  • 灵活性高
    • 可以在创建对象时直接初始化各种数据类型的属性,包括基本数据类型(如数字、字符串)和复杂数据类型(如数组、其他对象)。例如,let car = { brand: 'Toyota', colors: ['red', 'blue'], features: { hasAirbag: true, hasSunroof: false } };
缺点:
  • 不适合创建大量相似对象
    • 如果需要创建许多具有相同属性结构的对象(例如,创建多个表示学生的对象,都有姓名、学号、成绩等属性),使用对象字面量会导致代码重复。每创建一个新对象都需要重新编写属性名,这会使代码变得冗长且难以维护。
  • 缺乏复用性和封装性
    • 没有办法像构造函数或类那样,在对象之间共享方法或者进行属性的封装。例如,不能方便地为所有通过对象字面量创建的对象添加一个通用的方法来计算属性之间的关系(如计算个人所得税的方法)。
在react的{}中可以用上述的表达式,但是返回的数据或者展示的数据必须是有效的react元素
在 React 中,有效的 React 元素是可以被渲染为 UI 的对象。这些元素可以是基本的 HTML 标签、React 组件或者它们的组合。有效的 React 元素的特点包括:
  1. 基本 HTML 元素:比如 <div>, <span>, <h1> 等。这些元素会被渲染为相应的 HTML 元素。
    1. React 组件:自定义的 React 组件也可以作为有效的元素。它们可以是类组件或函数组件。
      1. 组合元素:可以将多个元素组合在一起,形成一个树状结构。
        1. Fragment:React 提供的 Fragment 组件可以让你返回多个子元素,而不需要额外的 DOM 节点。
          1. 字符串和数字:可以直接渲染字符串和数字,这会作为文本节点渲染。
            1. 数组:使用 map 等方法生成的包含有效 React 元素的数组。例如:
              JSX中实现列表渲染
              notion image
              语法: 在JSX中可以使用原生JS中的map方法遍历渲染列表。
              notion image
              案例:
              注意:
              在React中,渲染list的时候,重复的元素身上需要绑定一个独一无二的key值。(上面的key = item.id) key的作用:react框架内部使用,提升更新性能.
              关于map方法
              1. map是 JavaScript 数组的一个高阶函数。它的主要作用是对数组中的每个元素进行操作,并返回一个新的数组,新数组中的元素是原数组元素经过操作后的结果. 例如,给定一个数组numbers = [1, 2, 3],如果想得到每个元素的平方组成的新数组,可以使用map函数:let squaredNumbers = numbers.map((number) => number * number);,这样squaredNumbers就会是[1, 4, 9]。
              JSX中实现条件渲染
              notion image
              语法:在React中,可以通过逻辑与运算符&&三元表达式(?:)实现基础的条件渲染。
              案例一
              逻辑与运算符的其他运算规则是什么?
              1. 假值判断规则
              • 逻辑与运算符(&&)首先会检查左边操作数的值。如果左边操作数是假值(在 JavaScript 中,假值包括false0nullundefined""(空字符串)、NaN),那么整个逻辑与表达式的值就为左边的假值,并且右边操作数不会被计算。
              • 例如:let result1 = false && console.log("This won't be executed");,在这里,因为左边是false,所以整个表达式的值为false,并且console.log语句不会被执行。let result2 = 0 && "Some value";,由于0在 JavaScript 中被视为假值,所以result2的值为0,而"Some value"不会被考虑。
              2. 真值判断规则(除了上述提到的假值,其他值都被视为真值)
              • 如果左边操作数是真值(如非零数字、非空字符串、对象、数组等),那么整个逻辑与表达式的值由右边操作数决定。此时右边操作数会被计算。
              • 例如:let result3 = 5 && "Another value";,因为5是真值,所以会计算右边的操作数,result3的值为"Another value"let result4 = [1, 2, 3] && "Array is truthy";,数组[1, 2, 3]是真值,所以result4的值为"Array is truthy"
              1. 短路特性
              • 逻辑与运算符具有短路特性。这意味着当左边操作数为假值时,右边操作数不会被执行或计算。这在一些情况下非常有用,比如在条件判断中避免不必要的计算.
              • 例如,在一个函数中,如果要检查某个条件是否满足才执行另一个可能耗时的操作:
              • conditionfalse时,timeConsumingFunction不会被调用,从而节省了计算资源。
              关于逻辑与或
              let result = true && "This will be executed";
              在这段代码中,result的值为 "This will be executed"
              原因如下:
              当使用逻辑与运算符&&时,如果左侧操作数为真值,那么整个表达式的值为右侧操作数的值。在let result = true && "This will be executed";中,左侧操作数true是真值,所以继续计算右侧操作数,右侧操作数是字符串"This will be executed",因此整个表达式的值为这个字符串,所以result被赋值为"This will be executed"
              案例二
              需求:列表中需要根据文章状态适配三种情况,单图,三图和无图三种模式。
              解决方案:自定义函数 +  if判定语句

              事件绑定

              基本事件绑定
              在 JSX 中,可以通过将事件处理函数作为属性传递给元素来绑定事件。通常,事件的命名方式是小写字母 on 开头,后面跟随事件名,使用驼峰命名法。
              语法:on +  事件名称 = {  事件处理程序  } ,整体上遵循驼峰命名法。
              notion image

              注意事项

              1. 事件处理函数事件处理函数应该是一个函数引用,而不是函数调用。即不要加括号
                1. 传递参数:如果需要在事件处理函数中传递参数,可以使用箭头函数或 bind 方法:
                  1. 关于箭头函数
                    1. 箭头函数更简洁,适合小规模应用或不需要频繁重新渲染的场景
                    1. 箭头函数写在JSX中会在每次渲染时创建一个新函数,可能会有轻微的性能影响,通常只有在有大量列表或高频率更新时才需特别关注。
                    箭头函数的基本用法
                    • 箭头函数(Arrow Functions)是ES6引入的一种更简洁的函数写法,主要用于简化代码,并且有一些独特的特性,比如不绑定自己的 this
                    • 基本语法
                      • 箭头函数的基本语法形式如下:
                        当函数体只有一行返回值时,可以进一步简化为:
                    • 如果只有一个参数,可以省略圆括号 ()
                      • 如果没有参数,需要加上空的 ()
                        • [补充]表达式和语句的区别(更深的情况需要了解编译原理了)

                          1. 定义

                          • 表达式(Expression)
                            • 表达式是任何能产生一个值的代码片段。
                            • 表达式可以出现在需要值的地方,例如赋值、条件判断等。
                          • 语句(Statement)
                            • 语句是执行某个操作的代码块,通常用于控制程序的流程。
                            • 语句不直接产生值,而是完成某个动作或指令。

                          2. 特性

                          • 表达式的特性
                            • 产生值:表达式总是返回一个值,可以是数字、字符串、布尔值、对象等。
                            • 可嵌套:表达式可以嵌套在其他表达式中,形成更复杂的计算。
                            • 可用在赋值中:表达式的结果可以直接赋值给变量。
                          • 语句的特性
                            • 执行操作:语句通常执行某种操作,而不是返回一个值。
                            • 控制结构:语句可以用来控制程序的执行流程,例如条件语句、循环语句等。
                            • 不产生值:虽然语句可能包含表达式,但语句本身并不返回任何有意义的值。

                          3. 示例

                          表达式示例

                          • 基本数学表达式
                          • 函数调用
                          • 条件表达式(三元运算符):
                          在以上例子中,所有的代码片段都是表达式,因为它们都计算并返回了一个值。

                          语句示例

                          • 变量声明语句
                          • 条件语句
                          • 循环语句
                          在这些例子中,所有的代码片段都是语句,因为它们执行某个操作,而不是返回值。

                          4. 如何在 JavaScript 中使用

                          • 表达式可以出现在任何需要值的地方
                            • 例如,作为函数参数、赋值、条件判断等。
                          • 语句用于控制程序流
                            • 例如,定义逻辑条件、循环迭代、处理错误等。
                          函数调用在 JavaScript 中可以既作为表达式,也可以作为语句,这取决于如何使用它。让我们更详细地探讨这个概念。
                          • 函数调用作为表达式
                          当函数被调用并用于计算一个值或作为其他表达式的一部分时,它被视为表达式。此时,函数的返回值是一个有意义的结果,可以用于赋值或其他计算中。

                          示例:

                          在这里,square(5) 被用作一个表达式,其返回值 25 被赋值给变量 result
                          • 函数调用作为语句
                          当函数被调用以执行某些操作(例如打印输出、修改状态等),而不关注其返回值时,它被视为语句。在这种情况下,函数的调用只是为了执行该操作。

                          示例:

                          在这里,greet() 被用作语句,它执行 console.log("Hello!"),但我们并不关心 greet() 的返回值(它是 undefined)。

                          3. 结合使用

                          在复杂的代码中,函数调用可以同时作为表达式和语句,取决于上下文。例如,可以在 if 语句中调用一个函数,如果函数返回布尔值,则可以用作条件判断:

                          示例:

                          在这个例子中,isEven(4) 是一个表达式,它的返回值 true 被用作 if 语句的条件。

                          4. 总结

                          • 作为表达式:当函数调用用于计算一个值,或作为其他表达式的一部分时,它是表达式。例如:const result = square(5);
                          • 作为语句:当函数调用仅用于执行某些操作,而不关心返回值时,它是语句。例如:greet();

                          5. 总结

                          • 表达式总是返回一个值,可以在任何需要值的地方使用。
                          • 语句执行某种操作,不直接返回值,主要用于控制程序的流程。
                          理解这两者之间的区别有助于你在编写和阅读代码时,更清楚地知道代码的意图和执行方式。
                      特性和注意事项
                      1. this的绑定
                      箭头函数和普通函数最大的不同在于:箭头函数不会创建自己的 this,它会继承外层作用域中的 this。这个特性非常有用,可以避免在回调函数中手动绑定 this
                      示例:
                      上面的例子中,this 不会指向 Counter 类的实例。如果用箭头函数来定义 setTimeout 的回调,this 会自动指向外层的实例:
                      2. 简化回调函数
                      箭头函数通常用于回调函数中,使代码更加简洁。常见场景包括数组的 mapfilterreduce 等方法:
                      3. 不绑定 arguments 对象
                      箭头函数没有 arguments 对象。如果需要访问参数,可以用剩余参数(rest parameter)来代替:
                      4. 不支持 new 关键字
                      箭头函数不能用作构造函数,不能与 new 关键字一起使用,否则会报错。因为箭头函数没有自己的 this,所以无法作为构造函数实例化对象。

                      5. 不支持 prototype 属性

                      由于箭头函数没有 prototype 属性,不能作为构造函数来生成原型对象。一般来说,如果函数需要构造一个对象或提供原型方法,不适合用箭头函数。
                      适用场景
                      • 回调函数:如事件监听、数组操作、Promise 等异步操作。
                      • 保持 this 绑定:在需要使用外层 this 的情况下,可以用箭头函数避免手动绑定。
                      • 简化短小函数:箭头函数语法简洁,适合定义简单逻辑的函数。
                      更多了解
                      关于bind
                      bind 方法可以预先绑定参数,从而在事件触发时传递给事件处理函数:
                      解释
                      • this.handleClick.bind(this, param1, param2) 使用 bind 方法创建了一个新的函数,这个函数的第一个参数 this 是组件实例的上下文,后面的参数 param1param2 是需要传递的自定义参数。
                      • 这样当 onClick 触发时,绑定的函数会自动传递这些参数给 handleClick
                      注意
                      • bind 会立即返回一个绑定了 this 和参数的新函数,所以每次渲染时,这个绑定动作都会重新进行,可能会影响性能。为了优化性能,你可以在构造函数中绑定事件,避免每次渲染重复绑定。
                      • 复杂场景:在构造函数中预先使用 bind 更有效率,适合需要高性能的场景
                  1. 事件对象:React 会自动将事件对象传递给事件处理函数,允许你访问事件的属性:
                    1. 事件对象+参数都要:在事件绑定的位置传递事件实参e和自定义参数,clickHandler中声明形参,注意顺序对应.
                      1. notion image
                    1. 合成事件:React 使用合成事件(Synthetic Events),这是对浏览器原生事件的封装,提供了跨浏览器的一致性。
                    1. 清理事件:在类组件中,如果在 componentDidMount 或其他生命周期方法中添加事件监听器,请确保在 componentWillUnmount 中清理它们。

                    React中的组件

                    1. 一个组件就是用户界面的一部分,它可以有自己的逻辑和外观,组件之间可以互相嵌套,也可以复用多次。组件化开发可以让开发者像搭积木一样构建一个完整的庞大的应用。在 React 中,组件是构建用户界面的基本单元。每个组件可以看作是一个独立的、可复用的 UI 模块,负责呈现特定部分的内容,并管理相关的逻辑和数据。React 应用通常由多个组件组成,通过组件的组合来构建完整的用户界面。
                      1. notion image
                    1. 在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI,渲染组件只需要把组件当成标签书写即可。
                      1. 组件的核心概念
                        1. 组件是独立的:每个组件负责展示和管理自己的那部分 UI,处理自身的数据和交互逻辑,不依赖于其他组件。
                        2. 组件是可复用的:组件设计为可复用的代码块。通过重用相同的组件,你可以避免重复代码,实现统一的设计样式和行为。
                        3. 组件是声明式的:React 组件使用一种声明式语法,即你描述“UI 应该是什么样的”,而不是逐步指示如何构建 UI。React 会根据状态和属性的变化来自动更新 UI。
                      1. 组件的类型
                        1. React 中的组件主要分为两种类型:
                        2. 函数组件(Function Component)
                          • 函数组件是使用 JavaScript 函数定义的组件。
                          • 它们没有 this,而是直接通过参数 props 接收外部数据。
                          • 通过 React Hooks,可以让函数组件处理状态和副作用。
                            b. 类组件(Class Component)
                            • 类组件是使用 ES6 类语法定义的组件,继承自 React.Component
                            • 它们可以访问生命周期方法(如 componentDidMountcomponentDidUpdate)。
                            • 类组件通过 this.props 访问属性,通过 this.state 管理内部状态。

                          useState

                          聊一下React hooks
                          React 的核心是组件。v16.8 版本之前,组件的标准写法是类(class).基于类的写法,一个组件类仅仅是一个按钮,它的代码已经很"重"了。真实的 React App 由多个类按照层级,一层层构成,复杂度成倍增长。再加入 Redux,就变得更复杂。
                          React 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。 组件的最佳写法应该是函数,而不是类。但是,这种写法有重大限制,必须是纯函数,不能包含状态,也不支持生命周期方法,因此无法取代类。React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。
                          Hook 这个单词的意思是"钩子"。React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。
                          你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用use前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。
                          React 默认提供的四个最常用的钩子。
                          useState() useContext() useReducer() useEffect()
                          React的Hook函数可以看成是一组工具,它们帮助我们在函数组件里实现以前只能在类组件中使用的一些功能,比如管理状态和生命周期。
                          Hook的几个小特点:
                          • 只能在函数组件或自定义Hook里调用,不能在普通函数中使用。
                          • 不能在条件语句或循环中调用,必须在组件的最顶层调用,保证每次渲染时Hook的顺序一致。
                          React Hook的设计初衷就是为了让函数组件也能拥有“类组件”的功能,同时让代码更简洁和灵活,在没有React Hook之前,类组件虽然能实现功能,但代码会相对复杂。我们用React Hook的原因就是让代码更简洁、逻辑更清晰
                          不用Hook可以实现什么?
                          在类组件中,不用Hook确实也可以实现组件状态管理和生命周期控制,但要写很多额外代码,比如构造函数、this关键字绑定、生命周期方法(如componentDidMountcomponentDidUpdate等)。这使得代码看起来复杂,而且容易出错。
                          有了Hook,组件代码更简洁
                          1. Hook的函数式写法更简单:相比类组件,函数组件没有this、没有复杂的生命周期函数,代码量更少。
                          1. 逻辑更清晰:Hook让我们可以更灵活地组织逻辑,把相关的状态和效果放在一起,代码更易读,维护起来也更方便。
                          类组件和函数组件+Hook实现同样功能时的代码对比
                          这个计数器会显示一个数字,每点击一次按钮数字就会加1。我们用类组件函数组件+Hook分别实现这个功能。

                          1. 用类组件实现计数器
                          解释
                          • 这里需要定义一个构造函数(constructor),来初始化状态count
                          • this.setState来更新状态,这会导致组件重新渲染。
                          • handleClick是一个事件处理函数,用于点击按钮时更新count
                          • this关键字的使用在类组件中很常见,容易出错,尤其是对初学者。

                          2. 用函数组件+Hook实现计数器
                          解释
                          • useState(0)来初始化状态,这里count是状态值,setCount是更新状态的函数。
                          • 点击按钮时直接调用setCount(count + 1)更新状态,无需this关键字。
                          • 没有构造函数,逻辑直接写在组件中,代码更加简洁明了。

                          总结对比
                          类组件实现
                          函数组件 + Hook 实现
                          需要构造函数初始化状态
                          不需要构造函数
                          this.statethis.setState管理状态
                          useState直接管理状态
                          需要用this关键字调用方法
                          无需this关键字,函数式写法更简单
                          代码相对繁琐,逻辑分散
                          代码简洁,逻辑集中
                          如果函数组件不用Hook的话,就无法实现状态管理和副作用处理,因此也就无法实现动态更新的效果.
                          如果我们用不用Hook的函数组件来实现计数器,它将无法正常工作。来看个示例:
                          效果
                          • 每次点击按钮,count变量的值确实在handleClick里增加,但组件不会重新渲染,因此页面上看到的count值始终是0,完全没法实现动态更新。
                          • 函数组件没有状态机制,无法“记住”count的值,点击按钮后,UI不会重新渲染
                          在类组件中继承Component后,我们确实可以实现很多像render这样的特定方法。这些方法被称为生命周期方法必需方法render就是其中一个关键的必需方法,
                          render方法的作用
                          在类组件中,render是必须要实现的方法。它的作用是返回组件的UI结构,也就是告诉React这个组件要如何展示在页面上,每当组件的stateprops变化时,render方法会被重新调用,更新界面。
                           
                          useState是一个React Hook(函数),它允许我们向组件添加一个状态变量,从而控制影响组件的渲染结果。
                          notion image
                          本质:和普通变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(数据驱动视图)。
                          1、useState是一个函数,返回值是一个数组。
                          2、数组中的第一个参数是状态变量,第二个参数是set函数用来修改状态的变量。
                          3、useState的参数将作为count的初始值 。
                          案例:点击按钮,数值增加。
                          修改状态的规则
                          1. 状态不可变:在React中,状态被认为是只读的,应该始终替换它而不是修改它,直接修改状态.react期望我们使用不可变数据。不可变数据的概念是每次修改状态时,创建一个新对象,而不是直接修改原有对象,这样React能清晰地检测到变化
                            1. notion image
                          1. 修改对象状态:对于对象类型的状态变量,应该始终传给set方法一个全新的对象来进行修改。直接修改原对象,不引发视图变化。
                            1. notion image
                              这里先用展开运算符做个拷贝,然后再用后面重复属性进行替换即可.
                              解构
                              构赋值是ES6引入的一种便捷语法,可以从对象数组中快速提取数据并赋值给变量,这种方法可以让代码更简洁明了.
                              解构赋值的基本用法
                              1. 对象解构赋值
                              对象解构赋值可以从对象中提取指定的字段,然后赋值给新的变量。
                              基本例子
                              在这里,{ name, age }会从person对象中提取出nameage字段,并创建同名变量。
                              给变量重命名
                              如果想提取字段并给它起个新名字,可以这样写:
                              默认值
                              当我们提取的属性不存在时,可以给变量设置一个默认值:
                              2. 数组解构赋值
                              数组解构赋值允许我们按照顺序提取数组中的元素,并赋值给变量。
                              基本例子
                              数组解构赋值是基于位置的,所以我们可以很方便地提取特定位置的元素。
                              跳过元素
                              如果不需要数组中的某个值,可以跳过它:
                              使用默认值
                              和对象解构一样,数组解构也支持默认值:
                              3. 嵌套解构
                              对象和数组可以嵌套解构,这在处理复杂结构的数据时特别有用。
                              对象中的嵌套解构
                              数组中的嵌套解构
                              4. 解构的应用场景
                              解构赋值非常适合用在以下场景:
                              • 函数参数:可以直接解构函数的参数。
                                • 交换变量:可以用解构轻松交换两个变量的值。
                                  5. 解构更新
                                  在实际开发中,当我们希望更新对象的某些字段而不修改原对象时,可以用解构创建一个新对象,将更新内容解构到新对象中。通过解构来更新对象,是一种符合不可变数据原则的方法。
                                  例如,假设我们有一个对象person,需要更新其中的name字段。
                                  示例:对象解构更新
                                  解释
                                  1. ...person:使用解构语法,将person对象的所有属性复制到updatedPerson中。
                                  1. name: "Bob":覆盖person中的name字段,使updatedPerson中的name变为"Bob"
                                  1. 这种方式确保原始对象person不会被修改,而是生成了一个新对象updatedPerson
                                  更新多个字段
                                  我们可以在解构更新中覆盖多个字段:
                                  2. 数组解构更新
                                  数组解构更新比较常见的场景是添加、删除或替换特定元素。数组不像对象那样可以通过字段名直接覆盖,我们需要通过构造新的数组来进行更新。
                                  示例:数组解构更新
                                  解构更新的总结
                                  • 对象解构更新:使用...解构原对象,再指定需要更新的字段即可。
                                  • 数组解构更新:可以使用解构来构建新数组,也可以结合mapfilter等数组方法实现更复杂的更新逻辑。

                              基础样式控制

                              React组件基础的样式控制有两种方案:
                              1. 内联样式(或者行内样式)
                              内联样式是将样式直接应用于React组件的元素中。这种方式通过style属性实现,样式对象的属性名为驼峰命名法,值为字符串。
                              示例
                              优点
                              • 动态样式:内联样式允许根据组件的状态动态计算样式值。
                              • 封装性:样式与组件逻辑在一起,便于管理。
                              缺点
                              • 缺乏伪类和媒体查询:内联样式不能使用CSS伪类(如:hover)和媒体查询。
                              • 可读性差:样式与结构混在一起,可能导致代码可读性下降。
                              2. 外部样式表(CSS)
                              外部样式表是将样式定义在单独的CSS文件中,通过classNameid属性将样式应用到React组件的元素。

                              示例

                              优点
                              • 分离关注点:将样式与组件逻辑分开,提高了代码的可维护性。
                              • 更丰富的样式功能:可以使用伪类、媒体查询等CSS功能。
                              缺点
                              • 全局命名冲突:样式可能与其他样式冲突,需要注意命名。
                              • 缺乏动态控制:无法轻易根据状态变化动态调整样式(需要额外逻辑处理)。
                              小结
                              在React中,内联样式和外部样式表各有优缺点,选择哪种方案取决于具体的使用场景:
                              • 内联样式适合于需要动态更新样式的场景。
                              • 外部样式表适合于样式复杂且需要复用的场景。

                              受控表单绑定

                              概念:使用React组件的状态(useState)控制表单的状态。在React中,受控组件指的是那些将表单的值存储在组件的状态中,并且通过状态更新来改变表单值的组件。受控组件的完全由组件的状态控制,通过React的state管理表单的输入值。通常我们会将输入框的值绑定到组件的状态上,并在每次输入变化时更新状态。
                              React中的受控组件包括各种表单元素,如文本框、复选框、单选按钮、下拉菜单等
                              notion image

                              受控组件的实现步骤

                              以文本输入框为例,来实现一个受控组件:
                              1. 创建状态来存储表单的值。const [value,setValue] = useState('')
                              1. 使用value属性将状态绑定到输入框。
                              1. 使用onChange事件处理器,在输入框内容变化时更新状态。
                              <input type="text" value={value} onChange={(e)=> setValue(e.target.value)}/>

                              示例:文本输入框的受控组件

                              在这个例子中:
                              • name状态是输入框的值来源。
                              • value={name}确保了输入框的值总是与name状态同步。
                              • onChange事件每次触发都会更新name状态,这样输入框的值会实时反映在页面上。

                              受控组件的优点

                              • 数据同步:输入框的值与状态同步,任何地方访问name状态就可以得到最新的输入值。
                              • 便于验证:可以在onChange中添加数据校验、格式化等逻辑。
                              • 更易管理表单数据:在提交表单时,数据已经存在组件的状态中,无需额外处理。

                              示例:多输入框的受控表单

                              如果有多个输入框,可以使用多个状态,也可以用一个对象来管理。

                              总结

                              • 受控组件通过状态绑定和事件处理实现对表单输入的实时控制。
                              • 状态同步确保了表单输入的值始终和组件状态一致,便于数据操作和验证。
                              • 在多输入框场景中,使用对象来存储多个输入框的值会更加简洁

                              React获取Dom

                              在React中,获取和操作原生DOM元素通常通过**ref**(引用)来实现。ref允许我们直接访问某个DOM节点或React组件的实例,以便在特定场景下直接操作DOM,比如设置焦点、获取宽高等
                              1. 使用useRef获取DOM元素(适用于函数组件) hooks函数
                              在React的函数组件中,使用useRef来创建一个ref对象,并将它绑定到DOM元素上,之后就可以通过这个ref对象访问该DOM元素。
                              两个步骤:
                              1、使用useRef创建ref对象,并与JSX绑定
                              const inputRef = useRef(null)
                              <input type="text" ref={inputRef} /> 2、在DOM可用时,通过inputRef.current拿到DOM对象
                              console.log(input.current)
                              示例:获取输入框并设置焦点
                              在这个例子中:
                              • useRef初始化了一个ref对象inputRefnull表示初始值。
                              • ref对象绑定到<input ref={inputRef}>元素上。
                              • inputRef.current访问<input>元素,通过focus()方法设置焦点
                              2. 使用React.createRef获取DOM元素(适用于类组件)
                              在类组件中,可以使用React.createRef创建ref对象,然后将它附加到组件内的DOM元素上,以实现对该元素的直接操作。
                              示例:类组件获取输入框并设置焦点
                              在这里,this.inputRef是通过React.createRef()创建的。在render方法中,将其传递给<input>ref属性,this.inputRef.current就能访问<input>元素。
                              在现代React开发中,函数组件配合useRef使用最为常见。这主要是因为以下几个原因:
                              1. 函数组件已成为主流:React社区和官方的推荐趋势是使用函数组件配合Hooks,因为它们代码简洁,逻辑更容易复用,且减少了生命周期管理的复杂性。
                              1. useRef简单且灵活:在函数组件中,useRef提供了一个直接访问DOM节点的方式,而且不会在每次渲染时改变引用(即不会触发重渲染),因此性能较优。
                              1. 渐少使用类组件:随着React Hooks的引入,类组件在新项目中已不常用,因此React.createRef的使用频率也随之降低。

                              总结

                              • 如果是函数组件,使用useRef是最佳实践。
                              • 如果是旧代码或必须使用类组件的场景,则可以使用React.createRef

                              组件通信

                              概念
                              组件通信就是组件之间的数据传递,根据组件嵌套关系的不同,有不同的通信方法。
                              notion image
                              于React组件层级结构的特性,组件之间的通信分为以下几种典型的场景:
                              1. 父组件到子组件通信(props传递)
                              1. 子组件到父组件通信(回调函数)
                              1. 兄弟组件之间的通信(状态提升、Context、全局状态管理)
                              1. 跨层级组件的通信(Context、全局状态管理)
                              父传子——基础实现
                              notion image
                              实现步骤:
                              1. 父组件传递数据:在子组件标签上绑定属性
                              1. 子组件接受数据:子组件通过props参数接收数据
                              props可传递任意的数据
                              数字、字符串、布尔值、数组、对象、函数、JSX。
                              notion image
                              使用解构简化对props的使用
                              在这个例子中,我们直接解构了props对象,将name提取出来使用,简化了props.name的写法
                              props是只读对象
                              子组件只能读取props中的数据,不能直接进行修改,父组件的数据只能由父组件修改
                              props中children属性
                              在React中,children是一个特殊的props属性,允许父组件将嵌套在组件标签中的内容传递给子组件。这种方式在构建可复用组件时非常实用,比如用于包装内容的布局组件、卡片组件等。
                              children 的工作原理
                              当你在父组件中使用组件标签时,可以在标签内部嵌套任何内容(例如文本、HTML结构、其他组件等)。React会自动将这些嵌套内容作为children属性传递给子组件,子组件可以通过props.children访问和使用这些内容。
                              示例:基础用法
                              在这个例子中:
                              • 父组件Parent<Child>标签内部嵌套了一个<p>元素。
                              • Child组件通过{children}渲染这些嵌套内容。
                              • children允许父组件在不修改子组件代码的前提下控制其显示内容。
                               
                              子传父
                              notion image
                              核心思路:在子组件调用父组件中的函数并传参。
                              在React中,子组件传递数据到父组件的常见方法是通过回调函数。父组件可以将一个回调函数作为props传递给子组件,然后子组件在特定操作(例如点击、输入等)时调用该函数,将数据传回父组件。
                              子传父的基本实现方式
                              1. 在父组件中定义回调函数,这个函数会接收子组件传来的数据并进行处理(如更新状态)。
                              1. 将回调函数作为props传递给子组件
                              1. 子组件在需要时调用回调函数,并传入相应的数据。
                              示例:通过回调函数实现子传父
                              以下是一个简单的例子,演示如何通过回调函数实现子组件向父组件传递数据:
                              在这个例子中:
                              • 父组件Parent定义了一个handleReceiveMessage回调函数,该函数接收参数并更新父组件的message状态。
                              • 父组件将handleReceiveMessage函数通过onMessageSend属性传递给子组件。
                              • 子组件Child通过点击按钮,调用onMessageSend回调函数,将字符串"Hello from Child"传递回父组件。
                              • 父组件接收到子组件的数据后,更新message状态并显示。

                              另一种示例:子组件传递输入值到父组件

                              通过这种方式,子组件可以将用户输入的数据实时传递给父组件。
                              在这个例子中:
                              • 子组件的<input>在输入值改变时会调用handleChange函数,handleChange函数通过onInputChange回调将当前输入值传递给父组件。
                              • 父组件通过回调函数handleInputChange接收输入值并更新inputValue状态,从而实现实时展示子组件的输入内容。

                              总结

                              • 父组件定义回调函数:接收数据并进行处理。
                              • 通过props将回调函数传递给子组件
                              • 子组件在事件中调用回调函数,将数据传递给父组件。
                              状态提升实现兄弟组件通信
                              notion image
                              借助“状态提升”机制,通过父组件进行兄弟组件之间的数据传递。
                              1. A组件先通过子传父的方式把数据传给父组件App。
                              1. App拿到数据后通过父传子的方式再传给B组件。
                              使用context机制跨层级组件通信
                              notion image
                              实现步骤:
                              1. 使用createContext方法创建一个上下文对象Ctx
                              1. 在顶层组件(App)中通过Ctx.provider组件提供数据
                              1. 在底层组件(B)中通过useContext钩子函数获取消费数据。
                              notion image

                              useEffect

                              useEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作(副作用),比如发送AJAX请求,更改DOM等等。用于在函数组件中处理副作用。副作用通常包括数据获取订阅事件手动更改DOM,以及在组件生命周期中执行清理工作等。这些副作用原来只能在类组件的生命周期方法(如componentDidMountcomponentDidUpdatecomponentWillUnmount等)中处理,但通过useEffect,我们可以在函数组件中轻松管理这些逻辑。
                               
                              notion image
                              说明:上面的组件中没有发生任何的用户事件,组件渲染完毕之后就需要和服务器要数据,整个过程属于 “只由渲染引起的操作”。
                              useEffect 基本语法
                              • 第一个参数是一个回调函数,其中包含我们要执行的副作用逻辑。
                              • 第二个参数是一个可选的依赖数组,用于控制useEffect的调用频率。
                                • 依赖项
                                  副作用函数执行时机
                                  没有依赖项
                                  数组初始渲染+组件更新时执行
                                  空数组依赖
                                  只在初始渲染时执行一次
                                  添加特定依赖项
                                  组件初始渲染 + 特性依赖项变化时执行
                                  使用场景
                                  1. 无依赖的useEffect:仅在组件首次渲染时执行。
                                      • 空数组[]表示useEffect只会在组件首次渲染时执行一次。
                                  1. 带依赖的useEffect:依赖变化时执行。
                                      • name值发生变化时,useEffect会重新执行。
                                  1. 无依赖数组的useEffect:在每次渲染后都执行。
                                      • 不提供依赖数组时,useEffect会在每次组件渲染后执行。
                                  1. 清理副作用:在组件卸载或依赖项更改时清理。
                                    1. 在useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作,比如在useEffect中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉,这个过程就是清理副作用。
                                      说明:清除副作用的函数最常见的执行时机是在组件卸载时自动执行。
                                      • 当组件卸载时(或依赖项更改时),useEffect会调用返回的清理函数,以避免内存泄漏或重复订阅。
                              示例:获取数据并处理依赖项
                              以下示例中,useEffect用于组件加载时获取数据,并在userId更改时重新获取数据。
                              在这个例子中:
                              • useEffect依赖userId,当userId发生变化时,会重新获取用户数据。
                              • fetchData函数只在userId变化时执行。
                              假设我们从API获取一组用户数据并在组件中渲染:
                              执行顺序解析
                              1. 初次渲染
                                  • useState([])初始化data为一个空数组。
                                  • React执行组件的初次渲染,渲染出标题“用户列表”和一个空的<ul>列表。
                                  • 初次渲染完成后,useEffect的回调函数执行。
                              1. 执行useEffect中的异步请求
                                  • useEffect在初次渲染后立即执行。
                                  • fetchData函数通过API请求数据,当数据返回时,调用setData(result)更新data状态。
                                  • setData会触发组件重新渲染。
                              1. 组件重新渲染
                                  • data状态更新为新获取的数据后,React会重新渲染组件。
                                  • return语句中,React使用新的data值渲染出<ul>中的每一项。
                              渲染和函数执行顺序总结
                              • 初次渲染:先渲染空的数据结构,再执行useEffect中的副作用。
                              • 副作用执行:在副作用中执行异步数据请求,数据请求完成后更新状态。
                              • 再次渲染data状态更新触发重新渲染,将获取到的数据正确渲染在页面上。
                              注意事项
                              • 异步函数在useEffect中的用法:我们通常会在useEffect中定义并调用异步函数,而不是直接将async关键字放在useEffect回调函数上。
                              • 空依赖数组:确保useEffect仅在组件初次加载时执行一次,避免不必要的重复请求。
                              • 键值唯一性:在.map()遍历渲染时使用user.id作为key,确保每个子元素在DOM中具有唯一性。
                              关于副作用
                              useEffect可以理解为给组件添加一些“副作用”的机制。它的主要作用就是在组件完成渲染之后,执行一些在渲染过程中之外的操作,比如数据请求、事件监听、DOM操作等。
                              为什么叫“添加副作用”
                              useEffect的“副作用”作用于组件渲染之外,虽然这些操作并不会直接影响组件的核心渲染流程,但它们可以在特定时刻触发额外的行为。可以说,useEffect的本质就是让你在组件内安全地添加一些额外的行为,而这些行为可以根据组件的生命周期或状态变化来触发。
                              副作用简单来说就是:组件在渲染(显示)内容之外还需要做的额外操作,比如:
                              • 数据获取:从API请求数据,这种操作不直接影响页面显示本身,但获取到的数据会影响显示内容。
                              • 事件订阅:在某些情况下,组件需要监听事件(如鼠标点击、窗口大小变化)并响应。
                              • 手动修改DOM:例如向文档中添加、修改内容,这些操作影响页面结构或内容。
                              • 设置定时器:比如setTimeoutsetInterval等,影响页面状态。
                              这些操作都会产生副作用,因为它们不仅仅依赖组件的初始数据,还会导致组件外的环境发生变化或者依赖外部的数据资源。
                              1. 例如:给组件添加一些副作用
                              假设我们想要在页面加载时请求一个API数据,这个数据会影响组件的显示内容。此时可以用useEffect在组件加载后发起请求,然后更新状态:
                              在这个例子里:
                              • useEffect中包含了数据请求的逻辑。
                              • 这个逻辑不属于直接的渲染逻辑,而是渲染完成后需要执行的额外操作
                              • useEffect执行的数据请求就是一个“副作用”,而useEffect让我们能够优雅地管理这个副作用。
                              1. 示例:count变化时触发useEffect
                              假设我们有一个计数器,count每次更新时都会执行useEffect,在浏览器的localStorage中保存这个count值:
                              在这个例子中:
                              1. 初次渲染时:组件渲染<p>Count: 0</p>,然后useEffect运行一次,把count=0保存到localStorage
                              1. 点击按钮时setCount(count + 1)会让count变为1,触发组件重新渲染。
                              1. 重新渲染后useEffect检测到count变化,执行保存操作,把count=1存入localStorage
                              因此,每次count更新都会触发渲染,紧接着会执行useEffect的副作用。
                              对比vue中的生命周期函数
                              在React中,确实存在与Vue类似的组件生命周期,只不过表现方式不同。在React中,组件的生命周期可以通过类组件的生命周期方法函数组件中的useEffect钩子来管理。
                              1. 组件挂载(相当于componentDidMount
                              如果想在组件第一次挂载后执行某些逻辑(例如数据获取、事件监听等),可以传递一个空依赖数组[]useEffect
                              2. 组件更新(相当于componentDidUpdate
                              如果你需要在特定的状态或属性变化时执行某些逻辑,可以将这些依赖项放入useEffect的依赖数组中。例如,当某个状态(如count)变化时执行:
                              3. 组件卸载(相当于componentWillUnmount
                              要在组件卸载前执行一些清理工作,可以在useEffect中返回一个清理函数。这个函数会在组件卸载时自动调用,类似于componentWillUnmount的效果:
                              小结
                              通过useEffect,函数组件可以处理挂载、更新和卸载等生命周期逻辑,使得函数组件能够更加灵活和简洁地管理副作用
                              React中的生命周期
                              notion image
                              在React中,组件的生命周期分为三个主要阶段:挂载、更新和卸载。下面是每个阶段的具体说明和触发条件:
                              1. 组件挂载
                              什么是挂载?
                              • 挂载是指组件被创建并插入到DOM中的过程。在这个阶段,React会调用相关的方法来初始化组件。
                              什么时候发生挂载?
                              • 当组件第一次被渲染到页面上时会发生挂载。具体情况包括:
                                • 在父组件中使用子组件,React会创建子组件的实例并将其添加到DOM中。
                                • 在应用加载时,根组件会挂载并渲染其子组件。
                              对应的方法(类组件)
                              • constructor:初始化状态和绑定方法。
                              • componentDidMount:组件挂载后调用,可以进行数据请求、订阅等操作。
                              函数组件
                              • 使用useEffect(() => {}, []):在组件首次挂载后执行。
                              2. 组件更新
                              什么是更新?
                              • 更新是指组件的状态(state)或属性(props)发生变化时,React会重新渲染组件的过程。
                              组件更新的过程确实包含了组件挂载的概念,但两者是不同的阶段.
                              挂载(Mounting)
                              • 定义:挂载是指组件首次被创建并添加到DOM中的过程。这个阶段发生在组件的生命周期开始时。
                              • 发生情况:组件在首次渲染时,React调用挂载相关的生命周期方法。
                              • 对应的方法
                                • 对于类组件,constructorcomponentDidMount会被调用。
                                • 对于函数组件,useEffect会在首次挂载时执行。
                              更新(Updating)
                              • 定义:更新是指组件的状态(state)或属性(props)发生变化时,导致组件重新渲染的过程。
                              • 发生情况:当组件的状态或属性变化时,React会重新渲染该组件,这个过程可以被看作是“更新”。
                              • 包含的情况:更新不仅包括已经挂载的组件(即已存在于DOM中的组件),也包括组件因状态或属性变化而重新渲染的情况。第一次渲染(挂载)也是一种更新,因为组件从未存在于DOM中。
                              • 对应的方法
                                • 对于类组件,componentDidUpdate会被调用。
                                • 对于函数组件,依赖于useEffect的变化来执行逻辑。
                              总结
                              • 挂载是更新的一部分。更新包括:
                                • 组件的首次挂载(此时状态和属性为初始值)。
                                • 后续的状态或属性变化导致的重新渲染。
                              所以,尽管“更新”这个术语通常指的是状态或属性变化引起的变化,但它也涵盖了组件首次挂载时的情况。通过这种理解,开发者可以更清楚地管理组件的生命周期和状态变化。
                              什么时候发生更新?
                              • 组件的更新可以由以下几种情况触发:
                                • 更新状态:使用setStateuseState修改组件的状态。
                                • 更新属性:父组件传递给子组件的新props。
                                • 强制更新:使用forceUpdate方法(不常用,通常不推荐)。
                              对应的方法(类组件)
                              • componentDidUpdate:组件更新后调用,可以比较新旧状态或属性。
                              • shouldComponentUpdate:决定组件是否需要更新,返回truefalse
                              函数组件
                              • 使用useEffect(() => {}, [dependencies]):当依赖项变化时执行。
                              3. 组件卸载
                              什么是卸载?
                              • 卸载是指组件从DOM中移除的过程。在这个阶段,React会清理组件的资源,防止内存泄漏。
                              什么时候发生卸载?
                              • 组件被移除时会发生卸载,通常情况包括:
                                • 当父组件重新渲染并不再包含子组件。
                                • 在路由切换时,组件被卸载。
                                • 当条件渲染使得组件不再渲染时。
                              对应的方法(类组件)
                              • componentWillUnmount:在组件卸载前调用,用于清理订阅、定时器等副作用。
                              函数组件
                              • useEffect的返回函数中进行清理,这个函数会在组件卸载时被调用。
                              小结
                              • 挂载:当组件首次渲染到DOM中时,调用componentDidMount(类组件)或useEffect(函数组件)中的初始逻辑。
                              • 更新:当组件的状态或属性发生变化时,调用componentDidUpdate(类组件)或相应的useEffect(函数组件)。
                              • 卸载:当组件从DOM中移除时,调用componentWillUnmount(类组件)或useEffect的清理函数(函数组件)。

                              总结

                              useEffect允许你在组件渲染之外添加额外的行为,并确保这些行为在组件的整个生命周期中得到妥善的管理和清理。这种“添加副作用”的机制让React函数组件不仅能够完成显示内容,还能灵活地与外部环境进行交互。
                              总结
                              • useEffect使函数组件能够在不同渲染阶段处理副作用。
                              • 通过设置依赖项,可以控制useEffect何时执行。
                              • 清理函数可以防止组件卸载或依赖变化时的资源泄漏。

                              自定义hook

                              自定义Hook是以use打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用。
                              通过自定义 Hook,开发者可以在多个组件之间共享状态逻辑,而无需改变组件的结构。
                              什么是自定义 Hook?
                              自定义 Hook 是以 use 开头的普通 JavaScript 函数,内部可以使用其他 Hook,例如 useStateuseEffect 等。自定义 Hook 允许将状态逻辑提取到函数中,从而在多个组件中重用。
                              自定义 Hook 的步骤
                              1. 定义函数:创建一个以 use 开头的函数。
                              1. 使用内置 Hook:在该函数内部使用 React 的内置 Hook。
                              1. 返回值:返回需要共享的状态和行为。
                              示例
                              假设我们需要一个计数器的自定义 Hook,可以在多个组件中使用。
                              何时需要自定义 Hook?
                              • 逻辑复用:当多个组件需要共享相似的状态逻辑时,使用自定义 Hook 可以减少重复代码。
                              • 复杂逻辑管理:当某个组件的状态逻辑过于复杂,导致 useEffectuseState 嵌套过深时,可以考虑将其提取为自定义 Hook。
                              • 状态管理:处理复杂的状态管理时(例如,表单处理、异步请求等)。
                              注意事项
                              • 命名规则:自定义 Hook 必须以 use 开头,以便 React 能识别它并保证其遵循 Hook 的规则。
                              • 避免副作用:在自定义 Hook 中,确保副作用清晰,并在必要时进行清理。
                              • 保持简单:尽量保持自定义 Hook 简单易懂,避免过度复杂化。
                              • 只能在组件中或者其他自定义Hook函数中调用
                              • 只能在组件的顶层调用,不能嵌套在if、for、其他函数中。
                                • 理解:就是Hooks不能有条件的执行,只能直接在顶部采用 const {value,handleToggle} = useToggle( ) 这种方式,直接调用。
                              最佳实践
                              1. 保持可重用性:设计自定义 Hook 时,确保它足够通用,以便在多个地方使用。
                              1. 清晰的接口:自定义 Hook 应该有明确的输入(参数)和输出(返回值),使其易于使用。
                              1. 文档化:为自定义 Hook 提供使用示例和文档,便于团队成员理解和使用。
                              1. 组合 Hooks:可以将多个自定义 Hook 组合在一起,以实现更复杂的功能。
                               
                              和共用函数的区别
                              在 React 中,普通函数是不能直接使用 useStateuseEffect 这样的 Hook 的
                              Hook 的使用规则
                              1. 在函数组件或自定义 Hook 中调用
                                  • Hook 只能在函数组件的顶层调用,或者在自定义 Hook 中调用。也就是说,不能在常规的普通函数中调用 Hook。
                                  • React 通过这种方式确保了 Hook 的调用顺序是相同的,从而使 React 可以正确地管理组件的状态和生命周期。
                              1. 不能在条件语句、循环或嵌套函数中调用 Hook
                                  • 这同样是为了保证 Hook 的调用顺序不变。否则,React 无法正确追踪状态和副作用的管理。
                              为什么不能在普通函数中使用 Hook?
                              • 状态管理useStateuseEffect 是设计用来管理组件状态和副作用的。它们依赖于 React 的内部机制,只有在 React 管理的环境中才能正常工作。
                              • 生命周期:React 组件有明确的生命周期(如挂载、更新和卸载),而普通函数没有这样的生命周期。Hook 的实现依赖于这些生命周期。
                               

                              B站评论案例

                              需求:
                              notion image
                              notion image
                              渲染评论列表
                              1. 使用useState维护评论列表
                              1. 使用map方法对列表数据进行遍历渲染(别忘了加key)
                              删除评论
                              1. 只有自己的评论才显示删除按钮
                              1. 点击删除按钮,删除当前评论,列表中不再显示
                              渲染导航tab和高亮显示
                              点击谁就把谁的type(独一无二的标识)记录下来,然后和遍历时的每一项的type做匹配,谁匹配到就设置负责高亮的类名。
                              notion image
                              notion image
                              评论列表排序功能
                              1. 点击最新,评论列表按照创建时间排序(新的在前),点击最热按照点赞数排序(多的在前)
                              1. 把评论列表状态数据进行不同的排序处理,当成新值传给set函数重新渲染视图UI。
                              lodash
                              Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。
                              classnames优化类名控制
                              classnames是一个简单的JS库,可以非常方便的通过条件控制class类名的显示
                              notion image
                              发表评论
                              1. 获取评论内容
                                1. 点击发布按钮——发布评论
                                  1. 1、rpid要求一个唯一的随机数 id-uuid(uuid这个库)
                                    2、ctime要求以当前时间为标准,生成固定的格式 (day.js)
                                    3清空内容并重新聚焦
                                     
                                优化需求
                                通过接口获取评论列表并渲染
                                使用json-server 工具模拟接口服务,通过axios 发送接口请求。
                                注:json-server是一个快速以.json文件作为数据源模拟接口服务的工具。
                                axios是一个广泛使用的前端请求库。
                                json-server的使用
                                1. 安装
                                # D 指安装到开发时依赖 npm i json-server -D 2. 配置package.json
                                使用useEffect调用接口获取数据
                                自定义hook函数封装数据请求
                                封装一个可以执行数据请求的自定义 Hook,并将请求状态和数据返回给组件使用。
                                思路步骤
                                1. 编写一个 use 开头的函数
                                所有的自定义 Hook 都应该以 use 开头(例如 useFetch),这是 React 识别该函数为 Hook 的方式。使用 use 前缀还可以帮助开发者知道这个函数包含了可能影响 React 渲染的状态逻辑。
                                2. 函数内部编写封装的逻辑
                                在自定义 Hook 中封装数据请求的核心逻辑,包括:
                                • 定义状态:用于存储请求数据、请求状态(如加载中)和错误信息。
                                • 使用 useEffectuseEffect Hook 可以在组件挂载时触发请求。
                                • 异步请求:在 useEffect 中进行异步请求,并根据请求的结果更新状态。
                                3. return 出去组件中用到的状态和方法
                                将请求的数据、加载状态和错误信息返回给调用组件,以便组件能够访问这些值。
                                4. 组件中调用 Hook 并解构赋值使用
                                在组件中调用自定义 Hook,然后将其返回的状态和数据解构赋值使用,展示数据或控制加载、错误状态。
                                详细代码实现
                                步骤 1 和 2:创建 useFetch 自定义 Hook
                                步骤 3:在组件中调用 useFetch Hook
                                使用这个自定义 Hook,可以使组件代码更简洁,并集中管理数据请求的逻辑。
                                步骤 4:在组件中解构赋值和使用
                                封装评论项Item组件
                                notion image
                                抽象原则:App作为"智能组件"负责数据的获取,Item作为"UI组件"负责数据的渲染
                                在 React 代码设计中,“智能组件”和 “UI 组件” 是两种常见的组件分类方式,用于帮助开发者更好地分离关注点、提高代码的可复用性和可维护性。这种划分方式受到"容器组件"和"展示组件"设计模式的影响,尤其是在管理复杂的状态和逻辑时非常实用。

                                智能组件(Smart Component)

                                智能组件,顾名思义,负责“思考”——它们处理数据、业务逻辑和状态管理。智能组件常常是“容器组件”,负责连接数据源(比如后端 API 或 Redux 的全局状态),并将数据传递给子组件。它们通常不包含样式,专注于“如何获取和管理数据”。
                                特点
                                1. 负责状态和逻辑:通常包含 useStateuseEffect 等状态和副作用逻辑。
                                1. 与数据源交互:可以发起 API 请求或从全局状态(如 Redux)中获取数据。
                                1. 向子组件传递数据和回调函数:把处理好的数据和方法传递给子组件(即 UI 组件)。
                                1. 不关心样式和布局:专注于逻辑,而不直接管理 UI 样式。
                                示例
                                在这个例子中,UserContainer 是一个智能组件,负责获取用户数据并将数据传递给 UserList。它不负责呈现具体的 UI 样式。

                                UI 组件(Dumb Component)

                                UI 组件,也称为“展示组件”,仅负责显示内容。它们往往只关心接收的 props,并根据这些 props 来呈现 UI,通常不包含状态管理逻辑或数据获取逻辑。UI 组件关注于“如何展示数据”,并包含样式和布局。
                                特点
                                1. 只关注 UI:通常是无状态的函数组件,仅负责根据 props 渲染内容。
                                1. 接收并展示数据:通过 props 接收数据并在 UI 中显示。
                                1. 无数据源交互:不包含与外部数据源的交互。
                                1. 高可复用性:因为没有业务逻辑或状态依赖,可以在多个场景下复用。
                                示例
                                在这个例子中,UserList 是一个 UI 组件,它从父组件接收 usersloading 两个 props,根据这些 props 渲染内容。它不涉及数据获取或状态管理逻辑。

                                智能组件和 UI 组件的优势

                                这种划分方式能够帮助开发者清晰地分离逻辑代码和展示代码,带来以下好处:
                                1. 提高组件的可复用性:UI 组件没有逻辑依赖,能在不同场景下复用。
                                1. 降低复杂度:将业务逻辑和 UI 分离,使得每个组件职责单一、易于测试和维护。
                                1. 增强测试性:UI 组件由于没有状态,更容易测试,而智能组件的逻辑可以独立测试。

                                什么时候使用智能组件和 UI 组件

                                • 智能组件:当一个组件需要处理数据、状态、API 请求或其他复杂逻辑时,考虑将其设计为智能组件。
                                • UI 组件:当一个组件只负责展示内容而不需要管理数据时,优先将其设计为 UI 组件。

                                总结

                                智能组件和 UI 组件的划分是组件设计的一种思路。智能组件处理数据和逻辑,将数据传递给 UI 组件,而 UI 组件专注于展示。这样的分层设计可以有效提升代码的结构清晰度、复用性和可维护性,是 React 中常用的设计模式。
                                改造后的结果