vue知识
vue3.2更新
pinia使用 // counter.js import {defineStore} from 'pinia' import {useAppStore} from './app.js' // pinia通过defineStore函数来创建一个store,它接收一个id用来标识store,以及store选项 // 我们也可以使用一个回调函数来返回options,回调函数体内的写法类似vue的setup()写法 // 对状态的结构需要使用StoreToRefs函数 /* export const useCounterStore = defineStore('counter', () => { const count = ref(0) function doubleCount() { return count.value * 2 } function increment() { count.value++ } return { count, increment } }) * */ export const useCounterStore = defineStore('counter', { state: () => { return { count: 10, a: 123, } }, getters: { doubleCount: (state) => state.count * 2 }, actions: { async login(account, pwd) { const {data} = await api.login(account, pwd) // this.setData(data) // 调用另一个action的方法 const appStore = useAppStore() appStore.setData(data) // 调用 app store 里的 action 方法 return data }, setData(data) { console.log('data -> ', data) }, increment() { this.count++ const appStore = useAppStore() appStore.setData(this.count) // 调用 app store 里的 action 方法 }, } }) // app.js import { defineStore } from "pinia"; export const useAppStore = defineStore({ id: 'app', actions: { setData(data) { console.log('data -> ', data) } } }) 数据持久化 cnpm i pinia-plugin-persist --save import { createPinia } from 'pinia' import piniaPluginPersist from 'pinia-plugin-persist' const store = createPinia() store.use(piniaPluginPersist) export default store 接着在对应的 store 里开启 persist 即可。 export const useUserStore = defineStore({ id: 'user', state: () => { return { name: '张三' } }, // 开启数据缓存 persist: { enabled: true, strategies: [ { key: 'my_counter', storage: localStorage, paths: ['name', 'age'] } ] } })
// 演示三种方法修改state (直接,对象key val, 函数(里面可以执行复杂逻辑))
const changeAppstoreStateClick = () => {
// 方式一:直接修改 -> 'direct'
appStore.clientWidth += 400;
// 方式二:patch对象方式 -> 'patch object',调用$patch 传入要修改的键和val
appStore.$patch({
clientWidth: appStore.clientWidth + 400,
});
// 方式三:patch函数方式 -> 'patch function',可键入语句,执行复杂逻辑
appStore.$patch((state) => {
state.clientWidth += 400;
});
};
生命周期函数有: onBeforeMount、onMounted、onBeforeUpdate、onUpdated、
onBeforeUnmount、onUnmounted、onErrorCaptured、onRenderTracked、
onRenderTriggered、onActivated、onDeactivated
// 当捕获一个来自子孙组件的错误时被调用
onErrorCaptured(()=>{ console.log('onErrorCaptured',test) })
// onActivated 这个api要在setup函数才能触发生效,具体原因不知为啥,试过在setup语法中不生效的坑
<template>
<div>我是keep-alive组件 触发 onActivated</div>
</template>
<script>
import {defineComponent,onActivated} from 'vue'
export default defineComponent({
setup() {
onActivated(()=>{
console.log('我是 keep-alive')
})
},
})
</script>
defineEmits // 导入 defineEmits 组件 import { defineEmits } from "vue"; // 获取 emit const emit = defineEmits(["say-hi", "chang-name"]); // 调用 emit 打招呼 emit("say-hi", "Hello!"); // 调用 emit 改名 emit("chang-name", "Tom");
defineProps // 子组件获取父组件传递过来的值 defineProps({ names: { type: Array, default: () => [] } })
defineExpose
用在子组件暴露出自己的属性,如果不向外暴露这个api,父组件无法拿到对应的值 !!
但在script-setup模式下,所有数据只是默认return给template 使用,不会暴露到组件外,所以父组件是无法直接通过挂载ref 变量获取子组件的数据。
如果要调用子组件的数据,需要先在子组件显示的暴露出来,才能够正确的拿到,这个操作,就是由defineExpose来完成。
effectScope
<template> <h1>counter {{counter}}</h1> <h1>doubled {{doubled}}</h1> </template> <script setup> import {computed, effectScope, ref, watch, watchEffect} from 'vue' /* * * 使用effectScope API,创建一个 effect 作用域对象,以捕获在其内部创建的响应式 effect * (例如计算属性 computed或侦听器watch,watchEffect),使得这些 effect 可以一起被处理。 effectScope是一个函数,调用effectScope函数会返回一个对象,其中包含了run(一个函数)和stop(一个函数); * * */ /* * * 如果没有调用scope.stop(),浏览器一直会输出结果 * 当调用了stop之后,浏览器只会输出一次 * */ const scope = effectScope() const counter = ref(0) const doubled = ref(0) // 5秒修改一次 setInterval(() => { counter.value++ }, 3000) // 创建一个 effect 作用域对象 scope.run(() => { doubled.value = computed(() => counter.value * 2) watch(doubled.value, () => console.log('doubled:', doubled.value)) watchEffect(() => console.log('Count: ', counter.value)) }) scope.run(() => { watchEffect(() => console.log(`counter: ${counter.value}`)) }) scope.stop() </script>
// 具名插槽 App.vue 父组件 <template> <show-names :names="names"> <template v-slot="slotProps"> <button @click="slotProps.onClick">{{slotProps.item}}-{{slotProps.index}}</button> </template> <template #why> <!-- <template v-slot:why>--> <div>我是在有名字的插槽位置</div> </template> </show-names> </template> <script setup> import ShowNames from './ShowNames.vue' const names = ["why", "kobe", "james", "curry"] </script> // 子组件 show-names.vue <template> <div> <template v-for="(item) in names" :key="item"> <slot :item="item" :onClick="onClick" :index="(Math.random()).toFixed(3)"></slot> <slot name="why"></slot> </template> </div> </template> <script setup> defineProps({ names: { type: Array, default: () => [] } }); function onClick () { console.log("onClick 组件的监听-> "); } </script> -----------------------
<!-- 我们的插槽只有默认插槽时,组件的标签可以被当做插槽的模板来使用,这样,我们就可以将 v-slot 直 接用在组件上-->
<show-names :names="names" v-slot="slotProps">
<button>{{ slotProps.item }}-{{ slotProps.index }}</button>
</show-names>
给插槽绑定事件
// App.vue <template> <!-- 给插槽(slot)绑定事件 --> <add> <template v-slot="{ addNum }"> <h1 @click="addNum">点击我就可以啦</h1> </template> </add> </template> // add.vue <template> <div> <h1>{{ num }}</h1> <slot :add-num="addNum"> <button @click="addNum">add</button> </slot> </div> </template> <script setup> import {ref} from "vue" const num = ref(10) function addNum () { num.value++ } </script>
异步组件+component
<template> <button @click="btnClick">click</button> <!-- 使用组件 --> <component v-if="tabComponent" :is="tabComponent"></component> </template> <script setup> import { defineAsyncComponent, ref } from 'vue'; // 定义异步组件 const components = { testComponent: defineAsyncComponent(() => import('./asyncComponent.vue')), testComponent2: defineAsyncComponent(() => import('./asyncComponent2.vue')), } const tabComponent = ref('') const num = ref(0) // 切换异常组件 function btnClick() { num.value++ if (num.value === 1) { tabComponent.value = components['testComponent'] } if (num.value === 2) { tabComponent.value = components['testComponent2'] } num.value === 2 ? num.value = 0 : null // console.log('tabComponent.value -> ', tabComponent.value) // 异步组件,用对象嵌套就可以用 字符 去匹配异步组件了。 // tabComponent.value = tabComponent.value ? '' : components['testComponent'] } </script>
v-bind
<script setup> import {ref} from 'vue' const color = ref('blue') const onChange = () => { color.value = 'red' } </script> <template> <p class="colored" >{{ color }} colored text!</p> <button @click="onChange"> Change color to red! (once and for all) </button> </template> <style scoped> .colored { color: v-bind(color); } </style>
// mitt eventbus.js使用 import mitt from 'mitt' const emitter = mitt() // export const emitter1 = mitt(); // export const emitter2 = mitt(); // export const emitter3 = mitt(); export default emitter // app.vue <home/> <about/> // about.vue <button @click="btnClick">按钮点击</button> function btnClick() { console.log("about按钮的点击"); emitter.emit("why", {name: "why", age: 18}) // emitter.emit("kobe", {name: "kobe", age: 30}) } // home.vue <home-content /> // home-content.vue setup(){ emitter.on("why", (info) => { console.log("why000:", info); }); emitter.on("kobe", (info) => { console.log("kobe:", info); }) }
Suspense
// App.vue <template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>稍等,加载中...</h3> </template> </Suspense> </div> </template> <script> // import Child from './components/Child'//静态引入 import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child')) //异步引入 export default { name:'App', components:{Child}, } </script> <style> .app{ background-color: gray; padding: 10px; } </style> // Child <template> <div class="child"> <h3>我是Child组件</h3> {{sum}} </div> </template> <script> import {ref} from 'vue' export default { name:'Child', async setup(){ let sum = ref(0) let p = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve({sum}) },3000) }) return await p } } </script> <style> .child{ background-color: skyblue; padding: 10px; } </style>
Teleport
// App.vue -> Child.vue -> Son.vue // Son.vue有个Dialog.vue组件 // Son.vue <template> <div> <button @click="isShow = true">点我弹个窗</button> <teleport to="body"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一个弹窗</h3> <h4>一些内容</h4> <h4>一些内容</h4> <h4>一些内容</h4> <button @click="isShow = false">关闭弹窗</button> </div> </div> </teleport> </div> </template> <script> import {ref} from 'vue' export default { name:'Dialog', setup(){ let isShow = ref(false) return {isShow} } } </script> <style> .mask{ position: absolute; top: 0;bottom: 0;left: 0;right: 0; background-color: rgba(0, 0, 0, 0.5); } .dialog{ position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); text-align: center; width: 300px; height: 300px; background-color: green; } </style>
customRef
<template> <input type="text" v-model="keyWord"> <h3>{{keyWord}}</h3> </template> <script> import {ref,customRef} from 'vue' export default { name: 'App', setup() { //自定义一个ref——名为:myRef function myRef(value,delay){ let timer return customRef((track,trigger)=>{ return { get(){ console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`) track() //通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的) return value }, set(newValue){ console.log(`有人把myRef这个容器中数据改为了:${newValue}`) clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() //通知Vue去重新解析模板 },delay) }, } }) } // let keyWord = ref('hello') //使用Vue提供的ref let keyWord = myRef('hello',500) //使用程序员自定义的ref return {keyWord} } } </script>
provide/inject
// App.vue
<template>
<div>
<home></home>
<button @click="addName">+name</button>
</div>
</template>
<script setup>
import Home from "./Home.vue"
import { computed, provide, ref } from "vue"
const names = ref(["abc", "cba", "nba"])
provide("name", "why")
provide("age", 18)
provide("length", computed(() => names.value.length))
function addName () {
names.value.push("why")
}
</script>
// home.vue
<template>
<home-content />
</template>
<script>
import HomeContent from "./home-content.vue"
</script>
// home-content.vue
<template>
<div>
homeContent: {{name}} - {{age}} - {{length}}
</div>
</template>
<script setup>
import { inject } from "vue";
const name = inject('onShowChannel')
const age = inject('age')
const length = inject('length')
</script>
Vue3.0
vue3中组件发送的自定义事件需要在emits选项中 原生事件会触发两次,比如click 更好的指示组件工作方式 <template> <div @click="$emit('click')"> 这是一个emits事件 </div> </template> <script> export default { name: "Emits", emits: ['click'] } </script>
--------------------
父
<Emits @item-click="EmitsClick"/>
子组件
<template>
<div @click="$emit('item-click')">
这是一个emits事件
</div>
</template>
<script>
export default {
name: "Emits",
}
</script>
// 弹框设置在app外层 <teleport to="body"> <div class="mask" v-show="show"></div> </teleport>
// 引入组件: 静态引入和动态引入 // vue2中的动态引入组件的写法:(在vue3中这种写法不行) const AsyncComponent = () => import('./AsyncComponent.vue') // vue3中动态引入组件的写法 const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'))
// 自定义hook防抖的函数 function useDebouncedRef<T> (value: T, delay = 200) { // 准备一个存储定时器的id变量 let timeOutId: number return customRef((track, trigger) => { return { // 返回数据 get() { // 告诉Vue追踪数据 track() return value }, // 设置数据 set(newValue) { // 清理定时器 clearTimeout(timeOutId) // 开启定时器 timeOutId = setTimeout(() => { value = newValue // 告诉Vue更新界面 trigger() }, delay) }, } }) }
selectedKeys: computed(() => { return [route.path] })
provide / inject
---------------------
Father.vue
import { provide } from 'vue'
setup () {
provide('name', '张三');
provide('age', 26)
}
Child.vue
import { inject } from 'vue'
inject: ['name', 'age']
// 子组件中如果 inject没有获得到父组件,可以用默认值
// let title = inject('key', 123)
----------------------
Father.vue 父组件修改值,影响到子组件
setup () {
const name = ref('张三')
provide('name', name)
const changeName = () => {
name.value = '李四'
}
return {
changeName
}
}
------------------------
Father.vue 父组件传递修改的方法给子组件,子组件调用修改
setup () {
const name = ref('张三')
const changeName = () => {
name.value = '李四'
}
provide('name', name)
provide('changeName', changeName)
}
Child.vue
setup () {
const name = inject('name', '无名')
const changeName = inject('changeName')
return { name, changeName }
}
// 防抖 import { defineComponent, customRef } from "vue"; function useDebouncedRef<T>(value: T,delay=200) { let timeout: number return customRef((track,trigger) => { return{ get(){ //告诉vue追踪数据 track() return value }, set(newValue: T){ clearTimeout(timeout) timeout = setTimeout(() => { //更换数据 value = newValue //告诉vue去触发页面更新 trigger() },delay) } } }) }
var initData = function(){
return {
v: 1
}
}
class K{
data = initData()
}
var k1 = new K()
var k2 = new K()
console.group('1')
console.log(k1.data.v);
console.log(k2.data.v);
console.groupEnd('1')
k1.data.v += 1;
console.group('2')
console.log(k1.data.v);
console.log(k2.data.v);
console.groupEnd('2')
---------------------------------------------
function component(options) {
return function() {
this.data = options.data();
}
};
var K = component({
data() {
return {
v: 1
}
}
});
var k1 = new K()
var k2 = new K()
console.group('1')
console.log(k1.data.v);
console.log(k2.data.v);
console.groupEnd('1')
k1.data.v += 1;
console.group('2')
console.log(k1.data.v);
console.log(k2.data.v);
console.groupEnd('2')
// setup是在beforeCreate生命周期回调之前就执行了,而且就执行一次 // 由此可以推断出:setup在执行的时候,当前的组件还没有创建出来,也就意味着:组件实例对象this根本就不能用 // this是undefined,说明,就不能通过this再去调用data/computed/methods/props中的相关内容了 // 其实所有的composition API相关回调函数中也都不可以 // setup中的返回值是一个对象,内部的属性和方法是给html模版使用的 // setup中的对象内部的属性和data函数中的return对象的属性都可以在html模版中使用 // setup中的对象中的属性和data函数中的对象中的属性会合并为组件对象的属性 // setup中的对象中的方法和methods对象中的方法会合并为组件对象的方法 // 在Vue3中尽量不要混合的使用data和setup及methods和setup // 一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods // setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据 // beforeCreate() { // console.log('beforeCreate执行了') // }, // 界面渲染完毕 // mounted() {},
// 暴露出去一个定义好的组件 export default defineComponent({ // 当前组件的名字是App name: 'App', // 测试代码 setup是组合API中第一个要使用的函数 // setup(){ // const number =10 // return { // number // } // } })
const obj = { name: '小明', age: 20, wife: { name: '小甜甜', age: 18, cars: ['奔驰', '宝马', '奥迪'], }, } // 把数据变成响应式的数据 // 返回的是一个Proxy的代理对象,被代理的目标对象就是obj对象 // user现在是代理对象,obj是目标对象 // user对象的类型是Proxy const user = reactive<any>(obj) console.log(user) // 直接使用目标对象的方式来更新目标对象中的成员的值,是不可能的,只能使用代理对象的方式来更新数据(响应式数据) // obj.name += '===' // 下面的可以 // user.name += '==' // user.age += 2 // user.wife.name += '++' // user.wife.cars[0] = '玛莎拉蒂' // user---->代理对象,user---->目标对象 // user对象或者obj对象添加一个新的属性,哪一种方式会影响界面的更新 // obj.gender = '男' // 这种方式,界面没有更新渲染 // user.gender = '男' // 这种方式,界面可以更新渲染,而且这个数据最终也添加到了obj对象上了 // user对象或者obj对象中移除一个已经存在的属性,哪一种方式会影响界面的更新 // delete obj.age // 界面没有更新渲染,obj中确实没有了age这个属性 // delete user.age // 界面更新渲染了,obj中确实没有了age这个属性 // 总结: 如果操作代理对象,目标对象中的数据也会随之变化,同时如果想要在操作数据的时候,界面也要跟着重新更新渲染,那么也是操作代理对象 // 通过当前的代理对象找到该对象中的某个属性,更改该属性中的某个数组的数据 // user.wife.cars[1] = '玛莎拉蒂'
toRefs
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
为什么要用 ...toRefs(obj) 包裹起来
reactive,实际上是使用的proxy代理了整个对象,返回的是整个对象都是响应式,但是如果这个对象里边的层级很多怎么办,对象中还有对象,那么里边的对象就不是一个响应式了,所以就用toRefs包裹一下,这样相当于是整个对象里边的属性,在源码内部已经做了一次递归,把所有的属性都做了判断,如果是引用类型就会使用proxy再次代理一次,所以整个对象的属性都是响应式的,也就是下边这样的写法;
// 是Vue3的 composition API中2个最重要的响应式API(ref和reactive) // ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式) // 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象 // ref内部: 通过给value属性添加getter/setter来实现对数据的劫持 // reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据 // ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)
// Vue2中的动态引入组件的写法:(在Vue3中这种写法不行)
const AsyncComponent = () => import('./AsyncComponent.vue')
立即执行传入的一个函数,并响应式追踪其依赖,当依赖变更时重新运行该函数 const count = ref(0) watchEffect(() => console.log(count.value)) // -> 打印出 0 setTimeout(() => { count.value++ // -> 打印出 1 }, 100)
停止侦听,当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时, 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。 const stop = watchEffect(() => { /* ... */ }) // 之后 stop()
suspense template m xcvbm,c bmn, Vue.directive('focus', { bind: function(el){ 每当指令绑定元素上的时候,会立即执行这个bing函数,只执行一次 注意:在每个函数值中第一个参数永远是el,表示绑定了指令的那个元素,这个el参数是一个原生的js对象 在元素 刚绑定指令的时候,还没有插入到DOM中去,这个时候调用focus方法没有作用 }, inserted: function(){ i表示元素插入到DOM中的时候,会执行inserted函数 触发一次 }, updated: function(){ 当Node更新时候会执行updated,可能会触发多次 } }) default 异步组件 template v-solt:fallback h2 Loading... suspense
-----------------------------------------------------
完整的导航解析流程
- 触发进入其它路由
- 调用要离开路由的组件守卫beforeRouteLeave
- 调用全局的前置守卫beforeEach
- 在重用的组件里调用 beforeRouteUpdate
- 在路由配置里调用 beforeEnter
- 解析异步路由组件
- 在将要进入的路由组件中调用beforeRouteEnter
- 调用全局的解析守卫beforeResolve
- 导航被确认
- 调用全局的后置钩子afterEach。
- 触发 DOM 更新mounted。
- 执行beforeRouteEnter守卫中传给 next的回调函数
路由守卫分类 【1】全局守卫:是指路由实例上直接操作的钩子函数,特点是所有路由配置的组件都会触发,直白点就是触发路由就会触发这些钩子函数 beforeEach(to,from, next) beforeResolve(to,from, next) afterEach(to,from) 【2】路由守卫: 是指在单个路由配置的时候也可以设置的钩子函数 beforeEnter(to,from, next) 【3】组件守卫:是指在组件内执行的钩子函数,类似于组件内的生命周期,相当于为配置路由的组件添加的生命周期钩子函数。 beforeRouteEnter(to,from, next) beforeRouteUpdate(to,from, next) beforeRouteLeave(to,from, next) 路由守卫回调参数介绍 to:即将要进入的目标路由对象;from:即将要离开的路由对象 next:涉及到next参数的钩子函数,必须调用next()方法来resolve这个钩子,否则路由会中断在这,不会继续往下执行 next():进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是confirmed(确认的) next( false )中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from路由对应的地址 next( ' / ')或者next({ paht:' / ' }):跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。可传递的参数可以是router-link标签中的to属性参数或router.push中的选项 next( error ):如果传入next的参数是一个Error实例,则导航会被终止且该错误会被传递给router.onError()注册过的回调 路由守卫详解 【1】全局解析守卫(beforeEach): 在路由跳转前触发,这个钩子作用主要是用于登录验证,也就是路由还没跳转提前告知,以免跳转了再通知就为时已晚 const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... }) 【2】全局解析守卫(beforeResolve): 这个钩子和beforeEach类似,也是路由跳转前触发,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,即在 beforeEach 和 组件内beforeRouteEnter 之后,afterEach之前调用 【3】全局后置钩子(afterEach): 和beforeEach相反,它是在路由跳转完成后触发,它发生在beforeEach和beforeResolve之后,beforeRouteEnter(组件内守卫)之前。这些钩子不会接受next函数也不会改变导航本身 router.afterEach((to, from) => { // ... }) 4】路由独享守卫(beforeEnter): 和beforeEach完全相同,如果两个都设置了,beforeEnter则在beforeEach之后紧随执行。在路由配置上直接定义beforeEnter守卫 const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // ... } } ] }) 【5】组件内的守卫 <template> ... </template> <script> export default{ data(){ //... }, beforeRouteEnter (to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 `this` // 因为当守卫执行前,组件实例还没被创建 }, beforeRouteUpdate (to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 `this` }, beforeRouteLeave (to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 `this` } } </script> <style> ... </style> 1. beforeRouteEnter:该钩子在全局守卫beforeEach和独享守卫beforeEnter之后,全局beforeResolve和全局afterEach之前调用,要注意的是该守卫内访问不到组件的实例,也就是this为undefined。因为它在组件生命周期beforeCreate阶段触发,此时的新组件还没有被创建。在这个钩子函数中,可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数 beforeRouteEnter (to, from, next) { next(vm => { // 通过 `vm` 访问组件实例 }) } 2. beforeRouteUpdate:在当前路由改变时,并且该组件被复用时调用,可以通过this访问实例。3. beforeRouteLeave:导航离开该组件的对应路由时调用,可以访问组件实例this。这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过next( false )来取消。 beforeRouteLeave (to, from , next) { const answer = window.confirm('Do you really want to leave? you have unsaved changes!') if (answer) { next() } else { next(false) } }
Vue.directive
Vue.directive('focus', { bind: function(el){ 每当指令绑定元素上的时候,会立即执行这个bing函数,只执行一次 注意:在每个函数值中第一个参数永远是el,表示绑定了指令的那个元素,这个el参数是一个原生的js对象 在元素 刚绑定指令的时候,还没有插入到DOM中去,这个时候调用focus方法没有作用 }, inserted: function(){ i表示元素插入到DOM中的时候,会执行inserted函数 触发一次 }, updated: function(){ 当Node更新时候会执行updated,可能会触发多次 } })
事件修饰符
.stop 阻住冒泡
.prevent 阻止默认行为
.capture 捕获阶段发生
.self 自己作为事件源的时候才发生
.once 执行一次
.passive 一般在移动端的滚动事件中使用(不阻止默认行为),修饰符尤其能够提升移动端的性能
键盘修饰符
.enter
.tab
.delete
.esc
.space
.up
.down
.left
.right
儿子要调用父亲的方法
1. <my-button @click="change"></my-button> 父亲
<div><button @click="$listenters.click()">点我啊</button></div> 子
2. <div> 子
<button @click="$listenters.click()">点我啊</button>
<button @click="$emit('click')">点我啊</button>
</div>
3. v-bind=$attrs v-on=$listeners 绑定所有的方法
<div> 子
<button @click="$listenters.click()">点我啊</button>
<button @click="$emit('click')">点我啊</button>
<button v-on='$listeners'>点我啊</button>
</div>
watch可以检测到的数据 (凡是vue实例覆盖的东西,能this访问到的都可以检测)
data的数据
计算属性的数据
路由的数据 $route
vuex
data作用
声明响应式数据,跟计算属性不同,计算属性可以是负责一些逻辑
SPA
1.首屏加载速度慢 2.不利于SEO搜索引擎优化(可以通过SSR服务端渲染)
beforeRouteLeave(to, from, next) {
// 填写资料或编辑时候,还保存
const leave = confirm('您确定要离开吗?')
if(leave) next()
else next(false)
}
beforeRouteEnter(to, from, next) {
next(vm => { console.log(vm) }) 这个时候才能访问this
}
~Vuex管理的状态机制
vuex是一个专门为vue应用程序开发状态管理的vue插件。作用:集中式管理vue多个组件共享状态和后台获取的数据
~如果你希望对某个元素拥有一系列的操作,你可以封装一个指令(自定义指令)
computed和watch区别
相同:computed和watch属性值都是一个函数
不同:computed 必须return一个返回值,watch可以编写大量逻辑,computed里面不适合发一部请求
~computed属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算,主要当作属性来使用
get方法有缓存,如果值没有更改会从缓存中取值
computed方法里面不要执行异步代码,比如修改一个值,过1秒后执行,放在watch执行
watch数据变化时执行异步或开销较大的操作,1.执行异步:ajax、定时器、数据库的操作 2.开销较大的操作:递归、循环次数较多的情况
~wacth一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作,可以看做是computed和methods结合体
~provide inject 和 context (可以在父组件中声明一个公共数据),在子组件中可以注入原理(不推荐程序使用),组件库,多级通信为了方便可以使用
~$attrs:属性的集合 (如果组件中使用了 props 就会将 arrts 从当前$attrs移除掉)
$listeners:方法的集合
应用:父传递给子元素 -> 儿子 有三个属性用不到 -> 孙子 。孙子组件用到全部属性 <Grandson v-bind="$attrs" />
设置inheritAttrs: false 传递给子组件的属性就不会在标签上显示
~v-model sync有什么区别
sync 是 :value=value @update:value = 'change'
如果改变的属性叫 a 对应的方法 叫update:a => :a.sync
:value + @input => v-model
<Son :count="count" @update:count="newValue => count = newValue"></Son>
这个写法是上面的替代品 默认组件内部需要触发 update:count 规定写法
<Son :count.sync="count">
<Son :count="count" @input="newValue => count = newValue"></Son>
这个写法是上面的替代品 默认组件内部需要触发 input 规定写法
<Son v-model="count">
v-model局限只能传递一个属性 如果只有一个可以使用 v-model,多个依然需要使用 .sync
~插槽(具名插槽、作用域插槽)
作用:将父组件前后标签中的HTML文件渲染插入模板中使用
具名插槽:父组件标签中的元素 添加 <h4 slot="aaa" >具名插槽</h4> ,然后在父组件<slot name="bbb"></slot>
~每个组件都有一个uid标识,v-for循环 :key = `${uid}_${index}`
~设置找别名
通过内置的组件 <component :is='mode' /> 动态绑定组件,要显示哪个组件
从父类到子类的所有 props
从父类到子类的所有事件侦听器
假设有一个按钮组件,并且在某些情况下想监听单击事件,而在其他情况下想监听双击事件。 这就是动态指令派上用场的地方了
使用watch替代created就去请求数据 (immediate为真就会去执行handler方法)
handler (newVal, oldVal)-
这是我们的watch方法本身。immediate: true
- 代表如果在 wacth 里声明了之后,就会立即先去执行里面的handler
方法,如果为 false
就跟我们以前的效果一样,不会在绑定的时候就执行// 好的做法 methods: { handleChange() { // stuff happens } }, watch () { property { immediate: true handler() { this.handleChange() } } }
样式绑定
1. 直接传递一个数组,注意:这里的class 需要使用 v-bind做数据绑定
:class="['thin', 'italic']"
2.在数组中使用三元表达式
:class="['thin', 'italic', flag? 'active' :'']"
3.在数组中使用对象来代替三元表达式,提高代码的可读性
~嵌套子路由不要加 / 。。path:/argu/:name 记得加斜杠
nextTick实现原理?
nextTick方法主要是使用了宏任务和微任务,定义一个异步方法,多次调用nextTick会将方法存在队列中,通过这个异步方法清空当前队列。所以这个nextTick方法就是异步方法
这句话说的很乱,典型的让面试官忍不住想要深挖一探究竟的回答。(因为一听你就不是真的懂)
正确的流程应该是先去 嗅探环境
,依次去检测
Promise的then
-> MutationObserver的回调函数
-> setImmediate
-> setTimeout
是否存在,找到存在的就使用它,以此来确定回调函数队列是以哪个 api 来异步执行。
在 nextTick
函数接受到一个 callback
函数的时候,先不去调用它,而是把它 push 到一个全局的 queue
队列中,等待下一个任务队列的时候再一次性的把这个 queue
里的函数依次执行。
这个队列可能是 microTask
队列,也可能是 macroTask
队列,前两个 api 属于微任务队列,后两个 api 属于宏任务队列。
简化实现一个异步合并任务队列:
let pending = false
// 存放需要异步调用的任务
const callbacks = []
function flushCallbacks () {
pending = false
// 循环执行队列
for (let i = 0; i < callbacks.length; i++) {
callbacks[i]()
}
// 清空
callbacks.length = 0
}
function nextTick(cb) {
callbacks.push(cb)
if (!pending) {
pending = true
// 利用Promise的then方法 在下一个微任务队列中把函数全部执行
// 在微任务开始之前 依然可以往callbacks里放入新的回调函数
Promise.resolve().then(flushCallbacks)
}
}
复制代码
测试一下:
// 第一次调用 then方法已经被调用了 但是 flushCallbacks 还没执行
nextTick(() => console.log(1))
// callbacks里push这个函数
nextTick(() => console.log(2))
// callbacks里push这个函数
nextTick(() => console.log(3))
// 同步函数优先执行
console.log(4)
// 此时调用栈清空了,浏览器开始检查微任务队列,发现了 flushCallbacks 方法,执行。
// 此时 callbacks 里的 3 个函数被依次执行。
// 4
// 1
// 2
// 3
watch用法
1.常用用法
1.场景:表格初始进来需要调查询接口 getList(),然后input 改变会重新查询 created(){ this.getList() }, watch: { inpVal(){ this.getList() } }
2.立即执行
2.可以直接利用 watch 的immediate和handler属性简写 watch: { inpVal:{ handler: 'getList', immediate: true } }
3.深度监听
watch 的 deep 属性,深度监听,也就是监听复杂数据类型 watch:{ inpValObj:{ handler(newVal,oldVal){ console.log(newVal) console.log(oldVal) }, deep:true } }
此时发现oldVal和 newVal 值一样; 因为它们索引同一个对象/数组,Vue 不会保留修改之前值的副本;
所以深度监听虽然可以监听到对象的变化,但是无法监听到具体对象里面那个属性的变化
组件通讯?
1.props
父传子的属性; props 值可以是一个数组或对象;
// 数组:不建议使用 props:[] // 对象 props:{ inpVal:{ type:Number, //传入值限定类型 // type 值可为String,Number,Boolean,Array,Object,Date,Function,Symbol // type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认 required: true, //是否必传 default:200, //默认值,对象或数组默认值必须从一个工厂函数获取如 default:()=>[] validator:(value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } }
2.$emit
子组件触发父组件给自己绑定的事件,其实就是子传父的方法
// 父组件 <home @title="title"> // 子组件 this.$emit('title',[{title:'这是title'}])
3.vuex
state:定义存贮数据的仓库,可通过this.$store.state 或mapState访问 getter:获取 store 值,可认为是 store 的计算属性,可通过this.$store.getter 或mapGetters访问 mutation:同步改变 store 值,为什么会设计成同步,因为mutation是直接改变 store 值,vue 对操作进行了记录,如果是异步无法追踪改变.可通过mapMutations调用 action:异步调用函数执行mutation,进而改变 store 值,可通过 this.$dispatch或mapActions访问 modules:模块,如果状态过多,可以拆分成模块,最后在入口通过...解构引入
4.attrs:如果父传子有很多值,那么在子组件需要定义多个props解决 this.$attrs
listeners:子组件需要调用父组件的方法解决,父组件的方法可以通过 v-on=‘’listners传入内部组件