vue3的学习笔记:MVC、Vue3概要、模板、数据绑定、用Vue3 + element ui、react框架实现购物车案例
一、前端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 可以通过事件绑定 Model,Model 可以通过数据绑定 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。
优点:
AngularJS模板功能强大丰富,自带了极其丰富的angular指令。
AngularJS是完全可扩展的,与其他库的兼容效果很好,每一个功能可以修改或更换,以满足开发者独特的开发流程和功能的需求。
AngularJS是一个比较完善的前端MVC框架,包含服务,模板,数据双向绑定,模块化,路由,过滤器,依赖注入等所有功能;
AngularJS是互联网巨人谷歌开发,这也意味着他有一个坚实的基础和社区支持。
缺点:
AngularJS强约束导致学习成本较高,对前端不友好。但遵守 AngularJS 的约定时,生产力会很高,对 Java 程序员友好。
AngularJS不利于SEO,因为所有内容都是动态获取并渲染生成的,搜索引擎没法爬取。
1.5.3、Vue.js
Vue.js 是一个轻巧、高性能、可组件化的MVVM库,是一套构建用户界面的渐进式框架,同时拥有非常容易上手的API,作者是尤雨溪(中国人)、
文档与资源大全: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、生命周期
// 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)
<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,我们可以用包含多个选项的对象来描述组件的逻辑,例如 data
、methods
和 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框架实现购物车案例的更多相关文章
- Django 学习笔记(五)模板标签
关于Django模板标签官方网址https://docs.djangoproject.com/en/1.11/ref/templates/builtins/ 1.IF标签 Hello World/vi ...
- Django 学习笔记(四)模板变量
关于Django模板变量官方网址:https://docs.djangoproject.com/en/1.11/ref/templates/builtins/ 1.传入普通变量 在hello/Hell ...
- Django 学习笔记(三)模板导入
本章内容是将一个html网页放进模板中,并运行服务器将其展现出来. 平台:windows平台下Liunx子系统 目前的目录: hello ├── manage.py ├── hello │ ├── _ ...
- 2.《Spring学习笔记-MVC》系列文章,讲解返回json数据的文章共有3篇,分别为:
转自:https://www.cnblogs.com/ssslinppp/p/4528892.html 个人认为,使用@ResponseBody方式来实现json数据的返回比较方便,推荐使用. 摘要 ...
- 3.《Spring学习笔记-MVC》系列文章,讲解返回json数据的文章共有3篇,分别为:
转自:https://www.cnblogs.com/ssslinppp/p/4528892.html 概述 在文章:<[Spring学习笔记-MVC-3]SpringMVC返回Json数据-方 ...
- 1.《Spring学习笔记-MVC》系列文章,讲解返回json数据的文章共有3篇,分别为:
转自:https://www.cnblogs.com/ssslinppp/p/4528892.html [Spring学习笔记-MVC-3]SpringMVC返回Json数据-方式1:http://w ...
- NGUI 学习笔记实战之二——商城数据绑定(Ndata)
上次笔记实现了游戏商城的UI界面,没有实现动态数据绑定,所以是远远不够的.今天采用NData来做一个商城. 如果你之前没看过,可以参考上一篇博客 NGUI 学习笔记实战——制作商城UI界面 ht ...
- canvas学习笔记(下篇) -- canvas入门教程--保存状态/变形/旋转/缩放/矩阵变换/综合案例(星空/时钟/小球)
[下篇] -- 建议学习时间4小时 课程共(上中下)三篇 此笔记是我初次接触canvas的时候的学习笔记,这次特意整理为博客供大家入门学习,几乎涵盖了canvas所有的基础知识,并且有众多练习案例, ...
- JDBC 学习笔记(十)—— 使用 JDBC 搭建一个简易的 ORM 框架
1. 数据映射 当我们获取到 ResultSet 之后,显然这个不是我们想要的数据结构. 数据库中的每一个表,在 Java 代码中,一定会有一个类与之对应,例如: package com.gerrar ...
- vue3.0学习笔记(一)
一.搭建工作环境环境 1.从node.js官网下载相应版本进行安装即可 https://nodejs.org/zh-cn/download/,安装完成后在命令行输入 node -v 如果可以查询到版 ...
随机推荐
- 四 多例模式【Multition Pattern】 来自CBF4LIFE 的设计模式
出现在明朝,那三国期间的算不算,不算,各自称帝,各有各的地盘,国号不同.大家还记得那首诗<石灰吟>吗?作者是谁?于谦,他是被谁杀死的?明英宗朱祁镇,对,就是那个在土木堡之变中被瓦刺俘虏的皇 ...
- Windows平台Unity3d播放多路RTMP或RTSP流
好多开发者在做AR.VR或者教育类产品时,苦于如何在windows平台构建一个稳定且低延迟的RTSP或者RTMP播放器,如果基于Unity3d完全重新开发一个播放器,代价大.而且周期长,不适合快速出产 ...
- .NET 部署Https(SSL)通过代码方式
在上一个文章中,传送门,给大家介绍了怎么在配置文件中使用 Kestrel 部署 Https,正好今天有小伙伴稳问到:可以通过代码的方式实现 Kestrel 的 Https 的部署吗?答案是肯定的,我们 ...
- ProxySQL 定时调度
转载自:https://www.jianshu.com/p/410ff5897c27 Scheduler是 v1.2.0 引入的特性. ProxySQL的Scheduler是一个类似于定时任务系统(c ...
- Kubernetes 部署 Nacos 1.4 集群
文章转载自:http://www.mydlq.club/article/104/ 系统环境: Nacos 版本:1.4.1 Mysql 版本:8.0.19 Kubernetes 版本:1.20.1 一 ...
- 了解Elasticsearch写入磁盘的数据
文章转载自: https://mp.weixin.qq.com/s?__biz=MzI2NDY1MTA3OQ==&mid=2247484171&idx=1&sn=985a71a ...
- 示例:Service连接应用程序
整体思路: 1.创建pod 2.创建关联上一步pod的service 3.使用不同的方式配置service从而能够在集群内部访问 4.使用ssl方式加密访问service 5.配置service为no ...
- 容器监控工具WeaveScope初步安装,了解
Weave Scope是Docker和Kubernetes的可视化和监视工具.它提供了自上而下的应用程序视图以及整个基础架构视图,并允许您实时诊断将分布式容器化应用程序部署到云提供商时遇到的任何问题. ...
- 关于aws cli命令的exit/return code分析
最近总是收到一个备份脚本的失败邮件,脚本是之前同事写的,没有加入任何有调试信息,及有用的日志 于是去分析 ,脚本中有一条 aws s3 sync $srclocal $dsts3 命令,然后根据这条 ...
- 一个C#开发者学习SpringCloud搭建微服务的心路历程
前言 Spring Cloud很火,很多文章都有介绍如何使用,但对于我这种初学者,我需要从创建项目开始学起,所以这些文章对于我的启蒙,帮助不大,所以只好自己写一篇文章,用于备忘. SpringClou ...