React学习笔记
目录
此文章内容主要与另一前端框架“Vue”框架作对比学习,介绍React的基本操作/使用。
介绍
React是一个JavaScript库,由Facebook团队开发和维护,是用来构建Web和原生交互界面的库。简单说就是用来简化前端页面搭建的工具。
开发环境搭建(脚手架)
脚手架:帮助我们快速搭建项目的基本结构,减少前期配置,加快项目开发。
CRA(create-react-app)构建
CRA是一个快速创建React开发环境的工具,底层由Webpack构建,封装了配置细节,开箱即用。
我们使用CRA构建工具,可以快速的构建React项目的基本结构,直接进行开发。
快速开始
1.使用命令"npx create-react-app 项目名",快速创建项目基本结构代码。
2.进入项目目录,执行npm start
3.访问localhost:3000,查看脚手架创建并启动的项目。
Vite构建
Vite的构建速度相对CRA来说快很多,因为Vite在构建项目时不会把所有的项目依赖先下载,需要构建项目之后,我们使用"npm i"命令进行依赖的下载,然后使用"npm run dev"启动项目。
1. 使用命令"npm create vite@latest",然后在命令窗口选择好react项目选项。
2.进入目录,执行"npm i"下载项目依赖
3.使用命令"npm run dev"启动项目,访问localhost:5173查看项目界面。
两种脚手架,使用哪一个都可以,目的都是为了帮助我们快速构建项目的基本代码和配置,让我们可以专注于开发更重要的界面和业务逻辑代码。
JSX基本
查看脚手架代码
在说明JSX之前,我们先看看使用脚手架构建的项目基本代码的模样(在此使用CRA脚手架构建的代码作展示说明)。
将非必要代码删除,留下App.js 和 index.js :
说明:
1.在React中,组件是一个.js后缀文件中的函数,此函数返回的是元素节点<div>。
2.<App>组件外包裹的React.StrictMode作用:用来帮助我们识别项目开发时存在的潜在问题(类似语法检查),渲染时不会影响。
JSX的本质/解析
概念:JSX是JavaScript和XML的缩写,代表我们可以在HTML模板中编写JS代码,这种编写方式是React编写页面模板的方式。
例如:
我们在<div>中使用{}来引用并显示我们JS变量内容,实际上这就是一种JSX语法。
实际上,JSX并不是标准的JS语法,浏览器本身并不能识别,需要通过解析工具解析成js代码之后,才能够让js解释器理解并在浏览器中运行。
JS表达式
在上面的演示中,我们通过{ }来引用并显示JS变量内容,实际上{}的引用方式就是JS的表示式。
JS表达式不仅能够引用变量,还可以引用字符串,方法和对象。
JSX中的列表渲染方式
渲染列表是一个常见的操作,例如我们向后端调用接口获取列表数据,然后渲染到页面上显示。
在Vue中我们使用v-for来遍历列表,实现列表渲染。
那么在React中我们该如何渲染列表呢?
示例:
在React中,没有像Vue的v-for指令,我们仅能通过遍历数组的方式,将数组中的元素遍历取出渲染,如上Map遍历。
简单条件渲染
描述元素的显示和控制,例如我们在Vue中使用指令v-if来控制元素标签的显示和控制。
示例:
如上,我们使用&& 和 三元运算符控制元素标签及组件的控制渲染。
复杂的条件渲染
在上面,我们使用了&& 和 三元运算符实现了简单的条件控制,但是在复杂的条件渲染场景,例如存在"else-if","else"的情况,仅仅通过简单的&& 和三元运算符就无能为例了。
示例:
通过声明一个函数,在函数中使用if条件语句,条件返回JSX内容,实现复杂的条件渲染控制。
事件系统
绑定事件:
语法:on + 事件名称 = { 事件的处理程序 }
绑定点击事件示例:
获取事件参数对象
获取事件触发时产生的一些参数,如事件触发时鼠标点击的位置参数。
传递自定义参数
在触发事件时,传递自定义参数到处理事件的函数中。
如果需要传递自定义参数,必须将函数的函数的声明改为箭头函数的写法。
获取事件参数对象的同时接收自定义参数
在箭头函数的参数中声明e参数,注意在处理函数中获取e的顺序。
React中的组件
React中的组件定义不像Vue以.vue文件后缀定义,在上面"脚手架的代码查看"中我们看到了React的组件定义形式,就是一个JS函数,函数中返回JSX结构。
我们一般将具有复用性的页面/功能代码进行封装成组件,然后在多个地方引用。降低代码冗余度,提高复用性。
同一文件声明多组件例如:
上方,我们直接在同一个组件文件(App.js)中定义多个组件,然后直接组合使用。
但是实际开发中,这样的方式并不推荐,会造成代码冗余。
一般是将一个组件的代码声明到同一个组件文件中,然后通过js引入的方式引用。
组件文件封装:
值得注意的是:我们在给组件命名的时候,需要给组件名称以大写字母开头。
useState(数据驱动视图)
useState在Vue中就类似:我们使用ref 或 reactive定义变量,当变量数据发生变化的时候,会自动驱动页面视图进行自定更新。
在React中我们使用useState定义的变量,称为状态变量。
当状态变量发生变化的时候,会自动驱动视图进行更新。
在实际开发中,例如我们可以在页面初次加载时异步获取数据展示,当获取到数据赋值给状态变量之后,会自动驱动视图进行更新,使得我们可以动态看到数据展示。
状态变量的定义
可以看到,useState函数返回一个数组,其中第一个是状态变量,而第二个是更新该状态的函数(函数名自定义)。
只有使用该函数对状态变量值进行修改,才会动态更新视图。
点击计数器示例
如下,我们会使用状态变量实现一个”点击计数器“案例。
主要是为了演示:当数据发生变化,页面数据也会动态发生变化(数据驱动视图)
当我们调用setCount函数更新count值时,页面会动态更新。
修改对象状态
组件样式处理
react一般的样式处理有两种:①行内式 ②className类名
值得注意的是:元素属性"className"属性名是固定不可自定义的。
classnames库
使用classnames库,可以非常方便的通过条件动态控制class类名的显示。
不使用classnames库的条件控制样式
如果元素存在多个样式,我们进行条件判断就需要使用字符串拼接的方式设置clss。
还是比较繁琐。
整合clssnames库
安装依赖:
npm i classnames
使用:
使用语法,官方描述:
classNames库更多信息:点击查看
表单的双向绑定
在React中并没有类似Vue中的"v-modle"指令来指定数据和视图的双向绑定。
我们仅有useState实现数据驱动视图,还差视图操作改变数据,实现双向绑定。
实际上在React中的双向绑定我们可以使用监听表单输入和useState配合使用实现表单和变量数据的双向绑定。
获取DOM元素
在React中想要获取/操作DOM元素,需要使用useRef函数。
组件间通信
React中的组件通信原理和Vue类似:
父传子:调用子组件时设置值属性,子组件props接收。
子传父:调用子组件时传递代参回调函数。
兄弟组件通信:状态提升,通过父组件作中间人传递。
以下通过“同一文件声明多个组件”的方式说明组件间数据传递。
父传子
传入多个参数,查看props:
props接收参数类型说明
----->
当我们在调用子组件时,在子组件标签内声明有其他的标签元素,那么这些标签元素在子组件中也会接收到:
子传父
关于兄弟间的通信不做演示,无非就是先子传父,在通过父传子。
父组件作为中间人为两个子组件传递数据。
useEffect
useEffect不仅可以充当类似Vue中的生命周期函数Mounted,还可以作到监听器的作用
使用场景:当我们想要页面初次加载完毕执行函数,或者想要监听某个状态变量的变化时可以使用。
语法
useEffect(() => {}, [])
参数1:一个函数,编写我们想要执行的操作。
参数2:依赖项,通过这个参数,我们可以控制Effect的功能(是Mounted还是监听器)。
依赖项参数说明
依赖项:可以看作就是"状态变量"
1.在没有声明依赖项(第二个参数为空)时,组件加载完毕会渲染页面,每一个状态变量发生变化时也会重新执行操作函数。
2.依赖项为空数组时:作用类似于Vue中的Mounted,仅在页面初次加载完毕后执行函数。
3.声明特定依赖项时:作用类似Vue中的监听器watch,每当监听的依赖项发生变化时会执行函数。
空依赖项代码示例:
期待的控制台输出:当页面加载完毕,执行一次getData(),而当任一状态变量,如count发生变化执行getData()。
页面加载完毕,查看控制台我们会发现一个问题:getData函数默认执行了两次。
原因:由于React的严格模式(Strict Mode)在开发环境下会强制执行一些额外的渲染步骤(例如模拟组件卸载和重新挂载步骤)
解决:我们可以将严格模式关闭,但是这样的方式并不推荐。
值得注意的是,虽然关闭严格模式可以解决问题,但是一般不推荐,因为上述问题仅存在于开发环境中,在生产环境中并不会出现。
空数组依赖项示例
期待的控制台输出:仅在页面初次加载完毕时调用函数getData(),点击按钮修改状态变量,或其他状态变量发生变化时不会重新调用getData()。
页面加载完毕,控制台默认打印两次(函数调用两次),依然是上方所说的严格模式的问题。,当点击按钮,控制台不再会有打印(无函数调用)。
特定依赖项示例
期待的控制台输出:当页面加载时调用getData()函数打印,当点击第一个按钮时,修改依赖项count值时调用函数打印,当点击第二个按钮时不会调用函数打印。
通过测试可以知道,我们在useEffect的依赖项声明了count之后,当count值发生变化,会重新调用执行函数,而其他状态变量发生变化不会调用useEffect中的执行函数,这实际上就是监听器的作用。
清除副作用函数
什么是"副作用"?
我们在useEffect中的执行函数程序就是副作用。
为什么要清除副作用?
实际上就类似于我们在Vue中的unMoutned(组件卸载)时做的收尾操作,例如我们在组件挂载时声明了一个定时器,那么我们在组件卸载时就可以将还在执行的定时器删除,以免浪费性能。
语法:
我们将清除副作用逻辑代码放到return的箭头函数中即可。
自定义Hook函数实现
当我们想要封装一些逻辑代码时,常见的作法就是声明一个普通函数进行逻辑封装,但是在React中,我们在普通函数中不能直接使用
useState
或其他 React 特定的 Hook,因此封装必然受到限制,因此有时候我们不得不将代码逻辑封装到Hook函数中。
自定义Hook示例:
定义细节:自定义Hook函数必须以use开头
ps: React 不会自动去检查函数是否符合 hook 的规范,而是通过检查名称是否以
use
开头,来决定是否要将该函数视为 hook。如果你没有遵循这个命名规则,React 就无法正确处理你的 hook,可能会导致状态管理错误或其他不可预期的问题。
自定义Hook使用注意点
1. 只能在组件或其他Hook函数中调用。
2. 只能在组件的顶层调用,不能嵌套在if,for或其他嵌套的函数中。
例如:错误例子
Redux - 状态管理库
类似于在Vue开发时所使用的状态管理库:Vuex 或 Pinia。
我们一般将需要共享的数据(状态)存放到Redux中,以便所有组件获取或修改,实现状态共享。
Redux原理简述
核心原理是通过单一数据源(称为 Store)来管理应用的全局状态,确保状态的可预测性和一致性。它遵循以下几个关键原则:
-
单一状态树(Single Source of Truth):整个应用的状态以一个对象树的形式存储在唯一的 Store 中。所有组件都从这个 Store 读取状态,这样应用的状态可以集中管理和跟踪。
-
状态只读(State is Read-Only):唯一能够改变状态的方法是通过触发(dispatch)一个 Action。Action 是一个描述事件的普通 JavaScript 对象,包含类型(type)和可能的负载(payload)。不能直接修改状态,必须通过 Action 来表达“想要做什么”。
-
使用纯函数来执行状态更新(State Changes are Made with Pure Functions):更新状态的逻辑被封装在 Reducer 函数中。Reducer 是一个纯函数,它接收当前的状态和 Action,返回新的状态。Reducer 不允许有副作用,不能直接修改传入的状态,只能返回新的状态对象。
这些原则确保 Redux 的状态管理可预测,调试容易,特别是当应用变得复杂时,可以通过日志工具跟踪状态变化。同时,通过时间旅行(time-traveling debugging)功能,开发者可以轻松回溯应用的状态,确保每次状态变更都是透明和可追溯的。
React整合Redux
安装依赖
1. Redux Toolkit 2.react-redux
npm i @reduxjs/toolkit react-redux
创建目录结构
modules:Redux子模块,实现模块化。
index.js:Redux入口文件,整合子模块,暴露store。
编写子模块代码
// 计数器Store子模块 counterStore.js
import { createSlice } from "@reduxjs/toolkit";
const counterStore = createSlice({
// 定义状态
name: "count",
// 初始化状态
initialState:{
count: 0
},
// 声明更新状态的函数
reducers:{
increment(state){
state.count++;
},
decrement(state){
state.count--;
}
}
})
// 解构出更新函数
const { increment, decrement } = counterStore.actions;
// 获取Reducer
const reducer = counterStore.reducer;
// 按需导出函数
export { increment, decrement }
// 默认导出Reducer
export default reducer;
编写index.js(整合子模块)
// 导入子模块reducer - index.js
import { configureStore } from "@reduxjs/toolkit"
import counterReducer from "./modules/counterStore"
// 定义store导出
const store = configureStore({
reducer: {
counter: {
counter: counterReducer // counter为自定义名称,在组件内使用时的名称
}
}
})
export default store;
store配置到React入口文件
到此,一个count共享状态就整合好了。
组件中获取共享状态
接下来我们尝试在组件中获取共享状态值:
获取更新状态的函数
获取我们在redux子模块中定义的更新函数,用来更新状态值。
调用更新函数时传参
在子模块中定义带参数的更新函数:
组件中引入并调用传参:
Redux调试(浏览器插件)
ReactRouter(路由)
通过访问不同的地址路径显示不同的组件/视图,与Vue中的Vue-Router一样。
整合路由
下面演示整合步骤,构建出"一级路由",当访问不同地址路径,显示不同页面视图。
安装依赖
npm i react-router-dom
配置路由映射
如下会设计"登录"和 "首页"两个路由映射。
当我们访问/main路径时,显示<App>组件作为页面视图。
访问"/loign" 和 "/mian"测试:
路由配置模块化
在上面的路由整合中,我们将路由配置放到了React入口,如果许多配置都放到入口js文件,这样就会使得入口代码比较混乱。
在一般情况下,我们会像整合store一样,将路由配置放到特定文件夹中模块化。
编写页面:
编写路由配置映射:
在React入口文件引入路由配置并使用:
路由导航
在上面我们想要访问不同的路由页面,访问方式为手动修改浏览器地址栏为路由地址。
在实际开发中我们不可能让用户自己在浏览器地址栏访问不同页面吧。
所以,路由导航说的就是在代码层面进行路由页面跳转。
声明式导航
通过<link to="/路由地址"/>标签进行路由跳转,当用户点击该标签,自动跳转到声明的路由地址,显示路由页面。
示例:
通过<link>标签的to属性值控制,实现路由页面跳转。
编程式导航
通过useNavigate()函数进行跳转,可以在JS逻辑中进行调用实现路由跳转。
通过useNavigate()函数可以在js代码逻辑中进行跳转,这种方式是更灵活的路由跳转方式。
路由跳转传参
当我们在进行路由/页面跳转的时候,如果想要给跳转页面传递一些参数,可以在跳转路由地址时将参数声明上。
例如:我们从首页跳转到文章页,我们就可以在跳转时携带参数"文章编号",跳转后文章页就可以获取跳转参数获取文章数据。
我们传参的方式有两种:1. searchParams传参 2.params传参
searchParams传参
这种传参方式就像我们调用后端接口传入请求参数的方式,通过?和&拼接参数。
示例:
在跳转首页时传递用户名称:
在跳转目标页面接收参数:
如上仅传递了一个参数,如果要传递多个,请使用&进行拼接。
例如:"/main?userName=Tom&age=18"
params传参
这种方式的传参不用?和&拼接,直接使用/进行拼接,像调用后端接口的"路径参数"一样。
示例:
1.需要到路由配置的位置声明接收路径参数
如上就是声明了想要在跳转/login的时候接收id路径参数。
如果想要声明多个请使用"/"分隔
2.路由参数声明
3.接收路径参数
嵌套路由配置
也就是配置多级路由,如二级路由,三级路由...。
最直观的例子就是我们一般的数据管理后台,当点击不同导航,显示不同数据管理页。
一级路由用于切换我们的页面,二级路由在一级路由页面中进行组件切换路由显示。
实现二级路由配置
如下演示"二级路由用户管理和文章管理的切换":
1.声明两个二级路由视图组件
2. 配置二级路由
3.在组件中跳转二级路由
当我们点击不同的link,就会在<Outlet>路由出口显示配置好的路由组件。
默认二级路由
"默认二级路由"说的是:当页面加载,路由默认访问为"/",那么在路由出口就不会显示任何组件视图。在一般情况下我们希望当页面加载完毕,显示一个默认的组件视图。
例如:当页面加载完毕,我们设置默认显示"用户管理"。
这是一种实现方式。
404路由配置
我们希望:当访问未知的(未配置)的路由的时候,不是显示默认的404页面,而是我们自己定义的404提示页面,以做到提示使用者出现的错误情况。
1.编写404组件视图/页面
2.编写配置路由:
路由模式
我们一般使用history模式,使用的选择在创建路由配置的时候:
如果想要使用Hash模式,将构造函数改为"createHashRouter"即可。
开发优化部分
下面会介绍关于开发优化的一些工具/操作和hook函数等。
别名路径配置
在开发中,我们一般引用文件资源都是通过"相对路径"的方式进行文件引用。
如果在目录文件层级比较深的项目中,引用找起来就不太方便了。
如下,会介绍通过"@/"的方式来引用文件资源。
1.安装依赖
npm i @craco/craco -D
2.编写配置文件
在项目根目录创建文件"craco.config.js"
3.修改启动命令和打包命令
至此,我们就可以@/来引用静态资源文件了,@/ 对应目录 src/
例如:我们在Login组件中引用组件
路径联想配置
虽然我们在上面确实可以通过"@/"来引用静态资源文件了,但是我们在引用的时候并没有文件路径提示,不太友好。
开启路径联想:
项目根目录创建并编写配置文件:jsconfig.js
打包优化-包体积查看
可视化查看项目打包后的个JS文件的体积。
1.安装依赖
npm i source-map-explorer
2.配置查看解析命令
其中的analyze命令自定义
3.执行"npm run analyze"
执行命令之后,自动打开解析后的html页面:
React.memo
优化组件渲染,允许组件在props没有变化的情况下跳过重新渲染。
正常情况下,我们的父组件如果发生重新渲染,必然会带动子组件进行重新渲染,memo要做的就是控制这种无端的渲染。
示例:
使用memo之后,只有当组件的props发生变化,才会重新渲染此组件。
props比较规则
也就是说:如果我们传入的props是值类型,那么可以通过值的变化来判断props是否有变化,但如果是"引用类型",那么比较机制不会比较内容是否变化,仅比较引用地址,因此每一次都会重新渲染,导致memo()失效。
解决引用类型比较机制问题:
使用useMemo()
为了解决引用类型没有变化也会导致props变化的问题,需要使用useMemo对引用类型进行声明。
useCallback
作用:当父组件中的状态变量发生变化时父组件会重新渲染,也会导致子组件重新渲染,上面我们使用缓存解决了传入值的重新渲染问题,在这解决的就是传入函数到子组件中,确保父组件状态变量更新不会导致子组件重新渲染。
使用useCallback()包裹住想要传递的函数即可。
注意useCollback函数使用时不要漏掉第二个函数:[]
forwardRed
作用:父组件获取子组件中的DOM元素。
zustand(极简的状态管理库)
在上面介绍的Redux也是状态管理库,但是整合时会发现:整合步骤/配置固定统一,需要整合的步骤比较多。
实际上使用状态管理库,无非就是将状态进行共享存储,然后再定义更新该状态的函数就行。Redux的配置过程确实麻烦了些。
zustand就是配置和使用极其简单的状态管理库。
整合步骤
1.安装依赖
npm i zustand
2.创建store.js管理所有状态
如上声明了count共享状态和count状态配套的一些更新函数。
3.组件中使用
React组件库
为了加快我们的开发效率,在搭建页面的时基本都是使用现成的组件进行组合搭建。
如下会分别推荐"移动端组件库" 和 "PC端组件库"。
AntDesignMobile(移动端组件库)
官网:Ant Design Mobile - Ant Design Mobile
整合非常简单,安装依赖,然后找到想要使用的组件,引入使用即可,具体查看官网【快速上手】介绍。
AntDesign(PC端-管理端组件库)
官网:Ant Design of React - Ant Design (antgroup.com)
使用和上面的移动端类似(安装依赖,引入使用),具体看官网整合步骤。
原文地址:https://blog.csdn.net/m0_60155232/article/details/142833126
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!