Options API & Composition API

150 阅读7分钟

Mixin

Mixin

对相同的代码逻辑进行抽取

const sayHelloMixin = {
    created(){
        console.log('hello world sayHelloMixin')
        console.log('title-----', this.title)
        this.sayHello()
    },
    data(){
        return {
          title: 'hello world title'
        }
    },
    methods: {
        sayHello(){
            console.log('hello page component')
        }
    },
}
export default sayHelloMixin;
<template>
  <div>
    hello
  </div>
</template>

<script>
import sayHelloMixin from '../utils/sayHello'
export default {
  name: '',
  mixins: [sayHelloMixin],
  data(){
    return {
      title: 'hello world title'
    }
  },
  methods: {
    sayHello(){
      console.log('hello world123')
    }
  },
  created() {
    console.log('hello world created')
    this.sayHello()
  }
}
</script>

<style lang="scss" scoped>
</style>

image.png

Mixin的合并规则

全局混入

// main.js
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mixin({
    data(){
        return {}
    },
    methods: {

    },
    created() {
        console.log('全局的created mixin');
    }
})
app.mount('#app')

extends

类似于Mixin basePage

<template>
  <div>
  </div>
</template>
<script>
export default {
  name: '',
  data() {
    return {
        title: 'hello Page'
    }
  },
  methods: {
    bar(){
        console.log("base page bar");
    }
  }
}
</script>
<style lang="scss" scoped>
</style>

home

<template>
  <div>
    <div>{{title}}</div>
    <button @click="bar">dianji</button>
  </div>
</template>

<script>
import BasePage from './BasePage.vue'
export default {
  name: '',
  extends: BasePage
}
</script>

<style lang="scss" scoped>
</style>

image.png

在开发中extends使用的非常少,在VUE2中比较推荐使用Mixin,而在Vue3中推荐使用Composition API

Options API的弊端

  • 在Vue2中,我们编写组件的方式是Options API:
    • Options API的一大特点就是在对应的属性中编写对应的功能模块;
    • 比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命 周期钩子;
  • 但是这种代码有一个很大的弊端:
    • 当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中;
    • 当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散;
    • 尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人);
  • 下面我们来看一个非常大的组件,其中的逻辑功能按照颜色进行了划分:
    • 这种碎片化的代码使用理解和维护这个复杂的组件变得异常困难,并且隐藏了潜在的逻辑问题;
    • 并且当我们处理单个逻辑关注点时,需要不断的跳到相应的代码块中;

image.png

image.png

  • 如果我们能将同一个逻辑关注点相关的代码收集在一起会更好。
  • 这就是Composition API想要做的事情,以及可以帮助我们完成的事情。
  • 也有人把Vue CompositionAPI简称为VCA

setup

setup-vue官网

参数

  • props
  • context

props

父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可 以直接通过props参数获取

  • 对于定义props的类型,我们还是和之前的规则是一样的,在props选项中定义;
  • 并且在template中依然是可以正常去使用props中的属性,比如message;
  • 如果我们在setup函数中想要使用props,那么不可以通过 this 去获取
  • 因为props有直接作为参数传递到setup函数中,所以我们可以直接通过参数来使用即可

context

称之为是一个SetupContext,它里面包含三个属性:

attrs

所有的非prop的attribute

slots

父组件传递过来的插件(这个在以渲染哈数返回时会有作用)

emit

当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件)

返回值

作用

  • setup的返回值可以在模板template中被使用
  • 也就是说我们可以通过setup的返回值来替代data选项
<template>
  <div>
    <div>title: {{title}}</div>
    <button @click="increment">+1</button>
    <div>{{counter}}</div>
  </div>
</template>

<script>
export default {
  name: '',
  setup() {
    let counter = 1;
    const increment = () => {
      counter++
      console.log(counter)
    }
    return {
      title: '标题',
      counter,
      increment
    }
  }
}
</script>

image.png

  • 将 counter 在 increment 或者 decrement进行操作时,是否可以实现界面的响应式呢?

不可以,因为对于一个定义的变量来说,默认情况下,Vue并不会跟踪它的变化,来引起界面的响应式操作;

Reactive API 和 Ref API

reactive

Reactive

  • 处理对象(递归深度响应式)
  • 内部使用Proxy来实现对对象内部所有数据的劫持,并通过Reflect操作对象内部数据
<template>
  <div>
    message: {{ message }}
    <button @click="btnClick">按钮</button>
    <div>{{state.counter}}</div>
  </div>
</template>

<script>
import { reactive } from 'vue'
export default {
  name: "",
  props: {
    message: {
      type: String,
      default: "xiaoxiao",
    },
  },
  setup(props, { attrs, slots, emit }) {
    const state = reactive({
      counter: 1
    })
    const btnClick = function() {
      // console.log('123yyy')
      state.counter++
      emit('btnClick', 123)
    }
    
    return {
      btnClick,
      state
    }
  }
};
</script>

传入基本数据类型(String、Number、Boolean)就会报一个警告

响应式的原因

使用reactive函数处理数据之后,数据再次被使用时就会进行依赖收集;当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);data选项也是在内部交给了reactive函数将其编程响应式对象的;

Ref

Ref

  • 处理基本类型数据
  • 通过给value属性添加getter/setter来实现对数据的劫持
  • 如果用ref对象/数组,内部会自动将对象/数组转换为reactive的代理对象

注意事项

  • 模板中引入ref的值,Vue会自动进行解包,并不需要在模板中通过ref.value的方式来使用
  • setup函数内部,依然是一个ref引用,所以对其进行操作时,依然需要使用ref.value的方式

模板中的解包是浅层的解包

<template>
  <div>
    <div>title: {{title}}</div>
    <button @click="increment">+1</button>
    <div>{{counter}}</div>
    <!-- 如果最外层包裹的时候一个reactive可响应式对象,那么内容的ref可以解包 -->
    <div>{{info.counter}}</div>
  </div>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
  name: '',
  components: {
    About
  },
  setup() {
    let counter = ref(1);
    let info = reactive({
      counter
    })
    const increment = () => {
      counter.value++
      console.log(counter.value)
    }
    return {
      title: '标题',
      counter,
      increment,
      info
    }
  }
}
</script>

readonly

readonly

  • 通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改,这个时候如何防止这种情况的出现呢?
    • Vue3为我们提供了readonly的方法;readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改);

参数

  • 普通对象
  • reactive返回的对象
  • ref的对象

使用

在readonly的使用过程中,有如下规则:

  • preadonly返回的对象都是不允许修改的;
  • 但是经过readonly处理的原来的对象是允许被修改的;
    • 比如 const info = readonly(obj),info对象是不允许被修改的;
    • 当obj被修改时,readonly返回的info对象也会被修改;
    • 但是我们不能去修改readonly返回的对象info;
  • 其实本质上就是readonly返回的对象的setter方法被劫持了而已;

image.png

应用

传递给其他组件数据时,希望其他组件使用传递的内容,但是不允许它们修改

Reactive判断的API

isProxy

isReactive

isReadonly

toRaw

toRaw

返回reactive或readonly代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用。)

<template>
  <div>
    <button @click="showInfo">showInfo</button>
  </div>
</template>
<script>
import { reactive, toRaw } from 'vue'
export default {
  name: '',
  setup(){
    const info = reactive({name: 'free'})
    const rawInfo = toRaw(info)
    const showInfo = () => {
        console.log(rawInfo);
    }
    return {
        showInfo
    }
  }
}
</script>

shallowReactive

shallowReadonly

toRefs

toRefs

  • 使用ES6的结构语法,对reactive返回的对象进行解构获取值,那么之后是修改解构后的变量,还是修改reactive返回的对象,数据都不再是响应式的
  • 使用toRefs解构出来的属性是响应式
    • Vue提供的toRefs的函数,可以将reactive返回的对象中的属性转成ref
  • 这种做法相当于在对象的属性(info.name)和ref.value之间建立了链接,任何一个修改都会引起另一个变化
<template>
  <div>
    <button @click="showInfo">showInfo</button>
    <div>{{info.name}} - {{info.age}} - {{info.sex}}</div>
    <div>{{name}} - {{age}}</div>
    <div>{{sex}}</div>
  </div>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
  name: '',
  setup(){
    const info = reactive({name: 'free', age: 18, sex: '女'})
    const rawInfo = info
    // let { name, age } = info
    let { sex } = info
    let { name, age } = toRefs(info)
    const showInfo = () => {
        info.name = 'unfree'
        info.sex = '男'
        age.value++
        console.log(rawInfo);
    }
    return {
        showInfo,
        info,
        name,
        age,
        sex
    }
  }
}
</script>

image.png

toRef

toRef

只希望转换一个reactive对象中的属性为ref

<template>
  <div>
    <button @click="showInfo">showInfo</button>
    <div>{{info.name}} - {{info.age}} - {{info.sex}}</div>
    <div>{{name}} - {{age}}</div>
    <div>{{sex}}</div>
  </div>
</template>

<script>
import { reactive, toRefs, toRef } from 'vue'
export default {
  name: '',
  setup(){
    const info = reactive({name: 'free', age: 18, sex: '女'})
    const rawInfo = info
    // let { name, age } = info
    // let { sex } = info
    let { name, age } = toRefs(info)
    let sex = toRef(info, 'sex')
    const showInfo = () => {
        info.name = 'unfree'
        info.sex = '男'
        age.value++
        console.log(rawInfo);
    }
    return {
        showInfo,
        info,
        name,
        age,
        sex
    }
  }
}
</script>

image.png

unref

  • 如果想要获取一个ref引用中的value,那么也可以通过unref方法
    • 如果参数是一个ref,则返回内部值,否则返回参数本身
    • val = isRef(val) ? val.value : val的语法糖函数
const name = ref(why)
foo(name)
foo('why')
function foo(bar){
    unref(bar) // 是val = isRef(val) ? val.value : val的语法糖函数
}

isRef

判断值是否是一个ref对象

shallowRef

创建一个浅层的ref对象

trigger

手动触发和shallowRef相关联的副作用

<template>
  <div>
    <button @click="changeInfo">changeInfo</button>
    <div>info: {{info.name}}</div>
    <button @click="changeInfo1">changeInfo1</button>
    <div>info1: {{info1.name}}</div>
  </div>
</template>

<script>
import { ref, shallowRef, triggerRef } from 'vue'
export default {
  name: '',
  setup(){
    const info = ref({name: 'code'})
    const info1 = shallowRef({name: 'high'})
    const changeInfo = () => {
      info.value.name = 'high'
      console.log('info', info.value);
    }
    const changeInfo1 = () => {
      info1.value.name = 'code'
      console.log('info1', info1.value);
      triggerRef(info1)
    }
    return {
      info,
      info1,
      changeInfo,
      changeInfo1
    }
  }
}
</script>

副作用

  • 界面的刷新
  • 依赖

customRef

  • 创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制
    • 它需要一个工厂函数,该函数接受track和trigger函数作为参数
    • 并且应该返回一个带有
<template>
  <div>
    <input v-model="name" />
    <div>{{name}}</div>
  </div>
</template>

<script>
import { ref } from 'vue'
import debounceRef from '@/hook/useDebounceRef'
export default {
  name: '',
  setup(){
    // const name = ref('zhansghan')
    const name = debounceRef('')
    return {
      name
    }
  }
}
</script>
import { customRef } from "vue";
// 自定义ref
export default function (value) {
    return customRef((track, trigger) => {
        /*
            track: 決定什么时候收集依赖
            trigger:决定什么时候触发所有依赖来更新
        */ 
       return {
        get() {
            // 收集依赖
            track()
            // 返回具体值
            return value
        },
        set(newValue) {
            value = newValue
            // 触发所有依赖更新
            trigger()
        }                        
       }
    })
}

上面的使用与直接使用ref的效果一致

案例:对双向绑定的属性进行debounce(节流)的操作

import { customRef } from "vue";
// 自定义ref
export default function (value) {
    return customRef((track, trigger) => {
        // 防抖
        let timer = null
        /*
            track: 決定什么时候收集依赖
            trigger:决定什么时候触发所有依赖来更新
        */ 
       return {
        get() {
            // 收集依赖
            track()
            // 返回具体值
            return value
        },
        set(newValue) {
            clearTimeout(timer)
            timer = setTimeout(() => {
                value = newValue
                // 触发所有依赖更新
                trigger()
            }, 1000);
        }                        
       }
    })
}

computed

  • 当某些属性是依赖其他状态是,可以使用计算属性来处理
    • 在Options API中,使用computed选项来完成
    • 在Composition API中,在setup函数中使用computed方法来编写一个计算属性

使用

  • 方法一:接收一个getter函数,并为getter函数返回的值,返回一个不变的ref对象
  • 方法二:接收一个具有get和set的对象,返回一个可变的(可读写)ref对象
<template>
  <div>
    <div>computed</div>
    <br>
    <input v-model="first.name" />
    <br>
    <br>
    <input type="text" v-model="lastname" />
    <br>
    <br>
    fullname: {{fullName}}
    <button @click="changeFull">修改</button>
  </div>
</template>

<script>
import { computed, reactive, ref } from 'vue'
export default {
  name: '',
  setup(){
    const first = reactive({
      name: 's'
    })
    const lastname = ref('jj')
    // 1.用法1:传如一个getter函数
    // computed的返回值是一个ref对象
    // const fullName = computed(() => first.name + lastname.value)
    // 2.用法2:传入一个对象,对象包含getter/setter
    const fullName = computed({
      get: () => first.name + lastname.value,
      set: (newVal) => {
        first.name = newVal.split(' ')[0]
        lastname.value = newVal.split(' ')[1]
      }
    })
    const changeFull = () => {
      fullName.value = 'haohao xuexi'
    }
    return {
      first,
      lastname,
      fullName,
      changeFull
    }
  }
}
</script>

watchEffect

watcheffect-官网文档

  • 当侦听到某些响应式数据变化时,希望执行某些操作,这个时候可以使用watchEffect

watchEffect: 自动收集响应式的依赖

特点

  • 传入的函数会被立即执行一次,并且在执行的过程中会收集依赖
  • 只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行
<template>
  <div>
    <div>{{info.name}}</div>
    <div>{{info.age}}</div>
    <button @click="changeName">修改名称</button>
    <button @click="changeAge">修改年龄</button>
  </div>
</template>
<script>
import { reactive, watchEffect } from 'vue'
export default {
  name: '',
  setup(){
    const info = reactive({
      name: 'code',
      age: 18
    })
    const changeName = () => {
      info.name = 'codeWhy'
    }
    const changeAge = () => {
      info.age++
    }
    const stopWatchEffect = watchEffect(() => {
      console.log('info', info.name)
    })
    console.log(stopWatchEffect)
    return {
      info,
      changeName,
      changeAge
    }
  }
}
</script>

image.png

停止侦听

停止侦听,获取watchEffect的返回值函数,调用函数即可

    const info = reactive({
      name: 'code',
      age: 18
    })
    const changeName = () => {
      info.name = 'codeWhy'
    }
    const changeAge = () => {
      info.age++
      if (info.age > 26) {
        stopWatchEffect()
      }
    }
    const stopWatchEffect = watchEffect(() => {
      console.log('info', info.name, info.age)
    })

image.png

清除副作用

  • 什么是清除副作用呢?

    • 比如在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了。
    • 那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用;
  • 在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate

    • 当副作用即将重新执行 或者 侦听器被停止 时会执行该函数传入的回调函数;
    • 我们可以在传入的回调函数中,执行一些清楚工作;

执行时机

<template>
  <div>
    <h2 ref="title"></h2>
  </div>
</template>

<script>
import { ref, watchEffect } from 'vue'
export default {
  name: '',
  setup(){
    const title = ref(null)
    watchEffect(() => {
      console.log('title', title.value)
    })
    return {
      title
    }
  }
}
</script>

image.png 打印结果打印了两次

  • 是因为setup函数在执行时就会立即执行传入的副作用函数,这个时候DOM并没有挂载,所以打印为null;
  • 而当DOM挂载时,会给title的ref对象赋值新的值,副作用函数会再次执行,打印出来对应的元素;

调整watchEffect的执行时机

延后执行

第二个参数,flush

  • flush:
    • pre(默认值): 元素 挂载或更新 之前执行
    • post
    • sync:强制效果始终同步触发
    watchEffect(() => {
      console.log('title', title.value)
    }, {
      flush: 'post'
    })

image.png

setup中使用ref

定义一个ref对象,绑定到元素或者组件的ref属性上

<template>
  <div>
    <h2 ref="title"></h2>
  </div>
</template>

<script>
import { ref, watchEffect } from 'vue'
export default {
  name: '',
  setup(){
    const title = ref(null)
    return {
      title
    }
  }
}
</script>

watch

  • watch的API完全等同于组件watch选项的Property:
    • watch需要侦听特定的数据源,并在回调函数中执行副作用;
    • 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调;
  • 与watchEffect的比较,watch允许我们:
    • 懒执行副作用(第一次不会直接执行);
    • 更具体的说明当哪些状态发生变化时,触发侦听器的执行;
    • 访问侦听状态变化前后的值;

侦听单个数据源

watch-vue

  • watch侦听函数的数据源有两种类型:
    • 一个getter函数:但是该getter函数引用响应式对象(比如reactive或者ref)
    • 直接写入一个可响应式对象,reactive或者ref(比较常用的是ref)
<template>
  <div>
    <div>{{info}}</div>
    <div>{{info.name}}</div>
    <button @click="changeName">change</button>
    <button @click="addNew">add</button>
  </div>
</template>

<script>
import { watch, reactive } from 'vue'

export default {
  name: '',
  setup(){
    const info = reactive({
      name: 'zhangsan',
      age: 12
    })
    const changeName = () => {
      info.name = 'lisi'
      info.age++
    }
    // watch(() => info.name, (newValue, oldValue) => {
    //   console.log(newValue, oldValue)
    // })
    watch(() => ({...info}), (newValue, oldValue) => {
      console.log(newValue, oldValue)
    })
    const addNew = () => {
      info.sex = 'nan'
    }
    return {
      info,
      changeName,
      addNew
    }
  }
}
</script>

image.png

    const name = ref('zhangsan')
    const changeYourName = () => {
      name.value = 'lisi'
    }
    watch(name, (newValue, oldValue) => {
      console.log(newValue, oldValue)
    })

侦听多个数据源

侦听器还可以使用数组同时侦听多个源

<template>
  <div>
    <div>{{name}}</div>
    <button @click="changeYourName">changeYourName</button>
    <div>{{age}}</div>
    <button @click="changeYourAge">changeYourAge</button>
  </div>
</template>

<script>
import { watch, ref } from 'vue'

export default {
  name: '',
  setup(){
    // ref
    const name = ref('zhangsan')
    const age = ref(12)
    const changeYourName = () => {
      name.value = 'lisi'
    }
    const changeYourAge = () => {
      age.value++
    }
    // watch(name, (newValue, oldValue) => {
    //   console.log(newValue, oldValue)
    // })
    watch([name, age], (newValue, oldValue) => {
      console.log(newValue, oldValue)
    })
    return {
      name,
      changeYourName,
      age,
      changeYourAge
    }
  }
}
</script>

image.png

侦听响应式对象

如果希望侦听一个数组或者对象,可以使用一个getter函数,并且对可响应式对象进行解构

    const arr = reactive(['abc', 'nba', 'cba'])
     watch(() => [...arr], (newValue, oldValue) => {
      console.log(newValue, oldValue)
    })
    const changeArr = () => {
      arr.push('bbb')
      // arr[0] = 'gba'
    }

watch选项

  • deep 深度监听
  • immediate 立即执行
const info = reactive({
      name: 'zhangsan',
      age: 12,
      info: {
        sonName: 'xiaoxiao'
      }
    })
    const changeName = () => {
      info.info.sonName = 'dada'
    }
    // watch(info, (newValue, oldValue) => {
    //   console.log(newValue, oldValue)
    // }, {
    //   immediate: true
    // })
    watch(() => info, (newValue, oldValue) => {
      console.log(newValue, oldValue)
    }, {
      deep: true,
      immediate: true
    })

生命周期

生命周期钩子

声明周期函数可以多次使用

options APIComposition API
beforeCreatenot needed*
creatednot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateOnBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTraggeredonRenderTraggered
activatedonActivated
deactivatedonDeactivated

provide

import { provide, ref } from 'vue'
export default {
    setup(){
        const name = ref('codewhy')
        let couter = ref(100)
        provide('name', name)
    }
}
import { inject, ref } from 'vue'
export default {
    setup(){
        const name = ref('codewhy')
        let couter = ref(100)
        inject('name', name) // 第二次参数:当provide没有传值时,为第二个参数(默认值)
    }
}

能用ref就用ref,方便抽离和解耦

readonly

只读,不能修改

import { provide, ref } from 'vue'
export default {
    setup(){
        const name = ref('codewhy')
        let couter = ref(100)
        provide('name', readonly(name))
    }
}

数据遵循单向数据流。避免多处子组件使用,而其中一个改变时导致父组件改变,从而使得其他子组件全部受到影响。

render函数

image.png

h函数

vue-h

  • h()函数时一个用于创建vnode的一个函数
  • 更准确的命名是createVNode()函数,简化为h()函数

参数

vue-h参数

type/tag

  • 类型:String/Object/Function
  • HTML标签名、组件、异步组件或函数式组件。使用返回null的函数将渲染一个注释。此参数是必需的。

props

  • Object
  • 一个对象,与我们将在模板中使用attribute、prop和事件相对应。可选。

children

  • String|Array|Object
  • 子代VNode,使用h()生成,或者使用字符串来获取“文本VNode”,或带有插槽的对象。可选。

注意事项

  • 如果没有props,那么通常可以将children作为第二个参数传入
  • 如果会产生歧义,可以将null作为第二个参数传入,将children作为第三个参数传入

基本使用

h函数可以在两个地方使用

  • render函数选项中
  • setup函数选项中(setup本身需要是一个函数类型,函数再返回h函数创建的VNode)
<script>
import { h } from 'vue'
export default {
  render() {
    return h('div', { class: 'app'}, "hello world")
  }
}
</script>
<script>
import { h } from 'vue'
export default {
  setup(){
    return () => h('div', { class: 'app'}, "hello world")
  }
}
</script>

h函数计数器案例

<script>
import { h, ref } from 'vue'
export default {
  setup(){
    const counter = ref(1)
    return () => h('div', { class: 'app'}, [
      h('button', {
        onClick: () => counter.value++
      }, counter.value)
    ])
  }
}
</script>

函数组件和插槽

插槽

sonComponent

<script>
import { h } from "vue";
export default {
  render() {
    return h("div", { class: "son" }, [
        h('h2', null, '你好China'),
        this.$slots.default ? this.$slots.default({info: 'hahahh'}) : h('span', null, '默认值')
    ]);
  },
};
</script>

fatherComponent

<script>
import sonComponent from './sonComponent.vue'
import { h, ref } from 'vue'
export default {
  setup(){
    const counter = ref(1)
    return () => h('div', { class: 'app'}, [
      h('button', {
        onClick: () => counter.value++
      }, counter.value),
      h(sonComponent, null, {
        default: props => h('span', `app传入:${props.info}`)
      })
    ])
  }
}
</script>

image.png 更多相关学习 vue-h

JSX

vue-JSX

JSX的babel配置

  • 项目中使用JSX,需要使用babel进行支持

安装

npm install @vue/babel-plugin-jsx -D

babel.config.js配置文件中配置插件

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  plugins: [
    '@vue/babel-plugin-jsx'
  ]
}
<script>
export default {
  render () {
    return (
      <div>JSX使用</div>
    )
  }
}
</script>

jsx计数器案例

<script>
import { ref } from 'vue'
export default {
  setup() {
    const counter  = ref(1)
    const increment = () => counter.value++
    return {
      counter,
      increment
    }
  },
  render () {
    return (
      <div>
        <div>当前计数:{this.counter}</div>
        <button onClick={this.increment}>+1</button>
      </div>
    )
  }
}
</script>

image.png

jsx组件的使用

export default {
  render() {
    return (
        <div>
            <h2>son</h2>
            <div className="con">
                {
                    this.$slots.default ? 
                    this.$slots.default({name: 'code'})
                    :<span>默认值</span>
                }
            </div>
        </div>
    )
  },
};
import sonComponent from './sonComponent.vue'
import { ref } from 'vue'
export default {
  setup() {
    const counter  = ref(1)
    const increment = () => counter.value++
    return {
      counter,
      increment
    }
  },
  render () {
    return (
      <div>
        <div>当前计数:{this.counter}</div>
        <button onClick={this.increment}>+1</button>
        <sonComponent>
          {{default: props => <button>{props.name}</button>}}
        </sonComponent>
      </div>
    )
  }
}

自定义指令

vue3-自定义指令

  • 在Vue中,代码的复用和抽象主要还是通过组件
  • 通常在某些情况下,需要对DOM元素进行底层操作,这个时候就会用到自定义指令

image.png

局部自定义指令

  • 组件选项中使用directives
  • 是一个对象,在对象中编写自定义指令的名称(注意:这里不需要加v-)
  • 自定义指令有一个生命周期,是在组组件挂载后调用的mounted,可以在其中完成操作
<template>
  <div>
    <input type="text" v-focus />
  </div>
</template>

<script>
export default {
  // 局部指令 
  directives: {
    focus: {
      mounted(el, bindings, vnode, preVnode) {
        console.log("focus mounted");
        el.focus()
      },
    },
  },
};
</script>

自定义全局指令

自定义一个全局的v-focus指令可以让我们在任何地方直接使用

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.directive('focus', {
    mounted(el, bindings, vnode, preVnode) {
        console.log("focus mounted");
        el.focus()
    }
})
app.mount('#app')

指令的生命周期

名称作用
created在绑定元素的attribute或事件监听器被应用之前调用
beforeMount在指令第一次绑定到元素并且在挂载父组件之前调用
mounted在绑定元素的父组件被挂载后调用
beforeUpdate在更新包含组件的VNode之前调用
updated在包含组件的VNode及其子组件的VNode更新后调用
beforeUnmount在卸载绑定元素的父组件之前调用
unmounted当指令与元素解除绑定且父组件已卸载时,只调用一次

指令的参数和修饰符

<template>
  <div>
    <button type="text" v-formaTtime:info.aaa.bbb="{ name: 'code', age: 18 }">
      {{ counter }}
    </button>
  </div>
</template>
<script>
import { ref } from "vue";
export default {
  // 局部指令
  directives: {
    formaTtime: {
      mounted(el, bindings, vnode, preVnode) {
        console.log("focus mounted", el, bindings, vnode, preVnode);
      },
    },
  },
  setup() {
    const counter = ref(123);
    return {
      counter
    }
  },
};
</script>
  • info是参数的名称
  • aaa、bbb是修饰符的名称
  • {name: 'code', age: 18}是传入的具体的值

image.png

时间格式化

  • 自定义指令案例:时间戳的显示需求:
    • 在开发中,大多数情况下从服务器获取到的都是时间戳;
    • 我们需要将时间戳转换成具体格式化的时间来展示;
    • 在Vue2中我们可以通过过滤器来完成;
    • 在Vue3中我们可以通过计算属性(computed) 或者 自定义一个方法(methods)来完成;
    • 其实我们还可以通过一个自定义的指令来完成;
  • 我们来实现一个可以自动对时间格式化的指令v-format-time:
    • 封装一个函数,在首页(main.js)中只需要调用这个函数并且传入app即可;
<template>
  <div>
    <button type="text" v-formaTtime:info.aaa.bbb="'YYYY/MM/DD'">
      {{ counter }}
    </button>
  </div>
</template>

<script>
import { ref } from "vue";
export default {
  setup() {
    const timestamp = new Date().getTime()
    const counter = ref(timestamp);
    return {
      counter
    }
  },
};
</script>

设置全局的自定义指令 image.png

import formatTime from './formatTime'
export default function registerDirectives(app){
    formatTime(app)
}

使用dayJS进行日期格式化 dayJS

import dayjs from 'dayjs'
export default function (app) {
    // let format = 'YYYY-MM-DD HH:mm:ss' // 会有bug, 当第一次使用了,后期使用默认的但却还是第一次的,因为format被修改了
    app.directive('formaTtime', {
        created(el, bindings) {
            bingings.format = 'YYYY-MM-DD HH:mm:ss'
            if (bindings.value) {
                // format = bindings.value
                bingings.format = bindings.value
            }
        },
        mounted(el, bindings, vnode, preVnode) {
            console.log("focus mounted", el, bindings, vnode, preVnode);
            const textContent = el.textContent
            let timestamp = parseInt(textContent)
            if (timestamp.length == 10) {
                timestamp = timestamp * 1000
            }
            
            // el.textContent = dayjs(timestamp).format(format)
            el.textContent = dayjs(timestamp).format(bingings.format)
        },
    })
}

main.js

import { createApp } from 'vue'
import App from './App.vue'
import registerDirectives from './directives'
const app = createApp(App)
registerDirectives(app)
app.mount('#app')

Teleport

  • 在组件化开发中,我们封装一个组件A,在另外一个组件B中使用:

    • 那么组件A中template的元素,会被挂载到组件B中template的某个位置;

    • 最终我们的应用程序会形成一颗DOM树结构;

  • 但是某些情况下,我们希望组件不是挂载在这个组件树上的,可能是移动到Vue app之外的其他位置:

    • 比如移动到body元素上,或者我们有其他的div#app之外的元素上;
    • 这个时候我们就可以通过teleport来完成;
  • Teleport是什么呢?

    • 它是一个Vue提供的内置组件,类似于react的Portals;
    • teleport翻译过来是心灵传输、远距离运输的意思;
  • 它有两个属性:

    Ø to:指定将其中的内容移动到的目标元素,可以使用选择器;

    Ø disabled:是否禁用 teleport 的功能;

  • 多个teleport应用到同一个目标上(to的值相同),那么这些目标会进行合并

image.png

<template>
  <div>
    <button>點擊</button>
    <teleport to='#why'>
      <button>+1</button>
    </teleport>
  </div>
</template>

image.png

认识插件

  • 通常我们向Vue全局添加一些功能时,会采用插件的模式,它有两种编写方式:
    • 对象类型:一个对象,但是必须包含一个 install 的函数,该函数会在安装插件时执行;
    • 函数类型:一个function,这个函数会在安装插件时自动执行;
  • 插件可以完成的功能没有限制,比如下面的几种都是可以的:
    • 添加全局方法或者 property,通过把它们添加到 config.globalProperties 上实现;
    • 添加全局资源指令/过滤器/过渡等;
    • 通过全局 mixin来添加一些组件选项
    • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能

对象

image.png

<template>
  <div>
    <button>點擊</button>
    <teleport to='#why'>
      <button>+1</button>
    </teleport>
  </div>
</template>
<script>
// getCurrentInstance拿到组件实例
import { getCurrentInstance } from 'vue'
export default {
  name: '',
  mounted(){
    console.log(this.$name)
  },
  setup(){
    /**
     * setup拿不到this,因为没有进行this绑定
     * getCurrentInstance拿到当前组件的实例
     * $name 没有放在组件实例instance上,直接instance.$name 拿不到内容
     * appContext是APP的上下文(app)
     * instance.appContext.config.globalProperties.$name
     */
    const instance = getCurrentInstance()
    console.log(instance.appContext.config.globalProperties.$name);
  }
}
</script>

function

export default function(app) {
    // app.component('')
    // app.mixin()
    console.log(app)
}

学习笔记,版权归codeWhy所有