vue基础
el:元素自动挂载(值可以选择是css选择器或DOM元素)el: ‘#app’
$mount
:元素手动挂载(挂载到末尾),$mount('#app')
data:模型数据(值是一个对象)
methods:控制逻辑(值是函数)
组件化
date必须是一个函数,组件模板内容必须是单个根元素,模板内容可以使模板字符串用``包裹,组件名称使用驼峰式时只能在模板字符串中使用,在普通标签模板中必须用短横线式使用组件
- 全局组件注册
Vue.component('组件名称', {
date: function (){
return {
组件数据
}
},
template: '组件模板内容'
})
- 局部组件注册:在vue实例化对象里面添加一个component对象在对象内,局部组件只能在注册他的父组件内使用
var HelloWorld = {
date: function (){
return {
组件数据
}
},
template: '组件模板内容'
}
var vm = new Vue({
el: 'app',
data: {},
component: {
'HelloWorld': HelloWorld
}
})
- 组件传值
- 子组件通过props[‘值’]接收父组件传值
Vue.component('组件名称', {
props['title'],
template: '{title}(组件模板内容)'
})
-
- 父组件通过属性将值传递给子组件,props中使用驼峰,html中需要使用短横线方式,字符串形式模板中没有限制
<组件名称 title="来自父组件的数据"></组件名称>
<组件名称 :title="title1"></组件名称>(title1可以绑定到实例化vue对象的data中实现动态绑定)
插值表达式(将数据填充到html标签内,支持基本计算)
内容用法:{{表达式}}
(支持js数据类型、运算符,三目运算符,短路原则,数组操作)
指令(自定义属性)
格式:v-
开始
v-cloak
:解决插值表达式闪动问题(先隐藏,替换好值再显示)- 添加css样式:
[v-cloak]{displlay: none}
- 在插值表达式所在标签添加
v-cloak
- 数据绑定指令:
v-text
:数据绑定,填充纯文本(相比插值表达式更简洁)v-html
:数据绑定,填充html片段(存在安全问题容易导致xss攻击,本网站内部数据可以使用,第三方数据不可用)v-pre
:数据绑定,填充原始信息(直接在标签内添加没有值,显示原始信息,跳过编译过程)- 数据响应式(数据变化页面内容随之变化)
- 数据绑定:将数据填充到标签内
- 双向数据绑定:视图界面数据发生变化,控制台信息也随之发生变化,
v-model
v-model
:双向数据绑定(绑定vue实例化对象data中的值)
- 双向数据绑定:视图界面数据发生变化,控制台信息也随之发生变化,
v-once
:只编译一次(直接在标签内添加没有值,显示内容后不再具有响应式功能,可以节省性能)
- MVVM设计思想:model代表模型、数据,view代表视图、模板、dom- 自定义指令:
// 注册一个全局自定义指令 v-focus
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
// 局部指令,必须挂载到实例上和data平级
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
- 过滤器
Vue.filter('过滤器名称', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
- 过滤器使用
<div>{{数据 | 过滤器名称}}</div>
事件(函数)绑定
- v-on指令:
v-on:click='事件'
(类似行内式) - v-on指令简写:
@click='事件'
- 事件函数调用:
- 绑定函数名称:
v-on:click='函数名'
(事件函数第一个参数为事件对象) - 调用函数:
v-on:click='函数名()'
(事件函数最后一个参数为事件对象,事件对象名称必须为$event) - 事件修饰符:
v-on:click.stop='事件'
- 阻止冒泡:
.stop
- 阻止默认行为:
.prevent
- 按键修饰符:
v-on:keyup.enter='事件'
- 回车键:
.enter
- 删除键:
.delete
- 自定义按键修饰符:
- 全局config.keyCode对象:
Vue.config.keyCode.f1 = 112
- 表单域修饰符:
v-model.muber="数值"
- 转化为数值:
.number
- 去除头尾空格:
.trim
- input事件切换为change事件:
.lazy
(表单验证用户名常用)
属性绑定
- v-bind指令:
v-bind:href='url'
,用法:<a v-bind:href='url'>跳转</a>
- v-bind指令简写:
:href='url'
,用法:<a :href='url'>跳转</a>
- v-model低层实现原理:
<input v-bind: value = "msg" v-on: input = "msg = $event.target.value">
样式绑定
- class样式处理:
- 对象语法:
v-bind:class='{属性: 值}'
- 数组语法:
v-bind:class='[数组名, 数组名]'
- style样式处理:
- 对象语法:
v-bind:style='{属性: 值}'
- 数组语法:
v-bind:style='[数组名, 数组名]'
分支循环结构
- 分支结构:
v-if
(控制元素是否渲染到页面)
v-else
v-else-if
v-show
(控制元素是否显示) - 循环结构:
- 遍历数组:
v-for='item in 数组名'
或v-for='(item,索引) in 数组名'
(使用{{item.key}}
填充遍历数据) - 遍历对象:
v-for='(value, key, 索引) in object'
或v-if='value==12' v-for='(value, key, 索引) in object'
(v-if和v-for结合使用) - 绑定唯一标识帮助Vue区分不同元素提高性能:
:key='item.id'
API
- 修改响应式数据
Vue.set(要处理数组名称, 要处理数据索引, 要处理数组值)
vue.$set(要处理数组名称, 要处理数据索引, 要处理数组值)
模块化
传统开发模式问题:命名冲突,文件依赖
模块化:功能单独封装到模块(文件)中,模块相互隔离通过特定接口公开内部成员,也可依赖别的模块(方便代码复用,提高开发效率,方便后期维护)
48. 浏览器端模块化:
49. AMD:Rquire.js(https://requirejs.org/)
50. CMD:Sea.js(https://seajs.github.io/seajs/docs/)
51. 服务器端模块化:
52. commonJS:
- 模块分为单文件模块与包
- 模块成员导出:module.exports={}
和exports.变量名=() ={}
- 模块成员导入:require('模块标识符')
53. ES6模块化:浏览器和服务器通用模块化标准
- 每个js文件都是独立模块,导入模块成员使用import
关键字,暴露模块成员使用exports
关键字
- 体验:通过node.js中安装babel插件体验
- 安装依赖:npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node
- npm install --save @babel/polyfill
- 根目录创建文件babel.config.js,文件代码如下
const presets = [
["@babel/env", {
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
}
}]
];
module.exports = { presets };
- 代码执行:
npx babel-node index.js
- 默认导入导出:
- 导出:exports default 导出成员
- 导入:import 导出文件名称 from '导出文件路径'
- 按需导入导出:
- 导出:exports let 变量名 = '值'
- 导入:import {变量名, 变量名} from '导出文件路径'
- 直接导入并执行:
import '执行文件路径'
(只执行模块中代码,不暴露成员)
vue单文件
组件模板区:<template></template>
业务逻辑区:<script></script>
样式区:<style scoped></style>
(添加scoped属性防止样式冲突)
vue-router
vuex:非关系组件之间共享数据方式
父向子传值:v-bind(属性绑定)props
子向父传值:v-on(事件绑定)$emit
59. 兄弟组件之间共享数据:EventBus
或公共$emit
60. $on
(接收数据组件)
61. $emit
(发送数据组件)
vuex基本使用
- 安装依赖包:
npm install vuex --save
- 导入依赖包
import Vuex from 'vuex'
vue.use(Vuex)
- 创建store对象:state中存放全局共享数据
const store = new Vuex.Store({
state: {},
mutations: {}
Actions: {}
Getter: {}
})
- store对象挂载到vue实例中
// 先导入
import store from './store'
// 后挂载
new Vue({
el:'#app',
render: h => h(app),
router,
store
})
vuex核心概念
State: 提供唯一公共数据源,存储所有共享数据state: {key: value}
- 数据访问方式:
this.$store.state.全局数据名称
(this可省略) - 数据访问方式:
- 从vuex中按需导入mapstate函数:
import { mapState } from 'vuex'
- 通过函数将当前组件全局数据映射为computed计算属性:
computed: {...mapState(['count'])}
Mutation:用于变更store中的数据,不允许直接操作store中的数据,使用commit
触发Mutation函数,定义中step对应触发中step所填值
- 触发方式1
// 先定义
const store = new Vuex.Store({
state: {
count: 0
},
mutation: {
add(state, step) {
state.count++
state.count += step
}
}
})
// 后触发:在单文件中调用,使用step携带函数
methods: {
handlel(){
this.$store.commit('add', step)(触发mutation方式)
}
}
- 触发方式2
- 从vuex中按需导入mapMutations函数:
import { mapMutations } from 'vuex'
- 通过导入mapMutations函数将所需mutations函数映射为methods方法:
methods: {...mapMutations(['add', 'addN'])}
Action:处理异步任务(通过异步操作变更数据必须Action而不能用Mutation,但变更数据要在Action中触发Mutation间接变更数据)
- 触发方式1
// 先定义:step接收出发时携带参数
const store = new Vuex.Store({
actions: {
addAsync (context, step) {
setTimeout( () => {
context.commit('add', step);
}, 1000)
}
}
})
// 后触发
mutations: {
handle () {
this.$store.dispatch('addAsync', 参数)(触发actions方式)
}
}
- 触发方式2
- 从vuex中按需导入mapActions函数:
import { mapActions } from 'vuex'
- 通过导入mapActions函数将所需actions函数映射为methods方法:
methods: {...mapActions(['addAsync', 'addNAsync'])}
Getter:用于对store中数据处理形成新数据,store中数据变化getter数据随之变化
- 触发方式1
// 先定义
const store = new Vuex.Store({
state:{
count: 0
},
getters: {
名称: state => {
return '当前最新数量是'+ state.count
}
}
})
// 后触发
this.$store.gstters.名称
- 触发方式2
- 从vuex中按需导入mapActions函数:
import { mapGetters } from 'vuex'
- 通过导入mapActions函数将所需actions函数映射为methods方法:
computed: {...mapGetters(['addAsync'])}
Module:拆分vuex中的数据、更新、操作,分解为一个个模块便于维护
- 获取方式1
// 先定义
const store = new Vuex.Store({
module: {
user: {
namespaced: true, //防止通过全局`this.$store.commit('count')`调用vuex中的模块,必须`this.$store.commit('user/count')`,映射也类似,或者在组件中建立一个方法,在方法内部封装this['user/count']()
state: {
count: 0
},
mutation: {
add(state, step) {
state.count++
state.count += step
}
},
setting: {
state: {
count: 0
},
mutation: {
add(state, step) {
state.count++
state.count += step
}
}
}
})
// 后获取
使用组件中获取方式`$store.state.模块名.属性名`
- 获取方式2
- 在根级别getters中定义:
属性名: state => state.模块名.属性名'
,在使用组件中computed: {...mapGetters(['属性名', '属性名'])}
- 创建基于某个命名空间辅助函数:
createnamespacedHelpers
- 先引入import { mapGetters, createnamespacedHelpers } from 'vuex'
const{ mapMutations } = createnamespacedHelpers('user')
- 使用methods: {...mapActions(['count'])}
点击调用<button @click="count"> </button>
vue脚手架(快速生成vue项目基本架构,https://cli.vuejs.org/zh/)
安装3.x版本:npm install -g @vue/cli
查看脚手架安装完成与否及版本号:vue -V
86. 创建Vue项目:
87. 基于交互式命令行的方式,创建新版vue项目:vue create project
88. 基于图形化界面的方式,创建新版vue项目:vue ui
- 必选功能:Babel,Router,Linter/Formatter(ESLint+Standard config,Lint on save),使用配置文件
89. 基于2.x的旧模板,创建旧版vue项目:
- npm install -g @vue/cli-init
- vue init webpack my-project
90. 自定义配置
91. 通过package.json配置
"vue":{
"devServer":{
"port":8888,
"open":true
}
}
- 通过单独的配置文件配置项目
- 在项目的跟目录创建文件 vue.config.js
- 在vue.config.js文件中进行相关配置,从而覆盖默认配置
module.exports = {
devServer: {
//自动打开浏览器
open:true
//配置端口号
port: 8888
}
}
axios使用
- 终端安装axios包
npm i axios
- 在main.js中引入axios
import axios from 'axios
- 设置请求的根路径
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
- 将axios包挂载到vue的原型对象上,使得vue的每一个组件能够通过this访问
$http
,从而发起ajax请求
Vue.prototype.$http = axios
- 通过
$http.post
发起请求login 为请求路径,后面的为请求需要携带的参数,其返回值为promise对象包含的数据很多,通过await简化promise,awai只能用在async修饰的方法中,返回的是具体的响应对象data{},其中有六个属性都是axios封装好的,属性中只有data属性是服务器返回的真实数据。
const res = await this.$http.post('login',this.loginForm)
- 通过判断服务器响应的status,做出消息提示
if (res.data.meta.status!=200) return this.$message.error('登录失败');
this.$message.success('登录成功')
实操步骤
前端技术栈:Vue2/3、Vue-router、Element-UI、Axios、Echarts
后端技术栈:Node.js、Express、Jwt、Mysql、Sequelize
- 前端项目初始化:
- 安装Vue脚手架
- 通过Vue脚手架创建项目
- 配置Vue路由
- 配置Element-UI组件库
- 配置axios库
- 初始化git远程仓库
- 本地项目托管Github中
- 后台项目环境配置:
- 安装MySQL数据库
- 安装Node.js环境
- 配置项目相关信息
- 启动项目
- 使用Postman测试后台项目接口是否正常
vue2
生命周期
beforeCreate()
-创建前
created()
-创建后(常用,开始请求)
beforeMount()
-载入前
mounted()
-载入后(开始操作dom)
beforeUpdate()
-更新前
updated()
-更新后
beforeDestory()
-销毁前(释放vue以外资源,如清除定时器、延时器)
destoryed()
-销毁后
响应式原理
var book = {
_year : 2004,
edition : 1
}
book.year = 2005;
利用原生Object.defineProperty方法
Object.defineProperty(book,"year",{
get(){ //访问book对象中year属性时自动调用的方法,返回值就是拿到的值
return this._year
},
set(newYear){ //设置book对象中year属性时自动调用的函数,属性最新值会当成实参传入
this._year = newYear;
}
})
获取单个元素
通过ref属性绑定该元素
<div ref="box"></div>
通过this.$refs.box
获取元素
获取v-for便利的多个元素
通过ref属性绑定被遍历元素
<li v-for="i in 4" :key="i" ref="li"></li>
通过this.$refs.li
获取所有遍历元素
除去v-model实现双向数据绑定的另外方式
<Son :money='money' @updata:money="fn" />
简写<Son :money.sync='money' />
v-modul
和.sync
已经合并成v-modul
指令
语法糖
<c :value="msg" @input="msg=$event" />
mixins语法(vue2中解决逻辑代码冲突问题,vue3组合API解决了问题不推荐使用)
混入(mixin)提供一种灵活的方式来分发vue组件中的可复用功能,一个混入对象可以包含任意组件选项,组件使用混入对象是,所有混入对象的选项将混合进入该组件本身选项中
在main.js中app实例挂在前app.maxin({//可以按照组件setup内容写入)}
模块化之后在模块中暴露出去,使用时在需要使用的组件中import导入,写入mixins: []
与components同级,因为可能不止写入一个逻辑
vue3
生命周期
setup()
-创建实例前,创建data和method,新的组件选项,组合API的起点,在vue2的beforeCreate前执行,此时this不是组件实例而是undefined,在模板中使用的数据和函数在setup中返回
onBeforeMount()
-挂载DOM前
onMounted()
-挂载DOM后
onBeforeUpdate()
-更新前
onUpdated()
-更新后
onBeforeUnmount()
-卸载前
onUnmounted()
-卸载后
onActivated()
-被包含在 中的组件,多出两个生命周期钩子函数,激活时
onDeactivated()
-组件切换,切换组件消失执行
onErrorCaptured()
-子孙组件异常时
响应式原理
var book = {
_year : 2004,
edition : 1
}
book.year = 2005;
let proxy = new Proxy(obj, {
get : function (target, prop) {
return target[prop]
},
set : function (target, prop, value) {
target[prop] = value;
if(prop === 'count') {
double = getDouble(value)
}
},
deleteProperty(target, prop) {
delete target[prop]
if(prop === 'count') {
double = NaN
}
}
})
ref函数:定义简单类型数据也可以定义复杂数据类型,成为响应式数据(重点)
// 先导入函数
import { ref } from 'vue'
// 后定义函数
const obj = ref('ls')
// 使用,修改值需要.value
obj.value
ref属性:获取DOM或组件实例
-
获取单个DOM或组件**
- 先使用ref定义一个空的响应式数据
const dom = ref(null)
- setup中返回该数据,想要获取dom元素在该元素上使用ref属性绑定该数据即可
<div ref="dom"></div>
- 先使用ref定义一个空的响应式数据
-
获取v-for遍历的DOM或组件
- 定义一个空数组接受所有li
const domList = []
- 定义一个函数,网空数组pushDOM
- 定义一个空数组接受所有li
const setDom = (el) => {
domList.push(el)
}
reactive函数:只能定义复杂类型数据,成为响应式数据(重点)
// 先导入函数
import { reactive } from 'vue'
// 后定义函数
const obj = reactive({
name: 'ls',
})
// 使用
obj.name
toRef函数:转换响应式对象中某个属性为单独响应式数据,并且值关联(解构数据不能实现响应式)
// 先导入函数
import { reactive, toRef } from 'vue'
// 后定义函数
`const name = toRef(obj, 'name')`
// 使用,toRef修改数据需要添加value,value是存放值的地方
name.value = 'zs'
toRefs函数:转换响应式中所有属性为响应式数据,用于解构|展开reactive定义对象
// 先导入函数
import { reactive, toRef } from 'vue'
// 后定义函数,转化之后可以返回解构出的对象
const obj1 = toRefs(obj)`
// 使用,toRefs修改数据需要添加value,value是存放值的地方
// 与reactive函数定义数据相通`obj.name = 'zs'`也可以
obj1.name.value = 'zs'
Vue3语法糖
<Son :modelValue="msg" @update:modelValue="msg=$event" />
vue3封装组件支持v-model的时候父传子:modelValue
子传父@update:modelValue
-
想获取原生事件对象
- 绑定是函数fn,
fn(e){ //e就是事件对象}
- 绑定是js表达式,提供一个默认变量
$event
<h1 @click="$event.target.style.color='red'">父组件 {{count}}</h1>
- 绑定是函数fn,
-
想获取自定义事件
- 绑定是函数fn,
fn(data){ //data是触发自定义事件的传参}
- 绑定是js表达式,
$event
代表触发自定义事件的传参
<Son :modelValue="count" @update:modelValue="count=$event" />
- 绑定是函数fn,
步骤
- 在main.js中导入createApp函数
import {createApp} from 'vue'
- 定义App.vue根组件,导入main.js
import App from './App.vue'
- 使用createApp函数基于App.vue组件创建应用实例
const app = createApp(App)
- 挂载至index.html的#app容器
app.mount('#app')
vue2/3
区别
vue2:选项(Options)API(易于学习和使用,但是代码组织性差)
vue3:组合(composition)API(体积小,性能提升,更好支持TS)
响应式对比
vue2响应式原理:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)
vue3响应式原理:通过Proxy(代理)拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等
computed函数:定义计算属性,不能修改
// 基础用法
const obj = computed( () => {
return 计算值
})
// 高级用法:支持v-model修改数据
const obj = computed( () => {
get(){ // 获取计算属性值
return 计算值 // 效果和基础用法相同
}
set(value) { // 监听计算属性变化
age.value = value + 2
}
})
watch函数:定义侦听器
监听ref和reactive定义的响应式数据
data: {}
watch: {
// 简单写法
数据属性名(newVal, oldVal){
this.key = newVal
}
'对象.属性名'(newVal, oldVal){
this.key = newVal
}
// 完整写法
数据属性名: {
deep: true, // 是否对复杂类型深度监视
immediate: true, // 是否组件初始化立刻执行一次handler方法
handler(newVal, oldVal){
this.key = newVal
}
}
}
watch(count, (newVal, oldVal) => {}, {deep: true, immediate: true})
watch(count, (newVal, oldVal) => {}, {deep: true, immediate: true})
第一个参数是需要监听的对象,第二个参数是改变后触发的函数,第三个参数是个对象,可以设置deep深度监听数据变化和immediate默认执行
监听多个数据变化
watch([count, obj], (newVal, oldVal) => {})
监听对象中某个属性变化需要写成函数返回该属性的方式
watch(() => obj.name, (newVal, oldVal) => {})
父子通讯
父传子
父组件定义一个ref数据,使用return返回并使用属性绑定到父组件中的子组件占位符上:money="导出ref对象"
子组件中使用props接收父组件数据,props和setup同级且setup(props)中的props就是父组件数据
子传父
触发自定义事件的时候emit来自setup(props, {emit}){//emit就是触发事件函数}
<Son :money="money" @updata:money="updataMoney" />
简写<Son v-modul:money='money' />
非父子通讯
- event bus事件总线:非父子组件之间简单消息传递
// 创建一个都能访问到的实践总线(空vue实例)
// utils/EventBus.js
import Vue from 'vue'
const Bus = new Vue()
export default Bus
// A组件(接收方)监听Bus实例的事件
created(){
Bus.$on('sendMsg', () => {
this.msg = msg
})
}
// B组件(发送方)触发Bus实例的事件
Bus.$emit('sendMsg','这是一个消息')
- vuex:非父子组件之间简单消息传递
依赖注入:后代组件通讯(单向数据流)
父组件使用provide('约定接收名称', 父组件传递数据对象名或函数名)
子孙组件使用inject('约定接收名称')
vue踩坑
TypeError: Cannot read properties of undefined (reading ‘_withTask‘)
类型错误:无法读取未定义的属性(读取“ with Task ”)
_withTask
在vue的源码中表示编译错误,例如使用了一个未定义的方法,在组件中使用@定义了一个方法,在methods中没有定义该方法就会出现该错误