乐趣区

关于前端:vue3之Composition-API详解

Composition API也叫组合式 API,是 Vue3.x 的新个性。

通过创立 Vue 组件,咱们能够将接口的可重复部分及其性能提取到可重用的代码段中。仅此一项就能够使咱们的应用程序在可维护性和灵活性方面走得更远。然而,咱们的教训曾经证实,光靠这一点可能是不够的,尤其是当你的应用程序变得十分大的时候——想想几百个组件。在解决如此大的应用程序时,共享和重用代码变得尤为重要

艰深的讲:

没有 Composition API 之前 vue 相干业务的代码须要配置到 option 的特定的区域,中小型我的项目是没有问题的,然而在大型项目中会导致前期的维护性比较复杂,同时代码可复用性不高。Vue3.x 中的 composition-api 就是为了解决这个问题而生的

compositon api 提供了以下几个函数:

  • setup
  • ref
  • reactive
  • watchEffect
  • watch
  • computed
  • toRefs
  • 生命周期的hooks

一、setup 组件选项

新的 setup 组件选项在 创立组件之前 执行,一旦 props 被解析,并充当合成 API 的入口点

提醒:

因为在执行 setup 时尚未创立组件实例,因而在 setup 选项中没有 this。这意味着,除了props 之外,你将无法访问组件中申明的任何属性——本地状态、计算属性或办法。

应用 setup 函数时,它将承受两个参数:

  1. props
  2. context

让咱们更深刻地钻研如何应用每个参数

1. Props

setup 函数中的第一个参数是 props。正如在一个规范组件中所冀望的那样,setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新

// MyBook.vue

export default {
  props: {title: String},
  setup(props) {console.log(props.title)
  }
}

留神:

然而,因为 props 是响应式的,你不能应用 ES6 解构,因为它会打消 prop 的响应性。

如果须要解构 prop,能够通过应用 setup 函数中的 toRefs 来平安地实现此操作。

// MyBook.vue

import {toRefs} from 'vue'

setup(props) {const { title} = toRefs(props)

    console.log(title.value)
}

2. 上下文

传递给 setup 函数的第二个参数是 contextcontext 是一个一般的 JavaScript 对象,它裸露三个组件的 property

// MyBook.vue

export default {setup(props, context) {// Attribute (非响应式对象)
    console.log(context.attrs)

    // 插槽 (非响应式对象)
    console.log(context.slots)

    // 触发事件 (办法)
    console.log(context.emit)
  }
}

context 是一个一般的 JavaScript 对象,也就是说,它不是响应式的,这意味着你能够平安地对 context 应用 ES6 解构

// MyBook.vue
export default {setup(props, { attrs, slots, emit}) {...}
}

attrsslots 是有状态的对象,它们总是会随组件自身的更新而更新。这意味着你应该防止对它们进行解构,并始终以 attrs.xslots.x 的形式援用 property。请留神,与 props 不同,attrsslots响应式的。如果你打算依据 attrsslots 更改利用副作用,那么应该在 onUpdated 生命周期钩子中执行此操作。

3. setup 组件的 property

执行 setup 时,组件实例尚未被创立。因而,你只能拜访以下 property:

  • props
  • attrs
  • slots
  • emit

换句话说,你 将无法访问 以下组件选项:

  • data
  • computed
  • methods

4. ref reactive 以及 setup 联合模板应用

在看 setup 联合模板应用之前,咱们首先得晓得refreactive 办法。

如果 setup 返回一个对象则能够在模板中绑定对象中的属性和办法,然而要定义响应式数据的时候能够应用 ref, reactive 办法定义响应式的数据

谬误写法:
<template>
{{msg}}
<br>

<button @click="updateMsg"> 扭转 etup 中的 msg</button>

<br>
</template>

<script>
export default {data() {return {}
    },
    setup() {
        let msg = "这是 setup 中的 msg";
        let updateMsg = () => {alert("触发办法")
            msg = "扭转后的值"
        }
        return {
            msg,
            updateMsg
        }
    },

}
</script>

<style lang="scss">
.home {position: relative;}
</style>
正确写法一:

ref用来定义响应式的 字符串、数值、数组、Bool类型

import {ref} from 'vue'
<template>
{{msg}}
<br>
<br>
<button @click="updateMsg"> 扭转 etup 中的 msg</button>
<br>
<br>
<ul>
    <li v-for="(item,index) in list" :key="index">
        {{item}}
    </li>
</ul>

<br>
</template>

<script>
import {ref} from 'vue'

export default {data() {return {}
    },
    setup() {let msg = ref("这是 setup 中的 msg");

        let list = ref(["马总", "李总", "刘总"])

        let updateMsg = () => {alert("触发办法");
            msg.value = "扭转后的值"
        }
        return {
            msg,
            list,
            updateMsg
        }
    },

}
</script>

<style lang="scss">
.home {position: relative;}
</style>
正确写法二:

reactive 用来定义响应式的对象

import {reactive} from 'vue'
<template>
{{msg}}
<br>
<br>
<button @click="updateMsg"> 扭转 setup 中的 msg</button>
<br>
<br>
<ul>
    <li v-for="(item,index) in list" :key="index">
        {{item}}
    </li>
</ul>
<br>
{{setupData.title}}
<br>
<button @click="updateTitle"> 更新 setup 中的 title</button>
<br>
<br>
</template>

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

export default {data() {return {}
    },
    setup() {let msg = ref("这是 setup 中的 msg");

        let setupData = reactive({
            title: "reactive 定义响应式数据的 title",
            userinfo: {
                username: "张三",
                age: 20
            }

        })

        let updateMsg = () => {alert("触发办法");
            msg.value = "扭转后的值"
        }
        let updateTitle = () => {alert("触发办法");
            setupData.title = "我是扭转后的 title"

        }
        return {
            msg,
            setupData,
            updateMsg,
            updateTitle
        }
    },

}
</script>

<style lang="scss">
.home {position: relative;}
</style>

阐明:要扭转 ref 定义的属性名称须要通过 属性名称.value来批改,要扭转 reactive 中定义的对象名称能够间接

5. 应用 this

setup() 外部,this 不会是该沉闷实例的援用,因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 外部的 this 的行为与其它选项中的 this 齐全不同。这在和其它选项式 API 一起应用 setup() 时可能会导致混同

二、toRefs – 解构响应式对象数据

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

<template>
<div>
    <h1> 解构响应式对象数据 </h1>
    <p>Username: {{username}}</p>
    <p>Age: {{age}}</p>
</div>
</template>

<script>
import {
    reactive,
    toRefs
} from "vue";

export default {
    name: "解构响应式对象数据",
    setup() {
        const user = reactive({
            username: "张三",
            age: 10000,
        });

        return {...toRefs(user)
        };
    },
};
</script>

当想要从一个组合逻辑函数中返回响应式对象时,用 toRefs 是很无效的,该 API 让生产组件能够 解构 / 扩大(应用 操作符)返回的对象,并不会失落响应性:

function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2,
  })

  // 对 state 的逻辑操作
  // ....

  // 返回时将属性都转为 ref
  return toRefs(state)
}

export default {setup() {
    // 能够解构,不会失落响应性
    const {foo, bar} = useFeatureX()

    return {
      foo,
      bar,
    }
  },
}

三、computed – 计算属性

<template>
<div>
    <h1> 解构响应式对象数据 +computed</h1>

    <input type="text" v-model="firstName" placeholder="firstName" />
    <br>
    <br>
    <input type="text" v-model="lastName" placeholder="lastName" />

    <br>
    {{fullName}}
</div>
</template>

<script>
import {
    reactive,
    toRefs,
    computed
} from "vue";

export default {
    name: "解构响应式对象数据",
    setup() {
        const user = reactive({
            firstName: "",
            lastName: "",
        });

        const fullName = computed(() => {return user.firstName + " " + user.lastName})

        return {...toRefs(user),
            fullName
        };
    },
};
</script>

四、readonly“深层”的只读代理

传入一个对象(响应式或一般)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象外部任何嵌套的属性也都是只读的

<template>
  <div>
    <h1>readonly -“深层”的只读代理 </h1>
    <p>original.count: {{original.count}}</p>
    <p>copy.count: {{copy.count}}</p>
  </div>
</template>

<script>
import {reactive, readonly} from "vue";

export default {
  name: "Readonly",
  setup() {const original = reactive({ count: 0});
    const copy = readonly(original);

    setInterval(() => {
      original.count++;
      copy.count++; // 报正告,Set operation on key "count" failed: target is readonly. Proxy {count: 1}
    }, 1000);


    return {original, copy};
  },
};
</script>

五、watchEffect

在响应式地跟踪其依赖项时立刻运行一个函数,并在更改依赖项时从新运行它。

<template>
<div>
    <h1>watchEffect - 侦听器 </h1>
    <p>{{data.count}}</p>
    <button @click="stop"> 手动敞开侦听器 </button>
</div>
</template>

<script>
import {
    reactive,
    watchEffect
} from "vue";
export default {
    name: "WatchEffect",
    setup() {
        const data = reactive({
            count: 1,
            num: 1
        });
        const stop = watchEffect(() => console.log(` 侦听器:${data.count}`));
        setInterval(() => {data.count++;}, 1000);
        return {
            data,
            stop
        };
    },
};
</script>

六、watch、watch 与 watchEffect 区别

比照 watchEffectwatch 容许咱们

  • 懒执行,也就是说仅在侦听的源变更时才执行回调;
  • 更明确哪些状态的扭转会触发侦听器从新运行;
  • 拜访侦听状态变动前后的值

更明确哪些状态的扭转会触发侦听器从新运行

<template>
<div>
    <h1>watch - 侦听器 </h1>
    <p>count1: {{data.count1}}</p>
    <p>count2: {{data.count2}}</p>
    <button @click="stopAll">Stop All</button>
</div>
</template>

<script>
import {
    reactive,
    watch
} from "vue";
export default {
    name: "Watch",
    setup() {
        const data = reactive({
            count1: 0,
            count2: 0
        });
        // 侦听单个数据源
        const stop1 = watch(data, () =>
            console.log("watch1", data.count1, data.count2)
        );
        // 侦听多个数据源
        const stop2 = watch([data], () => {console.log("watch2", data.count1, data.count2);
        });
        setInterval(() => {data.count1++;}, 1000);
        return {
            data,
            stopAll: () => {stop1();
                stop2();},
        };
    },
};
</script>

拜访侦听状态变动前后的值

<template>
<div>
    <h1>watch - 侦听器 </h1>
    <input type="text" v-model="keywords" />
</div>
</template>

<script>
import {
    ref,
    watch
} from "vue";
export default {
    name: "Watch",
    setup() {let keywords = ref("111");
        // 侦听单个数据源
        watch(keywords, (newValue, oldValue) => {console.log(newValue, oldValue)
        });

        return {keywords};
    },
};
</script>

懒执行,也就是说仅在侦听的源变更时才执行回调

<template>
<div>
    <h1>watch - 侦听器 </h1>
    <p>num1={{num1}}</p>
    <p>num2={{num2}}</p>
</div>
</template>

<script>
import {
    ref,
    watch,
    watchEffect
} from "vue";
export default {
    name: "Watch",
    setup() {let num1 = ref(10);
        let num2 = ref(10);
        // 侦听单个数据源
        watch(num1, (newValue, oldValue) => {console.log(newValue, oldValue)
        });

        watchEffect(() => console.log(`watchEffect 侦听器:${num2.value}`));

        return {
            num1,
            num2
        };
    },
};
</script>

七、组合式 api 生命周期钩子

你能够通过在生命周期钩子后面加上“on”来拜访组件的生命周期钩子。

下表蕴含如何在 setup () 外部调用生命周期钩子:

选项式 API Hook inside setup
beforeCreate 不须要 *
created 不须要 *
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered

因为 setup 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不须要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该间接在 setup 函数中编写

export default {setup() {
    // mounted
    onMounted(() => {console.log('Component is mounted!')
    })
  }
}

八、Provider Inject

通常,当咱们须要将数据从父组件传递到子组件时,咱们应用 props。设想一下这样的构造:你有一些深嵌套的组件,而你只须要来自深嵌套子组件中父组件的某些内容。在这种状况下,你依然须要将 prop 传递到整个组件链中,这可能会很烦人

对于这种状况,咱们能够应用 provideinject 对父组件能够作为其所有子组件的依赖项提供程序,而不论组件层次结构有多深。这个个性有两个局部:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始应用这个数据

1. 非组合式 api 中的写法

<!-- src/components/MyMap.vue -->
<template>
  <MyMarker />
</template>

<script>
import MyMarker from './MyMarker.vue'

export default {
  components: {MyMarker},
  provide: {
    location: 'North Pole',
    geolocation: {
      longitude: 90,
      latitude: 135
    }
  }
}
</script>
<!-- src/components/MyMarker.vue -->
<script>
export default {inject: ['location', 'geolocation']
}
</script>

2. 组合式 api 中的写法

Provider:

setup() 中应用 provide 时,咱们首先从 vue 显式导入 provide 办法。这使咱们可能调用 provide 时来定义每个 property

provide 函数容许你通过两个参数定义 property

  1. propertyname (<String> 类型)
  2. propertyvalue

应用 MyMap 组件,咱们提供的值能够按如下形式重构:

<!-- src/components/MyMap.vue -->
<template>
  <MyMarker />
</template>

<script>
import {provide} from 'vue'
import MyMarker from './MyMarker.vue

export default {
  components: {MyMarker},
  setup() {provide('location', 'North Pole')
    provide('geolocation', {
      longitude: 90,
      latitude: 135
    })
  }
}
</script>
Inject:

setup() 中应用 inject 时,还须要从 vue 显式导入它。一旦咱们这样做了,咱们就能够调用它来定义如何将它裸露给咱们的组件。

inject 函数有两个参数:

  1. 要注入的 property 的名称
  2. 一个默认的值 (可选)

应用 MyMarker 组件,能够应用以下代码对其进行重构:

<!-- src/components/MyMarker.vue -->
<script>
import {inject} from 'vue'

export default {setup() {const userLocation = inject('location', 'The Universe')
    const userGeolocation = inject('geolocation')

    return {
      userLocation,
      userGeolocation
    }
  }
}
</script>

Provider Inject 响应性

父组件:

import {
    provide,
    ref,
    reactive
} from 'vue'

setup() {const location = ref('北京')
        const geolocation = reactive({
            longitude: 90,
            latitude: 135
        })
        const updateLocation = () => {location.value = '上海'}
        provide('location', location);
        provide('geolocation', geolocation);
        return {updateLocation}
    }
<button @click="updateLocation"> 扭转 location</button>

子组件:

import {inject} from 'vue'

export default {setup() {const userLocation = inject('location', 'The Universe')
    const userGeolocation = inject('geolocation')

    return {
      userLocation,
      userGeolocation
    }
  }
}
</script>

退出移动版