一种渐进式的JS框架,所谓渐进式,就是只提供了最基础和最核心的代码,后续我们需要什么可以再子行扩展。
官网:https://cn.vuejs.org/
Vue环境搭建
1.脚手架搭建(用命令搭建一个空的vue项目)
2.在html中引入vue.js(也就是CDN方式)
差值表达式
{{js表达式}}这样的式子就叫差值表达式(mustahce)
含有插值表达式的html叫模板(template)
最基础的vue代码模板
<script src="path/vue.global.js"></script>
<body>
<div class="box">
{{str}}
</div>
</body>
let vm = Vue.createApp({
data(){
return {
str: 'Hello World!'
}
}
}).mount('.box')
其中数据放到data中,并return,会返回一个Proxy(代理)对象,所谓代理,也就是对data中的数据进行了监听,能够实时知道对数据的操作,也就是成了一种响应式数据。
mount('.box')表示把创建的vm实例挂载到页面上对应选择器为.box的结点上
MVVM
M:model 模型---数据
V:view 视图---渲染数据
VM:viewmodel 视图模型---数据驱动
Vue的两大核心是数据驱动和组件系统
指令(directive)
组件(component)
路由(router)
vuex
第三方库
Vue常用的8个指令
v-text:语法----- <p v-text="str"></p>
作用:和{{插值表达式一样}}
v-html: 语法----- <p v-html="str"></p>
与v-text的区别在于v-html可以解析文本中的html标签
v-bind:语法----- <p v-bind:attribute="str"></p>
作用:用于把变量绑定到指定的属性上,属于单项绑定,数据变化,视图就会变化,视图变化,数据不会变化
该指令可以简写为 :attribute="str"
v-model:语法----- <input type="text" v-model="str"/>
作用:双向绑定,视图变化,数据也会变化,不需要指定特定属性,会自动找寻会变化的属性。
该指令有三个修饰符:
lazy:惰性,视图变化时,数据不会立马跟着变化,只有当失去焦点时数据才会变化
number:把输入的数据转化为数字
trim:去除输入字符串两边的空白
v-on:语法----- <button v-on:click="clickHandler($event, ...arg)"></button>
作用:给对应的标签添加事件,其中$event表示事件对象,...arg表示传递的参数,对应的事件处理函数放在methods里面。
methods: { clickHandler(e, ...arg){} }//methods是放在data的外面的。
该指令可以简写为@click="clickHandler($event, ...arg)"
v-if和v-show:语法——<p v-if="BlooeanVar"></p>,<p v-show="BlooeanVar"></p>
两个指令都是控制某一块视图是否可见。当BlooeanVar的值为true时,才会渲染p标签的内容
v-if是惰性指令,条件渲染(条件为true时会创建p标签,为false时会删除p标签)
v-show只是切换p标签的css样式(条件为true时为display="block",为false时为display)
v-for:语法——<p v-for="item in list" :key="item.id"></p>
其中key可以提高循环的执行效率
绑定样式
绑定style:
语法——<p v-bind:style="{属性:属性值}"></p>或者<p v-bind:style="[styleObj1, styleObj2]"></p>
绑定css:
语法——<p v-bind:class="{class1: isClass1}"></p>或者<p :class="[{class1: isClass1}, {class2: isClass2}]"></p>
v-on修饰符
stop: 用法——v-on:事件.stop,阻止冒泡行为
prevent: 用法——@事件.prevent,阻止默认行为
enter: 用法——@keyup.enter或者@keydown.enter,按下回车事件
self:用法——@事件.self,当前元素只触发它自己绑定的事件
capture:用法——@事件.capture,捕获事件
once:用法——@事件.once,这事件只触发一次
v-for遍历字符串,对象,数字
遍历数字:<p v-for="n in num">{{n}}</p>,遍历数字是从1开始遍历到num
遍历字符串:<p v-for="char in str">{{char}}</p>,从左往右依次遍历字符串中的每一个字符
遍历对象:<p v-for="(value, key, index) in obj"></p>依次遍历对象的每一个键值对,注意顺序(值,键,下标(从0开始))
计算属性:
computed: 用法——
computed: {
value(){
return 表达式(计算式)
}
}
作用:将表达式或者计算式的结果返回给value变量,value变量的值会跟随表达式的变化自动变化。
computed的高级用法:
computed: {
value: {
get(){
return 表达式
},
set(v){
这里可给data中的变量进行赋值操作,比如this.str='23'
}
}
},
案例:基于computed实现全选和全不选功能
computed: {
get(){
return this.arr.every(item => item.flag)//这里flag用于绑定渲染列表中的checkbox
},
set(v){
this.arr.forEach(item => item.flag = v)//这里的v接收的是全选的checkbox的checked值
}
}
组件(component)
组件就是指可以复用的页面的一部分(本身就是对DOM元素的一种封装)
组件分为全局组件和局部组件
全局组件定义实例代码:
let app = Vue.createApp({
data(){
return {}
}
}).mount('.box')
//app.component('全局组件名',全局组件的内容(obj)),
app.component('MyComponent', {
template: '<p>123</p>'
})
//接下来只需要在要需要使用组件的地方,插入组件即可,但是插入组件的时候需要注意,在html中插入标签时,需要用短横线连接两个单词,并且除第一个单词以外的单词需要小写
局部组件示例
const ChildComponent={
template=`<p>child</p>`,
data(){
return{}
},
methods: {}
}
在实例中注册子组件
Vue.create({
components: {
ChildComponent
},
data(){
return{}
}
})
//从语法定义上来说,全局组件不需要注册就可以使用,而局部组件需要先注册才能使用
//全局组件可以在整个实例(应用)的任何位置(包括其子组件)使用,而局部组件只能在这个实例中使用,不能在其它组件中使用。
//组件和一个实例一样,也可以有自己的data,methods等等属性
组件通讯
父组件给子组件传值,示例:
<Child :data="parentData"></Child>
在子组件内部,还需要用props接收一下父组件传递的值
props: ['data'],需要和自定义属性名一样,
也可以用对象来接收
props: {
data: {
type: Number, //指定了接收的数据的类型
default: 213, //如果没有传递值时,使用的默认值
validator(value){
return value满足的条件 //规定了传递的值必须满足的约束条件
},
required: true //是否是必须传递值(这个属性通常不予default连用)
}
}
子组件向父组件传值(通过this.$emit('evt', value))
示例:<Child @click="sendData"></Child>
sendData(){
this.$emit('evt', '123')
},
父组件通过子组件的自定义事件接收子组件传递的值
<Child @evt="receive($event)" @click="sendData"></Child>
在父组件的methods中,通过evt方法接收子组件传递的值
receive(value){
console.log(value)
}
//说明,自定义事件的名字是自己取的,只要保持一致即可,同理接收子组件传值的方法名也是自己取的,前后保持一致即可。组件之间的传值,通过$event进行传递
watch: 监听数据变化——语法,写在methods外面:
watch: {
data1: (newValue, oldValue){
//当监听的组件里的data1有变化时,就会执行下面的代码
console.log(newValue,oldValue)
//如果data1是一个对象类型数据(引用类型),并且改变的是该对象内的内容时
//需要特别声明deep: true,表示深度监听,并且通过hanlder监听对象数据变化
//示例
hanlder(newV){
console.log(newV)
}
deep: true,
immediate: true//表示运行就开始监听,(即运行就执行一次代码块)
//如果是直接对整个对象进行赋值,也就是改变了对象的地址,那么就和普通数据监听一样即可
}
}
computed与watch的区别:
computed可以缓存计算过的数据,而watch则不能
watch可以监听异步数据,但是computed不能
Vue中的8个钩子函数
beforecreate(){console.log('beforeCreate')}//即拿不到数据,也拿不到结点
//特别说明,在beforeCreate中,可以通过this.$nextTick(()=>{console.log(str)}),来拿到数据,nextTick表示在下论循环执行,在这里也就是整个DOM树构建好之后才执行。所以是可以拿到数据的。
created(){console.log('created')}//拿得到数据,但是拿不到结点
beforeMount(){consolo.log('beforeMount')}//可以拿到数据,但是拿不到结点
mounted(){console.log('mounted'), console.log(this.$refs.box)}//即可以拿到数据,又可以拿到结点
//特别说明,在Vue3中,可以通过在组件中绑定ref来获取结点,示例:
<My-component ref="box"></My-component>
在父组件中就可以通过this.$refs.box获取MyComponent组件结点
beforeUpdate(){console.log('beforeUpdate')}//数据更新之后,视频更新之前执行
updated(){console.log('updated')}//数据更新之后,视图更新之后执行
beforeUnmount(){console.log('beforeUnmount')}//组件卸载之前执行
unmounted(){console.log('unmounted')}组件卸载之后执行
动态组件
在Vue中有一个标签可以支持动态的替换组件,示例代码如下:
<div class="box">
<component :is="cname"></component>
<button @click="cname='One'">组件1</button>
<button @click="cname='Two'">组件2</button>
</box>
<script>
const One = {template: ''}
const Two = {template: ''}
let app = Vue.CreateAPP({
components: {One, Two},
data(){return {cname: 'One'}}
})
app.mount('.box')
</script>
特别说明:组件的切换是组件的挂载与卸载,所以组件之间的切换并不会保留之前组件的数据,但是可以通过把component放到keep-alive标签中保留之前组件的数据(即组件切换不再是卸载)。
插槽
插槽是组件预留的位置,可以在不改变现有组件的代码的前提下,进行扩展组件,Vue中的插槽有3种,分别是具名插槽,匿名插槽和作用域插槽。
匿名插槽:
const Com = {template: '<div></div><slot></slot>'}
<Com>234</Com>//此时234就会放在slot内,也就是放在组件的div后面
const Com = {template: '<slot></slot><div></div><slot></slot>'}
<Com><p>123</p><p>456</p></Com>//此时的效果是在div的两侧都会插入123,456。并不是左边123,右边456.
具名插槽:
const Com = {template: '<slot name="s1"></slot><div></div><slot name="s2"></slot>'}
<Com><p v-slot:s1>123</p><p v-slot:s2>456</p></Com>//此时的效果就是在div的左侧是123,右侧是456.
v-slot可以简写为#
作用域插槽:
const Com = {template: '<div></div><slot name="s1" :data="str"></slot>'}
<Com><p v-slot:s1="data"></p></Com>
此时插槽的str会通过以对象的形式传递给data
搭建Vue的脚手架环境流程
原来创建一个App实例的代码如下:
const App = Vue.create({
data(){
retutn {str: 'hello'}
}
}).mount('.box')
//在此基础上,引入ES module(ES6的模块化)
可以看到括号里的是一个对象,也就说我们可以把这个对象拿出来。即
const app = {data(){return {str: 'hello'}}}
在通过Vue的create方法创建一个示例。即
const App = Vue.create(app),App.mount('.box')。此时的我们只是做了一个类似变量替换,整体效果都是一样的。
到这里其实可以发现,根据ES module,我们可以把跟组件也就是app这个对象放到一个模块中去声明并导出,然后在需要的地方导入这个对象并通过create方法创建根组件即可。也就是可以写成下面的代码方式。
import App = from './path/App.js'
Vue.create(App).mount('.box'),其中在我们的App.js中的代码如下:
const App = {data(){return {str: 'hello'}}}
对于非根组件,写法都是一样,只不过我们需要在其父组件中进行注册一下。
到这里手动搭建脚手架就可以告一段落。下面介绍通过其它工具搭建脚手架。
Vite
Vite是一款前端构建工具。
首先需要先在项目中下载Vite,新建一个目录。然后在此目录下打开cmd,输入npm init -y,会生成一个package.json文件,路径不要包含中文!!!
创建好json文件后,即输入指令:npm i @vitejs/plugin-vue,只有安装了此插件,才能正常打开vue项目
在项目根目录下创建一个vite.config.js文件,内容如下:
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins:[vue()],
})
在此目录下创建一个index.html,内容如下:
<body>
<div id="box"></div>
<script src="/js/index/js" type="module"></script>
</body>
通过指令搭建脚手架:
npm init vue@latest
cd 项目名
npm i
npm run dev
sass安装指令
npm i sass-embedded
代码格式化工具
Eslint:用于检查项目中的代码是否存在格式问题。
pritter:用于自动格式化代码
setup的使用:
组合式API
import {ref} from 'vue'
setup(){
console.log('setup', this)
const k = ref(0)//只有通过ref进行声明的数据才具有响应式。相当于data里返回的数据。
const obj = reactive({name: 'zs'})//reactive是针对应用类型的数据,访问通过reactive声明的变量时,可以不用加value。
const inc = () => {k.value++}//此时的inc相当于methods里面的方法,并且通过ref声明的变量的值位 于value中,所有需要通过.value来获取。
return {
k, inc//无论是定义的变量还是方法,都需要return才能模板中使用
}
}
ref可以声明引用类型和基本类型的数据,而reactive只能声明引用类型的数据。并且ref声明的数据需要通过value来获取,而reactive声明的数据直接获取即可。
ref的实现原理是vue2中的Object.defineProperty(代理对象的属性),reactive的实现原理是vue3中的proxy(代理对象)
对于这种组合式api的使用,当在setup里面写方法和数据的,方法和数据都需要return。
toRef和toRefs
toRef和toRefs可以用来复制reactive里面的属性并将其转化为响应式的数据,同时保留了引用,也就是当视图发生改变时,reactive的属性值也会发生变化。两者的区别在于toRef只是将单个属性变成响应式的,而toRefs是将所有的属性都变成响应式的。语法如下:
const {x} = toRef(position, 'x'),这里将position对象里面的x属性转化成响应式。使用的方式和正常的ref一样,需要加.value
const {x, y} = toRefs(position),这里将position对象里的所有属性都变成响应式的。
注意:在template中使用toRef时,不需要加.value,而使用toRefs时需要加.value,否则会出现引号。
vue3中父子组件的传值:
父传子:父组件通过自定义属性向子组件传值,子组件通过defineProps(['父组件的自定义属性名'])来接收。
子传父:子组件通过const emit = defineEmits(['自定义事件']),然后emit('自定义事件',数据),向父组件传值,父组件通过子组件自定义的事件进行接收。
父组件获取子组件的属性和方法:子组件通过defineExpose({要暴露的属性和方法}),父组件通过在子组件上绑定ref属性,再通过const ref属性对应的值(组件的标识)=ref(),或者const 变量名 = useTemplateRef('ref标识')来获取子组件以及子组件暴露的属性和方法。
hooks:(vue3中的自定义函数)
hooks就是一种自定义函数,只不过这种自定义函数可以使用vue3中生命周期的各个钩子函数以及可以使用ref和reactive来声明变量。
约定:hooks定义的函数都用use开头,比如useTemplateRef。
路由:(router)
作用:让组件浏览器中的地址栏的地址产生一一对应关系。
路由分为两种:createWebHashHistory和createWebHistory
两种路由方式最明显的区别就是Hash模式中,在域名的后面会跟随这/#,而history模式就是没有/#的。
对于hash模式,服务器端不需要做额外配置,部署即可使用。而history则需要服务器端单独做配置。
因为history模式是直接/路由地址,会让服务器误以为是后端接口,就会出现404的情况。
路由懒加载:语法表现
{
path: '/地址名',
name: '地址名',
conponent: ()=> import('组件路径')
}
路由懒加载就是只有当前需要加载此组件时才会加载,而不是一开始就全部加载。
router和route的区别:
在我们的vue-router中,我们一般把vue-router的实例化对象叫作router,而将router中的routes的某一个对象叫作route,一个route至少包含path和component属性,还包含name,alias,redicret,meta等常用属性,其中alias是给路由的别名,redicret表示重定向,meta用于存储一些变量。
我们可以通过在script中用useRoute()(内置hook),来获取当前的路由对象,也就是当前是路由表的哪一个对象,该对象也包含name,path,component,meta,children等属性。通过children属性,我们可以实现嵌套路由。
我们可以通过在script中用useRouter()(内置hook),来获取当前的路由实例,也就是vue-router的那个实例,该实例主要包含一些方法,该实例的方法可以帮助我们很好的改变当前的路由。常用的方法包括push:向历史记录栈中推入一条路由记录,replace:将当前的路由记录替换成指定的路由记录,go:去到那一条路由,back:回到上一条,forward:去到前一条,go(-1)等价于back,go(1)等价于forward。
Element-Plus
Element-Plus是一款第三方UI组件库,提供了许多第三方组件和模板,可以提高我们的开发效率。
常用的Element-Plus组件:
<el-button></el-button>
ElMessage:弹出一个消息对话框
<el-header></el-header>:头部组件
<el-main></el-main>:内容组件
<el-aside></el-aside>:侧边栏组件
<el-menu></el-menu>:菜单组件
<el-sub-menu></el-sub-menu>:菜单组件的第一级子菜单组件。
<el-meun-item></el-menu-item>:菜单组件中的子菜单组件。
<el-table :data="list"></el-table>:表格组件,我们只需要提供list数据,并指定列对应的属性,就能自动循环生成对应的数据。
<el-table-column prop="id"></el-table-column>:表格中的列组件,通过prop和list中的每一项的id属性对应。
<el-tabs></el-tabs>:选项卡组件,包含type属性(card/border-card)
<el-tab-pane></el-tab-pane>:选项卡中的每一项属性,包括label属性(选项卡的名称),每一项的内容放在标签内。
<el-form></el-form>:表单组件,常用属性包括:model(绑定的数据),rules(表单的验证规则)。
<el-form-item></el-form-item>:表单里的每一行,常用的属性为prop(对应rules里某一项的验证规则)。
在表单验证时,通过ref获取表单结点,例如ref="form",那么通过form.value.validate(res=>{if(res){...}})//其中res是一个布尔值,表示表单验证的结果。
导航守卫
避免用户直接访问所有组件。对路由跳转时,做了一定的规则判断再决定路由的跳转。
语法:
router.beforeEach(to, from, next){
if(to.path === '/login'){return true}
if(localStorage.getItem('token')){return true}
return {path: '/login'}
}
//上述导航拦截实现的就是,每次进行路由跳转之前,先判断是否符合跳转规则,如果符合则跳转,否则就返回指定的页面。
页面传值
vue3中页面传值的常用方法有两种:query和params
示例:router.push('/user?username='+username)
跳转到user页面时,把username传递过去。在user页面需要通过route.query.username来接收,示例如下:
onMounted(()=>{
const username = route.query.username
})
params是在配置路由时,添加需要传递的参数。示例如下:
{
path: "/user/:username"
component: ()=>import('../views/user/User.vue')
}
在传递时:
router.push(`/user/${username}
`)
在接收时:route.params.username即可接收。
Axios
数据请求的方法,通过npm i axios下载。
import axios from 'axios'
//get请求获取数据
axios.get(url).then(res=>{console.log(res)})//请求的数据放在res.data里
或者
axios({
url,
method: 'get',
params: {}
}).then(res=>{console.log(res)})
post请求
axios.post(url, data).then(res=>{console.log(res)})
或者
axios({
url,
method: 'post',
headers: {
token,
'content-type': 'application.json'
}
data: {}
}).then(res=>console.log(res))
axios的基准地址baseURL,每次请求都会在请求地址前面加上的地址
axios.default.baseURL = '/api',
超时时间:timeout,请求超过该时间时则认为超时,单位毫秒
axios.default.timeout = 3000
拦截器: interceptors
const instance = axios.create({
baseURL: url,
timeout: 3000
})
请求拦截器:
instance.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if(token){
config.headers.token = token
}
})
响应拦截器:
instance.interceptors.response.use(res => {return res.data})
axios封装:
import axios from 'axios'
const instance = axios.create({
baseUrl: '/api',
timeOut: 3000
})
//设置拦截器
//请求拦截器
instance.interceptors.request.use(config=>{
const token = localStorage.getItem('token')
if(token){
config.headers.token = token
}
return config
})
//响应拦截器
instance.interceptors.response.use(res=>{return res.data})
//自定义get请求方法
const get = (url, params, options)=>{
return instance.get(url, {params, ...options})
}
//自定义post请求方法
const post = (url, data, optoins)=>{
return instance.post(url, data, options)
}
export{get, post}
解决跨域问题
后端解决跨域问题:后端服务器在配置的时候通过设置cors来运行指定的源访问。比如:
res.header("Access-Control-Allow-Origin", "*")//表示允许所有的源访问
res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")//表示允许的请求方式
res.header("Access-Control-Allow-Headers", "Content-type, Authorization")//表示允许的请求头
前端解决跨域问题:通过在vite.config.js中配置proxy解决。比如:
server: {
proxy: {
'/api': {
target: '需要代理的域名+端口',
changeOrigin: true,
rewrite: (path)=>path.replace(/^\/api/, 'replace')
}
}
}
在访问后端接口时,将接口的域名和端口号替换成/api即可。
pinia
pinia是vue专属的状态管理库,它允许跨组件或页面共享状态,和之前的vuex功能一样,可以把pinia立即为下一代的vuex工具。
pinia选项式语法:
import {defineStore} from 'pinia';
const userStore = defineStore('userStore', {
state: ()=>{//数据写在state里,并且return
return {
user: {
name: 'zs',
age: 20
},
n: 666,
}
},
actions: {//方法写在actions里
getUser=>{
return this.user
}
},
getter: {//类似于计算函数computed
doubleN: this.n * 2
}
persist: true//持久化存储
})
选项式语法:
import store from '@/stores/store'//在store.js脚本里对pinia进行一些初始化操作,包括持久化操作。
const useUserStore = define('userStore', ()=>{
const user = ref({})
const getUser = ()=>{
return user.value
}
const setUser = (newUser) => {
user.value = newUser
}
const removeUser = ()=>{
user.value = {}
}
return {
getUser,
setUser,
removeUser
}
})
export const userStore = useUserStore (store)
附:store.js
import {createPinia} from 'pinia';
import {createPersistedState} from 'pinia-persisted-plugin'//引入持久化插件
const store = createPinia();
store.use(createPersistedState())//使用持久化插件
export defalut store;
叨叨几句... NOTHING