vue3的学习笔记:MVC、Vue3概要、模板、数据绑定、用Vue3 + element ui、react框架实现购物车案例

2022-12-31,,,,

一、前端MVC概要

1.1、库与框架的区别

框架是一个软件的半成品,在全局范围内给了大的约束。库是工具,在单点上给我们提供功能。框架是依赖库的。Vue是框架而jQuery则是库。

1.2、MVC(Model View Controller)

1.2.1、MVC 是什么?

MVC是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件设计典范。它是用一种业务逻辑、数据与界面显示分离的方法来组织代码,将众多的业务逻辑聚集到一个部件里面,在需要改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑,达到减少编码的时间。

1.2.2、MVC的核心理念是?

把管理数据的代码(Model)、业务逻辑的代码(Controller)、以及向用户展示数据的代码(View)清晰的分离开

模型:代表应用当前的状态

视图:用于展示数据,用于接口

控制器:用来管理模型和视图之间的关系

典型思路是 View 层通过事件通知到 Controller 层,Controller 层经过对事件的处理完成相关业务逻辑,要求 Model 层改变数据状态,Model 层再将新数据更新到 View层。

1.2.3、MVC 的缺点

View 层和 Model 层相互持有、相互操作,导致紧密耦合,在可维护性上有待提升。由此,MVP 模式应运而生

通过MVC框架又衍生出了许多其它的架构,统称MV*,最常见的是MVP与MVVM

1.3、MVP (Model View Presenter)

MVP 模式将程序分为三个部分:模型(Model)、视图(View)、管理层(Presenter)。

Model 模型层: 只负责存储数据,与 View 呈现无关,也与 UI 处理逻辑无关,发生更新也不用主动通知 View;

View 视图层: 人机交互接口,一般为展示给用户的界面;

Presenter 管理层 : 负责连接 Model 层和 View 层,处理 View 层的事件,负责获取数据并将获取的数据经过处理后更新 View;

MVP 模式的目的就是: View 层和 Model 层完全解耦,使得对 View 层的修改不会影响到 Model 层,而对 Model 层的数据改动也不会影响到View 层。

典型流程是 :View 层触发的事件传递到 Presenter 层中处理,Presenter 层去操作 Model 层,并且将数据返回给 View层,这个过程中,View 层和 Model 层没有直接联系。而 View 层不部署业务逻辑,除了展示数据和触发事件之外,其它时间都在等着 Presenter 层来更新自己,被称为「被动视图」。

MVP 缺点:由于 Presenter 层负责了数据获取、数据处理、交互逻辑、UI 效果等等功能,所以 Presenter 层就变得强大起来,相应的,Model 层只负责数据存储,而 View 层只负责视图,Model 和 View 层的责任纯粹而单一,如果我们需要添加或修改功能模块,只需要修改 Presenter 层就够了。由于 Presenter 层需要调用 View 层的方法更新视图,Presenter 层直接持有 View 层导致了 Presenter 对 View 的依赖。

正如上所说,更新视图需要 Presenter 层直接持有 View 层,并通过调用 View 层中的方法来实现,还是需要一系列复杂操作,有没有什么机制自动去更新视图而不用我们手动去更新呢,所以,MVVM 模式应运而生。

MVP的主要特点:

把Activity里的许多逻辑都抽离到View和Presenter接口中去,并由具体的实现类来完成。

View与Model不直接交互,而是通过与Presenter来完成交互

Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,会导致Presenter臃肿,维护困难

1.4、MVVM (Model View ViewModel)

1.4.1、介绍

MVVM 模式将程序分为三个部分:模型(Model)、视图(View)、视图模型(View-Model)。

1.4.2、为什么要用 MVVP ?

在 MVP 中,View 和 Presenter 是直接交互的,View 的状态变化,都是通过 Presenter 直接改变和传递。 这会有几个问题:

View 和 Presenter 的强耦合: View 和 Presenter 直接调用,不相互独立

无法跟踪视图的状态变化。对于视图中发生的不同状态变化,没有可追溯性

可测试性不足,特别是 MVVM,只能通过观察属性的变化来进行测试

多线程环境中可能会有状态冲突的问题

Vue 与 Angular 就是一个 MVVM 框架,MVVM 与 MVC 最大的区别是模型与视图实现了双向绑定

1.4.3、MVVM的特点:

可以将 ViewModel 看作是 Model 和 View 的连接桥梁View 可以通过事件绑定 ModelModel 可以通过数据绑定 View通过 ViewMode 可以实现数据和视图的完全分离。

在 MVVM 设计模式中, Model 层负责存储数据,View 层用于显示数据。但 MVVM 设计模式中,没有 Presenter 层,取而代之的是ViewModel层级。

而 ViewModel 并不需要我们来 进行编写,使用 MVVM 设计模式进行编码的时候,无需关注 ViewModel 这一层是如何实现的,它完全是 Vue 内置的。而我们只需要更多的关注 M 层与 V层,即模型层和视图层。

在Vue框架下,我们可以直接操作数据而不是 dom 元素来实现 View 的改变

1.5、前端使用 MVVM 的框架有什么?

1.5.1、React

React官网地址: http://facebook.github.io/react

Github地址: https://github.com/facebook/react

React是用于构建用户界面的JavaScript库, 起源于Facebook的内部项目,该公司对市场上所有 JavaScript MVC框架都不满意,决定自行开发一套,用于架设 Instagram 的网站

框架用途:React主要用于构建UI。你可以在React里传递多种类型的参数,如声明代码,帮助你渲染出UI、也可以是静态的HTML、DOM元素、也可以传递动态变量、甚至是可交互的应用组件。

框架特点:

    声明式设计:React 使创建交互式 UI 变得轻而易举。为你应用的每一个状态设计简洁的视图,当数据变动时 React能高效更新并渲染合适的组件。

    组件化: 构建管理自身状态的封装组件,然后对其组合以构成复杂的 UI。

    高效:React通过对DOM的模拟,最大限度地减少与DOM的交互。

    灵活:无论你现在使用什么技术栈,在无需重写现有代码的前提下,通过引入React来开发新功能。

1.5.2、 AngularJS(字面意思是:有角的; 用角测量的)

AngularJS 是一个 JavaScript 框架。它可通过 <script> 标签添加到 HTML 页面。AngularJS通过指令扩展了HTML,并且通过表达式绑定数据到 HTML。

官网: https://www.angularjs.org

中文社区:http://www.angularjs.cn

中文网:http://www.apjs.net

优点:

    AngularJS模板功能强大丰富,自带了极其丰富的angular指令。

    AngularJS是完全可扩展的,与其他库的兼容效果很好,每一个功能可以修改或更换,以满足开发者独特的开发流程和功能的需求。

    AngularJS是一个比较完善的前端MVC框架,包含服务,模板,数据双向绑定,模块化,路由,过滤器,依赖注入等所有功能;

    AngularJS是互联网巨人谷歌开发,这也意味着他有一个坚实的基础和社区支持。

缺点:

    AngularJS强约束导致学习成本较高,对前端不友好。但遵守 AngularJS 的约定时,生产力会很高,对 Java 程序员友好。

    AngularJS不利于SEO,因为所有内容都是动态获取并渲染生成的,搜索引擎没法爬取。

1.5.3、Vue.js

Vue.js 是一个轻巧、高性能、可组件化的MVVM库,是一套构建用户界面的渐进式框架,同时拥有非常容易上手的API,作者是尤雨溪(中国人)、

官网: http://cn.vuejs.org/

仓库: https://github.com/vuejs

文档与资源大全:https://vue3js.cn/

易学易用
基于标准 HTML、CSS 和 JavaScript 构建,提供容易上手的 API 和一流的文档。

性能出色
经过编译器优化、完全响应式的渲染系统,几乎不需要手动优化。

灵活多变
丰富的、可渐进式集成的生态系统,可以根据应用规模在库和框架间切换自如。

1.5.4、当前三大前端MVC框架的对比

二、Vue3 简介

2.1、vue3 的介绍

Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。

Vue3 中文文档 :https://vue3js.cn/docs/zh/

Vue3 设计理念 :https://vue3js.cn/vue-composition/ 破坏性语法更新

官网 : https://vuejs.org/ https://cn.vuejs.org/

2.2、Vue2 与 Vue3 的区别

2.2.1、生命周期

对于生命周期来说,整体上变化不大,只是大部分生命周期钩子名称上 + “on”,功能上是类似的。不过有一点需要注意,Vue3 在组合式API(Composition API,下面展开)中使用生命周期钩子时需要先引入,而 Vue2 在选项API(Options API)中可以直接调用生命周期钩子,如下所示。

// vue3
<script setup>
import { onMounted } from 'vue'; // 使用前需引入生命周期钩子 onMounted(() => {
// ...
}); // 可将不同的逻辑拆开成多个onMounted,依然按顺序执行,不会被覆盖
onMounted(() => {
// ...
});
</script> // vue2
<script>
export default {
mounted() { // 直接调用生命周期钩子
// ...
},
}
</script>

注意: setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地去定义。

2.2.2、多根节点

// vue2中在template里存在多个根节点会报错
<template>
<header></header>
<main></main>
<footer></footer>
</template> // 只能存在一个根节点,需要用一个<div>来包裹着
<template>
<div>
<header></header>
<main></main>
<footer></footer>
</div>
</template> // Vue3 支持多个根节点,也就是 fragment
<template>
  <header></header>
  <main></main>
  <footer></footer>
</template>

2.2.3、Composition  API

Vue2 是选项API(Options API)一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期钩子等),导致代码的可读性变差。当需要修改某个逻辑时,需要上下来回跳转文件位置。

Vue3 是组合式API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。

2.2.3、异步组件(Suspense)

Vue3 提供 Suspense 组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如 loading ,使用户的体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。

<tempalte>
<suspense>
<template #default>
<List />
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</suspense>
</template>

在 List 组件(有可能是异步组件,也有可能是组件内部处理逻辑或查找操作过多导致加载过慢等)未加载完成前,显示 Loading...(即 fallback 插槽内容),加载完成时显示自身(即 default 插槽内容)

2.2.4、Teleport(传送门)

Vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 弹窗。

<button @click="dialogVisible = true">显示弹窗</button>
<teleport to="body">
<div class="dialog" v-if="dialogVisible">
我是弹窗,我直接移动到了body标签下
</div>
</teleport>

2.2.5、响应式原理

Vue2 响应式原理基础是 Object.defineProperty;Vue3 响应式原理基础是 Proxy

Object.defineProperty 基本用法:

直接在一个对象上定义新的属性或修改现有的属性,并返回对象。

进行数据劫持,即重写getter和setter,当数据改变的时候通知订阅者去改变。

Proxy Proxy 是 ES6 新特性,通过第2个参数 handler 拦截目标对象的行为。相较于 Object.defineProperty 提供语言全范围的响应能力,消除了局限性。

局限性:

(1)、对象/数组的新增、删除

(2)、监测 .length 修改

(3)、Map、Set、WeakMap、WeakSet 的支持

Object.defineProperty 栗子如下:

let obj = {};
let name = 'leo';
Object.defineProperty(obj, 'name', {
enumerable: true, // 可枚举(是否可通过 for...in 或 Object.keys() 进行访问)
configurable: true, // 可配置(是否可使用 delete 删除,是否可再次设置属性)
// value: '', // 任意类型的值,默认undefined
// writable: true, // 可重写
get() {
return name;
},
set(value) {
name = value;
}
});

注意: writable 和 value 与 getter 和 setter 不共存。

function defineReactive(obj, key, val) {
// 一 key 一个 dep
const dep = new Dep() // 获取 key 的属性描述符,发现它是不可配置对象的话直接 return
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) { return } // 获取 getter 和 setter,并获取 val 值
const getter = property && property.get
const setter = property && property.set
if((!getter || setter) && arguments.length === 2) { val = obj[key] } // 递归处理,保证对象中所有 key 被观察
let childOb = observe(val) Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// get 劫持 obj[key] 的 进行依赖收集
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
if(Dep.target) {
// 依赖收集
dep.depend()
if(childOb) {
// 针对嵌套对象,依赖收集
childOb.dep.depend()
// 触发数组响应式
if(Array.isArray(value)) {
dependArray(value)
}
}
}
}
return value
})
// set 派发更新 obj[key]
set: function reactiveSetter(newVal) {
...
if(setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 新值设置响应式
childOb = observe(val)
// 依赖通知更新
dep.notify()
}
}

这个方法的缺点是:无法监听对象或数组新增、删除的元素。

解决方法:针对常用数组原型方法 push、pop、shift、unshift、splice、sort、reverse 进行了 hack 处理;提供 Vue.set 监听对象/数组新增属性。对象的新增/删除响应,还可以 new 个新对象,新增则合并新属性和旧对象;删除则将删除属性后的对象深拷贝给新对象。

Proxy Proxy 基本用法:创建对象的代理,从而实现基本操作的拦截和自定义操作。栗子如下:

let handler = {
get(obj, prop) {
return prop in obj ? obj[prop] : '';
},
set() {
// ...
},
...
}; function createReactiveObject(target, isReadOnly, baseHandlers, collectionHandlers, proxyMap) {
...
// collectionHandlers: 处理Map、Set、WeakMap、WeakSet
// baseHandlers: 处理数组、对象
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}

2.2.6、v-if 和 v-for的优先级

vue2:

我们最好不要把 v-if 和 v-for 同时用在一个元素上,这样会带来性能的浪费(每次都要先渲染才会进行条件判断)

//v-for 优先于 v-if 生效
<div v-if="index == 1" v-for="(item,index) in arr" :key="index">{{item}}</div>

vue3:

//v-if 优先于 v-for 生效
<div v-if="index == 1" v-for="(item,index) in arr" :key="index">{{item}}</div>

以上的写法,vue 中会给我们报警告:
意思就是:属性 “index” 在渲染期间被访问,但未在实例上定义(v-if 先进行判断,但是这时候v-for还没有渲染,所以index是找不到的)

2.2.7、事件缓存

Vue3 的 cacheHandler可在第一次渲染后缓存我们的事件。相比于 Vue2 无需每次渲染都传递一个新函数。加一个 click 事件。

<div id="app">
<h1>vue3事件缓存讲解</h1>
<p>今天天气真不错</p>
<div>{{name}}</div>
<span onCLick=() => {}><span>
</div>

渲染函数如下所示。

import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from vue

const _withScopeId = n => (_pushScopeId(scope-id),n=n(),_popScopeId(),n)
const _hoisted_1 = { id: app }
const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(h1, null, vue3事件缓存讲解, -1 /* HOISTED */))
const _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(p, null, 今天天气真不错, -1 /* HOISTED */))
const _hoisted_4 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(span, { onCLick: () => {} }, [
/*#__PURE__*/_createElementVNode(span)
], -1 /* HOISTED */)) export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock(div, _hoisted_1, [
_hoisted_2,
_hoisted_3,
_createElementVNode(div, null, _toDisplayString(_ctx.name), 1 /* TEXT */),
_hoisted_4
]))
}

注意:观察以上渲染函数,你会发现 click 事件节点为静态节点(HOISTED 为 -1),即不需要每次重新渲染。

2.2.8、Diff 算法优化

搬运 Vue3 patchChildren 源码。patchFlag 帮助 diff 时区分静态节点,以及不同类型的动态节点。一定程度地减少节点本身及其属性的比对。

function patchChildren(n1, n2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) {
// 获取新老孩子节点
const c1 = n1 && n1.children
const c2 = n2.children
const prevShapeFlag = n1 ? n1.shapeFlag : 0
const { patchFlag, shapeFlag } = n2 // 处理 patchFlag 大于 0
if(patchFlag > 0) {
if(patchFlag && PatchFlags.KEYED_FRAGMENT) {
// 存在 key
patchKeyedChildren()
return
} els if(patchFlag && PatchFlags.UNKEYED_FRAGMENT) {
// 不存在 key
patchUnkeyedChildren()
return
}
} // 匹配是文本节点(静态):移除老节点,设置文本节点
if(shapeFlag && ShapeFlags.TEXT_CHILDREN) {
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
unmountChildren(c1 as VNode[], parentComponent, parentSuspense)
}
if (c2 !== c1) {
hostSetElementText(container, c2 as string)
}
} else {
// 匹配新老 Vnode 是数组,则全量比较;否则移除当前所有的节点
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense,...)
} else {
unmountChildren(c1 as VNode[], parentComponent, parentSuspense, true)
}
} else { if(prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(container, '')
}
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(c2 as VNodeArrayChildren, container,anchor,parentComponent,...)
}
}
}
}

patchUnkeyedChildren 源码如下所示。

function patchUnkeyedChildren(c1, c2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) {
c1 = c1 || EMPTY_ARR
c2 = c2 || EMPTY_ARR
const oldLength = c1.length
const newLength = c2.length
const commonLength = Math.min(oldLength, newLength)
let i
for(i = 0; i < commonLength; i++) {
// 如果新 Vnode 已经挂载,则直接 clone 一份,否则新建一个节点
const nextChild = (c2[i] = optimized ? cloneIfMounted(c2[i] as Vnode)) : normalizeVnode(c2[i])
patch()
}
if(oldLength > newLength) {
// 移除多余的节点
unmountedChildren()
} else {
// 创建新的节点
mountChildren()
} }

2.2.9、打包优化

Tree-shaking:模块打包 webpack、rollup 等中的概念。移除 JavaScript 上下文中未引用的代码。主要依赖于 import 和 export 语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用。

nextTick 为例子,在 Vue2 中,全局API暴露在Vue实例上,即使未使用,也无法通过 tree-shaking 进行消除。

import Vue from 'vue';

Vue.nextTick(() => {
// 一些和DOM有关的东西
});

Vue3 中针对全局和内部的API进行了重构,并考虑到 tree-shaking 的支持。因此,全局API现在只能作为ES模块构建的命名导出进行访问。

import { nextTick } from 'vue';   // 显式导入

nextTick(() => {
// 一些和DOM有关的东西
});

通过这一更改,只要模块绑定器支持 tree-shaking,则Vue应用程序中未使用的 api 将从最终的捆绑包中消除,获得最佳文件大小。

受此更改影响的全局API如下所示。

Vue.nextTick
Vue.observable (用 Vue.reactive 替换)
Vue.version
Vue.compile (仅全构建)
Vue.set (仅兼容构建)
Vue.delete (仅兼容构建)

内部API也有诸如 transition、v-model 等标签或者指令被命名导出。只有在程序真正使用才会被捆绑打包。Vue3 将所有运行功能打包也只有约22.5kb,比 Vue2 轻量很多。

2.2.10、TypeScript支持

Vue3 由 TypeScript 重写,相对于 Vue2 有更好的 TypeScript 支持。

Vue2 Options API 中 option 是个简单对象,而 TypeScript 是一种类型系统,面向对象的语法,不是特别匹配。

Vue2 需要 vue-class-component 强化 vue 原生组件,也需要 vue-property-decorator 增加更多结合Vue特性的装饰器,写法比较繁琐。

2.2.11、移除了某些元素

    实例方法 $on 移除   (eventBus现有实现模式不再支持 可以使用三方插件替代)

    过滤器 filter 移除 (插值表达式里不能再使用过滤器 可以使用methods替代)

    sync 语法移除  (和v-model语法合并)

2.3、Vue 组件可以用两种不同的 API 风格编写:Options API 和 Composition API

2.3.1、Options API

使用 Options API,我们使用选项对象定义组件的逻辑,例如 data、methods 和 mounted。由选项定义的属性在 this 内部函数中公开,指向组件实例,如下所示。

<template>
<button @click="increment">count is: {{ count }}</button>
</template> <script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++;
}
},
mounted() {
console.log(`The initial count is ${this.count}.`);
}
}
</script>

2.3.2、Composition API

使用 Composition API,我们使用导入的 API 函数定义组件的逻辑。在 SFC 中,Composition API 通常使用

<template>
<button @click="increment">Count is: {{ count }}</button>
</template> <script setup>
import { ref, onMounted } from 'vue'; const count = ref(0); function increment() {
count.value++;
} onMounted(() => {
console.log(`The initial count is ${count.value}.`);
})
</script>

2.4、创建项目

2.4.1、使用vue-cli 命令构建,前提是安装了node.js

① 如果之前安装过Vue2,需要把Vue2卸载

npm uninstall vue-cli -g

② 安装 Vue CLI 命令如下:

npm install -g @vue/cli
# 或者
yarn global add @vue/cli # 升级全局的 Vue CLI 包
npm update -g @vue/cli
# 或者
yarn global upgrade --latest @vue/cli

③ 使用 vue create命令创建项目,命令如下:

# 创建项目
vue create 项目名称

注意:项目名中不能包含大写字母

此时,开始选择模板:

我们现在用最后一个(手动选择并创建项目)为例,一般想要快捷的话,直接选择第一个就好啦!

(1)按需选择依赖,学习阶段一般选择前两个和路由

使用空格键可以选择,使用上下键可以移动

解析:

Babel:将源代码转换成指定版本的JS,如ES6=>ES5

TypeScript:使用强类型的JavaScript预处理语言

PWA:使用渐进式网页应用

Router:使用Vue路由

Vuex:使用Vuex状态管理

CSS Pre-processors:CSS预处理器,如Less、Sass

Linter/Formatter:使用代码风格检查和格式化器

Unit Testing:使用单元测试

E2E Testing:使用一种End to End (端到端)的黑盒测试

(2)选择版本

(3)是否使用Class风格的组件定义语法?

即原本是:home = new Vue()创建vue实例

使用装饰器后:class home extends Vue{}

(4)使用Babel与TypeScript一起用于自动检测的填充? 选择 Y(忘记截图了 ☹)

(5)Babel,ESLint 等配置文件的存放方式

(6)是否需要保存当前配置,为以后生成新项目时进行快速构建,这里选择n,不保存

(7)创建成功

(8)开始运行

(9)运行结果

2.4.2、使用图形化界面创建项目

(1)通过 vue ui 命令以图形化界面创建和管理项目:

浏览器自动打开以下页面

(2)选择创建新项目

(3)写文件名

(4)选择预设

(5)选择功能

(6)选择配置

(7)不设置预设名

(8)创建成功

(9)详细信息

2.4.2、使用 Vite(下一代前端开发与构建工具)构建项目

官方文档:v3.cn.vuejs.org/guide/insta…

vite官网:vitejs.cn

什么是vite?—— 是Vue团队打造的新一代前端构建工具。

优势如下:

开发环境中,无需打包操作,可快速的冷启动。
轻量快速的热重载(HMR)。
真正的按需编译,不再等待整个应用编译完成。

传统构建 与 vite构建对比图

传统构建模式,是将所有资源都打包好,再上线

而 Vite 有点按需加载的意思在那里了~

接下来我们就用 Vite 来创建一个Vue3的项目

(1)创建工程:npm init vite-app 项目名称

(2)进入工程目录

(3)安装依赖:npm install

 (4)运行项目:npm run dev

(5)运行结果

2.5、项目结构

(1)打包项目

生成dist目录

 注意:如果需要运行打包后的 html 文件就需要重新打开根目录为 dist 的文件夹,再重新运行 html 文件才可以!!!

(2)、目录结构

Vue/cli文档:https://cli.vuejs.org/zh/guide/

|-dist                          -- 生成打包后文件
|-node_modules -- 项目依赖包的目录
|-public -- 项目公用文件
|--favicon.ico -- 网站地址栏前面的小图标
|--index.html -- 项目的默认首页,Vue的组件需要挂载到这个文件上
|-src -- 源文件目录,程序员主要工作的地方
|-api -- 与后端交互使用相关方法和配置
|-assets -- 静态文件目录,图片图标、样式,比如网站logo
|-components -- Vue3.x的自定义组件目录
|-router -- vue-router相关配置
|--utils -- 通用工具包
|--views -- 页面
|--App.vue -- 项目的根组件,单页应用都需要的
|--main.css -- 一般项目的通用CSS样式写在这里,main.js引入
|--main.js -- 项目入口文件,SPA单页应用都需要入口文件
|--.gitignore -- git的管理配置文件,设置那些目录或文件不管理
|--package-lock.json -- 项目包的锁定文件,用于防止包版本不一样导致的错误
|--package.json -- 项目配置文件,包管理、项目名称、版本和命令

vue.config.js:当我们想要定义一些全局变量,比如常用的包管理器或者部署应用包的基本URL时,可以通过vue.config.js配置文件定义。这个文件是可选的,如果在项目的根目录下,那么它会被 @vue/cli-service 自动加载,比如我们常用到的vue-cli-service serve或者vue-cli-service build。当然,如果不想用这个文件,也可以直接写在package.json的vue字段。如果根目录下存在vue.config.js文件,那么package.json文件中的vue字段的配置会被忽略。

.browserslistrc:打包时我们需要兼容哪些浏览器

解析:

> 1% 代表着全球超过1%人使用的浏览器
last 2 versions  表示所有浏览器兼容到最后两个版本
not dead  不是24个月没有官方支持或更新的浏览器。目前它是IE 11,IE_Mob 11,黑莓10,黑莓7,三星4,OperaMobile 12.1和百度的所有版本。
not ie 11 排除ie11

如果你的项目要兼容IE,但你的browserslist规则是这样的,那么打包的就会有个提示:All browser targets in the browserslist configuration have supported ES module.Therefore we don't build two separate bundles for differential loading.那么这里就不会打包兼容IE的代码。

babel.config.js:ES6向上兼容

README.md:项目的概要信息

tsconfig.json:TypeScript的配置信息

(3)分析文件

main.ts 文件:

App.vue 文件(单文件组件):

 HelloWorld.vue 文件:

2.6、栗子:自定义组件,实现Hello Vue3的练习

(1)创建一个 test.vue 文件

(2)test.vue文件 代码如下:

<template>
<h1>{{msg}}</h1>
</template> <script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'test', // 组件名称
props: { // 组件的属性
msg: String,
},
}); </script> <style scoped>
h1 {
text-align: center;
line-height: 1500%;
}
</style>

(3)App.vue文件 代码如下:

<template>
<test msg="你好啊,Vue3!!!" /> </template> <!-- 用的语言为TypeScript -->
<script lang="ts">
// 导入模块,并解构出定义组件的函数
import { defineComponent } from 'vue';
// 导入组件
import test from './components/test.vue'; // 将定义好的组件以默认对象的形式导出
export default defineComponent({
name: 'App', // 组件名称
components: { // 注册本组件要使用到的组件
test
}
});
</script> <style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

(4)使用 npm run serve 命令,热部署运行项目

(5)运行结果

三、单文件组件

在大多数启用了构建工具的 Vue 项目中,我们可以使用一种类似 HTML 格式的文件来书写 Vue 组件,它被称为单文件组件 (也被称为 *.vue 文件,英文 Single-File Components,缩写为 SFC)。顾名思义,Vue 的单文件组件会将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个文件里

下面我们将用单文件组件的格式实现计数器栗子:

<script>
export default {
data() {
return {
count: 0
}
}
}
</script> <template>
<button @click="count++">Count is: {{ count }}</button>
</template> <style scoped>
button {
font-weight: bold;
}
</style>

四、API 风格

Vue 的组件可以按两种不同的风格书写:选项式 API (Vue2 常用)和 组合式 API(Vue3 常用)

4.1、选项式 API  (Options API)

使用选项式 API,我们可以用包含多个选项的对象来描述组件的逻辑,例如 datamethods 和 mounted选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。

<script>
export default {
// data() 返回的属性将会成为响应式的状态
// 并且暴露在 `this` 上
data() {
return {
count: 0
}
}, // methods 是一些用来更改状态与触发更新的函数
// 它们可以在模板中作为事件监听器绑定
methods: {
increment() {
this.count++
}
}, // 生命周期钩子会在组件生命周期的各个不同阶段被调用
// 例如这个函数就会在组件挂载完成后被调用
mounted() {
console.log(`The initial count is ${this.count}.`)
}
}
</script> <template>
<button @click="increment">Count is: {{ count }}</button>
</template>

4.2、组合式 API  (Composition  API)

通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 <script setup> 搭配使用。这个 setup attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,<script setup> 中的导入和顶层变量/函数都能够在模板中直接使用。

下面是使用了组合式 API 与 <script setup> 改造后和上面的模板完全一样的组件:

<template>
<div>
<button type="button" @click="increment()">count is : {{count}}</button>
</div>
</template>
<script>
// 导入需要用到的东西
import { ref, onMounted } from 'vue'
export default {
name: 'Hello',
setup () {
const count = ref(0); // 如果有多个,可以用数组或对象的方式 function increment () { // 自定义方法,发生事件时调用
count.value++
} onMounted(() => { // 钩子函数,生命周期
console.log(count)
})
  // 一定要觉得导出哦!
return { count, increment }
}
} </script>
<style scoped>
button{
font-weight: bold;
}
</style>

注意:使用vue3项目的时候,一般使用组合式!!!

五、Vue3 在HTML中创建多个App,并实现动态绑定

5.1、在浏览器中直接使用Vue3.0

借助 script 标签直接通过 CDN 来使用 Vue:

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

或者直接在浏览器打开 https://unpkg.com/vue@3/dist/vue.global.js 链接,另存为,保存在本地

这里我们使用了 unpkg,但你也可以使用任何提供 npm 包服务的 CDN,例如 jsdelivr 或 cdnjs。当然,你也可以下载此文件并自行提供服务。

通过 CDN 使用 Vue 时,不涉及“构建步骤”。这使得设置更加简单,并且可以用于增强静态的 HTML 或与后端框架集成。但是,你将无法使用单文件组件 (SFC) 语法

5.2、使用全局构建版本的栗子:

<body>
<div id="app">
姓名:<input type="text" v-model="yourname">
<h2>你好,{{yourname}}</h2>
</div> <script src="./js/vue.global.js"></script> <script>
const { createApp } = Vue;
createApp({
data() {
return {
yourname: "大美女",
};
},
}).mount("#app");
</script>
</body>

5.3、同时创建多个App 的栗子:

<body>
<div id="app1">
<h2>{{msg}}</h2>
</div>
<div id="app2">
<h2>{{msg}}</h2>
</div> <script src="./js/vue.global.js"></script> <script>
const { createApp } = Vue;
createApp({
data() {
return {
msg: "我是app1",
};
},
}).mount("#app1"); createApp({
data() {
return {
msg: "我是app2",
};
},
}).mount("#app2");
</script>
</body>

5.4、动态绑定:设置h2标签的标题为当前日期时间

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app1">
<h2>{{msg}}</h2>
</div>
<div id="app2">
<h2 :title="currentDate">{{msg}}</h2>
</div> <script src="./js/vue.global.js"></script> <script>
const { createApp } = Vue;
createApp({
data() {
return {
msg: "我是app1",
};
},
}).mount("#app1"); createApp({
data() {
return {
msg: "我是app2",
currentDate:"当前日期时间为:"+new Date().toLocaleString()
};
},
}).mount("#app2");
</script>
</body>
</html>

运行结果:

六、Vue3的v-if与v-for简单模板语法

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>vue3介绍</title>
</head> <body>
<div id="app3">
<span v-if="isShow"> isShow为true时你可以看到我 </span>
</div>
<div id="app4">
<span v-if="isShow">
<table border="1" cellspacing="1" cellpadding="1" width="50%">
<tr>
<th>序号</th>
<th>名称</th>
<th>价格</th>
</tr>
<tr v-for="(obj,index) in fruits">
<td>{{index+1}}</td>
<td>{{obj.name}}</td>
<td>{{obj.price}}</td>
</tr>
</table>
</span>
</div>
<script src="./js/vue3.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
//if指令
const { createApp } = Vue; //vue应用对象
var app3 = createApp({
data() {
return {
isShow: true,
};
},
}).mount("#app3"); //循环指令
var app4 = createApp({
data() {
return {
isShow: true,
fruits: [
{
name: "苹果",
price: "6.8",
},
{
name: "橙子",
price: "3.5",
},
{
name: "香蕉",
price: "2.3",
},
],
};
},
}).mount("#app4");
</script>
</body>
</html>

这个例子演示了我们不仅可以把数据绑定到 DOM 文本或特性,还可以绑定到 DOM 结构。

七、Vue3事件与计算属性

 实现文本倒序:

  <body>
<div id="app">
<button @click="reverseString">{{msg}}</button>
<input type="text" v-model="msg">
</div> <script src="./js/vue.global.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript"> let {createApp,ref} = Vue; createApp({
setup() {
let msg = ref("Hello,大美女"); function reverseString(){
msg.value = msg.value.split("").reverse().join("");
} return { msg,reverseString };
}
}).mount("#app") </script>
</body>

运行结果:

实现加法运算栗子:

<body>
<div id="app">
<button @click="n1++">+</button>
<input type="text" v-model="n1"> + <input type="text" v-model="n2"> = <input type="text" v-model="sum">
</div> <script src="./js/vue.global.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript"> let {createApp,ref,computed} = Vue; createApp({
setup() { let n1 = ref(0);
let n2 = ref(0); let sum = computed(()=>{
return parseInt(n1.value) + parseInt(n2.value);
}) return { n1,n2,sum };
}
}).mount("#app") </script>
</body>

运行结果:

八、javascript数组

8.1、创建数组的语法

var arrayObj = new Array();
var arrayObj = new Array([size]);
var arrayObj = new Array([element0[, element1[, ...[, elementN]]]])

栗子:

// 8.1、创建数组
var array11 = new Array(); //空数组
var array12 = new Array(5); //指定长度,可越界
var array13 = new Array("a","b","c",1,2,3,true,false); //定义并赋值
var array14=[]; //空数组,语法糖
var array15=[1,2,3,"x","y"]; //定义并赋值

8.2、访问与修改

var testGetArrValue=arrayObj[1];

arrayObj[1]= "值";

栗子:

            //8.2、访问与修改
array12[8]="hello array12"; //赋值或修改
console.log(array12[8]); //取值
//遍历
for (var i = 0; i < array13.length; i++) {
console.log("arrayl3["+i+"]="+array13[i]);
}
//枚举
for(var i in array15){
console.log(i+"="+array15[i]); //此处的i是下标
}

运行结果:

8.3、添加元素

将一个或多个新元素添加到数组未尾,并返回数组新长度

arrayObj. push([item1 [item2 [. . . [itemN ]]]]);

将一个或多个新元素添加到数组开始,数组中的元素自动后移,返回数组新长度

arrayObj.unshift([item1 [item2 [. . . [itemN ]]]]);

将一个或多个新元素插入到数组的指定位置,插入位置的元素自动后移,返回被删除元素数组,deleteCount要删除的元素个数

arrayObj.splice(insertPos,deleteCount,[item1[, item2[, . . . [,itemN]]]])

栗子:

       //8.3、添加元素
var array31=[5,8];
//添加到末尾
array31.push(9);
var len=array31.push(10,11);
console.log("长度为:"+len+"——"+array31);
//添加到开始
array31.unshift(4);
var len=array31.unshift(1,2,3);
console.log("长度为:"+len+"——"+array31);
//添加到中间
var len=array31.splice(5,1,6,7); //从第5位开始插入,删除第5位后的1个元素,返回被删除元素
console.log("被删除:"+len+"——"+array31);

8.4、删除

移除最后一个元素并返回该元素值

arrayObj.pop();

移除最前一个元素并返回该元素值,数组中元素自动前移

arrayObj.shift();

删除从指定位置deletePos开始的指定数量deleteCount的元素,数组形式返回所移除的元素

arrayObj.splice(deletePos,deleteCount);

栗子:

            //8.4、删除
var array41=[1,2,3,4,5,6,7,8];
console.log("array41:"+array41);
//删除最后一个元素,并返回
var e=array41.pop();
console.log("被删除:"+e+"——"+array41);
//删除首部元素,并返回
var e=array41.shift();
console.log("被删除:"+e+"——"+array41);
//删除指定位置与个数
var e=array41.splice(1,4); //从索引1开始删除4个
console.log("被删除:"+e+"——"+array41);

运行结果:

8.5、截取和合并

以数组的形式返回数组的一部分,注意不包括 end 对应的元素,如果省略 end 将复制 start 之后的所有元素

arrayObj.slice(start, [end]);

将多个数组(也可以是字符串,或者是数组和字符串的混合)连接为一个数组,返回连接好的新的数组

arrayObj.concat([item1[, item2[, . . . [,itemN]]]]);

栗子:

      //8.5、截取和合并
var array51=[1,2,3,4,5,6];
var array52=[7,8,9,0,"a","b","c"];
//截取,切片
var array53=array51.slice(2); //从第3个元素开始截取到最后
console.log("被截取:"+array53+"——"+array51);
var array54=array51.slice(1,4); //从第3个元素开始截取到索引号为3的元素
console.log("被截取:"+array54+"——"+array51);
//合并
var array55=array51.concat(array52,["d","e"],"f","g");
console.log("合并后:"+array55);

运行结果:

 

8.6、拷贝

返回数组的拷贝数组,注意是一个新的数组,不是指向

arrayObj.slice(0);

返回数组的拷贝数组,注意是一个新的数组,不是指向

arrayObj.concat();

因为数组是引用数据类型,直接赋值并没有达到真正实现拷贝,地址引用,我们需要的是深拷贝。

8.7、合并成字符

返回字符串,这个字符串将数组的每一个元素值连接在一起,中间用 separator 隔开。

arrayObj.join(separator);

 栗子:

            //8.7、合并成字符与将字符拆分成数组
var array81=[1,3,5,7,9];
var ids=array81.join(",");
console.log(ids); //拆分成数组
var text="hello nodejs and angular";
var array82=text.split(" ");
console.log(array82);

8.8、排序

反转元素(最前的排到最后、最后的排到最前),返回数组地址

arrayObj.reverse();

对数组元素排序,返回数组地址

arrayObj.sort();

arrayObj.sort(function(obj1,obj2){});

栗子:

       var array71=[4,5,6,1,2,3];
array71.sort();
console.log("排序后:"+array71);
var array72=[{name:"tom",age:19},{name:"jack",age:20},{name:"lucy",age:18}];
array72.sort(function(user1,user2){
return user1.age<user2.age;
});
console.log("排序后:");
for(var i in array72) console.log(array72[i].name+","+array72[i].age);

运行结果:

九、JavaScript排序

9.1、概要

javascript内置的sort函数是多种排序算法的集合,数组在原数组上进行排序,不生成副本。

JavaScript实现多维数组、对象数组排序,其实用的就是原生的sort()方法,用于对数组的元素进行排序。

sort() 方法用于对数组的元素进行排序。语法如下:

ArrayObject.sort(order); 

9.2、简单排序

如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码的顺序进行排序。要实现这一点,首先应把数组的元素都转换成字符串(如有必要),以便进行比较。

栗子:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>排序</title>
</head>
<body>
<script>
var numbers=[2,4,6,8,0,1,2,3,7,9];
numbers.sort(); //默认按升序排列
console.log(numbers.join(','));
numbers.reverse(); //反转
console.log(numbers.join(',')); //将元素用逗号连接成一个字符串
</script>
</body>
</html>

运行结果:

9.3、简单数组自定义排序

如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:

// 升序:  a>b 返回 1
// 不变 a=b 返回 0
// 降序 a<b 返回 -1

栗子:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>排序</title>
</head>
<body>
<script>
var numbers=[2,4,6,8,0,1,2,3,7,9];
//当a>b的结果为正数时则为升序
numbers.sort(function(a,b){
if(a>b){return 1;}
if(a<b){return -1;}
return 0;
});
console.log(numbers.join(','));
//简化,注意类型
numbers.sort(function(a,b){
return a-b;
});
console.log(numbers.join(',')); //降序
numbers.sort(function(a,b){
return b-a;
});
console.log(numbers.join(','));
</script>
</body>
</html>

运行结果:

9.4、简单对象List自定义属性排序

 栗子:

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title>排序</title>
</head> <body>
<script>
//对象数组
var pdts = [{
title: "z-paint pot",
quantity: 9,
price: 3.95
},{
title: "iPhone XS",
quantity: 10,
price: 8906.72
},{
title: "polka dots",
quantity: 17,
price: 12.3
}, {
title: "pebbles",
quantity: 5,
price: 6.71
}, {
title: "Mi Note5",
quantity: 8,
price: 2985.6
}]; //按价格升序
pdts.sort(function(x,y){
return x.price-y.price;
});
document.write(JSON.stringify(pdts)); document.write("<br/>");
//按名称排序
pdts.sort(function(x,y){
if(x.title>y.title) return 1;
if(x.title<y.title) return -1;
return 0;
});
document.write(JSON.stringify(pdts));
</script>
</body> </html>

9.5、封装通用的排序函数

如果排序的条件要不断变化,将反复写简单的排序函数,封装可以带来方便:

<!DOCTYPE html>
<html> <head>
<meta charset="UTF-8">
<title>排序</title>
</head> <body>
<script>
//对象数组
var pdts = [{
title: "z-paint pot",
quantity: 9,
price: 3.95
}, {
title: "iPhone XS",
quantity: 10,
price: 8906.72
}, {
title: "polka dots",
quantity: 17,
price: 12.3
}, {
title: "pebbles",
quantity: 5,
price: 6.71
}, {
title: "Mi Note5",
quantity: 8,
price: 2985.6
}]; //根据排序关键字与是否为升序产生排序方法
var sortExp = function(key, isAsc) {
return function(x, y) {
if(isNaN(x[key])) { //如果当前排序的不是数字
if(x[key] > y[key]) return 1*(isAsc?1:-1);
if(x[key] < y[key]) return -1*(isAsc?1:-1);
return 0;
}else{
return (x[key]-y[key])*(isAsc?1:-1);
}
}
}; //按价格升序
pdts.sort(sortExp("price",true));
document.write(JSON.stringify(pdts));
document.write("<br/>------------------------------<br/>");
pdts.sort(sortExp("price",false));
document.write(JSON.stringify(pdts));
document.write("<br/>------------------------------<br/>");
pdts.sort(sortExp("title",true));
document.write(JSON.stringify(pdts));
document.write("<br/>------------------------------<br/>");
pdts.sort(sortExp("title",false));
document.write(JSON.stringify(pdts));
</script>
</body> </html>

十、路由在Vue3的创建与使用(相当于选项卡)

10.1、创建项目时,把路由也勾选上

10.2、在view目录下创建一个hello.vue文件,代码如下:

<template lang="">
<div>
<h1>{{msg}}</h1>
</div>
</template>
<script lang="ts">
import {defineComponent,ref} from 'vue';
export default defineComponent({
name:"hello",
setup(){
let msg = ref("作业1");
return {msg}
}
})
</script>
<style lang=""> </style>

10.3、在router目录下的ts文件,添加两个代码

10.4、在App.vue文件里面使用,代码如下:

<template>
<nav>
<router-link to="/">作业一</router-link> |
<router-link to="/about">作业二</router-link>
</nav>
<router-view/>
</template> <style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
} nav {
padding: 30px;
} nav a {
font-weight: bold;
color: #2c3e50;
} nav a.router-link-exact-active {
color: #42b983;
}
</style>

10.5、运行结果:注意路径

结果一:

结果二:

十一、Vue3 + element ui 实现购物车功能

11.1、MySQL 数据库代码:

create table shopping
(
id int primary key auto_increment comment '编号',
`name` varchar(100) not null comment '名称',
price float not null comment '价格',
quantity int not null comment '数量'
)auto_increment=10001 insert into shopping(`name`,price,quantity)
values('Xiaomi Civi 2','2399','1'),
('Xiaomi MIX Fold 2','8999','1'),
('Redmi K50 至尊版','2999','1'),
('Xiaomi 12S Ultra','5999','1'),
('Xiaomi 12S Pro','4699','1'),
('Xiaomi 12S','3999','1') select * from shopping

11.2、spring boot 后台代码:

实体类:

package com.fairy.shopping.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor; /**
* 实体类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Shopping {
private int id; // 编号
private String name; // 名称
private float price; // 价格
private int quantity; // 数量
}

dao 包:

package com.fairy.shopping.dao;

import com.fairy.shopping.entity.Shopping;
import org.apache.ibatis.annotations.Mapper; import java.util.List; /**
* 映射
*/
@Mapper
public interface ShoppingDao {
/** 查询所有购物车商品 */
List<Shopping> findAll(); /** 删除某条商品信息 */
int delShopping(int id); }

mapper.xml文件

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.fairy.shopping.dao.ShoppingDao">
<!-- 查询所有的购物车信息 -->
<select id="findAll" resultType="Shopping">
SELECT
shopping.id,
shopping.`name`,
shopping.price,
shopping.quantity
FROM
shopping
</select>
<!-- 删除一条购物车信息 -->
<delete id="delShopping">
delete from shopping
where id = #{id}
</delete>
</mapper>

yaml 文件:

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/shoppingtrolley?serverTimezone=GMT&useUnicode=true&characterEncoding=utf-8
jackson:
date-format: yyyy-MM-dd
mybatis:
type-aliases-package: com.fairy.shopping.entity
mapper-locations: classpath:/mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # pagehelper
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql

服务类接口:

package com.fairy.shopping.service;

import com.fairy.shopping.entity.Shopping;

import java.util.List;

/**
* 服务类
*/
public interface ShoppingService { /** 查询所有购物车商品 */
List<Shopping> findAll(); /** 删除某条商品信息 */
int delShopping(int id); }

服务实现类:

package com.fairy.shopping.service.impl;

import com.fairy.shopping.dao.ShoppingDao;
import com.fairy.shopping.entity.Shopping;
import com.fairy.shopping.service.ShoppingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import java.util.List; @Service
public class ShoppingServiceImpl implements ShoppingService { @Autowired
ShoppingDao shoppingDao; @Override
public List<Shopping> findAll() {
return shoppingDao.findAll();
} @Override
public int delShopping(int id) {
return shoppingDao.delShopping(id);
}
}

11.3、Vue3 前端代码:

添加 element ui 的依赖:

npm install element-plus --save

全局引入:

如果你对打包后的文件大小不是很在乎,那么使用完整导入会更方便。

// main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue' const app = createApp(App) app.use(ElementPlus)
app.mount('#app')

子组件:

<template>
<div class="hello">
<!-- :data="datas.arr:所有的数据 -->
<!-- @sort-change="sorts:自定义排序的方法 -->
<!-- 定义完排序的方法之后,在需要排序的列中定义sortable="custom" -->
<el-table :data="datas.arr" border @sort-change="sorts" >
<el-table-column prop="id" label="编号" sortable="custom" />
<el-table-column prop="name" label="名称" sortable="custom" />
<el-table-column prop="price" label="价格" sortable="custom" />
<el-table-column prop="quantity" id="shuliang" sortable="custom" label="数量" #default="scope">
<el-input-number v-model="scope.row.quantity" :min="1" :max="10" />
</el-table-column>
<el-table-column label="小计">
<!-- #default="scope":数据 -->
<template #default="scope">{{scope.row.price*scope.row.quantity}}</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button size="small" type="danger" @click="delshopping(scope.row.id)">移除</el-button>
</template>
</el-table-column> </el-table>
</div>
</template> <script lang="ts">
// 导入需要的依赖
import { defineComponent, reactive} from 'vue';
import axios from 'axios' // 注册组件
export default defineComponent({
name: 'HelloWorld',
props: {
msg: String,
}, setup() { // 定义数组,存储所有的数据
var datas = reactive({
arr: []
}); // 查询所有的数据
async function init() {
let { data: res } = await axios.get("http://localhost:8080/findAll");
datas.arr = res;
} // 加载时就调用
init(); // 删除购物车里某条商品信息
async function delshopping(id: object) { let { data: res } = await axios.delete("http://localhost:8080/delShopping", {
params: {
id: id
}
}) if (res > 0) {
alert("删除成功!");
init();
} } //根据排序关键字与是否为升序产生排序方法
// key:代表属性,isAsc:代表是否为真
var sortExp = function (key: any, isAsc: any) {
// x:代表是的数组的任意一个数,y:也是如此
// 它们的意义:两个数对比,哪个数大就在前面,相当于冒泡排序
return function (x: any, y: any) {
if (isNaN(x[key])) { //如果当前排序的不是数字
// x[key]:这个数的属性值是什么
// 返回1:升序;返回2:降序
if (x[key] > y[key]) return 1 * (isAsc ? 1 : -1);
if (x[key] < y[key]) return -1 * (isAsc ? 1 : -1); return 0;
} else {
return (x[key] - y[key]) * (isAsc ? 1 : -1);
}
}
};
// 自定义排序的方法
async function sorts(sb: any) {
// 如果是升序
if (sb.order === "ascending") {
datas.arr = reactive(datas.arr.sort(sortExp(sb.prop, true)));
} else {
datas.arr = reactive(datas.arr.sort(sortExp(sb.prop, false)));
}
console.log(sb);
} // 暴露方法出去
return { datas, delshopping, sorts }
} }); </script> <!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>

App.vue 组件:

<template>
<h1>cc商城购物车</h1>
<div id="big">
<!-- 把子组件的东西放这里 -->
<hello />
</div> </template> <script lang="ts"> import hello from './components/HelloWorld.vue' export default {
name: 'app',
components:{
hello
} };
</script> <style>
#big{
width: 80%;
margin: auto;
}
h1{
text-align: center;
}
</style>

运行命令:npm run serve;

运行结果如下:

十二、react 框架实现购物车功能

12.1、创建 react 项目

npx create-react-app 项目名称 // 创建react项目
cd 项目名称 // 切换到项目的目录里
npm start // 运行项目

12.2、导入 axios 插件

npm i axios // 如果报错了,在这个命令后面加 --legacy-peer-deps

12.3、项目结构

12.4、数据库上面有!!!

12.5、后台 curd 方法

12.5.1、dao 包

package com.fairy.shopping.dao;

import com.fairy.shopping.entity.Shopping;
import org.apache.ibatis.annotations.Mapper; import java.util.List; /**
* 映射
*/
@Mapper
public interface ShoppingDao {
/** 查询所有购物车商品 */
List<Shopping> findAll(); /** 删除某条商品信息 */
int delShopping(int id); /** 添加商品信息 */
int addShopping (Shopping shopping); /** 修改商品信息 */
int updateShopping (Shopping shopping);
}

12.5.2、mapper.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.fairy.shopping.dao.ShoppingDao">
<!-- 查询所有的购物车信息 -->
<select id="findAll" resultType="Shopping">
SELECT
shopping.id,
shopping.`name`,
shopping.price,
shopping.quantity
FROM
shopping
</select>
<!-- 删除一条购物车信息 -->
<delete id="delShopping">
delete from shopping
where id = #{id}
</delete>
<insert id="addShopping">
insert into shopping values (0,#{name},#{price},#{quantity})
</insert>
<update id="updateShopping">
update shopping
set `name` = #{name}, `price` = #{price}, `quantity` = #{quantity}
where `id` = #{id}
</update>
</mapper>

12.5.3、服务类接口

package com.fairy.shopping.service;

import com.fairy.shopping.entity.Shopping;

import java.util.List;

/**
* 服务类
*/
public interface ShoppingService { /** 查询所有购物车商品 */
List<Shopping> findAll(); /** 删除某条商品信息 */
int delShopping(int id); /** 添加商品信息 */
int addShopping (Shopping shopping); /** 修改商品信息 */
int updateShopping (Shopping shopping);
}

12.5.4、服务实现类

package com.fairy.shopping.service.impl;

import com.fairy.shopping.dao.ShoppingDao;
import com.fairy.shopping.entity.Shopping;
import com.fairy.shopping.service.ShoppingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import java.util.List; @Service
public class ShoppingServiceImpl implements ShoppingService { @Autowired
ShoppingDao shoppingDao; @Override
public List<Shopping> findAll() {
return shoppingDao.findAll();
} @Override
public int delShopping(int id) {
return shoppingDao.delShopping(id);
} @Override
public int addShopping(Shopping shopping) {
return shoppingDao.addShopping(shopping);
} @Override
public int updateShopping(Shopping shopping) {
return shoppingDao.updateShopping(shopping);
}
}

12.5.5、控制器

package com.fairy.shopping.controller;

import com.fairy.shopping.entity.Shopping;
import com.fairy.shopping.service.ShoppingService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import java.util.List; /**
* 控制器
*/
@RestController
@CrossOrigin(origins="*")
@RequiredArgsConstructor // 注入service
public class ShoppingController {
// 注意:前面记得加final,否则会报错
final ShoppingService shoppingService; @GetMapping("/findAll")
public List<Shopping> findAll(){
return shoppingService.findAll();
} @DeleteMapping("/delShopping")
public int delShopping(int id){
return shoppingService.delShopping(id);
} @PostMapping("/addShopping")
public int addShopping(@RequestBody Shopping shopping){
return shoppingService.addShopping(shopping);
} @PutMapping("/updateShopping")
public int updateShopping(@RequestBody Shopping shopping){
return shoppingService.updateShopping(shopping);
}
}

13、前端代码

13.1、App.js

import './App.css';
import React from 'react';
import axios from 'axios';
import '../src/css/index.css' // 类组件
// this返回的都是APP组件
export default class App extends React.Component{
// 存储变量的
state = ({list:[],productInfo:{id:0,name:null,price:null,quantity:null},total:0})
// 返回给页面的东西
render() {
return (
<div>
<h1>CC商城的购物车</h1>
<table border={1} width={"80%"}>
<tbody>
<tr>
<th>编号</th>
<th>姓名</th>
<th>价格</th>
<th>数量</th>
<th>小计</th>
<th>操作</th>
</tr>
</tbody>
{/* 循环遍历商品信息 */}
{this.state.list.map((item,i)=>
<tbody key={item.id}>
<tr>
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.price}</td>
<td>
<button className='btncount' onClick={()=>this.count(i,1)}>+</button>
<input type='text' id='count' value={item.quantity}></input>
<button className='btncount' onClick={()=>this.count(i,0)}>-</button>
</td>
<td>{item.price * item.quantity}</td>
<td className='ywd2'>
<button onClick={()=>this.del(item.id)}>删除</button>
<button id='update' onClick={()=>this.show(item)}>编辑</button>
</td>
</tr>
</tbody>
)}
<tbody>
<tr >
<td colSpan={6} >
<span id="total">合计:{this.state.total}</span>
</td>
</tr>
</tbody>
</table>
{/* 下面文本框的编写 */}
<fieldset id='fieldset'>
<legend>商品管理</legend>
<p>
<label>商品名称:</label>
<input type="text" id='sname' defaultValue={this.state.productInfo.name} onInput={()=>this.inputss('name','sname')}/>
</p>
<p>
<label>商品价格:</label>
<input type="text" id='prices' defaultValue={this.state.productInfo.price} onInput={()=>this.inputss('price','prices')} />
</p>
<p>
<label>商品数量:</label>
<input type="number" id='q1' defaultValue={this.state.productInfo.quantity} onInput={()=>this.inputss('quantity','q1')}/>
</p>
<div className='ywd'>
<button onClick={this.addShopping}>添加</button>
<button onClick={this.updateds}>修改</button>
</div>
</fieldset>
</div>
)
} ///////////////////////////////////////////////////////////////////////////////////////////// // 1、查询所有方法
async init(){
let {data:res} = await axios.get("http://localhost:8080/findAll");
this.state.list = res
this.setState({list:this.state.list})
this.total();
} ///////////////////////////////////////////////////////////////////////////////////////////// // 2、计算合计的价格
total = ()=>{
// 循环所有的数据,然后存一个变量total在state里面,+=遍历出来的所有数据里面的价格
this.state.list.forEach(item=>{
this.state.total += (item.quantity*item.price);
})
// 进行修改操作
this.setState({total:this.state.total})
} ///////////////////////////////////////////////////////////////////////////////////////////// // 3、改变数量的方法,传下标和1或0的数(目的是:判断是+还是-)
count(i,number){
if (number) { // 如果数不是0就为true
this.state.list[i].quantity+=1;
this.setState({list:this.state.list})
}else{
this.state.list[i].quantity-=1;
// 如果数量小于0的话,它就一直等于0
if (this.state.list[i].quantity<0) {
this.state.list[i].quantity = 0;
}
// 修改list对象的状态
this.setState({list:this.state.list})
} // 调用计算总价的方法,对总价进行刷新
this.total(); } ///////////////////////////////////////////////////////////////////////////////////////////// // 4、添加的方法
addShopping = async()=>{
let {data:res} = await axios.post('http://localhost:8080/addShopping',this.state.productInfo)
if(res){
alert('添加成功')
this.init()
}else{
alert('添加失败')
}
} ///////////////////////////////////////////////////////////////////////////////////////////// // 5、删除的方法
async del(id){
let {data:res} = await axios.delete('http://localhost:8080/delShopping',{
params:{
id
}
})
if(res){
alert('删除成功')
this.init()
}else{
alert('删除失败')
}
} ///////////////////////////////////////////////////////////////////////////////////////////// // 6、编辑的方法,通过这个方法拿到该行的数据,进行重新赋值给productInfo
show(row){
this.state.productInfo.id = row.id;
this.state.productInfo.name = row.name;
this.state.productInfo.price = row.price;
this.state.productInfo.quantity = row.quantity;
this.setState({productInfo:this.state.productInfo})
}
// 改变文本框的值,便于进行修改操作,传属性值和id值
inputss (key,value){
// DOM操作获取文本框的值,重新赋值给productInfo的某个属性值
this.state.productInfo[key] = document.getElementById(value).value this.setState({list:this.state.list,productInfo:this.state.productInfo})
} ///////////////////////////////////////////////////////////////////////////////////////////// // 7、修改的方法
updateds = async()=>{
let {data:res} = await axios.put('http://localhost:8080/updateShopping',this.state.productInfo)
if(res){
alert('修改成功')
this.init()
}else{
alert('修改失败')
}
} ///////////////////////////////////////////////////////////////////////////////////////////// // 8、清除下面文本框的方法
clearInfo = ()=>{
this.setState ({productInfo:{id:0,name:this.state.productInfo.name,price:null,quantity:null}})
this.setState({productInfo:{id:0,name:'',price:null,quantity:null}})
} ///////////////////////////////////////////////////////////////////////////////////////////// //生命周期
componentDidMount(){
this.init()
}
}

13.2、src/css/index.css

.ywd{
display: flex;
} h1{
text-align: center;
} table{
height: 250px;
margin: auto;
text-align: center;
border-collapse: collapse;
} th{
background-color: darkblue;
color: #fff;
}
button{
width: 70px;
height: 30px;
background-color: cornflowerblue;
color: #fff;
border-radius: 20px;
border: 0;
margin-left: 10px;
} #update{
background-color: coral;
} #count{
text-align: center;
width: 40px;
} #fieldset{
width: 30%;
margin-left: 10%;
margin-top: 30px;
} #total{
font-size: 25px;
color: rebeccapurple;
float: right;
margin-right: 20px;
} .btncount{
width: 30px;
height: 20px;
border-radius: 5px;
margin: 5px;
}

14、运行命令:npm start

vue3的学习笔记:MVC、Vue3概要、模板、数据绑定、用Vue3 + element ui、react框架实现购物车案例的相关教程结束。

《vue3的学习笔记:MVC、Vue3概要、模板、数据绑定、用Vue3 + element ui、react框架实现购物车案例.doc》

下载本文的Word格式文档,以方便收藏与打印。