vue.js
用于构建用户界面的渐进式框架,双向数据绑定
双向数据绑定原理:通过数据劫持和发布-订阅模式实现,数据劫持通过Object.defineProperty()实现,
劫持属性的setter与getter,数据属性发生变动,发布消息给订阅,触发对应的监听回调
const app = new Vue ( {
el: '#app' ,
data: { msg: '小猪皮' }
} ) ;
MVVM
model:模型,数据对象(data)
view:视图界面
viewModel:视图模型(vue的实例)
视图通过数据绑定读取model数据对象,
model数据对象通过DOM监听获取DOM内容
模板语法(动态的html)
1.双大括号表达式:{{kn}}
2.强制数据绑定:v-bind:src="kn"/:src="kn"
3.绑定事件监听:v-on:event="mn"/@event="mn"
<!-- kn:keyName mn:methodsName -->
计算属性computed/监视watch
< input type = " text" v-model = " computedValue" >
< input type = " text" v-model = " comVal" >
< script>
const vm = new Vue ( {
el: "#app" ,
data: { firstName: 'luo' , lastName: 'xianze' } ,
computed: {
computedValue ( ) {
return this . firstName + this . lastName;
} ,
comVal: {
get ( ) {
return this . firstName + '-' this . lastName;
} ,
set ( val) {
const names = val. split ( '-' ) ;
this . firstName = names[ 0 ] ;
this . lastName = names[ 1 ] ;
}
}
} ,
watch: {
firstName: function ( value) {
this . computedValue = value + this . lastName;
} ,
todos: {
deep: true ,
immediate: true ,
handler: function ( value) {
}
}
}
} )
vm. $watch ( 'keyName' , function ( value) { } )
</ script>
class绑定/style绑定
:class="xxx" xxx字符串->data中data[xxx]
:class="{xClass:true/false,yClass:isY}" 对象
true,新增类,false,元素不新增该类;isY为data[isY]
:class='getClassName()' 函数
methods:{
getClassName:function(){
return {active:this.isActive}
}
}
:class="['active', 'line']" 数组字符串
className = 'active line'
:class="[active,line]" 数组变量
根据data中的active与line
:style="{color:dc, fontSize: dfs + 'px'}" 变量
dc、dfs为data中的属性值
v-if、v-else-if、v-else
使用示例:
< span v-if = ' 变量值/表达式' > </ span>
< span v-else-if = ' 变量值/表达式' > </ span>
< span v-else > </ span>
< div class = " form-control" v-if = ' isHowName' >
< label for = " userName" > 用户账号</ label>
< input type = " text" name = " userName" id = " userName" >
</ div>
< div class = " form-control" v-else >
< label for = " userEmail" > 用户邮箱</ label>
< input type = " text" name = " userEmail" id = " userEmail" >
</ div>
v-show
v-show 与 v-if的区别,v-show:false将DOM元素设置display:none;
v-if:false将DOM元素删除
显示与隐藏切换频繁建议使用v-show
v-for
建议使用v-for时,内容元素新增key,可更高效地更新虚拟DOM
数组触发响应式的方法:
push,pop,shift,unshift,splice,sort,reverse
不触发:arr[1] = 'xzp';
< li v-for = " (item, index) in arr" :key = " item.id" >
{{index}}-{{item}}
</ li>
< li v-for = " (value, key, index) in obj" :key = " key" >
{{key}}-{{value}}
</ li>
事件监听v-on
示例1:<button @click='add'>点击</button>
示例2:<button @click='add()'>点击</button>
示例3:<button @click='add(2)'>点击</button>
示例4:<button @click='add(2, $event)'>点击</button>
示例5:<button @click='add(name, $event)'>点击</button>
add: function ( num) {
console. log ( num) ;
}
事件修饰符
< div>
< button @click.stop = ' add' > </ button>
< button @contextmenu.prevent = ' add' > </ button>
< input @keyup.keyCode = " xzpKeyUp" >
< input @keyup.enter = " xzpKeyUp" >
< input @click.once = " xzpOnce" >
</ div>
< script>
new Vue ( {
methods: {
xzpKeyUp ( event) {
console. log ( event. keyCode)
}
}
} )
</ script>
v-model数据双向绑定
v-model由 v-bind 与 v-on组合实现<input :value="name" @input="changeName">
< form @submit.prevent = " handleSubmit" >
<input type = 'text' v-model='name'>
< input type = " radio" id = " male" value = " male" v-model = ' sex' >
< label for = " male" > 男</ label>
< input type = " radio" id = " female" value = " female" v-model = ' sex' >
< label for = " female" > 女</ label>
< input type = " checkbox" id = " yyy" value = ' yyy' v-model = ' like' >
< label for = " yyy" > yyy</ label>
< input type = " checkbox" id = " jsx" value = ' jsx' v-model = ' like' >
< label for = " jsx" > jsx</ label>
< label v-for = " (item, index) in likeArr" :for = " index+item" >
< input type = " checkbox" id = " index+item" :value = " item" v-model = ' like' > {{item}}
</ label>
< select v-model = ' fruit' >
< option v-for = " (item, index) in fruitArr"
:key = " item.value" :value = " item.value" > {{item.name}}</ option>
</ select>
</ form>
new Vue ( { el: '#app' ,
data: {
name: 'xzp' , sex: 'male' , like: 'jsx' ,
likeArr: [ 'yyy' , 'jsx' ] , fruit: '' ,
fruitArr: [
{ name: '无' , value: '' } ,
{ name: '橙子' , value: 'o' } ,
{ name: '西瓜' , value: 'w' } ,
{ name: '苹果' , value: 'a' }
]
} ,
methods: {
handleSubmit ( ) { console. log ( this ) ; }
}
} ) ;
双向绑定的修饰符
< input type = ' text' v-model.lazy = ' city' >
< input type = ' number' v-model.number = ' age' >
< input type = ' text' v-model.trim = ' email' >
vue的生命周期
new Vue ( { el: '#app' ,
data: { } ,
beforeCreate ( ) { } ,
created ( ) { } ,
beforeMount ( ) { } ,
mounted ( ) { } ,
beforeUpdate ( ) { } ,
updated ( ) { } ,
beforeDestroy ( ) { } ,
destroyed ( ) { }
methods: {
destroyVM ( ) {
this . $destroy ( ) ;
}
}
} ) ;
v-once
单次渲染,data.message改变时,页面将不变
<span v-once>{{message}}</span>
v-html
将数据解析为html,data.url = '<a href="www.baidu.com"></a>
<span v-html='url'></span>
v-text (不经常使用)
使用v-text 会覆盖标签原有的内容
<span v-text='text'></span>
v-pre (不解析内容)
<span v-pre>{{name}}</span>
界面显示:{{name}}
v-cloak
<div id='app' v-cloak></div>
vue解析之前,div存在属性v-cloak
vue解析之后,div属性v-cloak消失
样式写入[v-cloak]{ display:none; }
防止闪屏(不常用)
ref
< input ref = ' keyName' value = ' xxx' >
< script>
this . $refs. keyName;
</ script>
过渡/动画
<transition name = 'xxx'>
< div v-show = ' isShow' > 示例显示隐藏</ div>
</ transition>
.xxx-enter-active,.xxx-leave-active {
transition : opacity 1s;
}
.xxx-enter, .xxx-leave-to { opacity : 0; }
<transition name = 'move'>
< div v-show = ' isShow' > 示例移动</ div>
</ transition>
.move-enter-active { transition : all 1s; }
.move-leave-active { transition : all 2s; }
.move-enter, .move-leave-to {
opacity : 0;
transform : translateX ( 20px) ;
}
过滤器
Vue. filter ( 'filterName' , function ( value, format) {
return filterValue;
} ) ;
filters{ 'filterName' : function ( value, format) {
return filterValue;
} } ;
< div> {{data | filterName(format)}}</ div>
指令新增directive
Vue. directive ( 'directive-name' , function ( el, binding) {
} )
directives: {
'directive-name' ( el, binding) {
}
}
v- directive- name
插件plugin
( function ( ) {
const MyPlugin = { } ;
MyPlugin. install = function ( Vue, options) {
Vue. globalFunction = function ( ) {
}
Vue. directive ( 'directive-name' , function ( el, binding) {
} )
Vue. prototype. $myMethod = function ( ) {
}
}
window. MyPlugin = MyPlugin;
} ) ( )
Vue. use ( MyPlugin) ;
Vue. globalFunction ( ) ;
vm. $myMethod ( ) ;
组件实例
< template>
< div> 组件示例:App</ div>
</ template>
< script>
export default {
data ( ) {
return { } ;
}
}
</ script>
< style lang = " less" > </ style>
import Vue from 'vue'
import App from './App.vue'
new Vue ( {
el: '#app' ,
components: {
App
} ,
template: `< App / >
} )
组件通信(父组件->子组件/子组件->父组件)
< template>
< div @click = " deleteItem" > {{itemprops}}</ div>
</ template>
< script>
export default {
props: {
itempropstype: Array| String| Number| Object| Function,
itemsex: {
type: String,
default : 'male' ,
required: true
validator ( value) { return true / false ; }
}
} ,
methods: {
deleteItem ( ) {
let { deleteF} = this ;
deleteF ( ) ;
this . $emit ( 'deleteLove' , value) ;
}
}
}
</ script>
< style lang = " less" > </ style>
< template>
< div>
父组件传递props
< item :propstype = " type" :itemsex = " sex"
:deleteF = " deleteF" @deleteLove = " deleteL" />
</ div>
</ template>
< script>
export default {
data: { type: 'String' , sex: 'male' }
methods: {
deleteF ( ) { } ,
deleteL ( ) { }
}
}
</ script>
PubSub
安装npm install --save pubsub-js
import PubSub from 'pubsub-js'
PubSub. subscribe ( 'titleName' , function ( msg, data) {
} ) ;
PubSub. publish ( 'titleName' , data)
子组件访问父组件
this.$parent 访问父组件
this.$root 访问根组件
插槽slot
< cpn slot > < button> 我是插槽按钮</ button> </ cpn>
< div>
< span> 我是子组件</ span>
< slot> < button> 我是插槽默认值</ button> </ slot>
</ div>
具名插槽
< div>
< slot name = ' left' > </ slot>
< slot name = ' center' > </ slot>
< slot name = ' right' > </ slot>
</ div>
< cpn>
< span slot = ' center' > center插槽内容</ span>
< span slot = ' right' > right插槽内容</ span>
</ cpn>
插槽作用域
父组件替换插槽的数据,由子组件提供
< cpn>
< template slot-scope = " slot" >
< span v-for = " item in slot.data" > {{item}}</ span>
</ template>
</ cpn>
< slot :data = " nameArr" > </ slot>
vue cli脚手架
安装脚手架:npm install -g vue-cli
vue 路由的使用
npm install--> vue-router
新增路径文件夹router/index.js
import Vue from 'vue' ;
import VueRouter from 'vue-router' ;
import Home from '../views/Home.vue' ;
import About from '../views/About.vue' ;
Vue. use ( VueRouter) ;
export default new VueRouter ( {
routes: [
{ path: '/about' , component: About } ,
{ path: '/home' , component: Home } ,
{ path: '/' , redirect: '/home' }
]
} )
import router from './router'
new Vue ( {
router
} )
/* 入口页面引用 */
< div class = " list-link" >
< router-link to = " /about" > about</ router-link>
< router-link to = " /home" > home</ router-link>
</ div>
< div class = " div-view" >
< router-view> </ router-view>
</ div>
vue嵌套路由
export default new VueRouter ( {
routes: [
{ path: '/about' , component: About } ,
{ path: '/home' , component: Home,
children: [
{
path: '/home/new' ,
component: New
} ,
{
path: 'message' ,
component: Message
} ,
{ path: '' , redirect: '/home/new' }
]
} ,
{ path: '/' , redirect: '/home' }
]
} )
< router-link to = " /home/new" > new</ router-link>
< router-link to = " /home/message" > message</ router-link>
缓存路由组件
切换路由组件,原有的数据将被初始化
需要使用标签keep-alive
< div id = " app" class = ' wrapper' >
< keep-alive>
< router-view v-if = " $route.meta.keepAlive" > </ router-view>
</ keep-alive>
< router-view v-if = " !$route.meta.keepAlive" > </ router-view>
</ div>
路由组件传递数据
{
path: "/home/message/detail/:id" ,
component: Detail
}
< router-link :to = " `/home/message/detail/${msg.id}`" > {{msg.title}}</ router-link>
< router-link :to = " `/home/message/detail/?id=${msg.id}`" > {{msg.title}}</ router-link>
< span> {{$route.params.id}}</ span>
< span> {{$route.query.id}}</ span>
< script>
export default {
mounted ( ) {
const id = this . $route. params. id;
} ,
watch: {
$route: function ( value) {
const id = value. param. id;
}
}
}
</ script>
< router-view :id = ' id' > </ router-view>
< script>
export default {
props: {
id: {
type: Number,
required: true
}
}
}
</ script>
vuex
对vue应用中多个组件的共享状态进行集中式管理
状态自管理应用(单向数据流)
state:驱动应用的数据源
view:已声明方式将state映射到视图
actions:响应在view上的用户输入导致状态变化
多组件共享状态
1.多个视图依赖相同的状态
2.不同视图行为需要变更同一状态
import Vue from 'vue'
import Vuex from 'vuex'
Vue. use ( Vuex)
const state = {
count: 0
}
const mutations = {
ADD ( state, val) {
state. count += val
} ,
SUB ( state, val) {
state. count -= val
}
}
const actions = {
add ( { commit} , val) {
commit ( 'ADD' , val)
} ,
sub ( { commit} , val) {
commit ( 'SUB' , val)
} ,
oddAdd ( { commit, state} , val) {
if ( state. count% 2 === 1 ) {
commit ( 'ADD' , val)
}
}
}
const getters = {
evenOrOdd ( state) {
return state. count% 2 === 0 ? '偶数' : '奇数' ;
}
}
import otherVuex from './otherVuex.stroe'
const modules = {
{
otherVuex. state, otherVuex. getters, otherVuex. mutations, otherVuex. actions
}
}
export default new Vuex. Store ( {
state,
mutations,
actions,
getters
} )
import store from './store'
new Vue ( {
store
} ) . $mount ( '#app' )
this . $store. state[ keyName] ;
export default {
computed: {
evenOrOdd ( ) {
return this . $store. getters. evenOrOdd
}
} ,
methods: {
add ( val) {
this . $store. dispatch ( 'add' , val)
} ,
sub ( val) {
this . $stor. dispatch ( 'sub' , val)
}
}
}
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
... mapState ( [ 'count' ] ) ,
... mapGetters ( [ 'evenOrOdd' ] )
} ,
methods: {
... mapActions ( [ 'add' , 'sub' , 'oddAdd' ] )
}
}
总结:vuex通过全局注入store对象实现组件间状态共享;
组件通过dispatch分发action给store,store调用对应事件的回调函数;
回调函数提交对mutations的请求,mutations对state进行修改;
state改变后直接分发或通过getter将数据给组件。
vue3
1.性能提升
2.新增特性 composition、setup、新组件、新API
安装命令 npm install -g @vue/cli
新建项目 vue create 项目名称
vue3与vue2
1.vue3模板template可以没有根标签
2.lang='ts'可以使用typescript
3.defineComponent函数,定义组件,传入组件对象
vue3-setup、ref、reactive
1.setup-函数,执行一次
返回对象,对象中属性可在模板中直接使用
2.ref-定义响应式数据(基本类型:number,boolean,sting,undefined,null)
const num = ref(0),num为对象
3.reactive-定义响应式数据(复杂数据,对象,数组)
const obj = reactive({}),proxy代理对象(深层次)
< template>
< span> {{count}}</ span>
< button @click = " add" > +1</ button>
< button @click = " updateAge" > birthday</ button>
</ template>
< script lang = " ts" >
import { defineComponent, ref, reactive } from 'vue' ;
export default defineComponent ( {
name: 'App' ,
setup ( ) {
console. log ( '执行一次' )
const count = ref ( 0 )
const obj = {
name: 'xzp' ,
age: 18 ,
fruit: [ 'apple' ]
}
const user = reactive ( obj)
function add ( ) {
count. value ++
}
const updateAge = ( ) => {
user. age += 1
user. fruit. push ( 'org' )
user. love = 'yyy'
}
return { count, add, updateAge }
}
} ) ;
</ script>
setup细节问题
1.setup于beforeCreate回调之前执行,且执行单次
2.不可以在setup中使用this
3.composition API相关回调不可用
4.setup返回值为对象,内部属性与方法,模板可用对象属性
5.setup对象、data对象、methods对象合并,模板中可用对象属性
6.vue3中尽量不要混合使用data,methods,setup(重名,setup优先)
7.methods可访问setup提供的属性与方法,setup不可访问data与methods
8.setup不可以异步
setup参数
setup ( props, context) {
context. emit ( 'xxx' , str: string) ;
}
ref/reactive细节问题
1.ref传入对象,需要经过reactive处理,形参proxy类型的对象
2.ref在js中需要.value获取内容,模板中不需要(模板解析自动添加.value)
属性计算computed和监视watch
import { computed, watch, watchEffect} from 'vue'
setup ( ) {
const user = reactive ( {
firstName: 'x' ,
lastName: 'zp'
} )
const fullName = computed ( ( ) => {
return user. firstName + '_' + user. lastName
} )
const fullName2 = computed ( {
get ( ) {
return user. firstName + user. lastName
} ,
set ( val: string) {
let names = val. split ( '_' )
user. firstName = names[ 0 ]
user. lastName = names[ 1 ]
}
} )
watch ( user, ( val) => {
fullName3. value = val. firstName + '_' + val. lastName
} , { immediate: true , deep: true } )
return {
user,
fullName,
fullName2,
fullName3
}
}
watchEffect 与 watch
1.watchEffect不需要配置immediate,默认执行
2.watch可以监视多个数据
3.watch监视非响应式数据需要
watch ( [ ( ) => user. firstName, full3Name] , ( ) => {
} )
vue3生命周期
beforeCreate -> setup
created -> setup
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
<template>
<div class="about">
<h2>msg: {{msg}}</h2>
<hr>
<button @click="update">更新</button>
</div>
</template>
<script lang="ts">
import {
ref, onMounted, onUpdated,
onUnmounted, onBeforeMount,
onBeforeUpdate, onBeforeUnmount
} from "vue"
export default {
/* ----2.x---- */
beforeCreate () {
console.log('beforeCreate()')
},
created () {
console.log('created')
},
beforeMount () {
console.log('beforeMount')
},
mounted () {
console.log('mounted')
},
beforeUpdate () {
console.log('beforeUpdate')
},
updated () {
console.log('updated')
},
/* 3.x中beforeDestroy */
beforeUnmount () {
console.log('beforeUnmount')
},
/* 3.x中destroyed */
unmounted () {
console.log('unmounted')
},
/* ----3.x---- */
setup() {
const msg = ref('abc')
const update = () => {
msg.value += '--'
}
onBeforeMount(() => {
console.log('--onBeforeMount')
})
onMounted(() => {
console.log('--onMounted')
})
onBeforeUpdate(() => {
console.log('--onBeforeUpdate')
})
onUpdated(() => {
console.log('--onUpdated')
})
onBeforeUnmount(() => {
console.log('--onBeforeUnmount')
})
onUnmounted(() => {
console.log('--onUnmounted')
})
return { msg, update}
}
}
</script>
3.x的生命周期函数执行比2.x中的快
hook函数
新建文件夹hooks,文件命名useXXX.ts
import { ref, onMounted, onBeforeUnmount } from 'vue'
export default function ( ) {
const x = ref ( 0 )
const y = res ( 0 )
const clickEvent = ( event: MouseEvent) {
x. value = event. pageX
y. value = event. pageY
}
onMounted ( ( ) => {
window. addEventListener ( 'click' , clickEvent)
} )
onBeforeUnmount ( ( ) => {
window. removeEventListener ( 'click' , clickEvent)
} )
return { x, y}
}
import useMouseP from './hooks/useMouseP'
setup ( ) {
const { x, y} = useMouseP ( )
}
自定义hooks函数
封装request
import { ref } from 'vue'
import axios from 'axios'
export default function < T > ( url: string ) {
const loading = ref ( true )
const data = ref< T | null > ( null )
const errorMsg = ref ( '' )
axios. get ( url) . then ( response => {
loading. value = false
data. value = response. data
} ) . catch ( error => {
loading. value = false
errorMsg. value = error. message || '未知错误'
} )
return {
loading, data, errorMsg
}
}
const {
loading,
data, errorMsg } = useRequest< object> ( '/login' )
toRefs
可以将响应式对象转换成普通对象,该普通对象的每个属性
都是单个的ref
setup ( ) {
const state = reactive ( {
name: 'xzp' ,
age: 25
} )
const { name, age } = toRefs ( state)
function updateAge ( ) {
age. value ++
}
return {
name, age
}
}
ref获取元素
< template>
< input type = 'text' ref= 'inputName' >
< / template>
< script lang= "ts" >
import { defineComponent, ref, onMounted } from 'vue'
export default defineComponent ( {
name: 'app' ,
setup ( ) {
const inputRef = ref< HTMLElement | null > ( null )
onMounted ( ( ) => {
inputRef. value && inputRef. value. focus ( )
} )
return { inputRef }
}
} )
< / script>
< style>
< / style>
shallowReactive 与 shallowRef
1.shallowReactive 浅监控(data.a)
2.shallowRef 浅监控(处理value的响应式
不进行对象的reactive处理)
readonly 与 shallowReadonly
1.readonly 深度只读
2.shallowReadonly 浅只读(data.a.b深数据可以改)
toRaw 与 markRow
import { toRaw, markRow } from 'vue'
interface UserInfo {
name: string ;
age: number ;
likes? : string [ ] ;
}
setup ( ) {
const state = reactive ( {
name: "xzp" ,
age: 25
} )
const testToRaw = ( ) => {
const user = toRaw ( state)
user. age += 1
}
const testMarkRaw = ( ) => {
const like = [ 'yyy' , 'jsx' ]
state. likes = markRaw ( likes)
}
return {
state
}
}
toRef特点与使用
setup ( ) 【
const state = reactive ( {
age: 25 ,
money: 25
} )
const age = toRef ( state, 'age' )
const money = ref ( state. money)
自定义ref
自定义hook防抖函数
import { customRef } from 'vue'
function useDebouncedRef< T > ( value: T , delay = 200 ) {
let timeOutId: number
return customRef ( ( track, trigger) => {
return {
get ( ) {
track ( )
return value
} ,
set ( newValueT) {
clearTimeout ( timeOutId)
setTimeout ( ( ) => {
value = newValue
trigger ( )
} , delay)
}
}
} )
}
provide 与 inject
组件通信(数据非响应式)
import { provide, inject } from 'vue'
setup ( ) {
const name = ref ( 'name' )
provide ( 'name' , name)
return { name }
}
setup ( ) {
const name = inject ( 'name' )
return { name }
}
响应式数据判断
1.isRef 判断数据是否为ref对象
2.isReactive 判断数据是否为reactive创建的响应式代理
3.isReadonly 判断数据是否为readonly创建的只读代理
4.isProxy 判断数据是否为reactive或readonly方法创建的代理
vue2和vue3的区别
vue3支持大多数vue2的特性
vue3存在一套更强的组合API代替vue2中option IPI 复用性更强
更好的支持typescript
重写虚拟dom,性能更好