React基础知识大汇总

函数组件和类组件

函数组件与类组件有什么区别呢?

function getName(params:{name:string}){const count = 0;return params.name +'-'+count;
}
getName({name:"test"})
getName({name:"哈哈哈"})

getName是一个纯函数,不产生任何副作用,执行结束后,它的执行上下文和活动对象会被销毁,前后两次调用互不影响。对于不使用任何Hooks的函数组件而言,它也是纯函数,那么对于函数组件前后两次渲染,你能得出与调用getName函数类似的结论吗?

下面用类组件和函数组件实现相同的功能来对比二者的区别。在浏览器上显示一个按钮,单击按钮调用props中的方法来更新父组件的状态,隔1s之后打印this.props.count的值。类组件的代码如下:

import { Button } from 'antd'
import React, { Component } from 'react'class ClassCom extends Component {onClick = () => {this.props.updateCount()setTimeout(() => {console.log("类组件",this.props.count)}, 1000)}render() {return (<Button onClick={this.onClick}>这是类组件</Button>)}
}function FunCom(props) {const onClick = () => {props.updateCount()setTimeout(() => {console.log("函数组件",props.count)}, 1000)}return (<Button onClick={onClick}>这是函数组件</Button>)
}export default class FunComVsClassCom extends Component {constructor(props) {super(props)this.state = {count: 0}}updateCount = () => {this.setState({count:this.state.count+1})}render() {return (<div><FunComcount={this.state.count}updateCount={this.updateCount}/><ClassComcount={this.state.count}updateCount={this.updateCount}/></div>)}
}

在这里插入图片描述
单击FuncCom和ClassCom组件中的按钮都会使得父级重新刷新,从而导致FuncCom和ClassCom重新渲染。ClassCom是类组件,重新渲染不会创建新的组件实例,在setTimeout的回调函数中this.props拿到了最新的值。FuncCom是函数组件,重新渲染会创建新的执行环境和活动变量,所以访问props,无论何时拿到的都是调用FunCom时传递给它的参数,该参数不可变。

React Ref API

Ref的功能强大,它能够让组件与DOM元素,或类组件与其父级之间建立直接联系。总体而言,使用Ref出于以下3个目的。

  • 访问DOM元素。
  • 访问组件的实例。
  • 将Ref作为mutable数据的存储中心
  1. 创建Ref
    创建Ref有两种方式,分别为useRef和React.createRef。useRef是一种Hooks,只能在函数组件中使用。React.createRef的使用位置不限制,但不要在函数组件中使用它,如果在函数组件中使用它创建Ref,那么函数组件每次重新渲染都会创建新的Ref。

  2. 访问DOM元素或组件实例
    要想通过Ref访问DOM元素,必须将Ref绑定到浏览器内置的组件上。等组件装载了之后使用ref.current字段访问DOM元素。

    export default function App() {
    const inputRef = useRef(null)
    const onClick=()=>{if(inputRef.current){inputRef.current.focus()}
    }return (<input ref={inputRef}/>)
    }
    
  3. 将Ref作为mutable数据的存储中心
    将Ref作为mutable数据的存储中心,使用场景主要是函数组件,在类组件中大可不必如此。这是因为函数组件每一次重新渲染都会执行函数体,使函数体的各个变量都被重新创建。如果函数体中声明了一些只用于缓存的数据,即不会导致组件重新渲染的变量,那么将这些数据放在ref中能避免它们被反复创建。
    将Ref作为mutbale数据的存储中心,不需要将其绑定到React element上,创建之后就能直接使用,修改mutableRef.current的值,组件不会重新刷新。

React Hooks

React Hooks在React16.8时正式发布。它使函数组件拥有自己的状态。对类组件没有影响。一般函数组件相比起类组件存在如下3个优点:

  • 类组件必须时刻关注this关键字的指向。
  • 相同的生命周期在类组件中最多定义一个,这导致彼此无关的逻辑代码被糅杂在同一个函数中。
  • 不同的生命周期函数可能包含相同的代码。最常见的便是componentDidMount和componentDidUpdate。
useState

useState是一个与状态管理相关的Hooks,能让函数组件拥有状态,是最常用的Hooks之一,useState的基本用法。

  1. useState的参数不是函数
    此时,useState的参数将作为状态的初始值,如果没有传参数,那么状态的初始值为undefined。

    const [name,setName] = useState("test")
    const [age,setAge] = useState()
    
  2. useState的参数是函数
    此时,函数的返回值是状态的初始值。某些时候,状态的初始值要经过计算才能得到。此时推荐将函数作为useState的参数,该函数只在组件初始渲染时执行一次。

    const [count,setCount] = useState(()=>{//这个函数只在初始渲染时执行,后续的重新渲染不再执行return 0
    })
    
  3. 修改状态的值
    修改状态有两种方式:

    //用法一
    setCount((count)=>{return count+1
    })
    //用法二
    setCount(0)
    

如果setCount的参数时函数,那么count现在的值将以参数的形式传递给函数,函数的返回值用于更新状态。如果setCount的参数不是函数,那么该参数将用于更新状态。状态值发生变化将导致组件重新渲染,重新渲染时,useState返回的第一个值始终是状态最新的值,不会重置为初始值。

useRef

使用useState能让函数组将拥有状态,状态拥有不变性,它在组件前后两次渲染中相互独立。使用useRef能为组件创建一个可变的数据,该数据在组件的所有渲染中保持唯一的引用,所以对它取值始终会得到最新的值。

useEffect

函数组件可以多次调用useEffect,每使用一次就定义一个effect,这些effect的执行顺序与它们被定义的顺序一致,建议将不同职责的代码放在不同的effect中。接下来从effect的清理工作和依赖这两个方面介绍useEffect。

  1. effect的清理工作
    effect没有清理工作就意味着它没有返回值。effect的清理工作由effect返回的函数完成,该函数在组件重新渲染后和组件卸载时调用。

    useEffect(()=>{document.body.addEventListener('click',()=>{})//在返回的函数中定义与该effect相关的清理工作return()=>{document.body.removeEventListener('click',()=>{})} 
    })
    

    该effect在组件首次渲染和之后的每次重新渲染时都会执行,如果组件的状态更新频繁,那么组件重新渲染也会很频繁,这将导致body频繁绑定click事件又解绑click事件/是否有办法使组件只在首次渲染时给body绑定事件呢?那就是依赖。

  2. effect的依赖
    前面示例定义的effect没有指明依赖,因此组件的每一轮渲染都会执行它们。

    useEffect(()=>{document.body.addEventListener('click',()=>{})//在返回的函数中定义与该effect相关的清理工作return()=>{document.body.removeEventListener('click',()=>{})} 
    },[])//传空意味着该effect只在组件初始渲染时执行,它的清理工作在组件卸载时执行。useEffect(()=>{document.body.addEventListener('click',()=>{})//在返回的函数中定义与该effect相关的清理工作return()=>{document.body.removeEventListener('click',()=>{})} 
    },[name])//在组件初始渲染时会执行,当name发生变化导致组件重新渲染也会执行,相应的,组件卸载时和由name变化导致组件重新渲染之后将清理上一个effect
    

    注意:给effect传递依赖项,React会将本次渲染时依赖项的值与上一次渲染时依赖项的值进行浅对比,如果它们当中的一个有变化,那么该effect会被执行,否则不会执行。为了让effect拿到他所需状态和props的最新值,effect中所有要访问的外部变量都应该作为依赖项。函数组件每次渲染时,effect都是一个不同的函数,在函数组件内的每一个位置(包括事件处理函数、effects、定时器等)只能拿到定义他们的那次渲染的状态和props。

useReducer

useReducer是除useState之外另一个与状态管理相关的Hooks。这个Hooks笔者用得比较少,需要的同学可以参考下官方文档。
https://zh-hans.react.dev/reference/react/useReducer

用法如下:

import { useReducer } from 'react';function reducer(state, action) {if (action.type === 'incremented_age') {return {age: state.age + 1};}throw Error('Unknown action.');
}export default function Counter() {const [state, dispatch] = useReducer(reducer, { age: 42 });return (<><button onClick={() => {dispatch({ type: 'incremented_age' })}}>Increment age</button><p>Hello! You are {state.age}.</p></>);
}
自定义Hooks

如果在多个组件中使用了相同的useEffect或useState逻辑,推荐将这些相同的逻辑封装到函数中,这些函数被称为自定义的Hooks。下面举例3个自定义的Hooks的示例。

  1. useForceUpdate:返回一个让组件重新渲染的函数。

     function useForceUpdate(){const [,setTick]=useState(0)return ()=>setTick(t=>t+1)}const forceUpdate = useForceUpdate();const handleClick = () => {// 调用 forceUpdate 函数来强制组件重新渲染forceUpdate();};
  2. usePrevVal:获取状态的上一次的值,它利用了Ref的可变性,以及effect在DOM被绘制到屏幕上才执行的特性

    function usePrevVal(status){const ref = useRef()const [prevVal,setPrevVal] = useState()useEffect(()=>{setPrevVal(ref.current)ref.current = status},[status])return prevVal
    }
    
  3. useVisible:检测dom元素是否在浏览器视口内,它在effect中创建observer来异步观察目标元素是否与顶级文档视口相交。

    function useVisible(root:React.RefObject<HTMLElement>,rootMargin?:string) {const [isVisible, setIsVisible] = useState(false);useEffect(() => {const observer = new IntersectionObserver((entries) => {entries.forEach(entry => {setIsVisible(entry.isIntersecting);});}, {rootMargin});if (root.current) {observer.observe(root.current);}return () => {observer.disconnect() };}, [root,rootMargin]);return isVisible;
    }
    
React Context API

在React应用中,为了让数据在组件间共享,常见的方式是让它们以props的形式自顶向下传递。如果数据在组件树的不同层级共享,那么这些数据必须传递到目的地,这种情况称为prop-drilling。Context如同管道,他将数据从入口直接传递到出口,使用Context可以避免出现prop-drilling。
总体而言,使用Context分为以下三步:

  1. 创建Context对象

    const MyContext = React.createContext({lang:"zh_CN",changeLang:()=>{throw Error('xxxx')}
    })
    
  2. 用Context.Provider包裹组件树
    用Context.Provider圈选Context的作用域,只有作用域内的组件才能消费Context中的数据,此处是管道的入口,在这里放入想要传递的数据。

    class ContextDemo extends React.Component{render(){<MyContext.Providervalue={someValue}>//children</MyContext.Provider>}
    }
    
  3. 订阅Context
    订阅Context的位置是管道的出口,对于Context对象而言,管道入口只有一个,但出口可以有多个。订阅Context有3种方式。

    • 类组件的静态属性contextType。
      在类组件中使用contextType去订阅Context。用法如下。

      class MyNestedClass extends React.Component{static contextType = MyContext
      }
      

      contextType订阅了Context之后,除了不能在构造函数中使用this.context访问到contextvalue之外,在类组件的其他位置都能使用this.context访问到数据。React组件的shouldComponentUpdate的第三个参数是组件即将接收的context。

    • useContext。
      在函数组件中通过useContext订阅Context时,useContext的使用次数不限。用法如下。

      function MyNestedFunc(){const myContext = useContext(MyContext)
      }
      
    • Context.Consumer。
      Context.Consumer是react组件,在Context作用域的任何位置都能使用它,它只接收一个名为children的props,children必须是一个返回React.ReactNode的函数,该函数以context作为参数。用法如下:

      <MyContext.Consumer>{(context)=><MyNestedCom lang={context.lang}/>}
      </MyContext.Consumer>
      

无论如何订阅Context,只要context的值被更新,那么订阅该Context的组件一定会重新渲染,而不管context更新的那部分值是否被自己使用,也不管祖先组件是否跳过重新渲染。所以推荐将不同职责的数据保存到不同的context中,以减少不必要的重新渲染。
如果给Context.Provider的value属性传递一个对象字面量,那么Context.Provider的父组件每次重新刷新都会使得context的值发生变化,进而导致订阅该context的组件重新渲染,应当避免。

深入理解React的渲染流程
  • 类组件的生命周期流程图如下(18版本之后)。父组件重新渲染、调用this.setState()、调用this.forceUpdate()以及订阅Context的value发生变更都会导致类组件更新。
    在这里插入图片描述
    函数组件的生命周期流程如下所示:
    在这里插入图片描述
    装载是运行的惰性初始化程序指传递给useState和useReducer的函数。父组件重新渲染、状态发生变更以及订阅的Context的value发生变更都会导致函数组件更新。有图可知,上一次的effect会在组件更新后被清理,清理effect和运行effect都不会阻塞浏览器绘制。

  • 渲染流程
    渲染是React让组件根据当前的props和状态描述它要展示的内容;重新渲染是React让组件重新描述它要展示的内容。渲染和更新DOM不是同一件事情,组件经过了渲染,DOM不一定会更新。React渲染一个组件,如果组件返回的输出与上次的相同,那么它的DOM节点不需要有任何更新。
    将组件显示到屏幕上,React的工作分为如下两个阶段:

    • Render阶段(渲染阶段):计算组件的输出并收集所有需要应用到DOM上的变更。
    • Commit阶段(提交阶段):将Render阶段计算出的变更应用到DOM上。

    在Commit阶段React会更新DOM节点和组件实例的Ref。如果是类组件,React会同步运行componentDidMount或componentDidUpdate生命周期方法;如果是函数组件,React会同步运行useLayoutEffect Hooks,当浏览器绘制DOM之后,再运行所有的useEffect Hooks。

    初始化渲染之后,下面的方式会让React重新渲染组件。

    • 类组件—— 调用this.setState方法或调用this.forceUpdate方法
    • 函数组件—— 调用useState返回的setState或调用useReducer返回的dispatch
    • 其他——组件订阅的Context的value发生变更或重新调用ReactDOM.render(<AppRoot>)
  • 提高渲染性能
    要将组件显示在界面上,组件必须经过渲染流程,但是渲染有时候会被认为是浪费时间。如果渲染的输出结果没有改变,它对应的DOM节点也不需要更新,该组件的渲染工作就真的是在浪费时间。React组件的输出结果始终基于当前props和状态的值,因此,如果我们知道组件的propss和状态没有改变,那么便能让组件跳过重新渲染。

    1. shouldComponentUpdate:返回false,react将跳过重新渲染该组件的过程。使用它最常见的场景是检测组件的props和状态是否自上次以来发生变更,如果没有变更则返回false。
    2. PureComponent:它在Component的基础上添加了默认的 shouldComponentUpdate去比较组件的props和状态自上次渲染以来是否变更。
    3. React.memo:这是一个高阶组件,接收自定义组件作为参数,返回一个被包裹的组件。被包裹的组件的默认行为是检测props是否有更改,如果没有,则跳过重新渲染的过程。
    4. 如果组件在渲染过程中返回的元素的引用与上一次渲染时的引用完全相同,那么React不会重新渲染该组件。
function ShowChildren(props:{children}){const [count,setCount] = useState(0)return(<div>{count}<button onClick={()=>setCount(c=>c+1)}>click</button>{props.children} //点击按钮不会使其重新渲染<Children/> //点击按钮会使其重新渲染</div>)
}

默认情况下,只要组件重新渲染,React就会重新渲染所有被它嵌套的后代组件,即便组件的props没有变更。如果试图通过meo和PureComponent优化组件的渲染性能,那么要注意每个props的引用是否变更。

const MemoizedChildren = React.memo(Children)
function Parent(){const onClick=()=>{}return <MemoizedChildren onClick={onClick}/>
}

Parent被重新渲染会创建新的onClick函数,所以对MemoizedChildren 而言,props.onClick的引用发生变化,因此Children组件会重新渲染,如果必须让组件跳过重新渲染,可以使用useCallback。

const MemoizedChildren = React.memo(Children)
function Parent(){// 使用useCallback优化回调函数const handleClick = useCallback(() => {console.log('Button clicked! Count:', count);}, []);return <MemoizedChildren onClick={handleClick }/>
}
  • useCallback和useMome
  1. 功能不同:useCallback用于记忆化回调函数,而useMemo用于记忆化计算结果。

  2. 参数不同:useCallback接受一个回调函数和一个依赖项数组作为参数,只有当依赖项发生变化时,才会返回一个新的记忆化的回调函数。useMemo接受一个计算函数和一个依赖项数组作为参数,只有当依赖项发生变化时,才会重新计算并返回一个新的记忆化的计算结果。

  3. 返回值类型不同:useCallback返回一个记忆化的回调函数,而useMemo返回一个记忆化的计算结果。

  4. 使用场景不同:useCallback主要用于优化传递给子组件的回调函数,避免不必要的重新创建和渲染。useMemo主要用于优化计算操作,避免不必要的重复计算。

import React, { useState, useCallback, useMemo } from 'react';function MyComponent() {const [count, setCount] = useState(0);// 使用useCallback优化回调函数const handleClick = useCallback(() => {console.log('Button clicked! Count:', count);}, [count]);// 使用useMemo优化计算结果const doubledCount = useMemo(() => {console.log('Calculating doubled count...');return count * 2;}, [count]);return (<div><button onClick={handleClick}>Click Me</button><p>Count: {count}</p><p>Doubled Count: {doubledCount}</p></div>);
}

可以看到,在上面的例子中,handleClick回调函数通过useCallback进行记忆化,只有当count发生变化时才会重新创建。而doubledCount则通过useMemo进行记忆化,只有当count发生变化时才会重新计算。这样可以避免在每次组件渲染时不必要地重新创建回调函数和重复计算结果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/826744.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mfc140.dll丢失如何修复,分享多种有效的修复方法

在日常操作和使用电脑的过程中&#xff0c;我们可能会遇到一种较为常见的问题&#xff0c;即在尝试启动或运行某个应用程序时&#xff0c;系统突然弹出一个错误提示窗口&#xff0c;明确指出“mfc140.dll文件丢失”。这个mfc140.dll实际上是一个动态链接库文件&#xff08;DLL&…

mysql基础19——日志

日志 mysql的日志种类非常多 通用查询日志 慢查询日志 错误日志 与时间有关联 二进制日志 中继日志 与主从服务器的同步有关 重做日志 回滚日志 与数据丢失有关 通用查询日志 记录了所有用户的连接开始时间和截至时间 以及给mysql服务器发送的所有指令 当数据异常时&…

计算机体系结构

体系结构 CPU&#xff1a;运算器和控制器 运算器&#xff1a;进行算术和逻辑运算控制器&#xff1a; 输入设备&#xff1a;鼠标、键盘、显示器、磁盘、网卡等输出设备&#xff1a;显卡&#xff0c;磁盘、网卡、打印机等存储器&#xff1a;内存&#xff0c;掉电易失总线&#xf…

借助 NVivo 彻底改变业务创新

在收集定性数据时&#xff0c;通常很难确定信息的情感底蕴。尤其是在金融行业&#xff0c;当涉及到经济金融状况和股票走势等问题时&#xff0c;通过文章、社交媒体和其他消费者平台了解市场的真实整体感受至关重要。这就是对数据应用情绪分析可以提供帮助的地方。 在德勤 针对…

代码随想录第42天|416. 分割等和子集

416. 分割等和子集 416. 分割等和子集 - 力扣&#xff08;LeetCode&#xff09; 代码随想录 (programmercarl.com) 动态规划之背包问题&#xff0c;这个包能装满吗&#xff1f;| LeetCode&#xff1a;416.分割等和子集_哔哩哔哩_bilibili 给你一个 只包含正整数 的 非空 数组…

软件测试之【软件测试概论一】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 前言软件生命周期软件项目开发过程软件项目组织架构软件开发相关模型 软件测试…

深入理解与实践“git add”命令的作用

文章目录 **git add命令的作用****git add命令的基本作用****高级用法与注意事项** git add命令的作用 引言&#xff1a; 在Git分布式版本控制系统中&#xff0c;git add命令扮演着至关重要的角色&#xff0c;它是将本地工作区的文件变动整合进版本控制流程的关键步骤。本文旨…

《乱弹篇(30)厌战的杜诗》

时下地球村有一伙成天叫嚣着“打打杀杀”、鼓吹快快发动战争的狂人&#xff0c;他们视老百姓的生命如草芥&#xff0c;毫不珍惜。没有遭受过战火焚烧的人&#xff0c;也跟着成天吠叫“快开战吧”。然而中国唐朝大诗人却是个“厌战派”&#xff0c;他对战争的厌恶集中表现在诗《…

放大器DC参数测试(1)

放大器DC参数测试(1) Hi,uu们,最近在忙啥呢?想好5.1,端午去哪里玩了吗? 咱们直接开始正题,放大器的DC参数还挺多,在Bench测试中,需要自动化测试,通常需要很多Relay去切换不同的配置去测量不同的参数,在这里瑞萨给出了测试参考电路.如图1所示. 图1:直流关键参数测试电路 Re…

近期分享学习心得4

1、带有多的条件的if的语句 逻辑 || 的简写 if (x true || x 2523 || x 小明) {}// 简化操作if ([true, 2523, 小明].includes(x)) {}2、查找两个数组的交集 var numOne [0, 2, 4, 6, 8, 8]; var numTwo [1, 2, 3, 4, 5, 6]; var cross [...new Set(numOne)].filter(item…

【树莓派】如何刷个系统给树莓派4B,如何ssh登陆到树莓派

文章目录 下载树莓派镜像下载烧写软件烧写编辑设置连接树莓派4B重启ssh查看树莓派IPssh远程连接问询、帮助 下载树莓派镜像 https://www.raspberrypi.com/software/operating-systems/#raspberry-pi-os-64-bit 下载烧写软件 https://www.raspberrypi.com/software/ 烧写 编辑…

python使用redis存储时序数据

import redisdef ts_demo():"""时序数据存储RedisTimeSeries测试"""# 连接到Redisr redis.Redis(hostlocalhost, password"xxxx", port63790, db0)r1 r.ts()# print(r1.get("ts_key"))# print(r.exists(ts_key))# # 清空键…

【网络安全 | 信息收集】JS文件信息收集工具LinkFinder安装使用教程

文章目录 前言安装教程使用教程 前言 JavaScript文件可能会泄露敏感信息&#xff0c;如注释中的机密信息、内部IP地址&#xff0c;以及包含未授权访问或其他漏洞的URL。手动检查这些信息效率低下&#xff0c;而该工具——LinkFinder&#xff0c;可用于自动收集JavaScript文件中…

CefSharp.WinForms模拟登录

一、新建Web项目 {ViewData["Title"] "Home Page";Layout null; } <script src"~/lib/jquery/dist/jquery.min.js"></script> <script src"~/lib/jquery/dist/jquery.js"></script> <head><scrip…

FairAdaBN论文速读

FairAdaBN: Mitigating Unfairness with Adaptive Batch Normalization and Its Application to Dermatological Disease Classification 摘要 深度学习在医疗研究和应用中变得越来越普遍&#xff0c;同时涉及敏感信息和关键诊断决策。研究人员观察到不同人口统计属性子组之间…

变频器基础原理

文章目录 0. 基本知识1.三相的电压之和为02.正弦交流相量的相量表示法(相量只是表示正弦量&#xff0c;而不等于正弦量 &#xff1b;只有正弦量才能用相量表示)引入相量表示法目的:一种正弦量的产生方式:正弦量的相量表示&#xff0c;使用欧拉公式表示复数 3.用复数表示正弦量&…

基于SpringBoot + Vue实现的医护人员排(值)班系统设计与实现+毕业论文+开题报告

项目介绍 本医护人员排班系统包括管理员&#xff0c;医护。 管理员功能有个人中心&#xff0c;医院信息管理&#xff0c;医护信息管理&#xff0c;医护类型管理&#xff0c;排班信息管理&#xff0c;排班类型管理&#xff0c;科室信息管理&#xff0c;投诉信息管理。 医护人员…

Swift-20-基础数据类型

数据定义 语法规则 先来看下下面的代码 import Cocoavar num1 "four" //a var num2: String "four" //b var num3 4 //c var num4: Int 4 //d上面的几行代码都能正常运行&#xff0c;其中a和b行等价&#xff0c;c和d行等价。区另就在于是否声…

AppWizard的软件开发GUI的使用记录

前言 这个软件是针对于EmWin6.0以上的这个软件在emWin的基础上又封装了一层,也只提供的API函数.基于消息事件为核心&#xff08;个人理解&#xff09;一些组件的之间的交互可以通过软件界面进行配置,比较方便本次是基于模拟器进行测试记录,观察api 按键和文本之间的关联 通过…

基于SpringBoot+Vue七匹狼商城系统的设计与实现

系统介绍 近年来随着社会科技的不断发展&#xff0c;人们的生活方方面面进入了信息化时代。计算机的普及&#xff0c;使得我们的生活更加丰富多彩&#xff0c;越来越多的人使用通过网络来购买各类的商品。早期商品的销售和购买都是通过实体店&#xff0c;这种购买方式需要耗费…