vue3-学习笔记(一)

343 阅读11分钟

Vue3.0的亮点

  • Performance:性能比Vue 2.x快 1.2~2倍
  • Tree shaking support:按需编译,体比Vue2.x更小
  • Composition API:组合API(类似React Hooks)
  • Better Typescript support: 更好的Ts支持
  • Custom Render API:暴露了自定义渲染API
  • Fragment,Teleport(Protal),Suspense: 更先进的组件

Vue3.0是如何变快的

vue3 代码装换网址链接

vue2 代码装换网址链接

  1. diff算法优化
  • Vue2中虚拟dom是进行全量的对比
  • Vue3新增了静态标记(PatchFlag),在与上次虚拟节点进行对比的时候,只比对带有patch flag的节点,并且可以通过flag信息得知当前节点要对比的具体内容。
<div id="app">
    <p>标题</p>
    <p>用户名</p>
    <p>{{ msg }}</p>
</div>

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", { id: "app" }, [
    _createVNode("p", null, "标题"),
    _createVNode("p", null, "用户名"),
    _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)  // 添加了静态标记
  ]))
}
  1. hoistStatic 静态提升
  • Vue2 中无论元素是否参与更新,每次都会被重新创建,然后再渲染
  • Vue3 中对不参与更新的元素,会做静态提示,只会被创建一次,再渲染时直接复用
<div id="app">
    <p>标题</p>
    <p>用户名</p>
    <p>{{ msg }}</p>
</div>
// 静态提升之前
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", { id: "app" }, [
    _createVNode("p", null, "标题"),
    _createVNode("p", null, "用户名"),
    _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)  // 添加了静态标记
  ]))
}
// 静态提升之后 
const _hoisted_1 = { id: "app" }
const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "标题", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "用户名", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", _hoisted_1, [
    _hoisted_2,
    _hoisted_3,
    _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
  ]))
}
  1. cacheHanders 事件监听缓存
  • 默认情况下onClick会被视为动态绑定,所以每次都会去追踪它的变化,但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。
<div id="app">
  <button @click="handleClick"></button>
</div>
// 事件监听缓存之前 PatchFlag为8,事件被当做是一个动态属性,因此每次都会进行对比
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", { id: "app" }, [
    _createVNode("button", { onClick: _ctx.handleClick }, null, 8 /* PROPS */, ["onClick"])
  ]))
}
// 事件监听缓存之后
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", { id: "app" }, [
    _createVNode("button", {
      onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.handleClick && _ctx.handleClick(...args)))
    })
  ]))
}
// 开启事件监听之后,没有了静态标记,在Vue3的diff算法中,只有有静态标记的才会进行比较,才会追踪,因此,添加事件监听提高了性能。
  1. ssr渲染
  • 当有大量静态的内容的时候,这些内容会被当作纯字符推进一个buffer中,即使存在动态的绑定,会通过模板插值嵌入进去,这样会比通过虚拟DOM来渲染的快上很多很多。
  • 当静态内容大到一定量级的时候,会用_createStaticNode 方法在客户端去生成一个 static node,这些静态node,会直接innerHTML,就不需要创建对象,然后根据对象渲染。

创建Vue3 的方式

  • vue-cli
  • webpack
  • vite 什么是vite
  • vite是Vue作者开发的一款意图取代webpack的工具
  • 其实现原理利用ES6的import会发送请求去加载文件的特性,拦截这些请求,做一些编译,省去webpack冗长的打包时间。 安装vite

npm install -g create-vite-app

使用vite创建项目

create-vite-app projectName

组合API

setup函数

setup函数为我们使用vue3的composition API新特新提供了统一的入口,vue中过去的data、methods、watch、computed等部分都用对应的新增api写在setup函数中。

  1. setup函数的执行时机
  • beforeCreate:表示组件刚刚被创建出来,组件中的data和methods还没有初始化好。
  • setup:
  • created: 表示组件刚刚被创建出来,并且组件中的data和methods已经初始化完毕。
  • 当然,vue3中取消了beforeCreate和created这两个生命周期函数,统一使用setup代替。
  1. setup注意点
  • 由于在执行setup函数的时候,还没有执行created函数,所以setup函数中,是无法使用data和methods。
  • setup中不能使用this,setup中的this是undefined。
  • setup函数只能是同步的不能是异步的。
  1. setup函数接收两个参数:
  • props:用来接收props数据
  • context:用来定义上下文,上下文对象中包含了一些有用的属性
  • 返回值return{},返回响应式数据,模板中需要使用的函数。
  1. 添加和删除学生的案例使用方式类似于React Hooks
<template>
    <div>
        // 添加学生的表单
        <form action="">
            <input type="text" v-model="state2.stu.id">
            <input type="text" v-model="state2.stu.name">
            <input type="text" v-model="state2.stu.age">
            <input type="submit" @click="addStu">
        </form>
        // 展示学生列表
        <ul>
            <li v-for="(stu, index) in state.stus" :key="stu.id" @click="removeStu(index)">{{index}}---{{stu.name}}</li>
        </ul>
    </div>
</template>

<script>
import { ref, reactive } from 'vue'
export default {
    setup(props) {
        let [state, removeStu] = useRemoveStudent()
        let [state2, addStu] = useAddStu(state)
        return {
            state,
            removeStu,
            state2,
            addStu
        }
    }
}
// 删除指定的学生信息
function useRemoveStudent() {
    let stus = ref(0)
    let state = reactive({
        stus: [
            { id: 1, name: 'aaa', age: 10},
            { id: 2, name: 'bbb', age: 20},
            { id: 3, name: 'ccc', age: 30},

        ]
    })
    function removeStu(index) {
        state.stus = state.stus.filter((stu, idx) => idx !== index)
    }
    return [state, removeStu]
}
// 添加学生
function useAddStu(state) {
    let state2 = reactive({
        stu: {
            id: '',
            name: '',
            age: ''
        }
    })
    function addStu(e) {
        e.preventDefault();
        const stu = Object.assign({}, state2.stu)
        state.stus.push(stu)
    }
    return [state2, addStu]
}
</script>

<style lang="">
    
</style>

reactive函数

  1. 什么是reactive
  • reactive是vue3 中提供的实现响应式数据的方式
  • vue2中响应式数据是通过defineProperty来实现的,而在vue3中响应式数据是通过ES6的proxy来实现的。
  1. reactive注意点
  • reactive参数必须是对象(json/arr)
  • 如果给reactive传递了其他对象:
    • 默认情况下修改对象,界面是不会自动更新的
    • 如果想要更新,可以通过重新赋值的方式
<template>
    <div>
        <p>{{state.age}}</p>
        <p>{{timeState.time}}</p>
        <button @click="myFn">按钮</button>
    </div>
</template>

<script>
import { reactive } from 'vue'
export default {
    setup(props) {
        // 创建一个响应式数据
        // 本质:就是将传入的数据包装成一个Proxy对象
        let state = reactive({age: 123})  // json对象
        let arr = reactive([1,2,3])   // arr
        let timeState = reactive({   // 其他对象
            time: new Date()
        })
        function myFn()  {
            state.age = 22
            // 修改之前的界面不会重新更新
            // timeState.time = timeState.time.setDate(timeState.time.getDate() + 1)  
            // 重新赋值
            const newTime = new Date(timeState.time)
            newTime.setDate(timeState.time.getDate() + 1)
            timeState.time = newTime
            console.log(timeState.time)
        }
        return{
            state,
            timeState,
            myFn
        }
    }
}
</script>

<style lang="">
    
</style>

ref函数

  1. 什么是ref
  • ref和reactive一样,也是用来实现响应式数据的方法
  • 由于reactive必须传递一个对象,所以导致在企业开发中如果我们只想某个变量实现响应式的时候会非常麻烦,所以Vue3就给我们提供了ref方法,实现对简单数据的监听
  1. ref的本质:
  • ref底层还是reactive,系统会自动根据我们给ref传入的值将他转换成ref(xx) -> reactive({value:xx})
  1. ref的注意点
  • 在Vue中使用ref的值不用通过value获取 -- template中使用的时候vue会自动添加.value
  • 在JS中使用ref的值必须通过value获取 -- script中使用
<template>
    <div>
        <p>{{age}}</p>
        <button @click="myFn">按钮</button>
    </div>
</template>

<script>
import { ref } from 'vue'
export default {
    setup(props) {
        let age = ref(18)
        function myFn() {
            // age = 666
            age.value = 666
        }
        return {
            age,myFn
        }
        
    }
}
</script>

<style lang="">
    
</style>

使用ref函数获取元素

  1. 在vue2 中我们可以通过给元素添加 ref='xxx',然后通过 refs.xxx 的方式获取元素。
  2. 在vue3 中我们也可以通过ref来获取元素。
<template>
    <div>
        <p ref="pNode">我是p标签</p>
    </div>
</template>

<script>
import { ref, onMounted } from 'vue'
export default {
    setup(props) {
        let pNode = ref(null)
        /*
        beforeCreate
        setup
        created
        */
        console.log(pNode.value)  // 此时,获取不到元素 

        // 在组合API中使用 生命周期函数
        onMounted(() => {
            console.log('onMounted', pNode.value)  // onMounted <p>​我是p标签​</p>​
        })
        return {
            pNode
        }
    }
}
</script>


递归监听与非递归监听

  1. 递归监听
  • 默认情况下,无论是通过ref还是通过reactive都是递归监听
  1. 递归监听存在的问题
  • 如果数据量比较大的时候,非常消耗性能
  1. 非递归监听
  2. 应用场景
  • 一般情况下使用 ref 和 reactive 即可,只有在需要监听的数据量比较大的时候,才使用sallowRef 和 shallowReactive

递归监听

<template>
    <div>
        <p>{{state.a}}</p>
        <p>{{state.gf.b}}</p>
        <p>{{state.gf.f.c}}</p>
        <p>{{state.gf.f.s.d}}</p>
        <button @click="myFn">按钮</button>
    </div>
</template>

<script>
import { reactive, ref } from 'vue'
export default {
    setup(props) {
        let state = reactive({
        // let state  = ref({
            a: 'a',
            gf: {
                b: 'b',
                f: {
                    c: 'c',
                    s: {
                        d: 'd'
                    }
                }
            }
        });
        function myFn() {
        //修改reactive创建的state
            state.a = '1'
            state.gf.b = '2'
            state.gf.f.c = '3'
            state.gf.f.s.d = '4'
       //修改ref创建的state
            // state.value.a = '1'
            // state.value.gf.b = '2'
            // state.value.gf.f.c = '3'
            // state.value.gf.f.s.d = '4'
            console.log(state)
            console.log(state.gf)
            console.log(state.gf.f)
            console.log(state.gf.f.s)
        }
        return {
            state,
            myFn
        }
    }
}
</script>

<style lang="">
    
</style>

console.log()输出的结果是:

Proxy {a: "1", gf: {…}}
Proxy {b: "2", f: {…}}
Proxy {c: "3", s: {…}}
Proxy {d: "4"}

非递归监听

vue3中提供了两个函数来创建非递归监听数据:shallowReactive shallowRef

shallowReactive()函数

<template>
    <div>
        <p>{{state.a}}</p>
        <p>{{state.gf.b}}</p>
        <p>{{state.gf.f.c}}</p>
        <p>{{state.gf.f.s.d}}</p>
        <button @click="myFn">按钮</button>
    </div>
</template>

<script>
import { shallowReactive, shallowRef } from 'vue'
export default {
    setup(props) {
        let state = shallowReactive({
            a: 'a',
            gf: {
                b: 'b',
                f: {
                    c: 'c',
                    s: {
                        d: 'd'
                    }
                }
            }
        });
        function myFn() {
            state.a = '1'
            state.gf.b = '2'
            state.gf.f.c = '3'
            state.gf.f.s.d = '4'
            console.log(state)
            console.log(state.gf)
            console.log(state.gf.f)
            console.log(state.gf.f.s)

        }
        return {
            state,
            myFn
        }
    }
}
</script>

<style lang="">
    
</style>

1、 console.log()输出的结果是:

Proxy {a: "1", gf: {…}}
{b: "2", f: {…}}
{c: "3", s: {…}}
{d: "4"}

视图层由a、b、c、d ---> 1、2、3、4

2、 注释掉state.a = '1',console.log()输出的结果为:

Proxy {a: "a", gf: {…}}
{b: "2", f: {…}}
{c: "3", s: {…}}
{d: "4"}

由于只会监听第一层的并且第一层的数据没有发生改变,所以视图层还是:a、b、c、d

结论: 非递归监听只有第一层会被包装成proxy,如果将state.a = '1'注释掉,此时由于只会监听第一层,并且第一层没有发生数据的改变,因此,第二层、第三层...会发生数据的更新,但视图层不会改变。

shallowRef函数

<template>
    <div>
        <p>{{state.a}}</p>
        <p>{{state.gf.b}}</p>
        <p>{{state.gf.f.c}}</p>
        <p>{{state.gf.f.s.d}}</p>

        <button @click="myFn">按钮</button>
    </div>
</template>

<script>
import { shallowReactive, shallowRef } from 'vue'
export default {
    setup(props) {
        let state  = shallowRef({
            a: 'a',
            gf: {
                b: 'b',
                f: {
                    c: 'c',
                    s: {
                        d: 'd'
                    }
                }
            }
        });
        function myFn() {
            state.value.a = '1'
            state.value.gf.b = '2'
            state.value.gf.f.c = '3'
            state.value.gf.f.s.d = '4'
            
            // state.value = {
            //     a: '1',
            //     gf: {
            //         b: '2',
            //         f: {
            //             c: '3',
            //             s: {
            //                 d: '4'
            //             }
            //         }
            //     }
            // }
            
            console.log(state)
            console.log(state.value)
            console.log(state.value.gf)
            console.log(state.value.gf.f)
            console.log(state.value.gf.f.s)
        }
        return {
            state,
            myFn
        }
    }
}
</script>

<style lang="">
    
</style>

1、 console.log() 输出的结果是:

RefImpl {_rawValue: {…}, _shallow: true, __v_isRef: true, _value: {…}}
{a: "1", gf: {…}}
{b: "2", f: {…}}
{c: "3", s: {…}}
{d: "4"}

视图层是:a、b、c、d。 如果想使用这种方法,使视图层发生改变(包括只改变某一层的结果),vue3 提供了一个函数triggerRef(),在上面的程序中添加triggerRef(state)就可以使视图层发生改变 2、如果

// 将
state.value.a = '1'
state.value.gf.b = '2'
state.value.gf.f.c = '3'
state.value.gf.f.s.d = '4'
// 替换为
state.value = {
    a: '1',
    gf: {
        b: '2',
        f: {
            c: '3',
            s: {
                d: '4'
            }
        }
    }
}

此时console.log() 输出的结果是:

RefImpl {_rawValue: {…}, _shallow: true, __v_isRef: true, _value: {…}}
{a: "1", gf: {…}}
{b: "2", f: {…}}
{c: "3", s: {…}}
{d: "4"}

视图层是:a、b、c、d ---> 1、2、3、4

注意

  • 如果通过shallowRef创建数据,那么Vue监听的时.value的变化,并不是第一层的变化。
    • 因为shallowRef底层是:shallowRef(10) ----> shallowReactive({value: 10})
    • 因为底层本质上是value才是第一层
    • 所以如果通过shallowRef 创建的数据,它监听的是.vlaue 的变化
  • vue3只提供了triggerRef方法,没有提供triggerReactive方法,所以如果是reactive类型的数据,那么无法主动触发界面更新的。

toRaw函数

ref / reactive 数据类型每次修改的时候都会被追踪,都会更新UI界面,但是这样其实是非常消耗性能的。所以如果我们有一些操作不需要被追踪,不需要更新UI界面,那么这个时候,我们就可以通过toRaw方法拿到它的原始数据,对原始数据进行修改这样就不会被追踪,这样就不会更新UI界面,这样性能就好了。

reactive函数中使用toRaw

<template>
    <div>
        <p>{{state}}</p>
   

        <button @click="myFn">按钮</button>
    </div>
</template>

<script>

/*
1. toRaw
从reactive 或 ref 中得到原始数据
2. toRaw 作用
做一些不想被监听的事件(提升性能)
*/

import { reactive, toRaw } from 'vue'
export default {
    setup(props) {
        let obj = {name: 'zs', age: 20}
        let state = reactive(obj)

        let obj2 = toRaw(state) 
        console.log(obj === obj2)  // true
        // state 和 obj的关系
        // 引用关系,state 的本质是有一个proxy 对象,在这个 proxy 对象中引用了obj
        function myFn() {
            // obj.name = 'ls'
            // state.name = 'ls'
            // 如果直接修改了 obj,那么是无法触发界面更新的
            // 只有通过包装之后的对象来修改,才会触发界面的更新
             
            obj2.name = 'ls'
            console.log(obj2)   // {name: "ls", age: 20}
            console.log(state)   // Proxy {name: "ls", age: 20}
    
        }
        return {
            state,
            myFn
        }
    }
}
</script>

<style lang="">
    
</style>

ref函数中使用toRaw

如果想要使用toRaw获取到ref类型的原始数据,那么必须明确告诉 toRaw 方法,要获取的是.value的值。因为经过vue处理后, .value中保存的才是原始数据。

<template>
    <div>
        <p>{{state}}</p>
        <button @click="myFn">按钮</button>
    </div>
</template>

<script>
import { reactive, toRaw, ref } from 'vue'
export default {
    setup(props) {
        let obj = {name: 'zs', age: 20}
        let state = ref(obj)
        let obj2 = toRaw(state.value)  // 获取ref定义的数据
        function myFn() {
            obj2.name = 'ls'
            console.log(obj2)
            console.log(state)
        }
        return {
            state,
            myFn
        }
    }
}
</script>

<style lang="">
    
</style>

markRaw函数

使数据永远不会被追踪

<template>
    <div>
        <p>{{state}}</p>
        <button @click="myFn">按钮</button>
    </div>
</template>

<script>
import { reactive, ref, markRaw } from 'vue'
export default {
    setup(props) {
        let obj = {name: 'zs', age: 18}
        obj = markRaw(obj)
        let state = reactive(obj)
        function myFn() {
          state.name = 'abc'
        }
        return {
            state,
            myFn
        }
    }
}
</script>

<style lang="">
    
</style>

toRef函数

ref 和 toRef 的区别:

  • ref -> 复制,修改响应式数据不会影响原始的数据
  • toRef -> 引用,修改响应式数据会影响原始的数据
  • ref -> 数据发生改变了,界面会自动更新
  • toRef -> 数据改变了,界面不会自动更新

toRef的使用场景:

  • 如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后还不想更新UI,那么就可以使用toRef
<template>
    <div>
        <p>{{state}}</p>

        <button @click="myFn">按钮</button>
    </div>
</template>

<script>

/*

*/
import { reactive, ref, toRef } from 'vue'
export default {
    setup(props) {
        
        let [state, myFn] = useRef()
        // let [state, myFn] = useToRef()
        return {
            state,
            myFn
        }
    }
}
function useRef() { 
    let obj = {name: 'zs'}
    let state = ref(obj.name)
    /*
    结论: 如果我们利用ref将某一个对象中的属性变成响应式数据,当修改响应式数据时是不会影响原始数据的,会触发UI界面的更新
    */
    function myFn() {
        state.value = 'abc'
        console.log(obj)  //  {name: "zs"}
        console.log(state)
    }
    return [state, myFn]
 }
 function useToRef() { 
    let obj = {name: 'zs'}
    let state = toRef(obj, 'name')
    /*
    结论: 如果我们利用toRef将某一个对象中的属性变成响应式数据,当修改响应式数据时会影响原始数据,但不会触发UI界面的更新
    */
    function myFn() {
        state.value = 'abc'
        console.log(obj)  // {name: 'abc'}
        console.log(state)
    }
    return [state, myFn]
 }
</script>

<style lang="">
    
</style>

toRefs函数

toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据

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

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

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

<template>
    <div>
        <p>{{state}}</p>

        <button @click="myFn">按钮</button>
    </div>
</template>

<script>

import { reactive, ref, toRefs } from 'vue'
export default {
    setup(props) {
        let [state, myFn] = useToRefs()
        return {
            state,
            myFn
        }
    }
}
function useToRefs() { 
    let obj = {name: 'zs', age: 18}
    // 或者
    // let obj = reactive({name: 'zs', age: 18})  
    let state = toRefs(obj)
    function myFn() {
        state.name.value = 'abc'
        state.age.value = 30
        console.log(obj)   // {name: "abc", age: 30}
        console.log(state)
    }
    return [state, myFn]
 }
</script>

<style lang="">
    
</style>

customRef函数

customRef 返回一个ref对象,可以显式的控制依赖追踪和触发响应

<template>
    <div>
        <p>{{age}}</p>
        <button @click="myFn">按钮</button>
    </div>
</template>

<script>
import { reactive, ref, customRef } from 'vue'
export default {
    setup(props) {
        let [age, myFn] = useCustomRef()
        return {
            age,
            myFn
        }
    }
}
function myRef(value) { 
    return customRef((track, trigger) => {
        return {
            get() {
                track()  // 告诉vue这个数据是需要追踪变化的
                console.log('get', value)
                return value;
            },
            set(newValue) {
                console.log('set', newValue)
                value = newValue
                trigger()  // 告诉vue触发界面更新
            }
            
        }
    })
 }
function useCustomRef() { 
    let age = myRef(18)
    function myFn() {
        age.value += 1
    }
    return [age, myFn]
 }
</script>

<style lang="">
    
</style>

readonly, isReadonly, shallowReadonly 函数

<template>
    <div>
        <p>{{state.name}}</p>
        <p>{{state.attr.age}}</p>
        <p>{{state.attr.height}}</p>

        <button @click="myFn">按钮</button>
    </div>
</template>

<script>

import { readonly, isReadonly, shallowReadonly } from 'vue'
export default {
    setup(props) {
        // 创建只读属性属性,并且是递归只读
        // let state = readonly({ name: 'lnj', attr: { age: 18, height: 1.88}})  

        // 创建只读属性属性,第一层只读,不是递归只读,响应数据发生改变,ui视图没有重新渲染
        let state = shallowReadonly({ name: 'lnj', attr: { age: 18, height: 1.88}})  

        function myFn() { 
            state.name = 'aaa'
            state.attr.age = 666
            state.attr.height = 1.66
            console.log(state)
            console.log(isReadonly(state))  // 都是true
         }
        return {
            state,
            myFn
        }
    }
}

</script>

<style lang="">
    
</style>

computed、watch、watchEffect

  1. computed函数:
  • 与computed配置功能一致
  • 只有getter
  • 有getter和setter
  1. watch函数
  • 与watch配置功能一致
  • 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
  • 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
  • 通过配置deep为true, 来指定深度监视
  1. watchEffect函数
  • 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
  • 默认初始时就会执行第一次, 从而可以收集需要监视的数据
  • 监视数据发生变化时回调
<template>
    <div>
        <h2>计算属性和监视</h2>
        <fieldset>
            <legend>姓名操作</legend>
            姓名:<input type="text" placeholder="请输入姓氏" v-model="user.firstName" /><br />
            名字:<input type="text" placeholder="请输入名字" v-model="user.lastName" /><br />
        </fieldset>
        <fieldset>
            <legend>计算属性和监视演示</legend>
            姓名:<input type="text" placeholder="显示姓名" v-model="fullName1" /><br />
            姓名:<input type="text" placeholder="显示姓名" v-model="fullName2" /><br />
            姓名:<input type="text" placeholder="显示姓名" v-model="fullName3" /><br />
        </fieldset>
    </div>
</template>

<script lang="ts">
import { computed, reactive, ref, watch, watchEffect } from 'vue'
export default {
    setup() {
        // 定义一个响应式对象
        const user = reactive({
            firstName: '诸葛',
            lastName: '孔明'
        })  
        // 通过计算属性实现第一个名字的显示
        // 如果只传入一个回调函数,表示的是get

        // 第一个姓名
        // 返回的是Ref类型的对象
        const fullName1 = computed(() => {
            return user.firstName + '_' + user.lastName
        }) 

        // 第二个姓名 
        const fullName2 = computed({
            get(){
                return user.firstName + '_' + user.lastName
            },
            set(val: string){
                // console.log('====', val)
                const names = val.split('_')
                user.firstName = names[0]
                user.lastName = names[1]
            }
        })
        // 第三个姓名
        // 监视----监视指定的数据
        // immediate 开始时执行一次  deep 深度监视
        const fullName3 = ref('')
        watch(user, ({firstName, lastName}) => {
            fullName3.value = firstName + '_' + lastName
        }, { immediate: true, deep: true })

        // 监视---不需要配置 immediate ,本身默认就会监视(默认执行一次)
        // watchEffect(() => {
        //     fullName3.value = user.firstName + '_' + user.lastName
        // })

        // 监视fullName3的数据,改变 firstName 和 lastName
        watchEffect(() => {
            const names = fullName3.value.split('_')
            user.firstName = names[0]
            user.lastName = names[1]
        })
        // 当我们监听非响应式数据时需要:
        watch([()=>user.firstName, ()=>user.lastName, fullName3], () =>{
            console.log(1111)
        })
        return {
            user,
            fullName1,
            fullName2,
            fullName3
        }
    }
}
</script>
<style lang="">
    
</style>

provide 与 inject

  • provide和inject提供依赖注入,功能类似 2.x 的provide/inject

  • 实现跨层级组件(祖孙)间通信

// 父组件通过 provide 暴露要传递的数据
<script lang="ts">
import { provide, ref } from 'vue'

export default {
  
  setup() {
    const color = ref('red')

    provide('color', color)

    return {
      color
    }
  }
}
</script>
//  孙子组件通过 inject 接收父组件传递的数据
<script lang="ts">
import { inject } from 'vue'
export default {
  setup() {
    const color = inject('color')

    return {
      color
    }
  }
}
</script>

vue3生命周期

vue2生命周期

vue3生命周期

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

  • beforeCreate -> 使用 setup()
  • created -> 使用 setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured
<template>
    <div>
        <h2>App子级组件</h2>
        <h4>{{msg}}</h4>
        <button @click="update">更新数据</button>
    </div>
</template>

<script lang="ts">
import { defineComponent, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, ref } from 'vue'

export default defineComponent({
    setup () {
        const msg = ref('abc')
        const update = () => {
            msg.value += '===='
        }
        console.log('3.0 setup')
        onBeforeMount(() => {
            console.log('3.0 onBeforeMount')
        }),
        onMounted(() => {
            console.log('3.0 onMounted')
        }),
        onBeforeUpdate(() => {
            console.log('3.0 onBeforeUpdate')
        }),
        onUpdated(() => {
            console.log('3.0 onUpdated')
        }),
        onBeforeUnmount(() => {
            console.log('3.0 onBeforeUnmount')
        }),
        onUnmounted(() => {
            console.log('3.0 onUnmounted')
        })
        return {msg, update}
    },
    beforeCreate() {
        console.log('2.x beforecreate')
    },
    created() {
        console.log('2.x created')
    },
    beforeMount() {
        console.log('2.x beforeMount')
    },
    mounted() {
        console.log('2.x mounted')
    },
    beforeUpdate() {
        console.log('2.x beforeUpdate')
    },
    updated() {
        console.log('2.x updated')
    },
    beforeUnmount() {
        console.log('3.x beforeUnmount')
    },
    unmounted() {
        console.log('3.x unmounted')
    }
})
</script>

<style scoped>

</style>

新组件

Fragment(片段)

  • 在vue2中:组件必须有一个根标签
  • 在vue3中,组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处:减少标签层级,减少内存的占用
<template>
    <h2>aaaa</h2>
    <h2>aaaa</h2>
</template>

Teleport(瞬移)

  • 将Teleport 包裹的组件,瞬移到to属性指定的标签下

ModuleButton.vue 子组件

<template>
    <button @click="modalOpen=true">打开一个对话框</button>
    <teleport to='body'>
        <div v-if="modalOpen">
            <div>这是对话框</div>
            <button @click="modalOpen=false">关闭对话框</button>
        </div>
    </teleport>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'

export default defineComponent({
    setup () {
        
        const modalOpen  = ref(false)
        return {modalOpen}
    }
})
</script>

index.vue 父组件

<template>
    <h2>父级组件</h2>
    <ModuleButton />
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import ModuleButton from './ModuleButton.vue'
export default defineComponent({
    setup () {
        

        return {}
    },
    components: {
        ModuleButton
    }
})
</script>

<style scoped>

</style>

Suspense(不确定的)

  • 它允许我们在应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验。 异步组件
<template>
    <h2>asyncComponent</h2>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
    setup () {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve({
                    msg: 'asyncComponent'
                })
            }, 2000)
        })
    }
})
</script>

主组件

<template>
   <h2>父级组件</h2>
   <suspense>
       <!-- v-slot: 简写 # -->
       <template #default>  
           <!-- 异步组件 -->
           <Child />
       </template>
       <template v-slot:fallback>
           <!-- loading的内容 -->
           <h3>loading</h3>
       </template>
   </suspense>
</template>

<script lang="ts">
import { defineComponent, defineAsyncComponent } from 'vue'

// vue3 中动态导入组件
// const Child = defineAsyncComponent(() =>  import('./Child.vue'))
import Child from './Child.vue'

export default defineComponent({
    setup () {
        return {}
    },
    components: {
        Child
    }
})
</script>

<style scoped>

</style>

其他新的API

全新的全局API

  • createApp() 创建app应用
  • defineProperty()
  • defineAsyncComponent()
  • nextTick()

将原来的全局API转移到应用对象

  • app.component()
  • app.config()
  • app.directive()
  • app.mount()
  • app.unmount()
  • app.use()