根据自己的vue开发经验,自己总结出一些比较基础的知识点,加上自己的一些理解,希望大佬们在阅读后可以进行指正或提问
vue的优缺点
优点
- 组件化开发,提升效率,方便复用,便于协同开发
- 单页面路由
- 易于结合其他的第三方库
- 丰富的api方法
- 轻量高效,虚拟DOM
- MVVM,数据驱动视图
- 轻量级的框架
缺点
- 缺少高阶教程和文档
- 生态环境不如angular和react
- 社区不大
- 不支持ES6的浏览器无法使用,如ie10
- 报错不明显,适合单人开发或者中小型项目
- 不利于SEO优化
创建项目
vue create myapp
选择Manually select features(自定义配置)
选择Router(后面要使用,也可以先不安装,后续安装)
选择3.x的版本
剩下的一直回车,等待创建
一些常用的指令
模板语法
使用双花括号( {{}} )对变量输出,内部可以写简单的表达式用于对数据的处理
一个花括号中只能输出一个变量
v-text
相当于js的innerText
v-html
相当于js的innerHTML
v-bind
动态绑定属性,简写是冒号( : )
绑定class
操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 class 和 style 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。
对象语法
<div :class="{ active: isActive }"></div>
data() { return { isActive:true }; },
数组语法
<div :class="[activeClass, errorClass]"></div>
data() { return { activeClass: "active", errorClass: "text-danger", }; },
绑定内联样式
:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data() { return { activeColor: "red", fontSize: 30, }; },
直接绑定到一个样式对象通常更好,这会让模板更清晰:
<div :style="styleObject"></div>
data() { return { styleObject: { color: "red", fontSize: "13px", }, }; },
v-on
绑定事件,简写是@
v-model
可以用 v-model 指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定
v-model 会忽略所有表单元素的 value、checked、selected attribute的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用 value property 和 input 事件;
- checkbox 和 radio 使用 checked property 和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件。
v-if v-else-if v-else
v-if:条件性地渲染一块内容,这块内容只会在指令的表达式返回 truthy[1] 值的时候被渲染。
v-else、v-else-if:是跟js中的if一样,v-if的else块和else-if块
[1]truthy(真值)指的是在布尔值上下文中,转换后的值为真的值。所有值都是真值,除非它们被定义为 假值(即除 false、0、""、null、undefined 和 NaN 以外皆为真值)
v-show
根据条件展示元素, 不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS property display。
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
v-for
染数组列表,v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名
v-for 还支持一个可选的第二个参数,即当前项的索引。
为了高效的渲染,需要配合key,key的作用
v-pre
原样输出,不解析模板标签{{}}
v-cloak
当网络较慢,网页还在加载 Vue.js ,而导致 Vue 来不及渲染,这时页面就会显示出 Vue 源代码。我们可以使用 v-cloak 指令来解决这一问题。在简单项目中,使用 v-cloak 指令是解决屏幕闪动的好方法。但在大型、工程化的项目中(webpack、vue-router)只有一个空的 div 元素,元素中的内容是通过路由挂载来实现的,这时我们就不需要用到 v-cloak 指令咯。
当内部加载完,被渲染后,vue会自动删除v-cloak
<style> [v-cloak]{ display:none; } </style>
v-once
只渲染元素和组件一次,随后的渲染,使用了此指令的元素/组件及其所有的子节点,都会当作静态内容并跳过,这可以用于优化更新性能。
路由配置
在 router/index.js文件中进行配置
import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' const routes = [ { path: '/', name: 'home', component: HomeView }, { path: '/about', name: 'about', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: function () { return import(/* webpackChunkName: "about" */ '../views/AboutView.vue') } } ] const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router
页面跳转及传参
标签写法
使用<router-link>跳转,<router-link> 默认会被渲染成一个 `<a>` 标签
1. <router-link to="/btn">通过to指定链接</router-link>
2. <router-link :to="{ path:'/btn'} ">根据path跳转</router-link>
3. <router-link :to="{ name:'user'} ">根据name跳转</router-link>
编程式写法
router.go()、router.push()、router.replace()
router.go()类似于js的history.go(),0刷新,正数前进,负数后退
router.push()匹配某个路由,写法与标签式相似
1. this.$router.push('/')
2. this.$router.push({path:'/'})
3. this.$router.push({name:'home'})
传参及接收参数
传参使用router,接收参数使用route,route包含路由信息
传参有两种方式,params和query,query会将参数显示在地址栏
path跳转只能使用query传参,name跳转都可以
写法
1. path跳,query传
2. name跳,query传
3. name跳,params传
改成hash路由模式
import { createRouter, createWebHashHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' const routes = [ { path: '/', name: 'home', component: HomeView }, { path: '/about', name: 'about', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: function () { return import(/* webpackChunkName: "about" */ '../views/AboutView.vue') } } ] const router = createRouter({ history: createWebHashHistory(process.env.BASE_URL), routes }) export default router
路由导航守卫
直白的说,导航守卫就是路由跳转过程中的一些钩子函数,这些函数能让你在跳转过程中操作一些其他的事儿的时机,这就是导航守卫。
比如最常见的登录权限验证,当用户满足条件时,才让其进入导航,否则就取消跳转,并跳到登录页面让其登录。此时可以使用路由导航来实现。
导航守卫有三种:全局的, 单个路由独享的, 组件级的。
单个路由的
写在路由配置中
beforeEnter 有三个参数,to,from,next
全局的
在router/index.js中
beforeEach 使用 router.beforeEach注册一个全局前置守卫,有三个参数,to,from,next
beforeResolve 用 router.beforeResolve注册一个全局解析守卫,在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用
afterEach 全局后置钩子,它没有next 参数,也不会改变导航本身,因为此时导航已经完成。
组件内
作为属性写在组件内
beforeRouteEnter 此时组件实例还没被创建,不能获取组件实例this
beforeRouteUpdate 在当前路由改变,但是该组件被复用时调用,可以访问组件实例this
beforeRouteLeave 导航离开该组件的对应路由时调用,可以访问组件实例this
全局组件
在 app.vue 中的文件类型可能是这样的
<template> <nav> <router-link to="/">Home</router-link> | <router-link to="/about">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>
这是路由出口
所以说,在 template 模板中,我们可以在路由出口外写入html标签进行全局组件编写,在想要显示局部组件的容器中添加路由出口即可。
全局组件与路由
在路由页面中定义 meta 属性:
{ path: '/', name: 'home', meta:{ isShow:false, }, component: HomeView },
meta的定义:简单来说就是路由元信息,也就是每个路由身上携带的信息。
meta的作用:vue-router路由元信息说白了就是通过meta对象中的一些属性来判断当前路由是否需要进一步处理,如果需要处理,按照自己想要的效果进行处理即可
那么根据这一作用我们可以在 app.vue 中进行判断:
<template> <nav v-if="$route.meta.isShow == true"> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> </nav> <router-view/> </template>
这样有些组件的路由只要定义了这个属性为true,就可以在那个组件页面中看到全局组件啦!
配置代理
1、在项目框架的根目录下新建文件:vue.config.js
2、重启项目,这样的话新建的文件就可以被识别了,一定要重启
3、给新建的文件内添加解决跨域的代码部分
module.exports = { devServer: { proxy: { '/api': { target: 'https://域名/api',// API服务器的地址 ws: true, //代理websockets changeOrigin: true, // 虚拟的站点需要更管origin pathRewrite: { //重写路径 比如'/api/aaa/ccc'重写为'/aaa/ccc' '^/api': '' } } } } }
数据请求
npm install axios
引用axios:在需要使用的页面中引用
import axios from 'axios'
get请求
get请求使用params传参,本文只列举常用参数,更多详见: 使用说明 · Axios中文说明 ·看云 (kancloud.cn)
axios({ url: "", // url params: { // 参数 name: xxx, age: xxx, }, }) .then(function (res) { console.log(res); // 成功回调 }) .catch(function (err) { console.log(err); // 失败回调 });
post请求
qs配置
import qs from 'qs'
方法 qs.parse()、qs.stringify()
qs.stringify()将对象序列化成URL的形式,以&进行拼接
qs.parse()将URL解析成对象的形式
写法
axios({ method: "post", //请求方式 url: "", //url data: qs.stringify({ // 参数 }), }) .then(function (res) { console.log(res); //成功回调 }) .catch(function (err) { console.log(err); //失败回调 });
组件化开发
创建并使用组件
1. 创建.vue文件并编写内容
2. 使用export导出
3. 在要使用该组件的页面使用import导入
4. 在components属性里面注册
5. 在页面上使用标签来完成渲染
父子组件传值
父传子
传:
在"标签"上传属性
<Com :data="message"></Com>
接:
在props中
props:{ data:Array },
子传父
传:
触发,给一个事件传值
this.$emit("get","你好")
接:
绑定传的时候起的事件名,接收参数
<Com :data="message" @get="on"></Com>
Vue的一些小技巧
vue的插槽slot用法
什么是插槽
我们知道,在vue中,引入的子组件标签中间是不允许写内容的。为了解决这个问题,官方引入了插槽(slot)的概念。
插槽,其实就相当于占位符。它在组件中给你的HTML模板占了一个位置,让你来传入一些东西。插槽又分为匿名插槽、具名插槽以及作用域插槽。
你可能不太明白,为什么我要给子组件中传入HTML,而不直接写在子组件中呢?答案是这样的。你可以想象一个场景,你有五个页面,这五个页面中只有一个区域的内容不一样,你会怎么去写这五个页面呢?复制粘贴是一种办法,但在vue中,插槽(slot)是更好的做法。
匿名插槽
匿名插槽,我们又可以叫它单个插槽或者默认插槽。与具名插槽相对,它不需要设置name属性。(它隐藏的name属性为default。)
例子:
文件目录如下,Home组件是HelloWorld的父组件。
- 在HelloWorld中写一个匿名插槽
<template> <div class="hello"> Helloworld组件 <div class = 'slotTxt'> <slot></slot> </div> </div> </template> <script> export default { } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"> .hello{ width:100%; height:300px; background:#ccc; margin-top:50px; .slotTxt{ width:500px; height:200px; margin:30px auto; background:red; } } </style>
- 在Home组件中引入子组件,并在子组件标签中写入内容
<template> <div class="home"> 我是Home父组件 <HelloWorld> <!-- 没有插槽,这里的内容不显示 --> <h1>我是helloworld中的插槽啊</h1> </HelloWorld> </div> </template> <script> import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'home', components: { HelloWorld } } </script>
效果
具名插槽
上面已经说过,插槽有一个name属性。与匿名插槽相对,加了name属性的匿名插槽就是具名插槽。
- HelloWorld组件中写入name属性分别为left和right的插槽
<template> <div class="hello"> Helloworld组件 <div class = 'slotLeft'> <slot name='left'></slot> </div> <div class = 'slotRight'> <slot name='right'></slot> </div> </div> </template> <script> export default { } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"> .hello{ width:700px; height:300px; background:#ccc; margin: 0 auto; margin-top:50px; .slotLeft{ width:300px; height:200px; float:left; background:red; } .slotRight{ width:300px; height:200px; float:right; background:pink; } } </style>
- Home组件通过在template上写v-slot:name来使用具名插槽
<template> <div class="home"> 我是Home父组件 <HelloWorld> <template v-slot:left> <h1>name属性为left</h1> </template> <template v-slot:right> <h1>name属性为right</h1> </template> </HelloWorld> </div> </template> <script> import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'home', components: { HelloWorld } } </script> <style lang="less" scoped> .home{ width:900px; margin:0 auto; background:yellow; padding-bottom:100px; } </style>
注意 v-slot 只能添加在template标签上 (只有一种例外情况)。
效果
- 例外情况(被废弃的slot=‘name’)
带slot属性的具名插槽自 2.6.0 起被废弃,vue3.x被完全废弃。只有vue3之前的cli可以使用。
<template> <div class="home"> 我是Home父组件 <HelloWorld> <h1 slot='left'>name属性为left</h1> <h1 slot='right'>name属性为right</h1> </HelloWorld> </div> </template> <script> import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'home', components: { HelloWorld } } </script> <style lang="less" scoped> .home{ width:900px; margin:0 auto; background:yellow; padding-bottom:100px; } </style>
效果同上。
- 具名插槽的小知识点
跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header。
vue3 点击更换验证码图片以及登录不成功后更换验证码
这是基于后端已经生成验证码的情况。
首先点击更换验证码
const imgUrl = ref('你的验证码地址');
先将验证码地址给存起来,方便后续操作
然后是点击验证码更换,dom元素上的写法
<img :src="imgUrl" alt="验证码" class="picture" @click="changeCaptcha($event)">
然后是函数进行更换
// 更换验证码 const changeCaptcha = (event) => { event.target.src = `${imgUrl.value}?time=${new Date().getTime()}`; };
当登录失败自动时更换验证码
document.getElementsByTagName('img')[1].src = `${imgUrl.value}?time=${new Date().getTime()}`
加上时间戳即可!
(PS:必须加时间戳或者后面随机拼接随机数或者字符串)
VUE3 原生JS上传图片(支持多张上传)并实现预览删除功能
<template> <!-- 上传后显示 --> <div id="look"> <div id="look_img" v-for="(item, index) in imgSrc" :key="index"> <img :src="item" alt=""> <div id="look_event"> <img src="https://s1.ax1x.com/2022/08/08/vMEtPO.png" alt="" title="点击查看" @click="look(index)"> <img src="https://s1.ax1x.com/2022/08/08/vMEaxH.png" alt="" title="点击删除" @click="deletes(index)"> </div> </div> <!-- 上传图片按钮 --> <div id="demo" v-show="uploadnum"> <input type="file" id="demo_file" accept="image/png,image/gif,image/jpeg" multiple @change="update($event)"> <img src="https://s1.ax1x.com/2022/08/08/vMEwMd.png" alt="" id="demo_img"> </div> </div> <!-- 图片预览 --> <div id="preview" v-if="show" @click="() => { show = false }"> <div id="preview_close"> <img src="https://s1.ax1x.com/2022/08/08/vMEURe.png" alt="" title="关闭" @click="() => { show = false }"> </div> <div id="preview_last" v-if="pvwWhere != 0" @click.stop="previewLast()"> <img src="https://s1.ax1x.com/2022/08/08/vMEBqI.png" alt="" title="上一张"> </div> <div id="preview_next" v-if="pvwWhere != imgSrc.length - 1" @click.stop="previewNext()"> <img src="https://s1.ax1x.com/2022/08/08/vMErZt.png" alt="" title="下一张"> </div> <img :src="pvwSrc" alt=""> </div> </template> <script> import { ref } from "vue"; import { reactive } from "vue"; export default { setup() { const imgSrc = ref([]);//已上传图片数组 const arrLength = ref(9);//上传图片数量 const uploadnum = ref(true);//控制上传按钮的显示隐藏 const show = ref(false);//控制预览图片遮罩层显示隐藏 const pvwSrc = ref(null);//预览图片地址 const pvwWhere = ref(0);//选择哪一张进行预览以及控制上一张下一张 const update = (e) => { let file = e.target.files; let filesLength = arrLength.value - imgSrc.value.length; for (let i = 0; i < filesLength; i++) { let img = new FileReader(); img.readAsDataURL(file[i]); img.onload = ({ target }) => { imgSrc.value.push(target.result); //将img转化为二进制数据 panduan(); }; }; }; //判断照片数量是否满足规定数量;满足则隐藏上传按钮 const panduan = () => { if (imgSrc.value.length >= arrLength.value) { uploadnum.value = false; } else { uploadnum.value = true; } }; panduan(); //删除图片 const deletes = (i) => { imgSrc.value.splice(i, 1); panduan(); }; //图片预览 const look = (i) => { console.log(imgSrc.value); pvwWhere.value = i; show.value = true; pvwSrc.value = imgSrc.value[pvwWhere.value] }; //预览:上一张功能 const previewLast = () => { pvwWhere.value--; pvwSrc.value = imgSrc.value[pvwWhere.value] }; //预览:下一张功能 const previewNext = () => { pvwWhere.value++; pvwSrc.value = imgSrc.value[pvwWhere.value] } return { update, imgSrc, arrLength, uploadnum, deletes, look, show, pvwSrc, pvwWhere, previewLast, previewNext, } } } </script> <style> #demo { width: 20vh; height: 20vh; position: relative; border: 3px dashed #dcdcdc; display: flex; justify-content: center; align-items: center; margin-left: 1em; margin-top: 1em; } #demo_file { opacity: 0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; cursor: pointer; } #demo_img { display: block; width: 50%; height: 50%; } #look { width: 70vh; display: flex; flex-wrap: wrap; justify-content: left; align-items: center; } #look_img { width: 20vh; height: 20vh; margin-left: 1em; margin-top: 1em; display: flex; justify-content: space-around; } #look_img img { display: block; width: 20vh; height: 20vh; cursor: pointer; } #look_event { background: rgba(0, 0, 0, 0.6); width: 20vh; height: 0px; position: absolute; transition: 1s; display: flex; justify-content: center; align-items: center; } #look_event img { display: block; width: 2em; height: 0em; cursor: pointer; } #look_img:hover #look_event { height: 20vh; /* opacity: 50%; */ } #look_img:hover #look_event>img { height: 2em; } #preview { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: center; } #preview img { width: 40%; } #preview_close { position: absolute; top: 4vh; right: 0; display: flex; justify-content: center; } #preview_last { position: absolute; left: 0; top: 50%; display: flex; justify-content: center; } #preview_next { position: absolute; right: 0; top: 50%; display: flex; justify-content: center; } </style>
打包项目
npm run build
然后将生成的 dist 文件上传至服务器即可