render 中的 h 函数

h 函数可以创建虚拟 dom,通过创建的虚拟 dom 再转化为真的的 DOM,从而渲染到页面中。

<script>
  // render function
  // template -> render -> h -> 虚拟DOM(JS对象)-> 真实 DOM -> 展示到页面上
  const app = Vue.createApp({
    template: `
<my-title :level="2">
hello xiaokang
  </my-title>
`
  })

  app.component('my-title', {
    props: ['level'],
    render() {
      const { h } = Vue
      return h('h' + this.level, {}, [
        this.$slots.default(),
        h('h4', {}, 'xiaokang')
      ])
    }
  })

  const vm = app.mount('#root')
</script>

h 函数第一个参数为tag name,也就是你需要创建的标签名,第二参数为此标签上的属性,第三个参数为其内容数组。

image-20210319172822390

参考链接:https://vue3js.cn/docs/zh/guide/render-function.html

插件

vue 中插件主要用于把一些通用性的功能封装起来

插件定义的基本语法

const myPlugin = {
  install(app, options) {}
}

当使用插件时,会调用插件的 install 方法。第一个参数 app 为生成的 app 对象,第二个参数为传递的参数。

<script>
  // plugin 插件, 也是把通用性的功能封装起来
  const myPlugin = {
    install(app, options) {
      // 提供和注入
      app.provide('name', 'xiaokang')
      // 自定义指令
      app.directive('focus', {
        mounted(el) {
          el.focus()
        }
      })
      // 混入
      app.mixin({
        mounted() {
          console.log('mixin')
        }
      })
      // 全局属性
      app.config.globalProperties.$sayHello = 'hello world'
    }
  }

  const app = Vue.createApp({
    template: `
<my-title />
`
  })

  app.component('my-title', {
    inject: ['name'],
    mounted() {
      console.log(this.$sayHello)
    },
    template: `<div>{{name}}<input v-focus /></div>`
  })

  app.use(myPlugin, { name: 'xiaokang' })

  const vm = app.mount('#root')
</script>

数据校验插件

const app = Vue.createApp({
  data() {
    return { name: 'dell', age: 23 }
  },
  rules: {
    age: {
      validate: (age) => age > 25,
      message: 'too young, to simple'
    },
    name: {
      validate: (name) => name.length >= 4,
      message: 'name too short'
    }
  },
  template: `
<div>name:{{name}}, age:{{age}}</div>
`
})

// 对数据做校验的插件
const validatorPlugin = (app, options) => {
  app.mixin({
    created() {
      for (let key in this.$options.rules) {
        const item = this.$options.rules[key]
        this.$watch(key, (value) => {
          const result = item.validate(value)
          if (!result) console.log(item.message)
        })
      }
    }
  })
}

app.use(validatorPlugin)
const vm = app.mount('#root')

参考:https://vue3js.cn/docs/zh/guide/plugins.html

Teleport 传送门-vue3

主要用于将 dom 元素挂载到其他位置,例如挂载到head里。

<body>
  <div id="root"></div>
</body>
<script>
  // teleport 传送门
  const app = Vue.createApp({
    data() {
      return {
        show: false,
        message: 'hello'
      }
    },
    methods: {
      handleBtnClick() {
        this.show = !this.show
      }
    },
    template: `
      <div class="area">
        <button @click="handleBtnClick">按钮</button>
        <teleport to="body">
          <div class="mask" v-show="show">{{message}}</div>
        </teleport>
      </div>
    `
  })

  const vm = app.mount('#root')
</script>

image-20210319171148266

components API-vue3

setup 函数

该函数会在创建组件之前执行,由于在执行 setup 时尚未创建组件实例,因此在 setup 选项中没有 this。这意味着,除了 props 之外,你将无法访问组件中声明的任何属性——本地状态计算属性方法

// 对数据做校验的插件
const app = Vue.createApp({
  template: `
<div @click="handleClick">{{name}}</div>
`,
  methods: {
    test() {
      console.log(this.$options.setup())
    }
  },
  // 实例可以调用setup方法
  mounted() {
    this.test()
  },
  // 这里不能调用this
  setup(props, context) {
    return {
      name: 'xiaokang',
      handleClick: () => {
        alert(123)
      }
    }
  }
})
const vm = app.mount('#root')

setup 里返回的内容可以在实例中调用。

响应式引用与只读

主要用于将setup中的变量转换为响应式的变量。默认变量并不是响应式的。

其中ref处理基础类型的数据,reactive用于处理非基础类型的数据(对象和数组)。

方法名作用
ref将基础类型数据转化为响应式数据
reactive将非基础类型的数据(对象和数组)。
readonly将数据设为只读
toRefs将非基础类型的子元素设置为响应式数据
  1. 基础类型引用

    setup(props, context) {
      const { ref } = Vue
      let name = ref('1')
      setTimeout(() => {
        name.value = '2'
      }, 2000)
      return { name }
    }

    通过 ref 包装后,name 实则变成了proxy({value: '2'})这样的引用,当修改值时需要修改 name 的 value 属性。但是调用时不需要使用value,vue 会识别并自动调用。

  2. 非基础类型引用

    setup(props,context){
      const { reactive, readonly, toRefs } = Vue
      const nameObj = reactive({ name: 'xiaokang', age: 21 })
      setTimeout(() => {
        nameObj.age = '22'
      }, 2000)
      return { nameObj }
    }

    通过reactive包装后,nameObj 就是响应式的了。

  3. 组合使用

    在非响应式引用里,只有整个对象是响应式的,而对象里的某个属性并不是响应式的,因此,需要将这个对象再次进行包装才可以使其属性变成响应式的。

    setup(props,context){
      const { reactive, readonly, toRefs } = Vue
      const nameObj = reactive({ name: 'xiaokang', age: 21 })
      setTimeout(() => {
        nameObj.age = '22'
      }, 2000)
      const { name, age } = toRefs(nameObj)
      return { age }
    }
  4. 只读

    const nameObj = reactive({ name: 'xiaokang', age: 22 })
    const nameObjCopy = readonly(nameObj)
    setTimeout(() => {
      nameObj.age = 21
      nameObjCopy.age = 23
    }, 2000)

参考:https://vue3js.cn/docs/zh/guide/composition-api-introduction.html#带-ref-的响应式变量

toRef

可以用来为源响应式对象上的 property 性创建一个 ref。然后可以将 ref 传递出去,从而保持对其源 property 的响应式连接。

setup(props, context) {
  const { reactive, toRef } = Vue
  const nameObj = reactive({ name: 'xiaokang' })
  const age = toRef(data, 'age')
  setTimeout(() => {
    age.value = 21
  }, 2000)

  return { age }
}

setup 中 context 参数

context 参数一共可以结构出三个参数:

  • attrs

    None-Props 属性

    const app = Vue.createApp({
      template: `
    <child app='app' style='color:red'></child>
    `
    })
    app.component('child', {
      template: '<div>child</div>',
      setup(props, context) {
        const { attrs, slots, emit } = context
        console.log(attrs) // 接收没有被props接收的属性
      }
    })
    const vm = app.mount('#root')

    image-20210319190338185

  • slots

    插槽

    const app = Vue.createApp({
      template: `
    <child app='app' style='color:red'>
    插槽内容  
    </child>
    `
    })
    app.component('child', {
      setup(props, context) {
        const { h } = Vue
        const { attrs, slots, emit } = context
        return () => h('div', {}, slots.default())
      }
    })
    const vm = app.mount('#root')

    可以使用jsx进行模板渲染:https://github.com/vuejs/jsx-next#installation

  • emit

    触发自定义事件

    const app = Vue.createApp({
      template: `
    <child @change='handleChange' app='app' style='color:red'>
    插槽内容
    </child>
    `,
      methods: {
        handleChange() {
          console.log(123)
        }
      }
    })
    app.component('child', {
      template: "<div @click='handleClick'>123123</div>",
      setup(props, context) {
        const { h } = Vue
        const { attrs, slots, emit } = context
        function handleClick() {
          emit('change')
        }
        return {
          handleClick
        }
      }
    })
    const vm = app.mount('#root')

计算属性

计算属性同样使用Vue对象提供的computed方法。computed 方法接收参数有两种类型,一种是函数,另一种是对象。

setup(props, context) {
  const { ref, computed } = Vue
  let number = ref(1)
  let number2 = ref(0)
  const handle = () => {
    number.value += 1
    number2.value += 1
  }
  // 传入函数
  let cNumber = computed(() => {
    return number.value + 5
  })
  // 传入对象
  let cNumberObj = computed({
    get: () => {
      return number2.value + 5
    },
    set: (val) => {
      number2.value = val - 1
    }
  })
  return { number, cNumber, handle, cNumberObj }
}

watch 与 watchEffect

就像我们如何使用 watch 选项在组件内的 user property 上设置侦听器一样,我们也可以使用从 Vue 导入的 watch 函数执行相同的操作。它接受 3 个参数:

  • 一个响应式引用或我们想要侦听的 getter 函数
  • 一个回调函数
  • 可选的配置选项
  1. 侦听单个源

    // 侦听一个 getter
    const state = reactive({ count: 0 })
    watch(
      () => state.count,
      (count, prevCount) => {
        /* ... */
      }
    )
    
    // 直接侦听ref
    const count = ref(0)
    watch(count, (count, prevCount) => {
      /* ... */
    })
  2. 侦听多个源

    watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
      /* ... */
    })
不同点watchwatchEffect
惰性默认情况下是惰性的,但第三个参数传入immediate为 true 可以立即执行非惰性
能拿到原始值和当前值只能拿到当前值
只可以侦听多个数据可以可以
const stop = watchEffect(() => {
  // 会自动判断依赖并更新
  console.log(nameObj.name)
})
stop() // 调用后会停止监听

生命周期

setup() {
  const {
    ref,
    onBeforeMount,
    onMounted,
    onBeforeUpdate,
    onUpdated,
    onRenderTracked,
    onRenderTriggered
  } = Vue
  const name = ref('dell')
  onBeforeMount(() => {
    console.log('onBeforeMount')
  })
  onMounted(() => {
    console.log('onMounted')
  })
  onBeforeUpdate(() => {
    console.log('onBeforeUpdate')
  })
  onUpdated(() => {
    console.log('onUpdated')
  })
  // 每次渲染后重新收集响应式依赖
  onRenderTracked(() => {
    console.log('onRenderTracked')
  })
  // 每次触发页面重新渲染时自动执行
  onRenderTriggered(() => {
    console.log('onRenderTriggered')
  })
  const handleClick = () => {
    name.value = 'lee'
  }
  return { name, handleClick }
},

Provide、Inject 和 ref

提供与注入

const app = Vue.createApp({
  setup() {
    const { provide, ref, readonly } = Vue
    const name = ref('name1')
    provide('name', readonly(name))
    provide('changeName', (value) => {
      name.value = value
    })
    return {}
  },
  template: `
        <div>
          <child />
        </div>
      `
})

app.component('child', {
  setup() {
    const { inject } = Vue
    const name = inject('name')
    const changeName = inject('changeName')
    const handleClick = () => {
      changeName('name2')
    }
    return { name, handleClick }
  },
  template: '<div @click="handleClick">{{name}}</div>'
})

通过readonly对提供的变量进行包装,实现数据单向流(子组件不能修改父组件的值)。

ref

const app = Vue.createApp({
  setup() {
    const { ref, onMounted } = Vue
    const hello = ref(null)
    onMounted(() => {
      console.log(hello.value) // dom节点
    })
    return { hello }
  },
  template: `
<div>
<div ref="hello">hello world</div>
</div>
`
})