想无缝链接 Vue3.0,先体验一下 composition-api

阅读本文你能够了解到html

  1. 提早感觉一下 Vue3.x(以 composition-api 为例)
  2. composition-api 解决了什么问题
  3. composition-api 配合 TypeScript 的使用
  4. 对 Composition-api 理解的最佳实践

Vue3.x 的改动(介绍一些比较常见的)

Vue3.0 的 RFC 提出来也有一段时间了。从刚开始的 function-base-api 到如今的 composition-api。这二者都有一个共同的目标就是将 2.x 中与逻辑相关的选项都以函数形式抽离出来vue

  1. ref 和 reactive

这两个都能定义响应式数据,在这里其实有那么点区别,先举个例子node

const x = ref<number>(0)
const y = ref<string>("Hello World")
// x y的类型是 Ref<number> | Ref<string>

const state = reactive({
    text:"Hello World"
})
复制代码

这里的 Ref 类型是包装类型的意思。之前的 Vue2.x 直接监听的是字符串,还有数字的变化。由于这些值都是做为 data 的属性值存在,监听的实际上是 data 对象的属性值。可是如今单独在函数中返回的字符串,和数字等基本数字类型,不存在引用关系,是没法追踪原始变量后续的变化的。因此包装对象的意义就在于提供一个让咱们可以在函数之间以引用的方式传递任意类型值的容器。容器的存在能够经过引用的方式传回给组件。react

state 跟原来的 Vue2.x 的就很很相似,存在于 state 对象上,监听的是 state 属性的变化。ajax

怎么去选择定义数据的方法?其实很简单,均可以,看我的习惯问题。api

const state = reactive({
    user:{
        name:"A",
        age:10,
        where:"china"
    }
})
// 至关于
const state = {
    name :"A",
    age:10,
    where:"china"
}
const userName = ref<string>("A")
const userAge = ref<number>(10)
const userWhere = ref<string>("china")
// 至关于
const name = "A";
const age = 10;
const where = 'china'
复制代码

⚠️ 注意在使用 Ref 的时候,在模板会自动拆箱,可是在函数里面,传递的是对象。因此要取值的时候记得要加 ref.value 才能取值数组

2.watchpromise

watch 用过的同窗都知道是用来观察数据变化进行对应的操做的。此次的改动是将原来的 Options 的写法换成函数的形式进行调用。async

watch 的第一个参数能够是返回任意值的函数,一个包装对象,还有是包含两种数据的数组函数

...
const state = reactive<{count:number}>({
    count:1,
})
watch(
  () => state.count,
  (cur,pre) => {
      // 观察的是 state对象里面的一个值在这里进行操做
  },
);
...
...
const name = ref<string>("Hello World")
watch(
  () => name,
  (cur,pre) => {
      // do other things
  },
);
...
复制代码

⚠️ 注意的是包装对象不能用函数返回,由于是两种不一样类型的观察值,所对应的操做也是不同的。若是用函数返回,则没有监听的效果。(虽然你看到的值是改变了,可是你.value 事后仍然是初始的值)

⚠️ 注意 watch 的回调会在建立的时候就使用,相似于 2.x 设置了immediate: true,watch 的触发老是会在 DOM 更新完的状况下。因此说若是想要在 DOM 更新前就得设置flush选项

✏️ watch 还可以中止监听,这是 2.x 没有的

const stop = watch(()=>state.count)

// watch函数返回的是一个 中止观察数据的函数。 调用一下就能中止观察了
复制代码

❤️ 特别说明一下:若是 watch 实在setup或者是生命周期函数里面调用。watch会在销毁组件的时候自动中止

3.setup 函数和生命周期

Vue3.x 的生命周期发生了些变化,废弃了原来的createdbeforeCreated的生命周期,在这二者之间新增了一个组件选项setup函数。

// setup是在初始化props后面调用的,因此会接受props为参数。context是整个组件的上下文。之前都是用this类指向Vue组件,如今换成函数就用参数的形式传递上下文。
...
setup(props:Props,context:SetupContext){
    onMounted()

    onUpdate();

    onUnMounted();
}
...
复制代码

生命周期都改了名字。而且以函数 Api 的形式,更重要的是只能在 setup 函数里面调用~!

⚠️ 注意的 props 不能进行结构赋值,也不能进行扩展运算符等破坏监听值的操做。由于这些操做都会有中间量的生成致使破坏原有存在的监听系统。要用的话就得在state上面使用toRefs方法。这个方法可让对象不会破坏原来的监听。同时 Props 也是不可修改的。

Composition-api 比较 2.x 解决了什么痛点

组件间的逻辑复用

在用 Composition-api 的同时,也能够用原来基于选项的 options 的作法。composition-api 是为了解决一些 2.x 存在的问题。

vue 在应对简单的业务的时候确实很游刃有余。可是在大型项目,涉及不少组件,或者是组件之间的逻辑复用的问题的时候,可能就有点棘手了。 在这以前可能都知道有 Mixins,HOC 等解决逻辑复用的手段。可是他们都或多或少有点问题。譬如:来源不清。命名空间冲突。须要额外的开销等等。

composition-api 能够将组件的逻辑封装到一个函数里面进行复用。

// 举一个封装的例子 判断是否下拉到底

// useIsEnd.ts
...
const isEnd = ref<boolean>(false) // 初始化未到最底下

export function getEnd(node:Ref<HTMLElement>){
    // 已经封装好的三个函数 判断是否到底的 -10 是为了有些状况下会出现小数(在移动端内嵌的时候)
    if (
      getClientHeight(node) + (getScrollTop(node) as number) >=
      getScrollHeight(node) - 10
    ) {
      isEnd.value = true;
    } else {
      isEnd.value = false;
    }
    return {
      isEnd
    }
}
// 这里将isEnd放在外面是为了在触发触摸事件的时候(touch),不会每次都从新建立一个新的对象致使判断失误


// demo.vue
...
import { getEnd } from "useIsEnd";
export default defineComponent({
    ...
    setup(){
        const node = ref<HTMLElement>('node') // 获取一个node
        ...
        // 这是@touchmove 事件
        function handleGetIsEnd(){
            const { isEnd } = getEnd(node) // 判断当前是否到底
            // do other things like ajax
        }
        ...
        return {
            // 记得return
            node,
            handleGetIsEnd
        }
    }
    ...
})

...
复制代码

✏️ 不存在来源不清晰,返回值还能够从新定义,没有命名空间的冲突。把全部的逻辑封装成一个函数没有额外的组件的开销。还有能够更好的组织代码

更好的类型推导

Vue3.x 用 TypeScript 重写以后,对的类型推导自然支持。再加上是函数的 APi 的缘故,类型推导更上一层楼。

更加小的尺寸

也是由于函数的缘由。对 tree shakeing 友好。每一个函数均可以单独引入,而且也由于函数的 API 的缘由能够压缩函数名字达到极致的最小尺寸

TypeScript 配合 Composition-api

之前 Vue 项目假如要用 TypeScript,就得引入对应的插件(例如 vue-class-component or vue-property-decorator)。可是经过 TypeScript 重写之后,就能够直接使用 TypeScript。

用 TypeScript 也是想要 TypeScript 的类型推导,这里很少赘述 TypeScript 的概念

// 这里的interface可让Ts提供提示
interface Props{
    name:string;
    age:number;
}

interface State{
    title:string;
    head:string;
}
...
// 类型推导须要用defineCompoent or createComponent
export default defineComponent({
...
    props:{
        name:String;
        age:Number;
    }
    ...
    setup(props:Props,context:SetupContext){
        ...
        const value = ref<string>(""); // ref 的类型是Ref<T>
        ...
        const state = reactive<State>({
            title:"",
            head:""
        })
        ...
        const { demo } = useXXX(...)
        demo();
        ...
        onMounted(async ()=>{
            const res = await fetchData(...);
            state.XXX = res.data.XXX
        })
        ...
        return {
            value,
            // 这样的话 就能够直接 使用 title / head
            ...toRefs(state)
        }
    }
...
})
复制代码

TypeScript 和 Composition-api 最佳实践

在使用 Composition-api 的时候咱们须要知道咱们用的目的是什么。当咱们的逻辑很复杂的时候,能够考虑使用它来帮咱们抽离逻辑复用。当咱们想更好的组织代码的时候,咱们可使用它让咱们的代码更有条理性。

💪 实践 1: 将 data(state)状态跟方法抽离到一个文件中

// 之前多是会这样写
export default {
    ...
    data(){
        // 当数据很大的时候 这里可能会很长 对应的每一条也须要写长长的注释
        return {
            ...
            key1:value1,
            ...
        }
    }
    ...
    methods:{
      // 方法这里 可能会这样写, 假如方法过多 也会出现很冗余的状况,注释虽然能够帮助咱们很快的找到对应的代码。可是 可能会出现几百行的状况
      handleXXX(){
        ... do otherthings
      }
    }
}
复制代码

👍 如今咱们可能会这样写(一个函数包裹该函数的自身的状态,仅仅维护本函数自身状态)

// index.ts
interface InitState{
    key1:string;
    key2:number;
    key3:{
        'key3-1':string;
        'key3-2':boolean;
    }
}
export function init(){
    ...
    const initState = reactive<InitState>({
      key1:"";
      key2:0;
      key3:{
          'key3-1':"";
          'key3-2':false;
      }
    })
    // 这里也能够不watch 直接 外部 async 或者是 promise的then继续执行后续操做也行
    const stop = watch(async ()=>{
        const res = await initData(params);
        const { key1,key2,key3 } = res.data;
        state.key1 = key1;
        state.key2 = key2;
        state.key3 = key3;
    })
    stop(); // 假如不放在setup 里面的 或者是生命周期的 不会自动回收
    ...
    return {
      // 记得加上toRefs就不会丢失响应式了
      ...toRefs(initState)
    }
}

// index.vue
template
...
<script lang="ts">
import { defineComponent, reactive, SetupContext, toRefs } from '@vue/composition-api';
import { init } from "./index"

export default defineComponent({
  ...
  // 在某些状况下 context 能够进行解构 拿出你想要的 或者只是一个ui组件没有逻辑能够不传
  /** 常见的解构的类型 * {{ root }: { root: ComponentInstance } * { root: { $router } }: { root: { $router: VueRouter } } * { root: { $store } }: { root: { store: Store } } */
  setup(props:Props,context:SetupContext){
    const initState = init();
    return {
      // 这里是为了更方便的取值
      ...toRefs(initState)
    }
  }
  ...
})
<script>
...
style
复制代码

✏️ 这里只有一种状况,就是仅仅是初始化的时候的 state。这里能够解构赋值,可是我我的认为 直接用一个对象包裹以后,减小没必要要的命名冲突问题。这里表示的是 init 的时候的 state,后面可能会还有别的 state。

💪 实践 2: 别的请求的状况(value 值不会立刻拿到的状况)

// index.ts
// 这里的value不能直接拿到,须要等待的话 都须要用watch 等待改变以后再进行请求
...
function getSomeData(value) {
  const xxxState = reactive<XXXState>({
    key1:''
  })
  const stop = watch(
    () => value,
    async (cur, pre) => {
      const res = await fetchData(cur);
      xxxState.key1 = res.data
    },
  );
  // 这里的假如value值会变的 不会只用一次的,后续可能会变化的 就不须要 stop了
  return {
    ...toRefs(xxxState)
  }
}
...
复制代码

💪 实践 3: 初始化后或者其余方法初始化的 state 后续别的方法用到,该如何修改

// index.ts
/** * 这里可能有三种方法。第一将初始化的state放到全局上 * 就好像操做全局的变量同样操做全局的state,这样的作法是简单,这样作的后果就是后面须要查 * 现的问题的时候就不知道哪些函数有操做对应的state,致使难以定位问题。 */
const initState = reactive({
  key1:1;
})
async function init(){
  const { key1 } = await fetchData();
  initState.key1 = key1;
}
// 这个方法我想改变初始化的state
function handleChangeInitState(){
  const { key1 } = await fetchData();
  initState.key1 = key1;
}
复制代码
// index.ts
/** * 第二种 将初始化的state返回后,须要修改的将state做为参数进行传入修改 * 这样作比第一种好一点。知道是哪些函数修改,就直接看对应的函数就行,可是假如后续有不少须要修改的状况,就得传不少次参数形成冗余。 */
async function init(){
  const initState = reactive({
    key1:1;
  })
  const { key1 } = await fetchData();
  initState.key1 = key1;
  return {
    initState,
  }
}

function handleChangeInitState(initState){
  const { key1 } = await fetchData();
  initState.key1 = key1;
}
复制代码
// index.ts
/** * 最后一种 借鉴react-hook的思想,对外暴露操做改state中某个属性的方法 * 比较简洁,没有多余的参数传递 */
async function init(){
  const initState = reactive({
    key1:1;
  })
  const { key1 } = await fetchData();
  initState.key1 = key1;

  function changeKey1(value){
    initState.key1 = value;
  }

  return {
    changeKey1,
    ...toRefs(initState),
  }
}

// index.vue
import { init } from "./index"
...
setup(props,context){
  const {changeKey1,initState} = init();
  ...
  // 这里能够作操做state的方法
  const { xxxState } = useXXX();
  changeKey1(xxxState.key1)
  ...
  return {
    ...toRefs(initState)
  }
}
...
复制代码

总结

这是我的以为 composition-api 的最佳实践。组合函数就是能更好的组织代码,将多层嵌套抽离出来分红一个个组合函数,将其组合以后,就能让代码更加有条理了。

参考

  1. Vue Composition API(RPC)
  2. 探秘 Vue3.0 - Composition API 在真实业务中的尝鲜姿式
相关文章
相关标签/搜索