美文网首页
vue3教程

vue3教程

作者: WangLizhi | 来源:发表于2021-04-20 09:55 被阅读0次

    全局API

    createApp

    返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。

    const app = createApp(Component);
    app.mount('#root');
    

    defineComponent

    创建组件

    import { defineComponent, ref } from 'vue';
    
    export default defineComponent({
      setup() {
        let counter = ref(0);
        const increment = () => { 
          counter.value += 1;
        }
        return {
          counter,
          increment
        }
      },
      render() {
        const {counter, increment} = this;
        return (
          <div>
            <p>Counter: {counter}</p>
            <button onClick={increment}>+</button>
          </div>
        );
      },
    });
    
    

    或者是一个 setup 函数,函数名称将作为组件名称来使用

    import { defineComponent, ref } from 'vue';
    
    const Parent = defineComponent(function Parent() {
      let age = ref(10);
      const incrementAge = () => { 
        age.value += 1;
      }
      return {
        age,
        incrementAge
      }
    })
    console.log('Parent', Parent)
    // {
    //   name: "Parent2"
    //   setup: ƒ Parent2()
    // }
    

    defineAsyncComponent

    创建一个异步加载组件

    // 全局注册
    import { defineAsyncComponent } from 'vue'
    const AsyncComp = defineAsyncComponent(() =>
      import('./components/AsyncComponent.vue')
    )
    app.component('async-component', AsyncComp)
    // 局部注册
    import {createApp, defineAsyncComponent } from 'vue'
    createApp({
      components: {
        AsyncComponent: defineAsyncComponent(() =>
          import('./components/AsyncComponent.vue')
        )
      }
    })
    

    // 高阶组件

    import { defineAsyncComponent } from 'vue'
    
    const AsyncComp = defineAsyncComponent({
      // 工厂函数
      loader: () => import('./Foo.vue')
      // 加载异步组件时要使用的组件
      loadingComponent: LoadingComponent,
      // 加载失败时要使用的组件
      errorComponent: ErrorComponent,
      // 在显示 loadingComponent 之前的延迟 | 默认值:200(单位 ms)
      delay: 200,
      // 如果提供了 timeout ,并且加载组件的时间超过了设定值,将显示错误组件
      // 默认值:Infinity(即永不超时,单位 ms)
      timeout: 3000,
      // 定义组件是否可挂起 | 默认值:true
      suspensible: false,
      /**
       *
       * @param {*} error 错误信息对象
       * @param {*} retry 一个函数,用于指示当 promise 加载器 reject 时,加载器是否应该重试
       * @param {*} fail  一个函数,指示加载程序结束退出
       * @param {*} attempts 允许的最大重试次数
       */
      onError(error, retry, fail, attempts) {
        if (error.message.match(/fetch/) && attempts <= 3) {
          // 请求发生错误时重试,最多可尝试 3 次
          retry()
        } else {
          // 注意,retry/fail 就像 promise 的 resolve/reject 一样:
          // 必须调用其中一个才能继续错误处理。
          fail()
        }
      }
    })
    

    resolveComponent

    在当前应用中查找组件, 只能在 render 或 setup 函数中使用。

    import { createApp, defineComponent, resolveComponent} from 'vue'
    const app = createApp({});
    app.component('ResolveComponent', {
      render(){
        return (<h1>This is a ResolveComponent</h1>)
      }
    })
    app.mount('#root');
    
    defineComponent({
      render() {
        // return component or componentName;
        const ResolveComponent = resolveComponent('ResolveComponent');
    
        return (
          <div>
            <ResolveComponent />
          </div>
        )
      }
    })
    

    resolveDynamicComponent

    查找组件

    import {defineComponent, resolveDynamicComponent} from 'vue'
    defineComponent({
      setup() {
        const isShow = ref(false);
        return {
          isShow
        }
      },
      render() {
        // return component or componentName;
       const ResDynamicComponent = resolveDynamicComponent({
          render() {
            return (
              <div>
                <h1>This is a ResolveDynamicComponent</h1>
              </div>
            )
          }
        })
    
        return (
          <div>
           {
              isShow ? <ResDynamicComponent /> : ''
            }
          </div>
        )
      }
    })
    

    resolveDirective

    在当前应用中查找指令, 只能在 render 或 setup 函数中使用。

    import { createApp, defineComponent, resolveDirective} from 'vue'
    const app = createApp({})
    app.directive('highlight', {})
    defineComponent({
      setup() {
        // return Directive or undefined;
        const directive = resolveDirective('highlight');
      }
    })
    

    withDirectives

    defineComponent({
      render() {
        const focus = resolveDirective('focus')
        return withDirectives(<input type="text" v-focus />, [[focus]])
      }
    })
    

    createRenderer

    nextTick

    将回调推迟到下一个 DOM 更新周期之后执行。在更改了一些数据以等待 DOM 更新后立即使用它。

    mergeProps

     const props = mergeProps({
          class: 'active'
        },  this.$attrs)
    

    4) 比较Vue2与Vue3的响应式(重要)

    vue2的响应式

    • 核心:
      • 对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)
      • 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
    Object.defineProperty(data, 'count', {
        get () {}, 
        set () {}
    })
    
    
    • 问题
      • 对象直接新添加的属性或删除已有属性, 界面不会自动更新
      • 直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {}

    Vue3的响应式

    new Proxy(data, {
        // 拦截读取属性值
        get (target, prop) {
            return Reflect.get(target, prop)
        },
        // 拦截设置属性值或添加新属性
        set (target, prop, value) {
            return Reflect.set(target, prop, value)
        },
        // 拦截删除属性
        deleteProperty (target, prop) {
            return Reflect.deleteProperty(target, prop)
        }
    })
    
    proxy.name = 'tom'   
    
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Proxy 与 Reflect</title>
    </head>
    <body>
      <script>
    
        const user = {
          name: "John",
          age: 12
        };
    
        /* 
        proxyUser是代理对象, user是被代理对象
        后面所有的操作都是通过代理对象来操作被代理对象内部属性
        */
        const proxyUser = new Proxy(user, {
    
          get(target, prop) {
            console.log('劫持get()', prop)
            return Reflect.get(target, prop)
          },
    
          set(target, prop, val) {
            console.log('劫持set()', prop, val)
            return Reflect.set(target, prop, val); // (2)
          },
    
          deleteProperty (target, prop) {
            console.log('劫持delete属性', prop)
            return Reflect.deleteProperty(target, prop)
          }
        });
        // 读取属性值
        console.log(proxyUser===user)
        console.log(proxyUser.name, proxyUser.age)
        // 设置属性值
        proxyUser.name = 'bob'
        proxyUser.age = 13
        console.log(user)
        // 添加属性
        proxyUser.sex = '男'
        console.log(user)
        // 删除属性
        delete proxyUser.sex
        console.log(user)
      </script>
    </body>
    </html>
    
    

    5) setup细节

    • setup执行的时机

      • 在beforeCreate之前执行(一次), 此时组件对象还没有创建
      • this是undefined, 不能通过this来访问data/computed/methods / props
      • 其实所有的composition API相关回调函数中也都不可以
    • setup的返回值

      • 一般都返回一个对象: 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法
      • 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性
      • 返回对象中的方法会与methods中的方法合并成功组件对象的方法
      • 如果有重名, setup优先
      • 注意:
      • 一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods
      • setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据
    • setup的参数

      • setup(props, context) / setup(props, {attrs, slots, emit})
      • props: 包含props配置声明且传入了的所有属性的对象
      • attrs: 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs
      • slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
      • emit: 用来分发自定义事件的函数, 相当于 this.$emit
    <template>
      <h2>App</h2>
      <p>msg: {{msg}}</p>
      <button @click="fn('--')">更新</button>
    
      <child :msg="msg" msg2="cba" @fn="fn"/>
    </template>
    
    <script lang="ts">
    import {
      reactive,
      ref,
    } from 'vue'
    import child from './child.vue'
    
    export default {
    
      components: {
        child
      },
    
      setup () {
        const msg = ref('abc')
    
        function fn (content: string) {
          msg.value += content
        }
        return {
          msg,
          fn
        }
      }
    }
    </script>
    
    
    <template>
      <div>
        <h3>{{n}}</h3>
        <h3>{{m}}</h3>
    
        <h3>msg: {{msg}}</h3>
        <h3>msg2: {{$attrs.msg2}}</h3>
    
        <slot name="xxx"></slot>
    
        <button @click="update">更新</button>
      </div>
    </template>
    
    <script lang="ts">
    
    import {
      ref,
      defineComponent
    } from 'vue'
    
    export default defineComponent({
      name: 'child',
    
      props: ['msg'],
    
      emits: ['fn'], // 可选的, 声明了更利于程序员阅读, 且可以对分发的事件数据进行校验
    
      data () {
        console.log('data', this)
        return {
          // n: 1
        }
      },
    
      beforeCreate () {
        console.log('beforeCreate', this)
      },
    
      methods: {
        // update () {
        //   this.n++
        //   this.m++
        // }
      },
    
      // setup (props, context) {
      setup (props, {attrs, emit, slots}) {
    
        console.log('setup', this)
        console.log(props.msg, attrs.msg2, slots, emit)
    
        const m = ref(2)
        const n = ref(3)
    
        function update () {
          // console.log('--', this)
          // this.n += 2 
          // this.m += 2
    
          m.value += 2
          n.value += 2
    
          // 分发自定义事件
          emit('fn', '++')
        }
    
        return {
          m,
          n,
          update,
        }
      },
    })
    </script>
    
    

    6) reactive与ref-细节

    • 是Vue3的 composition API中2个最重要的响应式API
    • ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
    • 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象
    • ref内部: 通过给value属性添加getter/setter来实现对数据的劫持
    • reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据
    • ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)
    <template>
      <h2>App</h2>
      <p>m1: {{m1}}</p>
      <p>m2: {{m2}}</p>
      <p>m3: {{m3}}</p>
      <button @click="update">更新</button>
    </template>
    
    <script lang="ts">
    import {
      reactive,
      ref
    } from 'vue'
    
    export default {
    
      setup () {
        const m1 = ref('abc')
        const m2 = reactive({x: 1, y: {z: 'abc'}})
    
        // 使用ref处理对象  ==> 对象会被自动reactive为proxy对象
        const m3 = ref({a1: 2, a2: {a3: 'abc'}})
        console.log(m1, m2, m3)
        console.log(m3.value.a2) // 也是一个proxy对象
    
        function update() {
          m1.value += '--'
          m2.x += 1
          m2.y.z += '++'
    
          m3.value = {a1: 3, a2: {a3: 'abc---'}}
          m3.value.a2.a3 += '==' // reactive对对象进行了深度数据劫持
          console.log(m3.value.a2)
        }
    
        return {
          m1,
          m2,
          m3,
          update
        }
      }
    }
    </script>
    
    

    7) 计算属性与监视

    • computed函数:

      • 与computed配置功能一致
      • 只有getter
      • 有getter和setter
    • watch函数

      • 与watch配置功能一致
      • 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
      • 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
      • 通过配置deep为true, 来指定深度监视
    • watchEffect函数

      • 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
      • 默认初始时就会执行第一次, 从而可以收集需要监视的数据
      • 监视数据发生变化时回调
    <template>
      <h2>App</h2>
      fistName: <input v-model="user.firstName"/><br>
      lastName: <input v-model="user.lastName"/><br>
      fullName1: <input v-model="fullName1"/><br>
      fullName2: <input v-model="fullName2"><br>
      fullName3: <input v-model="fullName3"><br>
    
    </template>
    
    <script lang="ts">
    /*
    计算属性与监视
    1\. computed函数: 
      与computed配置功能一致
      只有getter
      有getter和setter
    2\. watch函数
      与watch配置功能一致
      监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
      默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
      通过配置deep为true, 来指定深度监视
    3\. watchEffect函数
      不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
      默认初始时就会执行第一次, 从而可以收集需要监视的数据
      监视数据发生变化时回调
    */
    
    import {
      reactive,
      ref,
      computed,
      watch,
      watchEffect
    } from 'vue'
    
    export default {
    
      setup () {
        const user = reactive({
          firstName: 'A',
          lastName: 'B'
        })
    
        // 只有getter的计算属性
        const fullName1 = computed(() => {
          console.log('fullName1')
          return user.firstName + '-' + user.lastName
        })
    
        // 有getter与setter的计算属性
        const fullName2 = computed({
          get () {
            console.log('fullName2 get')
            return user.firstName + '-' + user.lastName
          },
    
          set (value: string) {
            console.log('fullName2 set')
            const names = value.split('-')
            user.firstName = names[0]
            user.lastName = names[1]
          }
        })
    
        const fullName3 = ref('')
    
        /* 
        watchEffect: 监视所有回调中使用的数据
        */
        /* 
        watchEffect(() => {
          console.log('watchEffect')
          fullName3.value = user.firstName + '-' + user.lastName
        }) 
        */
    
        /* 
        使用watch的2个特性:
          深度监视
          初始化立即执行
        */
        watch(user, () => {
          fullName3.value = user.firstName + '-' + user.lastName
        }, {
          immediate: true,  // 是否初始化立即执行一次, 默认是false
          deep: true, // 是否是深度监视, 默认是false
        })
    
        /* 
        watch一个数据
          默认在数据发生改变时执行回调
        */
        watch(fullName3, (value) => {
          console.log('watch')
          const names = value.split('-')
          user.firstName = names[0]
          user.lastName = names[1]
        })
    
        /* 
        watch多个数据: 
          使用数组来指定
          如果是ref对象, 直接指定
          如果是reactive对象中的属性,  必须通过函数来指定
        */
        watch([() => user.firstName, () => user.lastName, fullName3], (values) => {
          console.log('监视多个数据', values)
        })
    
        return {
          user,
          fullName1,
          fullName2,
          fullName3
        }
      }
    }
    </script>
    
    

    8) 生命周期

    vue2.x的生命周期

    lifecycle.png

    vue3的生命周期

    lifecycle (1).png

    与 2.x 版本生命周期相对应的组合式 API

    • beforeCreate -> 使用 setup()
    • created -> 使用 setup()
    • beforeMount -> onBeforeMount
    • mounted -> onMounted
    • beforeUpdate -> onBeforeUpdate
    • updated -> onUpdated
    • beforeDestroy -> onBeforeUnmount
    • destroyed -> onUnmounted
    • errorCaptured -> onErrorCaptured

    新增的钩子函数

    组合式 API 还提供了以下调试钩子函数:

    • onRenderTracked
    • onRenderTriggered
    <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 {
      beforeCreate () {
        console.log('beforeCreate()')
      },
    
      created () {
        console.log('created')
      },
    
      beforeMount () {
        console.log('beforeMount')
      },
    
      mounted () {
        console.log('mounted')
      },
    
      beforeUpdate () {
        console.log('beforeUpdate')
      },
    
      updated () {
        console.log('updated')
      },
    
      beforeUnmount () {
        console.log('beforeUnmount')
      },
    
      unmounted () {
         console.log('unmounted')
      },
    
      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>
    
    
    <template>
      <h2>App</h2>
      <button @click="isShow=!isShow">切换</button>
      <hr>
      <Child v-if="isShow"/>
    </template>
    
    <script lang="ts">
    import Child from './Child.vue'
    export default {
    
      data () {
        return {
          isShow: true
        }
      },
    
      components: {
        Child
      }
    }
    </script>
    
    

    09) 自定义hook函数

    • 使用Vue3的组合API封装的可复用的功能函数

    • 自定义hook的作用类似于vue2中的mixin技术

    • 自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂

    • 需求1: 收集用户鼠标点击的页面坐标

      hooks/useMousePosition.ts

    import { ref, onMounted, onUnmounted } from 'vue'
    /* 
    收集用户鼠标点击的页面坐标
    */
    export default function useMousePosition () {
      // 初始化坐标数据
      const x = ref(-1)
      const y = ref(-1)
    
      // 用于收集点击事件坐标的函数
      const updatePosition = (e: MouseEvent) => {
        x.value = e.pageX
        y.value = e.pageY
      }
    
      // 挂载后绑定点击监听
      onMounted(() => {
        document.addEventListener('click', updatePosition)
      })
    
      // 卸载前解绑点击监听
      onUnmounted(() => {
        document.removeEventListener('click', updatePosition)
      })
    
      return {x, y}
    }
    
    
    <template>
    <div>
      <h2>x: {{x}}, y: {{y}}</h2>
    </div>
    </template>
    
    <script>
    
    import {
      ref
    } from "vue"
    /* 
    在组件中引入并使用自定义hook
    自定义hook的作用类似于vue2中的mixin技术
    自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂
    */
    import useMousePosition from './hooks/useMousePosition'
    
    export default {
      setup() {
    
        const {x, y} = useMousePosition()
    
        return {
          x,
          y,
        }
      }
    }
    </script>
    
    
    • 利用TS泛型强化类型检查

    • 需求2: 封装发ajax请求的hook函数

      hooks/useRequest.ts

    import { ref } from 'vue'
    import axios from 'axios'
    
    /* 
    使用axios发送异步ajax请求
    */
    export default function useUrlLoader<T>(url: string) {
    
      const result = ref<T | null>(null)
      const loading = ref(true)
      const errorMsg = ref(null)
    
      axios.get(url)
        .then(response => {
          loading.value = false
          result.value = response.data
        })
        .catch(e => {
          loading.value = false
          errorMsg.value = e.message || '未知错误'
        })
    
      return {
        loading,
        result,
        errorMsg,
      }
    }
    
    
    <template>
    <div class="about">
      <h2 v-if="loading">LOADING...</h2>
      <h2 v-else-if="errorMsg">{{errorMsg}}</h2>
      <!-- <ul v-else>
        <li>id: {{result.id}}</li>
        <li>name: {{result.name}}</li>
        <li>distance: {{result.distance}}</li>
      </ul> -->
    
      <ul v-for="p in result" :key="p.id">
        <li>id: {{p.id}}</li>
        <li>title: {{p.title}}</li>
        <li>price: {{p.price}}</li>
      </ul>
      <!-- <img v-if="result" :src="result[0].url" alt=""> -->
    </div>
    </template>
    
    <script lang="ts">
    import {
      watch
    } from "vue"
    import useRequest from './hooks/useRequest'
    
    // 地址数据接口
    interface AddressResult {
      id: number;
      name: string;
      distance: string;
    }
    
    // 产品数据接口
    interface ProductResult {
      id: string;
      title: string;
      price: number;
    }
    
    export default {
      setup() {
    
        // const {loading, result, errorMsg} = useRequest<AddressResult>('/data/address.json')
        const {loading, result, errorMsg} = useRequest<ProductResult[]>('/data/products.json')
    
        watch(result, () => {
          if (result.value) {
            console.log(result.value.length) // 有提示
          }
        })
    
        return {
          loading,
          result, 
          errorMsg
        }
      }
    }
    </script>
    
    

    10) toRefs

    把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref

    应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用

    问题: reactive 对象取出的所有属性值都是非响应式的

    解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性

    <template>
      <h2>App</h2>
      <h3>foo: {{foo}}</h3>
      <h3>bar: {{bar}}</h3>
      <h3>foo2: {{foo2}}</h3>
      <h3>bar2: {{bar2}}</h3>
    
    </template>
    
    <script lang="ts">
    import { reactive, toRefs } from 'vue'
    /*
    toRefs:
      将响应式对象中所有属性包装为ref对象, 并返回包含这些ref对象的普通对象
      应用: 当从合成函数返回响应式对象时,toRefs 非常有用,
            这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
    */
    export default {
    
      setup () {
    
        const state = reactive({
          foo: 'a',
          bar: 'b',
        })
    
        const stateAsRefs = toRefs(state)
    
        setTimeout(() => {
          state.foo += '++'
          state.bar += '++'
        }, 2000);
    
        const {foo2, bar2} = useReatureX()
    
        return {
          // ...state,
          ...stateAsRefs,
          foo2, 
          bar2
        }
      },
    }
    
    function useReatureX() {
      const state = reactive({
        foo2: 'a',
        bar2: 'b',
      })
    
      setTimeout(() => {
        state.foo2 += '++'
        state.bar2 += '++'
      }, 2000);
    
      return toRefs(state)
    }
    
    </script>
    
    

    11) ref获取元素

    利用ref函数获取组件中的标签元素

    功能需求: 让输入框自动获取焦点

    <template>
      <h2>App</h2>
      <input type="text">---
      <input type="text" ref="inputRef">
    </template>
    
    <script lang="ts">
    import { onMounted, ref } from 'vue'
    /* 
    ref获取元素: 利用ref函数获取组件中的标签元素
    功能需求: 让输入框自动获取焦点
    */
    export default {
      setup() {
        const inputRef = ref<HTMLElement|null>(null)
    
        onMounted(() => {
          inputRef.value && inputRef.value.focus()
        })
    
        return {
          inputRef
        }
      },
    }
    </script>
    
    

    相关文章

      网友评论

          本文标题:vue3教程

          本文链接:https://www.haomeiwen.com/subject/meyelltx.html