Vue3
0、前言
vue3的优化
- 对虚拟 DOM 进行了重写、模板编译速度的提升,对静态数据的跳过处理
- 对数组的监控
- 对ts有了很好的支持
- 对2.x版本的完全兼容
- 可以有多个根节点(也有bug,比如外层开了
display:flex
那么里面会收到影响,也就是说布局更灵活但也更要小心,总之请对自己与他人的代码负责) 6. 支持Source map,虽然没演示但是这点真的重要 7. vue2 大量的 API 挂载在 Vue 对象的原型上,难以实现 TreeShaking
使用CompositionAPI理由
- 更好的Typescript支持
- 在复杂功能组件中可以实现根据特性组织代码 - 代码内聚性。比如:排序和搜索逻辑内聚
- 组件间代码复用
经过了漫长的迭代,Vue 3.0 终于在上 2020-09-18 发布了,带了翻天覆地的变化,使用了 Typescript 进行了大规模的重构,带来了 Composition API RFC 版本,类似 React Hook 一样的写 Vue,可以自定义自己的 hook ,让使用者更加的灵活。
- setup()
- ref()
- reactive()
- isRef()
- toRefs()
- computed()
- watch()
- LifeCycle Hooks(新的生命周期)
- Template refs
- globalProperties
- Suspense
1、setup 函数
setup() 函数是 vue3 中,专门为组件提供的新属性。
它为我们使用 vue3 的 Composition API 新特性提供了统一的入口,setup 函数会在 beforeCreate 之后、created 之前执行,vue3 也是取消了这两个钩子,统一用 setup 代替,该函数相当于一个生命周期函数,vue 中过去的 data,methods,watch 等全部都用对应的新增 api 写在 setup()函数中。
setup(props, context) { context.attrs context.slots context.parent context.root context.emit context.refs return { } }
- props: 用来接收 props 数据
- context 用来定义上下文, 上下文对象中包含了一些有用的属性,这些属性在 vue 2.x 中需要通过 this 才能访问到,在 setup() 函数中
无法访问到 this
,是个undefined
- 返回值:
return {}
,返回响应式数据,模版中需要使用的函数 - 在以下方法前执行:
- Components
- Props
- Data
- Methods
- Computed Properties
- Lifecycle methods
2、reactive 函数
reactive() 函数接收一个普通对象,返回一个响应式的数据对象,想要使用创建的响应式数据也很简单,创建出来之后,在 setup 中 return 出去,在 template 中调用即可
<template> {{state.name}} // test <template> <script> import { defineComponent, reactive, ref, toRefs } from 'vue'; export default defineComponent({ setup(props, context) { let state = reactive({ name: 'test' }); return state } }); </script>
3、ref() 函数
ref()
函数用来根据给定的值创建一个响应式的数据对象,ref()
函数调用的返回值是一个对象,这个对象上只包含一个 value 属性,只在 setup 函数内部访问 ref 函数需要加 .value
<template> <div class="mine"> {{count}} // 10 </div> </template> <script> import { defineComponent, ref } from 'vue'; export default defineComponent({ setup() { const count = ref(10) // 在js 中获取ref 中定义的值, 需要通过value属性 console.log(count.value); return { count } } }); </script>
在 reactive 对象中访问 ref 创建的响应式数据
<template> <div class="mine"> {{count}} -{{t}} // 10 -100 </div> </template> <script> import { defineComponent, reactive, ref, toRefs } from 'vue'; export default defineComponent({ setup() { const count = ref<number>(10) const obj = reactive({ t: 100, count }) // 通过reactive 来获取ref 的值时,不需要使用.value属性 console.log(obj.count); return { ...toRefs(obj) } } }); </script>
4、isRef() 函数
isRef() 用来判断某个值是否为 ref() 创建出来的对象
<script> import { defineComponent, isRef, ref } from 'vue'; export default defineComponent({ setup(props, context) { const name = 'vue' const age = ref(18) console.log(isRef(age)); // true console.log(isRef(name)); // false return { age, name } } }); </script>
5、toRefs() 函数
toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据
<template> <div class="mine"> {{name}} // test {{age}} // 18 </div> </template> <script> import { defineComponent, reactive, ref, toRefs } from 'vue'; export default defineComponent({ setup(props, context) { let state = reactive({ name: 'test' }); const age = ref(18) return { ...toRefs(state), age } } }); </script>
6、computed()
该函数用来创造计算属性,和过去一样,它返回的值是一个 ref 对象。
里面可以传方法,或者一个对象,对象中包含 set()、get()方法。
<script> import { computed, defineComponent, ref } from 'vue'; export default defineComponent({ setup(props, context) { const age = ref(18) // 根据 age 的值,创建一个响应式的计算属性 readOnlyAge,它会根据依赖的 ref 自动计算并返回一个新的 ref const readOnlyAge = computed(() => age.value++) // 19 return { age, readOnlyAge } } }); </script>
6.2 通过 set()、get()方法创建一个可读可写的计算属性
<script> import { computed, defineComponent, ref } from 'vue'; export default defineComponent({ setup(props, context) { const age = ref(18) const computedAge = computed({ get: () => age.value + 1, set: value => age.value + value }) // 为计算属性赋值的操作,会触发 set 函数, 触发 set 函数后,age 的值会被更新 age.value = 100 return { age, computedAge } } }); </script>
7、 watch() 函数
watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。
默认情况是懒执行的,也就是说仅在侦听的源数据变更时才执行回调。
7.1 监听用 reactive 声明的数据源
<script> import { computed, defineComponent, reactive, toRefs, watch } from 'vue'; export default defineComponent({ setup(props, context) { const state = reactive({ name: 'vue', age: 10 }) watch( () => state.age, (age, preAge) => { console.log(age); // 100 console.log(preAge); // 10 } ) // 修改age 时会触发 watch 的回调, 打印变更前后的值 state.age = 100 return { ...toRefs(state) } } }); </script>
7.2 监听用 ref 声明的数据源
<script> import { defineComponent, ref, watch } from 'vue'; export default defineComponent({ setup(props, context) { const age = ref(10); watch(age, () => console.log(age.value)); // 100 // 修改age 时会触发watch 的回调, 打印变更后的值 age.value = 100 return { age } } }); </script>
7.3 同时监听多个值
<script> import { computed, defineComponent, reactive, toRefs, watch } from 'vue'; export default defineComponent({ setup(props, context) { const state = reactive({ name: 'vue', age: 10 }) watch( [() => state.age, () => state.name], ([newName, newAge], [oldName, oldAge]) => { console.log(newName); console.log(newAge); console.log(oldName); console.log(oldAge); } ) // 修改age 时会触发watch 的回调, 打印变更前后的值, 此时需要注意, 更改其中一个值, 都会执行watch的回调 state.age = 100 state.name = 'vue3' return { ...toRefs(state) } } }); </script>
7.4 stop 停止监听
在 setup() 函数内创建的 watch 监视,会在当前组件被销毁的时候自动停止。
如果想要明确地停止某个监视,可以调用 watch() 函数的返回值即可
,语法如下:
<script> import { set } from 'lodash'; import { computed, defineComponent, reactive, toRefs, watch } from 'vue'; export default defineComponent({ setup(props, context) { const state = reactive({ name: 'vue', age: 10 }) const stop = watch( [() => state.age, () => state.name], ([newName, newAge], [oldName, oldAge]) => { console.log(newName); console.log(newAge); console.log(oldName); console.log(oldAge); } ) // 修改age 时会触发 watch 的回调, 打印变更前后的值, 此时需要注意, 更改其中一个值, 都会执行watch的回调 state.age = 100 state.name = 'vue3' setTimeout(()=> { stop() // 此时修改时, 不会触发watch 回调 state.age = 1000 state.name = 'vue3-' }, 1000) // 1秒之后讲取消watch的监听 return { ...toRefs(state) } } }); </script>
8、LifeCycle Hooks(新的生命周期)
新版的生命周期函数,可以按需导入到组件中,且只能在 setup() 函数中使用,但是也可以在 setup 外定义,在 setup 中使用
onRenderTriggered
<script> import { set } from 'lodash'; import { defineComponent, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onErrorCaptured, onMounted, onUnmounted, onUpdated } from 'vue'; export default defineComponent({ setup(props, context) { onBeforeMount(()=> { console.log('beformounted!') }) onMounted(() => { console.log('mounted!') }) onBeforeUpdate(()=> { console.log('beforupdated!') }) onUpdated(() => { console.log('updated!') }) onBeforeUnmount(()=> { console.log('beforunmounted!') }) onUnmounted(() => { console.log('unmounted!') }) onErrorCaptured(()=> { console.log('errorCaptured!') }) return {} } }); </script>
9、Template refs
通过 refs 来回去真实 dom 元素,这个和 react 的用法一样,为了获得对模板内元素或组件实例的引用,我们可以像往常一样在 setup()中声明一个 ref 并返回它
<div> <div ref="content">第一步, 在dom上面定义, 他会有一个回调</div> </div> <ul> <li>v-for 出来的ref</li> <li>可以写为表达式的形式, 可以推导出vue是如何实现的</li> <li>vue2.x的时候v-for不用这么麻烦, 直接写上去会被组装成数组</li> <li :ref="el => { items[index] = el }" v-for="(item,index) in 6" :key="item">{{item}}</li> </ul>
import { ref, onMounted, onBeforeUpdate } from "vue"; export default { setup() { // 2: 定义一个变量接收dom, 名字无所谓, 但是与dom统一的话会有很好的语义化 const content = ref(null); const items = ref([]); // 4: 在生命周期下, 这个值已经完成了变化, 所以当然第一时间就拿到 onMounted(() => { console.log(content.value); console.log("li标签组", items.value); }); // 5: 确保在每次变更之前重置引用 onBeforeUpdate(() => { items.value = []; }); // 3: 返出去的名称要与dom的ref相同, 这样就可以接收到dom的回调 return { content, items }; } };
10、vue 的全局配置
通过 vue 实例上 config 来配置,包含 Vue 应用程序全局配置的对象。
您可以在挂载应用程序之前修改下面列出的属性:
const app = Vue.createApp({})
app.config = {...}
为组件渲染功能和观察程序期间的未捕获错误分配处理程序。
错误和应用程序实例将调用处理程序:
app.config.errorHandler = (err, vm, info) => {}
可以在应用程序内的任何组件实例中访问的全局属性,组件的属性将具有优先权。
这可以代替 Vue 2.xVue.prototype
扩展:
const app = Vue.createApp({})
app.config.globalProperties.$http = 'xxxxxxxxs'
可以在组件用通过 getCurrentInstance()
来获取全局 globalProperties
中配置的信息,getCurrentInstance()
方法获取当前组件的实例,然后通过 ctx 属性获得当前上下文,这样我们就能在 setup 中使用 router 和 vuex,通过这个属性我们就可以操作变量、全局属性、组件属性等等。
setup( ) { const { ctx } = getCurrentInstance(); ctx.$http }
11、Suspense 组件
在开始介绍 Vue 的 Suspense 组件之前,我们有必要先了解一下 React 的 Suspense 组件,因为他们的功能类似。
React.lazy 接受一个函数,这个函数需要动态调用 import()。
它必须返回一个 Promise,该 Promise 需要 resolve 一个 default export 的 React 组件。
import React, { Suspense } from 'react'; const myComponent = React.lazy(() => import('./Component')); function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <myComponent /> </Suspense> </div> ); }
Vue3 也新增了 React.lazy 类似功能的 defineAsyncComponent 函数,处理动态引入(的组件)。
defineAsyncComponent 可以接受返回承诺的工厂函数。
当您从服务器检索到组件定义时,应该调用 Promise 的解析回调。
您还可以调用 reject(reason) 来指示负载已经失败。
import { defineAsyncComponent } from 'vue' const AsyncComp = defineAsyncComponent(() => import('./components/AsyncComponent.vue') ) app.component('async-component', AsyncComp)
Vue3 也新增了 Suspense 组件:
<template> <Suspense> <template #default> <my-component /> </template> <template #fallback> Loading ... </template> </Suspense> </template> <script> import { defineComponent, defineAsyncComponent } from "vue"; const MyComponent = defineAsyncComponent(() => import('./Component')); export default defineComponent({ components: { MyComponent }, setup() { return {} } }) </script>
12、vuex, router, vue 初始化写法的变化
vue
import { createApp } from 'vue'; import App from './App.vue' import router from './router' import store from './store' // 方法一. 创建实例变成了链式, 直接写下去感觉语义与结构有点模糊, 但是我们要理解vue这样做的良苦用心, 前端趋近于函数化。 // createApp(App).use(router).use(store).mount('#app') // 方法二. const app = createApp(App); app.use(router); app.use(store); app.mount('#app');
router
import { createRouter, createWebHistory } from 'vue-router'; import Home from '../views/Home.vue' const routes = [ { path: '/', name: 'Home', component: Home } ] const router = createRouter({ // 专门创建history的函数 history: createWebHistory(process.env.BASE_URL), routes }) export default router
使用:
<template> <div> {{id}} </div> </template> <script> import { getCurrentInstance, ref } from 'vue'; export default { setup(){ const { ctx } = getCurrentInstance() // 1. 这样也是为了去掉this // 2. 方便类型推导 console.log(ctx.$router); // push等方法 console.log(ctx.$router.currentRoute.value); // 路由实例 // 这个其实没有必要变成ref因为这个值没必要动态 // 但是他太长了, 这个真的不能忍 const id = ref(ctx.$router.currentRoute.value.query.id) // 4: 页面拦截器 ctx.$router.beforeEach((to, from,next)=>{ console.log('路由的生命周期') next() }) return { id } } } </script>
vuex
import { createStore } from 'vuex' // 专门创建实例的一个方法 export default createStore({ state: { }, mutations: { }, actions: { }, modules: { } });
使用:
import { createStore } from 'vuex' // 难道前端趋势只有函数这一种吗 export default createStore({ state: { name:'牛逼, 你拿到我了', age: 24, a:'白', b:'黑' }, mutations: { updateName(state, n){ state.name += n } }, actions: { deferName(store) { setTimeout(()=>{ // 必须只有commit可以修改值, 这个设定我比较反对, 可以讨论 // vuex本身结构就很拖沓, 定义域使用个人都不喜欢 store.state.name = '牛逼, 你改回来了' },1000) } }, getters: { fullName(state){ return `${state.a} - + -${state.b}` } }, modules: { } });
<template> <div> <p>{{name}}</p> <button @click="updateName('+')">点击改变名字</button> <button @click="deferName('+')">改回来</button> <p>{{fullName}}</p> </div> </template> <script> import { useStore } from "vuex"; import { computed } from "vue"; export default { setup() { const store = useStore(); // 1: 单个引入 const name = computed(() => store.state.name); // 2: 引入整个state const state = computed(() => store.state); console.log("vuex的实例", state.value); // 别忘了.value // 3: 方法其实就直接从本体上取下来了 const updateName = newName => store.commit("updateName", newName); // 4: action一个意思 const deferName = () => store.dispatch("deferName"); // 5: getter 没变化 const fullName = computed(() => store.getters.fullName); return { name, fullName, deferName, updateName, }; } }; </script>
13、组件注入
父组件:
<template> <div> 组件: <zj :type="type" @ok="wancheng"></zj> </div> </template> <script> import zj from "../components/子组件.vue"; import { ref } from 'vue'; import { provide } from 'vue' export default { components: { zj }, setup() { provide('name','向下传值'); // 基础值 provide('name2', ref('向下传值')); // 监控值 const type = ref('大多数'); function wancheng(msg){ console.log('子组件-->',msg) setTimeout(()=>{ type.value = 'xxxxxxx' },2000) } return { type, wancheng } } }; </script>
子组件
<template> <div>props的属性不用setup去return --- {{type}}</div> </template> <script> import { inject, ref } from 'vue' export default { props: { type: String }, // 1: props也是不可以解构的, 会失去响应式 // 2: context是上下文, 我们可以获取到slots emit 等方法 // 3: props, context 分开也是为了ts更明确的类型推导 // setup({type}){ setup(props, context) { // 1: props console.log("props", props.type); console.log("上下文", context); context.emit('ok','传递完成') // 2: 注入 console.log('inject',inject('name')); console.log('inject',inject('xxxx','我是默认值')) inject('name1', ref('默认值')) // 接收方也可以这样 } }; </script>
14、vue 3.x 完整组件模版结构
一个完成的 vue 3.x 完整组件模版结构包含了:组件名称、 props、components、setup(hooks、computed、watch、methods 等)
<template> <div class="mine" ref="elmRefs"> <span>{{name}}</span> <br> <span>{{count}}</span> <div> <button @click="handleClick">测试按钮</button> </div> <ul> <li v-for="item in list" :key="item.id">{{item.name}}</li> </ul> </div> </template> <script lang="ts"> import { computed, defineComponent, getCurrentInstance, onMounted, PropType, reactive, ref, toRefs } from 'vue'; interface IState { count: 0, name: string, list: Array<object> } export default defineComponent({ name: 'demo', // 父组件传子组件参数 props: { name: { type: String as PropType<null | ''>, default: 'vue3.x' }, list: { type: Array as PropType<object[]>, default: () => [] } }, components: { /// TODO 组件注册 }, emits: ["emits-name"], // 为了提示作用 setup (props, context) { console.log(props.name) console.log(props.list) const state = reactive<IState>({ name: 'vue 3.0 组件', count: 0, list: [ { name: 'vue', id: 1 }, { name: 'vuex', id: 2 } ] }) const a = computed(() => state.name) onMounted(() => { }) function handleClick () { state.count ++ // 调用父组件的方法 context.emit('emits-name', state.count) } return { ...toRefs(state), handleClick } } }); </script>