前端工程化 Vue 前端工程化是使用软件工程的方法来单独解决前端的开发流程中模块化、组件化、规范化、自动化的问题,其主要目的为了提高效率和降低成本
前端工程化实现的技术栈有很多,我们采用ES6+nodejs+npm+Vite+VUE3+router+pinia+axios+Element-plus组合来实现
ECMAScript6 VUE3中大量使用ES6语法
Nodejs 前端项目运行环境
npm 依赖下载工具
vite 前端项目构建工具
VUE3 优秀的渐进式前端框架
router 通过路由实现页面切换
pinia 通过状态管理实现组件数据传递
axios ajax异步请求封装技术实现前后端数据交互
Element-plus 可以提供丰富的快速构建网页的组件仓库
TypeScript 静态类型检查和其他面向对象编程特性
ECMA6Script ECMAScript 6,简称ES6,是JavaScript 语言的一次重大更新。它于2015 年发布,是原来的ECMAScript标准的第六个版本。ES6带来了大量的新特性,包括箭头函数、模板字符串、let和const关键字、解构、默认参数值、模块系统等等,大大提升了JavaScript的开发体验。由于VUE3中大量使用了ES6的语法,所以ES6成为了学习VUE3的门槛之一
ES6 新增了let和const,用来声明变量,使用的细节上也存在诸多差异
let 和var的差别
1、let 不能重复声明
2、let有块级作用域,非函数的花括号遇见let会有块级作用域,也就是只能在花括号里面访问。
3、let不会预解析进行变量提升
4、let 定义的全局变量不会作为window的属性
5、let在es6中推荐优先使用
模板字符串(template string)是增强版的字符串,用反引号(`)标识
1、字符串中可以出现换行符
2、可以使用 ${xxx} 形式输出变量和拼接变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <script> let ulStr = '<ul>' + '<li>JAVA</li>' + '<li>html</li>' + '<li>VUE</li>' + '</ul>' console .log (ulStr) let ulStr2 = ` <ul> <li>JAVA</li> <li>html</li> <li>VUE</li> </ul>` console .log (ulStr2) let name ='张小明' let infoStr =name+'被评为本年级优秀学员' console .log (infoStr) let infoStr2 =`${name} 被评为本年级优秀学员` console .log (infoStr2) </script>
解构表达式
箭头函数 类似lambda表达式 1 2 3 4 5 6 7 8 9 10 xdd.onclick = function ( ){ console .log (this ) setTimeout (()=> { console .log (this ) this .style .backgroundColor = 'pink' ; },2000 ); }
rest和spread 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let fun3 = function (...args ){console .log (args)}let fun4 = (...args ) =>{console .log (args)}fun3 (1 ,2 ,3 )fun4 (1 ,2 ,3 ,4 )let arr2=[4 ,5 ,6 ]let arr3=[...arr,...arr2]console .log (arr3)let p1={name :"张三" }let p2={age :10 }let p3={gender :"boy" }let person ={...p1,...p2,...p3}console .log (person)
创建对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Person { #n; age; get name (){ return this .n ; } set name (n ){ this .n =n; } eat (food ){ console .log (this .age +"岁的" +this .n +"用筷子吃" +food) } static sum (a,b ){ return a+b; } constructor (name,age ){ this .n =name; this .age = age; } } let person =new Person ("张三" ,10 ); console .log (person.name ) console .log (person.n ) person.name ="小明" console .log (person.age ) person.eat ("火锅" ) console .log (Person .sum (1 ,2 )) class Student extends Person { grade ; score ; study ( ){ } constructor (name,age ) { super (name,age); } } let stu =new Student ("学生小李" ,18 ); stu.eat ("面条" )
浅拷贝和深拷贝 1 2 3 4 5 6 7 8 9 10 11 let person2 = person;person2.name ="小黑" console .log (person.name ) let person2 = JSON .parse (JSON .stringify (person))let person3 = {...person}person2.name ="小黑" console .log (person.name ) console .log (person2.name )
模块化处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 export const PI = 3.14 export function sum (a, b ) { return a + b; } export class Person { constructor (name, age ) { this .name = name; this .age = age; } sayHello ( ) { console .log (`Hello, my name is ${this .name} , I'm ${this .age} years old.` ); } } 3 默认和混合暴露export const PI = 3.14 function sum (a, b ) { return a + b; } class Person { constructor (name, age ) { this .name = name; this .age = age; } sayHello ( ) { console .log (`Hello, my name is ${this .name} , I'm ${this .age} years old.` ); } } export default sumexport { Person }
1 2 3 import * as m1 from './module.js' import {Person,sum,PI} from './module.js'
TypeScript 快速上手 它为 JavaScript 增加了静态类型检查和其他面向对象编程特性。TypeScript 的主要优势在于:提高代码可读性和可维护性、在编译时发现错误、支持大型项目开发。它最终会被编译成 JavaScript 代码,可以在任何支持 JavaScript 的环境中运行。
类型推断 ——强类型语言 1 2 3 4 5 6 7 8 9 10 let str = 'abc' str = 10 let str : string str = 'abc' let arr : number []= [1 , 2 , 3 ] arr[1 ] = '1' let arrl : Array <string > = ['a' , 'b' , 'c' ]let t1 : [number ,srting,number ?] = [1 ,'2' ]
类型断言 1 2 3 let numArrive = [1 ,2 ,3 ]const result = numArr.find (item => item>2 ) as number result*2
联合类型 1 2 3 4 let str : string | null = null let num : 1 |2 |3 = 2 type Mytype = string | number let a :Mytype = 'abc'
枚举类型 1 2 3 4 5 enum MyEnum {A,B,C } console .log (MyEnum .A )console .log (MyEnum [0 ])
函数 1 2 3 4 function MyFn (a = 10 , b : string , c ?: boolean , ...rest : number [] ): number { return 100 } const f = MyFn (20 , 'abc' , true , 1 , 2 , 3 )
接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface Obj1 { name : string , age : number } interface Obj2 extends Obj1 { sex : string } const obj :Obj2 = { name : 'a' , age : 8 sex : 'man' }
泛型 1 2 3 4 5 function myFn<T>(a : T,b : T): T[] { return [a, b] } myFn<number >(1 , 2 ) myFn ('a' ,1 )
类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Article { public title : string content : string aaa ?: string bbb = 100 private password : string protected innerData? : string static readonly author : string constructor (title : string , content : string ){ this .title = title this .content = content } get password ():string { return '*******' } set password (newPwd : string ){ this .password = newPwd } } const a = new Article ('标题' ,'内容' )
前端工程化环境搭建 安装 nodejs npm
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,可以使 JavaScript 运行在服务器端。使用 Node.js,可以方便地开发服务器端应用程序,如 Web 应用、API、后端服务,还可以通过 Node.js 构建命令行工具等。
Node.js 的核心是其管理事件和异步 I/O 的能力。Node.js 的异步 I/O 使其能够处理大量并发请求,并且能够避免在等待 I/O 资源时造成的阻塞。此外,Node.js 还拥有高性能网络库和文件系统库,可用于搭建 WebSocket 服务器、上传文件等。在 Node.js 中,我们可以使用 JavaScript 来编写服务器端程序,这也使得前端开发人员可以利用自己已经熟悉的技能来开发服务器端程序,同时也让 JavaScript 成为一种全栈语言。
NPM 全称Node Package Manager,是Node.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;也是Node.js的包管理工具,相当于后端的Maven 。
NPM配置依赖下载使用阿里镜像
1 npm config set registry https://registry.npmmirror.com
常用命令:
npm -v 查看版本
1.项目初始化
npm init
进入一个vscode创建好的项目中, 执行 npm init 命令后,npm 会引导您在命令行界面上回答一些问题,例如项目名称、版本号、作者、许可证等信息,并最终生成一个package.json 文件。package.json信息会包含项目基本信息!类似maven的pom.xml
npm init -y
执行,-y yes的意思,所有信息使用当前文件夹的默认值!不用挨个填写!
2.安装依赖 (查看所有依赖地址 https://www.npmjs.com )
npm install 包名 或者 npm install 包名@版本号
npm install -g 包名
安装全局依赖包(安装到d:/GlobalNodeModules)则可以在任何项目中使用它,而无需在每个项目中独立安装该包。
npm install
3.升级依赖
4.卸载依赖
5.查看依赖
Vue
Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。官网为:https://cn.vuejs.org/
Vue的两个核心功能:
声明式渲染 :Vue 基于标准 HTML 拓展了一套模板语法,使得我们可以声明式地描述最终输出的 HTML 和 JavaScript 状态之间的关系。
响应性 :Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM
Vue3通过Vite(前端脚手架)实现工程化 创建一个空目录用于存储多个前端项目,命令行运行如下命令
Vue3使用 Composition API (组合式API) 使用setup代替 配置式API(option)-> data, methods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <script setup lang="ts"> import { ref } from 'vue'; let message = ref('你好') function changeMessage() { message.value = '666' } </script> <script lang="ts"> export default { name: 'HelloWorld', props: { msg: String } } </script> <template> {{ message }} {{ msg }} <button @click="changeMessage">changeMessage</button> </template>
在VScode扩展使用 Vetur 开发 Vue + TypeScript 项目时会显示报错组件没默认导出,程序能正常执行,需更换支持ts的语法高亮插件Vue - Official以取代 Vetur。
Vue 响应式数据 定义一些要展示到html上的一些数据变量/对象响应式数据 :在数据变化时,vue框架会将变量最新的值更新到dom树中,页面数据就是实时最新的非响应式数据 :在数据变化时,vue框架不会将变量最新的值更新到dom树中,页面数据就不是实时最新的 vue2中,数据不做特殊处理,默认就是响应式的 vue3中,数据要经过ref /reactive函数的处理才是响应式的ref reactive 函数时vue框中给我们提供的方法,导入进来即可使用
1 import {ref,reactive} from 'vue'
让一个普通数据转换为响应式数据 两种方式 1.ref函数 更适合单个变量 在script标签中操作ref响应式数据要通过.value 在template中操作ref响应式数据则无需.value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <script> let counter = ref(10); function add(){ counter.value++; } </script> <template> <div> <button @click="add()">counter</button> <!--或--> <button @click="counter++">counter</button> </div> </template>
2.reactive函数 更适合对象 在script template 操作reactive响应式数据都直接使用对象名.属性名的方式即可
1 2 3 4 5 6 7 8 9 10 11 <script > let person = reactive ({ name :"" , age :10 }) </script > <template > <div > <button @click ="reactive.age++" > reactive</button > </div > </template >
3. 用Object.assgin() 改变整个对象 或者ref 不改变其地址值 原地改变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <script setup()> let person = reactive({ name:"", age:10 }) function change(){ Object.assgin(person,{name:'张三',age:12}) } </script> <template> <div> <button @click="change()">reactive</button> </div> </template>
解构响应式数据需要加上toRefs或toRef 1 2 3 4 5 6 let person = reactive ({ name :'zhangsan' , age :18 }) let {name,age} = toRefs (person)let n1 = toRef (person,'name' )
用ref绑定标签 1 2 3 4 5 6 <button @click ="showH2" >打印h2</button> const good = ref ('good' )function showH2 ( ) { console .log (good.value ) }
插值表达式 插值表达式:最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 ,即双大括号{{}}
插值表达式是将数据渲染到元素的指定位置的手段之一
插值表达式不绝对依赖标签,其位置相对自由
插值表达式中支持javascript的运算表达式
插值表达式中也支持函数的调用
1 2 3 4 5 6 7 8 9 10 11 12 <!--script略--> <template> <div> <h1>{{ msg }}</h1> msg的值为: {{ msg }} <br> getMsg返回的值为:{{ getMsg() }} <br> 是否成年: {{ age>=18?'true':'false' }} <br> 反转: {{ bee.split(' ').reverse().join('-') }} <br> 购物车总金额: {{ compute() }} <br/> 购物车总金额: {{carts[0].price*carts[0].number + carts[1].price*carts[1].number}} <br> </div> </template>
Props Props 是 Vue 组件系统中父组件向子组件传递数据 的主要方式。
下面一个案例介绍下基本用法
PersonInter.ts:
1 2 3 4 5 6 interface PersonInter { name : string ; age : number ; sex ?: string ; } export type Persons = PersonInter []
Person.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <template> <h1>{{ a }}</h1> <h2>{{ list }}</h2> </template> <script lang="ts" setup name="Person"> import { PropType } from 'vue' import {type Persons} from './PersonInter' // 定义props 并接收props let x = defineProps({ a: { type: String, default: '123' }, list: { //定义props时指定复杂类型 type: Object as PropType<Persons>, default:() => [{name:'张三',age:18},{name:'李四',age:19}] } }) // 也可以这样定义props defineProps<{a:string,list:Persons}>() console.log(x.a) console.log(x.list) </script>
App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script setup lang="ts"> import Person from './components/Person.vue' import { ref } from 'vue' import {type Persons} from './components/PersonInter' const personList = ref<Persons>([ {name: '张三',age: 18,sex: '男'}, {name: '李四',age: 19,sex: '女'} ]) </script> <template> <div> <Person a="123" :list="personList"></Person> </div> </template>
在 Vue 3.5 及以上版本中,从 defineProps 返回值解构出的变量是响应式的。当在同一个 <script setup> 块中的代码访问从 defineProps 解构出的变量时,Vue 的编译器会自动在前面添加 props.。
文本渲染命令 v-text/v-html 类似 innerText innerHTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <script setup type ="module" > let msg ='hello vue3' let getMsg = ( )=>{ return msg } let age = 19 let bee = '蜜 蜂' let redMsg ='<font color=\'red\'>msg</font>' let greenMsg =`<font color=\'green\'>${msg} </font>` </script > <template > <div > <span v-text ='msg' > </span > <br > <span v-text ='redMsg' > </span > <br > <span v-text ='getMsg()' > </span > <br > <span v-text ='age>18?"成年":"未成年"' > </span > <br > <span v-text ='bee.split(" ").reverse().join("-")' > </span > <br > <span v-html ='msg' > </span > <br > <span v-html ='redMsg' > </span > <br > <span v-html ='greenMsg' > </span > <br > <span v-html ="`<font color='green'>${msg}</font>`" > </span > <br > </div > </template >
属性渲染命令 v-bind 可以用于渲染任何元素的属性,语法为 v-bind:属性名='数据名', 可以简写为 :属性名='数据名'
事件渲染命令
我们可以使用 v-on 来监听 DOM 事件,并在事件触发时执行对应的 Vue的JavaScript代码。
用法:v-on:click="handler" 或简写为 @click="handler"
绑定事件时,可以通过一些绑定的修饰符,常见的事件修饰符如下
.once:只触发一次事件。[重点]
.prevent:阻止默认事件。[重点]
.stop:阻止事件冒泡。
.capture:使用事件捕获模式而不是冒泡模式。
.self:只在事件发送者自身触发时才触发事件。
条件渲染(展不展示) v-if =”表达式/数据”数据为true 则当前元素会渲染进入dom树v-else 自动和前一个v-if做取反操作v-show =””数据为true 元素则展示在页面上,否则不展示
v-if 数据为false时,元素则不再dom树中了 v-show 数据为false是,元素仍在dom树中,通过display的css样式控制元素
v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。
1 2 3 4 5 6 7 8 9 10 11 12 13 <script type="module" setup> import {ref} from 'vue' let awesome = ref(true) </script> <template> <div> <h1 id="ha" v-show="awesome">Vue is awesome!</h1> <h1 id="hb" v-if="awesome">Vue is awesome!</h1> <h1 id="hc" v-else>Oh no 😢</h1> <button @click="awesome = !awesome">Toggle</button> </div> </template>
列表渲染(展示一组)
我们可以使用 v-for 指令基于一个数组来渲染一个列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 <script type="module" setup> //引入模块 import { reactive} from 'vue' //准备购物车数据,设置成响应数据 const carts = reactive([{name:'可乐',price:3,number:10},{name:'薯片',price:6,number:8}]) //计算购物车总金额 function compute(){ let count = 0; for(let index in carts){ count += carts[index].price*carts[index].number; } return count; } //删除购物项方法 function removeCart(index){ carts.splice(index,1);//(0,xx.length)是删除全部 } </script> <template> <div> <table> <thead> <tr> <th>序号</th> <th>商品名</th> <th>价格</th> <th>数量</th> <th>小计</th> <th>操作</th> </tr> </thead> <tbody v-if="carts.length > 0"> <!-- 有数据显示--> <tr v-for="cart,index in carts" :key="index"> <th>{{ index+1 }}</th> <th>{{ cart.name }}</th> <th>{{ cart.price + '元' }}</th> <th>{{ cart.number }}</th> <th>{{ cart.price*cart.number + '元'}}</th> <th> <button @click="removeCart(index)">删除</button> </th> </tr> </tbody> <tbody v-else> <!-- 没有数据显示--> <tr> <td colspan="6">购物车没有数据!</td> </tr> </tbody> </table> 购物车总金额: {{ compute() }} 元 </div> </template> <style scoped> </style>
双向绑定 单项绑定: v-bind 响应式数据发生变化时,更新dom树 用户的操作如果造成页面内容的改变不会影响响应式数据 双向绑定: v-model 页面上的数据由于用户的操作造成了改变,也会同步修改对应的响应式数据 双向绑定一般都用于表单标签 双向绑定也有人称呼为收集表单信息的命令 v-model:value=”数据”双向 绑定 v-model:value 一般都省略 :value -> v-model=” “
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <script type="module" setup> //引入模块 import { reactive,ref} from 'vue' let hbs = ref([]); //装爱好的值 let user = reactive({username:null,password:null,introduce:null,pro:null}) function login(){ alert(hbs.value); alert(JSON.stringify(user)); } function clearx(){ //user = {};// 这中写法会将数据变成非响应的,应该是user.username="" user.username='' user.password='' user.introduce='' user.pro='' hbs.value.splice(0,hbs.value.length);; } </script> <template> <div> 账号: <input type="text" placeholder="请输入账号!" v-model="user.username"> <br> 密码: <input type="text" placeholder="请输入账号!" v-model="user.password"> <br> 爱好: 吃 <input type="checkbox" name="hbs" v-model="hbs" value="吃"> 喝 <input type="checkbox" name="hbs" v-model="hbs" value="喝"> 玩 <input type="checkbox" name="hbs" v-model="hbs" value="玩"> 乐 <input type="checkbox" name="hbs" v-model="hbs" value="乐"> <br> 简介:<textarea v-model="user.introduce"></textarea> <br> 籍贯: <select v-model="user.pro"> <option value="1">黑</option> <option value="2">吉</option> <option value="3">辽</option> <option value="4">京</option> <option value="5">津</option> <option value="6">冀</option> </select> <br> <button @click="login()">登录</button> <button @click="clearx()">重置</button> <hr> 显示爱好:{{ hbs }} <hr> 显示用户信息:{{ user }} </div> </template> <style scoped> </style>
新( Vue3.4+ ):
defineModel() 这个宏可以用来声明一个双向绑定 prop,通过父组件的 v-model 来使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const model = defineModel ()const model = defineModel ({ type : String })model.value = "hello" const count = defineModel ("count" )const count = defineModel ("count" , { type : Number , default : 0 })function inc ( ) { count.value ++ }
属性计算 computed 通过方法返回数据 每使用一次 执行一次
通过计算属性获得数据 每次使用是,如果和上次使用时,数据没有变化,则直接使用上一次的结果(缓存)
推荐使用计算属性 来描述依赖响应式状态的复杂逻辑
1 2 3 4 5 6 7 8 9 import { reactive,computed} from 'vue' const publishedBooksMessage = computed (() => { console .log ("publishedBooksMessage" ) return author.books .length > 0 ? 'Yes' : 'No' })
1 2 3 4 5 6 7 8 9 10 let fullName = computed ({ get ( ) { return firstName.value +'-' + lastName.value }, set (newValue ) { const [first,last] = newValue.split ('-' ) firstName.value = first lastName.value = last } })
数据监听器 watch
更改 DOM,或是根据异步操作的结果去修改另一处的状态。我们可以使用 watch 函数 在每次响应式状态发生变化时触发回调函数:
watch主要用于以下场景:
当数据发生变化时需要执行相应的操作
监听数据变化,当满足一定条件时触发相应操作
在异步操作前或操作后需要执行相应的操作
1 2 3 4 5 6 7 8 const stopWatch = watch (fullName, (newValue, oldValue ) => { console .log ('fullName' , oldValue,'变成了' ,newValue) if (newValue === '666' ) { stopWatch () } },{deep :true ,immediate :true })
newValue,oldValue 所指向的是对象的地址 所以如果只是对象属性值的变化 newValue===oldValue
1 2 3 4 watch (()=> person.name ,(value )=> { console .log ('name变化了: ' ,value) },{deep :true })
监视多个数据用数组包裹 略
1 2 3 4 5 6 7 8 watchEffect (()=> { console .log (firstname.value ) console .log (lastname.name ) fullname.value =`${firstname.value} ${lastname.name} ` })
组件(SFC)拼接页面 组件拼接 创建vue项目 安装依赖 在components目录下写需要引进的vue文件 在App.vue 引入
其中需要引进的vue文件 的css样式如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 .header { height : 80px ; border : 1px solid red; } .navigator { width : 15% ; height : 800px ; display : inline-block; border : 1px blue solid; float : left;//向左浮动 } .content { width : 83% ; height : 800px ; display : inline-block; border : 1px goldenrod solid; float : right;// }
父子 兄弟传递 参数(数据) 略 见 下文Pinia
路由 router 定义:路由就是根据不同的 URL 地址展示不同的内容或页面。(选择展示的vue组件的切换)
单页应用程序(SPA)中,路由可以实现不同视图之间的无刷新切换,提升用户体验;
路由还可以实现页面的认证和权限控制,保护用户的隐私和安全;
路由还可以利用浏览器的前进与后退,帮助用户更好地回到之前访问过的页面。
需要被路由的页面组件一般放在view或pages 目录下
路由组件 放在router 目录下 默认起名 index.ts
其他一般路由 放在components 目录下
History 路由模式 vs Hash 模式
Hash 模式 :URL 形如 http://example.com/#/path,# 后的变化不会触发页面刷新,服务器始终收到 /#/path 前的部分(如 /),因此无需特殊配置。
History 模式 :URL 形如 http://example.com/path,更简洁。但用户直接访问或刷新时,浏览器会向服务器请求 /path,如果服务器未配置处理逻辑 (
‘配置见上一篇笔记 Nginx重定向 ‘
),会返回 404 (因为 /path 不是真实存在的文件或目录)。
2.App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 <template> <h1>这是测试路由的主页</h1> <!-- 导航栏 --> <div class="nav"> <router-link to="/" class="active">首页</router-link> <router-link to="/help">帮助</router-link> <router-link to="/news">新闻1</router-link> </div> <div class="content"> <router-view></router-view> </div> </template>
3.src/router/index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const router = createRouter ({ history : createWebHistory (), routes : [ { name :'home' , path : '/' , component : Home }, { name :'help' , path : '/help' , component : Help }, {name :'news' , path :'/news' , component :News , children :[ {name :'news-detail' , path :'detail/:id/:title/:content' , component :NewsDetail } ]} {name :'news-detail' , path :'detail' , component :NewsDetail } ] }) export default router;
4.main.ts
1 2 3 4 5 6 7 8 9 import { createApp } from 'vue' import './style.css' import App from './App.vue' import router from './router' const app = createApp (App )app.mount ('#app' ) app.use (router)
5.News.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <script setup lang="ts"> import { reactive } from 'vue'; let newsList = reactive([ {id:1,title:'新闻1',content:'新闻1内容'}, {id:2,title:'新闻2',content:'新闻2内容'}, {id:3,title:'新闻3',content:'新闻3内容'}, {id:4,title:'新闻4',content:'新闻4内容'}, {id:5,title:'新闻5',content:'新闻5内容'} ]) </script> <template> <div class="news">News <ul> <li v-for="item in newsList" :key="item.id"> <!-- 这里可修改query或params --> <router-link :to="{name:'news-detail', query:{id:item.id, title:item.title, content:item.content}}">{{item.title}}</router-link> </li> </ul> </div> <div class="news-content"> <router-view ></router-view> </div> </template>
6.NewsDetail.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script setup lang="ts"> import { useRoute } from 'vue-router'; let route = useRoute(); </script> <template> <ul class="news-detail"> <!-- 这里可修改query或params --> <li>编号: {{route.query.id}}</li> <li>标题:{{ route.query.title }}</li> <li>内容:{{ route.query.content }}</li> </ul> </template>
路由Props 第一种写法 params传参写法
index.ts:
1 {name :'news-detail' , path :'detail/:id/:title/:content' , component :NewsDetail ,props :true }
NewsDetail.vue:
1 2 3 4 5 6 7 8 9 10 11 12 <script setup lang="ts"> defineProps(['id','title','content']) </script> <template> <ul class="news-detail"> <li>编号: {{id}}</li> <li>标题:{{ title }}</li> <li>内容:{{ content }}</li> </ul> </template>
第二种函数写法 自己决定返回什么
index.ts:
1 2 {name :'news-detail' , path :'detail' , component :NewsDetail ,props (route ) {return route.query }}
NewsDetail.vue同上。记得在父级展示路由的组件里改变params/query传参模式
router-link 加上replace可以覆盖当前纪录 不能回退 默认push 编程式路由(useRouter) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <script setup type ="module" > import {useRouter} from 'vue-router' import {ref} from 'vue' let router = useRouter () let routePath =ref ('' ) let showList = ( )=>{ router.push ({path :'/list' }) } </script > <template > <div > <button @click ="showList()" > showList</button > <br > </div > </template > <style scoped > </style >
回调函数 一些特殊的函数,表示未来才会执行的一些功能,后续代码不会等待该函数执行完毕就开始执行了
1 2 3 4 5 6 7 8 setTimeout (()=> { console .log ("setTimeout invoked" ) },2000 ) setInterval (()=> { console .log ("setInterval invoked" ) },2000 )
Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件 (通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
async和await
async 用于标识函数的
async标识函数后,async函数的返回值会变成一个promise对象
如果函数内部返回的数据是一个非promise对象,async函数的结果会返回一个成功状态 promise对象
如果函数内部返回的是一个promise对象,则async函数返回的状态与结果由该对象决定
如果函数内部抛出的是一个异常,则async函数返回的是一个失败的promise对象
1 2 3 4 5 6 7 8 async function fun1 ( ){ let promise = Promise .reject ("heihei" ) return promise } let promise =fun1 ()
await
await右侧的表达式一般为一个promise对象,但是也可以是一个其他值
如果表达式是promise对象,await返回的是promise成功的值
await会等右边的promise对象执行结束,然后再获取结果,后续代码也会等待await的执行
如果表达式是其他值,则直接返回该值
await必须在async函数中,但是async函数中可以没有await
如果await右边的promise失败了,就会抛出异常,需要通过 try … catch捕获处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 async function fun1 ( ){ return 10 } async function fun2 ( ){ try { let res = await fun1 () }catch (e){ console .log ("catch got:" +e) } console .log ("await got:" +res) } fun2 ()
Axios
Axios 是一个基于 promise 网络请求库,作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。它有如下特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <script setup> import { onMounted,ref } from 'vue' import axios from 'axios' let message = ref('') function getMessage() { axios({ method: 'get', url: 'https://api.uomg.com/api/rand.qinghua?format=json', data: { } }).then(function (response) { console.log(response) console.log(response.data) console.log(response.data.content) message.value = response.data.content }).catch(function (error) { console.log(error) }) } // 通过onMounted生命周期函数,在页面加载时,调用getMessage方法 // onMounted(() => { // getMessage() // }) </script> <template> <div> <h1 v-text="message"></h1> <button @click="getMessage()">变</button> </div> </template> <style scoped> </style>
简写:
1 2 3 4 5 6 7 function getWords ( ){ return axios.get ("https://api.uomg.com/api/rand.qinghua?format=json" ) } async function getMessage ( ){ let {data} = await getWords () Object .assign (message,data) }
Axios 拦截器 如果想在axios发送请求之前,或者是数据响应回来在执行then方法之前做一些额外的工作,可以通过拦截器完成
定义src/axios.js提取拦截器和配置语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import axios from 'axios' const instance = axios.create ({ baseURL :'https://api.uomg.com' , timeout :10000 }) instance.interceptors .request .use ( config => { console .log ("before request" ) config.headers .Accept = 'application/json, text/plain, text/html,*/*' return config }, error => { console .log ("request error" ) return Promise .reject (error) } ) instance.interceptors .response .use ( response => { console .log ("after success response" ) console .log (response) return response }, error => { console .log ("after fail response" ) console .log (error) return Promise .reject (error) } ) export default instance
跨域 同源策略(Sameoriginpolicy)是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号
产生跨域的原因 前后端分离模式下,客户端请求前端服务器获取视图资源,然后客户端自行向后端服务器获取数据资源,前端服务器的 协议,IP和端口和后端服务器很可能是不一样的,这样就产生了跨域
解决方案 代理或使用过滤器
未来我们使用框架,直接用一个@CrossOrigin 就可以解决跨域问题了
现在需手动添加: 后端/filter/CrosFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.atguigu.schedule.filter;import com.atguigu.schedule.common.Result;import com.atguigu.schedule.util.WebUtil;import jakarta.servlet.*;import jakarta.servlet.annotation.WebFilter;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;@WebFilter("/*") public class CrosFilter implements Filter { @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; System.out.println(request.getMethod()); HttpServletResponse response = (HttpServletResponse) servletResponse; response.setHeader("Access-Control-Allow-Origin" , "*" ); response.setHeader("Access-Control-Allow-Methods" , "POST, GET, PUT,OPTIONS, DELETE, HEAD" ); response.setHeader("Access-Control-Max-Age" , "3600" ); response.setHeader("Access-Control-Allow-Headers" , "access-control-allow-origin, authority, content-type, version-info, X-Requested-With" ); if (request.getMethod().equalsIgnoreCase("OPTIONS" )){ WebUtil.writeJson(response, Result.ok(null )); }else { filterChain.doFilter(servletRequest, servletResponse); } } }
Pinia
集中式状态(数据)管理
当我们有多个组件共享一个共同的状态(数据源)时,多个视图可能都依赖于同一份状态。来自不同视图的交互也可能需要更改同一份状态。虽然我们的手动状态管理解决方案(props,组件间通信,模块化)在简单的场景中已经足够了,但是在大规模的生产应用中还有很多其他事项需要考虑:
更强的团队协作约定
与 Vue DevTools 集成,包括时间轴、组件内部审查和时间旅行调试
模块热更新 (HMR)
服务端渲染支持
Pinia 就是一个实现了上述需求的状态管理库 ,由 Vue 核心团队维护,对 Vue 2 和 Vue 3 都可用。https://pinia.vuejs.org/zh/introduction.html
main.ts 引入并使用
1 2 3 4 import {createPinia} from 'pinia' let pinia = createPinia ()... app.use (pinia)
定义pinia store对象 src/store/store.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import {defineStore } from 'pinia' export const definedPerson = defineStore ( { id : 'personPinia' , state :()=> { return { username :'张三' , age :0 , hobbies :['唱歌' ,'跳舞' ] } }, getters :{ getHobbiesCount ( ){ return this .hobbies .length }, getAge ( ){ return this .age } }, actions :{ doubleAge ( ){ this .age =this .age *2 } } } )
(要操作pinia的vue).vue 中导入pinia定义的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <script setup type="module"> import { ref} from 'vue'; import { definedPerson} from '../store/store'; // 读取存储的数据 let person= definedPerson() //订阅 store 的状态变化 本次修改的信息 谁 和 修改后的数据 是什么 peron.$subscribe((mutate,state)=>{ consolg.log('peron数据发生了变化',mutate,state) }) let hobby = ref('') </script> <template> <div> <h1>operate视图,用户操作Pinia中的数据</h1> 请输入姓名:<input type="text" v-model="person.username"> <br> 请输入年龄:<input type="text" v-model="person.age"> <br> 请增加爱好: <input type="checkbox" value="吃饭" v-model="person.hobbies"> 吃饭 <input type="checkbox" value="睡觉" v-model="person.hobbies"> 睡觉 <input type="checkbox" value="打豆豆" v-model="person.hobbies"> 打豆豆 <br> <!-- 事件中调用person的doubleAge()方法 --> <button @click="person.doubleAge()">年龄加倍</button> <br> <!-- 事件中调用pinia提供的$reset()方法恢复数据的默认值 --> <button @click="person.$reset()">恢复默认值</button> <br> <!-- 事件中调用$patch方法一次性修改多个属性值 --> <button @click="person.$patch({username:'奥特曼',age:100,hobbies:['晒太阳','打怪兽']})">变身奥特曼</button> <br> 显示pinia中的person数据:{{person}} </div> </template> <style scoped> </style>
(要展示pinna的vue).vue 展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script setup type="module"> import { definedPerson} from '../store/store'; // 读取存储的数据 let person= definedPerson() </script> <template> <div> <h1>List页面,展示Pinia中的数据</h1> 读取姓名:{{person.username}} <br> 读取年龄:{{person.age}} <br> 通过get年龄:{{person.getAge}} <br> 爱好数量:{{person.getHobbiesCount}} <br> 所有的爱好: <ul> <li v-for='(hobby,index) in person.hobbies' :key="index" v-text="hobby"></li> </ul> </div> </template> <style scoped> </style>
另:
要解构store的数据,不用toRefs(会把所有的数据和方法都转成响应式),应引入storeToRefs (只把数据转成响应式)。
还可以用组合式写法 参考上面的setup 不过需要主动返回使用的方法、数据
自定义事件 用自定义事件实现数据 子传父
用mitt实现组件通信
在utils/tools文件夹下 创建emitter.ts :
1 2 3 4 import mitt from 'mitt' const emitter = mitt ()export default emitter
main.ts 引入:
1 import emitter from '@/utils/emitter'
其他方法
off:解绑事件
all:查看所有事件
建议在组件卸载(onUnmounted )时解绑对应事件
用依赖注入进行父传后代组件通信 provide,inject
插槽 默认插槽
具名插槽 v-slot:s1可以写成#
作用域插槽
其他API teleport 让组件传送到指定的容器/选择器下
Suspense 在异步请求完成之前先用别的组件作占位符
全局API
Element-plus Element Plus 是一套基于 Vue 3 的开源 UI 组件库,是由饿了么前端团队开发的升级版本 Element UI。Element Plus 提供了丰富的 UI 组件、易于使用的 API 接口和灵活的主题定制功能,可以帮助开发者快速构建高质量的 Web 应用程序。
官网https://element-plus.gitee.io/zh-CN/
1 npm install element-plus
选vue+TypeScript
main.ts
1 2 3 4 import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' ... app.use (ElementPlus )
Apache ECharts ApacheECharts是一款基于Javascript的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。 官网地址:https://echarts.apache.org/zh/index.html