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细节问题
// 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() {},


setup(props, { attrs, slots, emit }) {
// props参数,是一个对象,里面有父级组件向子级组件传递的数据,并且是在子级组件中使用props接收到的所有的属性
// 包含props配置声明且传入了的所有属性的对象
// console.log(props.msg)
// console.log(context.attrs)
// console.log(context.emit)
// context参数,是一个对象,里面有attrs对象(获取当前组件标签上的所有的属性的对象,但是该属性是在props中没有声明接收的所有的尚需经的对象),emit方法(分发事件的),slots对象(插槽)
// 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs
 

 

 

 

defineComponent 函数,目的是定义一个组件,内部可以传入一个配置对象
// 暴露出去一个定义好的组件
export default defineComponent({
  // 当前组件的名字是App
  name: 'App',
  // 测试代码 setup是组合API中第一个要使用的函数
  // setup(){
  //   const number =10
  //   return {
  //     number
  //   }
  // }
})

 

ref是一个函数,作用:定义一个响应式的数据,返回的是一个Ref对象,对象中有一个value属性,如果需要对数据进行操作,需要使用该Ref对象调用value属性的方式进行数据的操作

 

reactive 定义多个数据的响应式
const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
响应式转换是“深层的”:会影响对象内部所有嵌套的属性
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
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再次代理一次,所以整个对象的属性都是响应式的,也就是下边这样的写法;

 

 

reactive和ref的细节问题
// 是Vue3的 composition API中2个最重要的响应式API(ref和reactive)
   // ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
    // 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象
    // ref内部: 通过给value属性添加getter/setter来实现对数据的劫持
    // reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据
    // ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)

 

 

 

在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
好处: 减少标签层级, 减小内存占用
 
 

// 引入组件:静态引入和动态引入


//
Vue2中的动态引入组件的写法:(在Vue3中这种写法不行)
const AsyncComponent = () => import('./AsyncComponent.vue')


 // Vue3中的动态引入组件的写法
 const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'))

// 静态引入组件
// import AsyncComponent from './AsyncComponent.vue'



 

 watchEffect
立即执行传入的一个函数,并响应式追踪其依赖,当依赖变更时重新运行该函数
const count = ref(0)

watchEffect(() => console.log(count.value))  // -> 打印出 0

setTimeout(() => {
  count.value++   // -> 打印出 1
}, 100)


停止侦听,当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时, 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。

const stop = watchEffect(() => {
  /* ... */
})

// 之后
stop()

 

 
 
 
 
 
 
 
 suspense
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方法)

1. handler (newVal, oldVal)-这是我们的watch方法本身。
2.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传入内部组件 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2020-04-09 11:51  慕斯undefined  阅读(337)  评论(0编辑  收藏  举报