8

vue3+ts(3):vue3组合式api及重要属性变更

 2 years ago
source link: https://lianpf.github.io/posts/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/07.vue3%E7%BB%84%E5%90%88%E5%BC%8Fapi%E5%8F%8A%E9%87%8D%E8%A6%81%E5%B1%9E%E6%80%A7%E5%8F%98%E6%9B%B4/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

vue3+ts(3):vue3组合式api及重要属性变更

2021-03-18

本文是vue3+ts项目系列第3篇《vue3组合式api及重要属性变更》,会…

在 react 和 vue 社区中也越来越多人开始使用TypeScript,使用 TS 可以增加代码的可读性和可维护性。从发布的 Vue3 正式版本来看, Vue3 的源码是用 TS 编写的,更好的 TypeScript 支持也是这次升级的一大亮点。当然,在实际开发中如何正确拥抱 TS 以及如何迁移到 Vue3 也是项目中我们不得不面对的问题,这里针对 Vue3 和 TS单独做了一个系列和大家做一下交流,本篇是 vue3+ts项目系列3篇《vue3组合式api及重要属性变更》。

本系列其他内容如下:

Vue作为一种渐进式框架, 借鉴了 React 的组件化和虚拟 DOM、Angular 的模块化和双向数据绑定。随着 Vue 3 内核 API 与实现已趋稳定, 可以看到相对vue2.x,Vue3做了很多重要的变更,特别是Composition API的引入。

3.0 对比 2.x 的重要变更有 6 个方面:

  • Performance(性能): 优化了虚拟 DOM,有了更加优化的编译,实现了更加高效的组件初始化
    • Rewritten virtual dom implementation (重写了虚拟 DOM)
    • Compiler-informed fast paths (优化编译)、
    • More efficient component initialization (更高效的组件初始化)
    • 1.3-2x better update performance (1.3~2 倍的更新性能)
    • 2-3x faster SSR (2~3 倍的 SSR 速度)
  • Tree-shaking support (支持 Tree-shaking): 按需求引用的内置的指令和方法
    • All runtime features included: 22.5kb. More features but still lighter than Vue 2。大多数可选功能(如 v-model、)现在都是支持 Tree-shaking 的
    • Bare-bone HelloWorld size: 13.5kb. 11.75kb with only Composition API support
    • All runtime features included: 22.5kb. More features but still lighter than Vue 2
  • Composition API
    • Usable alongside existing Options API (可与现有选项 API 一起使用)
    • Flexible logic composition and reuse (灵活的逻辑组成和重用)
    • Reactivity module can be used as a standalone library (Reactivity 模块可以作为独立的库使用)
  • Fragment, Teleport, Suspense
    • Fragment: vue2时,由于组件必须只有一个根节点,很多时候会添加一些没有意义的节点用于包裹。Fragment组件就是用于解决这个问题的
    • Teleport其实就是React中的Portal,提供一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案
    • 和React中的Supense是一样的。Suspense 让你的组件在渲染之前进行“等待”,并在等待时显示 fallback 的内容
  • Better TypeScript support (更好的 TypeScript 支持度)
  • Custom Renderer API (自定义的 Renderer API)

本文主要接下来主要涉及到的内容为 Custom Renderer API。

三、Composition API 等核心特性

核心关注点:

  • setup
  • reactive
  • ref引用 & toRefs
  • Lifecycle Hooks
  • computed
  • watch
  • 依赖注入 provide & inject

1、setup

setup() 函数是 vue3 中,专门为组件提供的新属性。它为我们使用 vue3 的 Composition API 新特性提供了统一的入口。

  • setup 函数相当于 vue2.x 中 beforeCreate 和 created, 在 beforeCreate 之后、created 之前执行
  • setup第一个形参,接收 props 数据
  • setup第二个形参是一个上下文对象, 在 setup() 函数中无法访问到 this, 可以用这个context来访问

Tips:

setup 第一个参数接受一个响应式的props,这个props指向的是外部的props。如果你没有定义props选项,setup中的第一个参数将为undifined。遵循vue2.x的原则

  • props 定义
  • 不要在子组件中修改props;如果你尝试修改,将会给你警告甚至报错
  • 不要解构props。解构的props会失去响应性
import { inject, defineComponent } from "vue";
export default defineComponent({
  name: "ListItem",
  props: {
    data: Object
  },
  steup({data}) {
    console.log("--userData--", data);
    return {
      data
    };
  }
});

2、reactive

reactive() 函数接收一个普通对象,返回一个响应式的数据对象。

import { defineComponent, onUnmounted, reactive, ref, watchEffect } from "vue";

export default defineComponent({
  name: "About",
  components: {},
  setup() {
    const state = reactive({
      msg: '欢迎来到 "关于 vue3 和TS的语法DEMO"',
      testWatchEffectCount: 0
    });
    // watchEffect —— 1.自动收集数据源作为依赖、2.只有变更后的值、3.默认会执行一次寻找依赖,然后属性改变也会执行
    const count = ref(0);
    watchEffect(() => {
      console.log("--watchEffect-value--", count.value);
      state.testWatchEffectCount = count.value;
    });
    setInterval(() => {
      count.value++;
    }, 500);

    const stop = watchEffect(() => {
      console.log("--stop-effect--");
    });
    // 清除副作用
    watchEffect(onInvalidate => {
      console.log(count.value, "0-副作用");
      const token = setTimeout(() => {
        console.log(count.value, "1-副作用");
      }, 5000);
      onInvalidate(() => {
        // count(watchEffect函数依赖项) 改变时或停止侦听时,取消之前的异步操作
        token.cancel();
      });
    });

    onUnmounted(() => {
      stop();
    });
    return {
      state
    };
  }
});
</script>

3、ref引用 & toRefs

ref常用于基本类型,reactive用于引用类型。如果ref传入对象,其实内部会自动变为reactive

  • ref() 函数根据给定的值创建一个响应式的数据对象,返回值是一个对象,这个对象上只包含一个 .value 属性
  • toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据
  • 通过 ref() 还可以引用页面上的元素或组件,和vue2的ref概念类似

示例:父组件

import { ref, reactive, toRefs, onMounted } from 'vue'

export default {
    setup() {
        const count = ref(0) // 创建响应式数据对象 count,初始值为 0
        console.log(count.value) // 在setup内访问count值需要.value 属性才可以,但在template中可以直接访问

        const state = reactive({count: 0, name:'weedsFly'}) // 用reactive集中创建多个响应式对象
        const add = () => { // methods写在setup内
          state.count++
        }

        // 元素引用
        const h1Ref = ref(null) // 创建一个 DOM 引用
        onMounted(() => { // 在 DOM 首次加载完毕之后,才能获取到元素的引用
          h1Ref.value.style.color = 'pink' // h1Ref.value 是原生DOM对象
        })
        // 组件引用
        const compRef = ref(null) // 创建一个组件的 ref 引用
        showCompData = () => { // 展示子组件中 count 的值
          console.log(compRef.value.count) 
        }

        return {
          count,
          // ...state,  // 使用展开运算符后 用reactive创建的响应式数据 变成了 固定的值
          ...toRefs(state), // 可以用toRefs函数 将传进来的非响应式对象 转成 ref() 类型的响应式数据
          add,
          h1Ref,
          compRef,
          showCompData
        }
    },
 }

示例:子组件

import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0) // 定义响应式的数据
    return {
      count
    }
  }
}

4、Lifecycle Hooks

新版的生命周期函数,可以按需导入到组件中,且只能在 setup() 函数中使用

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

Tips:

无法设置 reactive的state

import { defineComponent, onUnmounted, reactive, ref, watchEffect } from "vue";

export default defineComponent({
  name: "About",
  components: {},
  setup() {
    const state = reactive({
      msg: '欢迎来到 "关于 vue3 和TS的语法DEMO"',
      testWatchEffectCount: 0
    });
    // watchEffect —— 1.自动收集数据源作为依赖、2.只有变更后的值、3.默认会执行一次寻找依赖,然后属性改变也会执行
    const count = ref(0);
    watchEffect(() => {
      console.log("--watchEffect-value--", count.value);
      state.testWatchEffectCount = count.value;
    });
    setInterval(() => {
      count.value++;
    }, 500);

    const stop = watchEffect(() => {
      console.log("--stop-effect--");
    });
    // 清除副作用
    watchEffect(onInvalidate => {
      console.log(count.value, "0-副作用");
      const token = setTimeout(() => {
        console.log(count.value, "1-副作用");
      }, 5000);
      onInvalidate(() => {
        // count(watchEffect函数依赖项) 改变时或停止侦听时,取消之前的异步操作
        token.cancel();
      });
    });

    onUnmounted(() => {
      stop();
    });
    return {
      state
    };
  }
});

5、computed

computed() 用来创建计算属性,computed() 函数的返回值是一个 ref 的实例

  • computed创建只读的计算属性(传入一个 function 函数,可以得到一个只读的计算属性)
  • computed创建可读可写的计算属性
import { ref, computed } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const computedCount = computed(() => count.value + 1)
    computedCount.value = 9 //  computed value is readonly.

    const count2 = ref(0)
    const computedCount2 = computed({
      get: () => count2.value + 1,
      set: val => {
        count2.value = val - 1
      }
    })
    computedCount2.value = 100 // 为计算属性赋值的操作,会触发 set 函数 
    console.log(count2.value) // 触发 set 函数后,count 的值会被更新
    
    return {
      count,
      computedCount,
      count2,
      computedCount2
    }
  },
}

6、watch

  • 监视单个数据源变动
    • 监视单个reactive创建的数据
    • 监视单个ref创建的数据源
  • 监视多个数据源
    • 监视多个reactive创建的数据源
    • 监视多个ref创建的数据源
  • 清除watch监视
  • 在 watch 中清除无效的异步任务(与节流防抖同效)
import { reactive, ref, watch } from 'vue'

export default {
  setup() {
    // 1-1 监视单个reactive创建的数据源
    const state = reactive({count: 100})
    watch(
      () => state.count,
      (newVal, oldVal) => { console.log(newVal, oldVal)},
      {lazy: true} // 在 watch 被创建的时候,不执行回调函数中的代码
    )

    // 1-2 监视单个ref创建的数据源
    const count2 = ref(100)
    const stop2 = watch(
      count2,
      (newVal, oldVal) => { console.log(newVal, oldVal)},
      {lazy: true} // 在 watch 被创建的时候,不执行回调函数中的代码
    )

    // 2-1 监视多个reactive创建的数据源
    const state3 = reactive({count: 100, name: 'Laiyj'})
    watch(
      [() => state3.count, () => state3.name],
      ([newCount, newName], [oldCount, oldName]) => {
        console.log(newCount, oldCount)
        console.log(newName, oldName)
      },
      {lazy: true} // 在 watch 被创建的时候,不执行回调函数中的代码
    )

    // 2-2 监视多个ref创建的数据源
    const count4 count = ref(100)
    const name4 = ref('Fei')
    watch(
      [count4, name4],
      ([newCount, newName], [oldCount, oldName]) => {
        console.log(newCount, oldCount)
        console.log(newName, oldName)
      },
      {lazy: true} // 在 watch 被创建的时候,不执行回调函数中的代码
    )

    setTimeout(() => {
      state.count++;
      count2++;
      state2.count++;
      state2.name = 'lian';
      count4++;
      name4 = 'lian4';
    }, 500)
  }

  // 3、清除watch监视
  const clearWatch = () => {
    stop()
  }

  // 4、watch 中清除无效的异步任务(与节流防抖同效)
  const keyword6 = ref('')
  const asyncPrint = (val) => { // 执行异步任务,并得到关闭异步任务的 timerId
    return setTimeout(() => {
      console.log(val)
    }, 1000)
  }
  watch(
    keyword6,
    (newVal, oldVal, onClean) => {
      const timeId = asyncPrint()
      onClean(() => {clearTimeout(timeId)}) // 如果 watch 监听被重复执行了,则会先清除上次未完成的异步任务
    }
  )

  return {
    state,
    count2,
    state3,
    count4,
    name4
    clearWatch,
    keyword6
  }
}

7、依赖注入 provide & inject

  • provide() 和 inject() 可以实现嵌套组件之间的数据传递
  • 只能在 setup() 函数中使用。
  • 父级组件中使用 provide() 函数向下传递数据;子级组件中使用 inject() 获取上层传递过来的数据

Tips:

steup() 初始化,在brforeCreate 前后, 所以,对于动态组件,类似 list 内加载 list-item, item是无法拿到list provide的值的

示例:父组件

import {
  defineComponent,
  reactive,
  computed,
  provide,
  readonly,
  ref,
  onMounted
} from "vue";
import ListItem from "./components/ListItem.vue";
import TestInject from "./components/TestInject.vue";
interface ListByStatusData {
  page: number;
  pageSize: number;
}
interface MovieItem {
  id: string;
  name: string;
  desc: string;
}

export default defineComponent({
  name: "List",
  components: {
    ListItem,
    TestInject
  },
  setup() {
    let list: Array<MovieItem> = [];
    /**
     * @name: reactive
     * @desc: 核心流程
     * @author: lianpf
     * @date: 2021.03.06
     * */
    let state = reactive({
      title: "List Page",
      count: 0,
      total: 0,
      list,
      provideStatus: false
    });

    const params = computed(() => ({
      count: state.count + 1
    }));
    /**
     * @name: 异步请求函数 & 泛型函数
     * @desc: 核心流程
     * @author: lianpf
     * @date: 2021.03.06
     * */
    type FnType = (x: number, y: number) => Promise<Array<MovieItem>>;
    const getInitList: FnType = (page, pageSize) => {
      console.log(`--req-params-page:${page}-pageSize:${pageSize}--`);
      return new Promise((resolve, reject) => {
        try {
          let res: Array<MovieItem> = [
            {
              id: "100",
              name: "list-item-001",
              desc: "001-001-001-001"
            },
            {
              id: "200",
              name: "list-item-002",
              desc: "002-002-002-002"
            },
            {
              id: "300",
              name: "list-item-003",
              desc: "003-003-003-003"
            }
          ];
          resolve(res);
        } catch (e) {
          reject(e);
        }
      });
    };
    // const tempList = () => getInitList()
    let reqParams: ListByStatusData = {
      page: 1,
      pageSize: 10
    };

    // 异步流控制函数
    const asyncFlow = async () => {
      let resData = await getInitList(reqParams.page, reqParams.pageSize);
      state.count = 2;
      state.list = resData;
      list = resData;
    };

    onMounted(async () => {
      await asyncFlow();
    });
    /**
     * @name: 依赖注入 —— 父组件通过 provide 函数向子级组件共享数据(不限层级)
     * @desc: 核心功能
     * @author: lianpf
     * @date: 2021.03.06
     * */
    const parentColor = ref("salmon");
    // provide('要共享的数据名称', 被共享的数据)
    provide("themeColor", readonly(parentColor));
    const updateThemeColor = () => {
      state.provideStatus = !state.provideStatus;
      parentColor.value = state.provideStatus ? "skyblue" : "salmon";
    };
    // 父组件 function update “注入”的值
    provide("updateThemeColor", updateThemeColor);
    provide("location", "North Pole");
    provide("geolocation", {
      longitude: 90,
      latitude: 135
    });

    return {
      state,
      params,
      list
    };
  }
});
</script>

示例: 子组件

import { inject, defineComponent } from "vue";

export default defineComponent({
  name: "TestInject",
  setup() {
    /**
     * @name: 依赖注入
     * @desc: 核心功能 —— 调用 inject 函数时,通过指定的数据名称,获取到父级共享的数据
     * @author: lianpf
     * @date: 2021.03.06
     *  */
    const userThemeColor = inject("themeColor");
    const updateThemeColor = inject("updateThemeColor");
    console.log("--userThemeColor--", userThemeColor);

    const userLocation = inject("location", "The Universe");
    const userGeolocation = inject("geolocation");

    return {
      userThemeColor,
      updateThemeColor,
      userLocation,
      userGeolocation
    };
  }
});
</script>

四、项目结构

├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
├── src
│   ├── App.vue
│   ├── assets
│   │   ├── logo.png
│   │   └── styles
│   │       └── varible.styl
│   ├── components
│   │   ├── HeaderNav.vue
│   │   └── HelloWorld.vue
│   ├── main.ts
│   ├── router
│   │   └── index.ts
│   ├── shims-vue.d.ts
│   ├── store
│   │   └── index.ts
│   ├── types
│   │   └── movie.ts
│   └── views
│       ├── About.vue
│       ├── Detail.vue
│       ├── Home.vue
│       ├── List.vue
│       └── components
│           ├── ListItem.vue
│           └── TestInject.vue
└── tsconfig.json

example源码地址: vue3-ts


最后, 希望大家早日实现:成为前端高手的伟大梦想!
欢迎交流~

微信公众号

本文版权归原作者曜灵所有!未经允许,严禁转载!对非法转载者, 原作者保留采用法律手段追究的权利!
若需转载,请联系微信公众号:连先生有猫病,可获取作者联系方式!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK