6

Vue3

[TOC]

学习

vue3重写的动机

  1. 使用新的js原生特性
  2. 解决设计和体系架构的缺陷

    在vue3中新增了es6的 proxy,proxy消除了vue2的所有限制(比如新对象属性的增加、数组元素的直接修改不会触发响应式机制,这是很多新手所谓的bug),而且有着更好的性能。

    Proxy就是真正意义给一个对象包上一层代理从而去完成数据侦听劫持的操作,所以总的来说这个复杂度是直接少了一个数量级的。只要你对这个代理后的对象访问或修改里面的东西都能被这层代理所感知到,进而去动态地决定返回什么东西给你,并且也不再需要把选项的这些东西再重复挂到组件实例的this上面,因为你访问的时候是有这个信息知道你访问的东西是属于props还是data还是其他的,vue只需要根据这个信息去对应的数据结构里面拿出来就可以了,单就这一点而言你就可以感觉到组件的内存占用已经少了一半。

vue3的改进及特点

1.性能的提升:打包大小减少 41%,初次渲染快 55%,更新快 133%,内存使用减少 54%。

2.新推出的Composition API 使组件更易维护,减少无用数据绑定页面更流畅。

4.更好TypeScript支持,可以在创建命令里直接配置,页面集成畅通无阻。

5.Teleport(瞬移组件)、Suspense(解决异步加载组件问题)和全局 API 的修改和优化。

6.Vue3兼容大部分Vue2的特性,用Vue2代码开发Vue3都可以。

Vue3.x中的新增点

1.多根节点组件

在vue3中,组件正式支持多根节点组件,即片段!

<template>

<header>...</header> 
<main>...</main> 
<footer>...</footer> 

</template>

2.setup

要开始使用Composition API,我们首先需要一个可以实际使用它的地方。在Vue组件中,我们将此位置称为setup。

setup执行时尚未创建组件实例,所以不能使用this,此时this 为 undefined。除了props,无法访问组件中声明的任何data、computed、methods。

注:setup是为了优化性能让程序按需引入全局统一

参数 (props, context)
// props 父组件传过来的props props是具有反应性的(传入新的props时会自动更新)
// context {attrs, emit, slots}
setup(props, context) {
    console.log(context)
    /**
     *  attrs: Proxy
     *  emit: (event, ...args) => instance.emit(event, ...args)
     *  slots: Proxy
    */
}
setup 生命周期钩子
钩子函数setup钩子
beforeCreate没有
created没有
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
由于setup是围绕beforeCreate和created生命周期挂钩运行的,因此您无需显式定义它们。换句话说,应该在这些钩子中编写的任何代码都应直接在setup函数中编写。
用法一:
<template>
  <div class="home">
    <div>名字:{{ name }}</div>
    <ul>
      <li v-for="item in list" :key="item" @click="show(item)">{{ item }}</li>
    </ul>
  </div>
</template>

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

// 注:defineComponent 在TypeScript下,给予了组件正确的参数类型推断

export default defineComponent({

  name: "Home",

  components: {},

  props:['msg'],

  setup(props,context) {

    // 注:setup函数是处于生命周期函数 beforeCreate 和 Created 两个钩子函数之间的函数 也就说在 setup 函数中是无法使用 data 和 methods 中的数据和方法,而methods等可以使用setup中return出去的数据。

    /*

    一.函数的第一个参数是 props 用于接收 props.msg
      这个props是一个响应式的Proxy对象,不可以解构,解构后会失去响应,如果要用解构的方式,要用toRefs
      let { msg } = toRefs(props) //但是解析成ref了要用msg.value,所以直接用props.msg更简单
      
    二.context对象在setup()中暴露三个属性 attrs 、slots 和 emit 因为在setup函数中还没有创建Vue实例,是无法使用vm.$attrs、vm.$slots和vm.$emit的,所以这三个属性充当了这样的作用,使用方法相同。

    注意:
      context.attrs和vm.$attrts包含的是在实例vm.props中没有被声明识别的attribute(class和style除外)。所以setup()中参数props中暴露的变量,就不会在context.attrs中暴露。
      context.slots和vm.$slots只能访问具名插槽,没有命名的插槽或者v-slot:default的是没有暴露的。
      context的attrs和slots是有状态的,当组件更新时也会实时更新,所以也不要解构。但与props不同的是,它们不是响应式的,在setup()中的使用应保持只读的状态,如果要改变可以在onUpdated的周期函数中进行。
      context.emit和vm.$emit可以触发实例上的监听事件。
    */
      
    const list = ref(["深圳", "北京", "上海"]);
    const name = ref("");
    //注:用ref是为了转换成引用类型,让全局引用保持一致,而之前原始类型是不行的,所以要name.value的方示赋值
    const show = (index: string) => {
        name.value = index;
    };
    // 注:不return出去的数据,模板是无法使用的。
    return {
        list,
        name,
        show
    };
  },
});
</script>
用法二: reactive() 优化
<template>
  <div class="home">
    <div>名字:{{ data.name }}</div>
    <ul>
      <li v-for="item in data.list" :key="item" @click="data.show(item)">{{ item }}</li>
    </ul>
  </div>
</template>
<script lang="ts">
import { defineComponent, reactive } from "vue";
export default defineComponent({
  name: "Home",
  components: {},
  setup() {
    const data = reactive({
      list: ["深圳", "北京", "上海"],
      name: "",
      show: (index: string) => {
        data.name = index;
      },
    });
    return {
      data
    };
  },
});

</script>
用法三: toRefs() 优化
<template>
  <div class="home">
    <div>名字:{{ name }}</div>
    <ul>
      <li v-for="item in list" :key="item" @click="show(item)">{{ item }}</li>
    </ul>
  </div>
</template>

<script lang="ts">
import { defineComponent,reactive,toRefs } from "vue";
export default defineComponent({
  name: "Home",
  components: {},
  setup() {
    const data = reactive({
      list: ["深圳", "北京", "上海"],
      name: "",
      show: (index: string) => {
        data.name = index;
      },
    });
    const refData = toRefs(data);
    //不能直接解析 ...data 必须用 toRefs()
    return {
      ...refData
    };
  },
});
</script>

jsx

setup() {
    const root = ref(null)
    return () => <div ref={root} />
}
在setup获取示例this的方法

setup里面this指向window,composition的文档中也没有提到怎么获取组件实例。

咱们可以通过getCurrentInstance()这个接口获取组件实例:

setup() {

  // getCurrentInstance()可以获取组件实例

  const instance = getCurrentInstance()

  console.log(instance);

  onMounted(()=>{

    // 组件实例的上下文才是我们熟悉的this

    console.log(instance.ctx.foo); // 'foo'

    console.log(instance.ctx.bar()); // '我是bar方法'

  })

  return {}

},

但是很快我们又蒙圈了,这个组件实例和我们以前熟悉的this不一样,直接访问this.foo还是找不到数据。

vue3中组件实例结构如下,各个选项中的this实际上是ctxproxy

图片

当然坑还是有的,你仔细观察这个ctx,发现它不是一个Proxy对象,也就是这位兄台只有值却没有响应性,所以如果要利用响应特性,还得用proxy这个属性返回上下文对象,如果只是想要数据,上图中不是有个data也是Proxy类型的嘛。

setup() {

  const { proxy, data } = getCurrentInstance()

  // 想要利用响应能力,就要使用proxy这个上下文

  watchEffect(() => {

    console.log(proxy.foo) // foo变化会有输出

    console.log(data.foo) // foo变化会有输出

  })

},

最后大家还要注意,setup()执行的时间点是很早的,甚至早于created,因此foo和bar的访问如果没有特意放到onMounted里面还真没有。

setup() {

  const instance = getCurrentInstance()

  console.log(instance.ctx.foo); // undefined

  console.log(instance.ctx.bar()); // undefined

},

常用解决篇:

setup函数太长了怎么办?

setup(){

  let { val, todos, addTodo } = useTodo()

  let {count,double,add} = useCounter() 

  let {x, double:doubleX} = useMouse()

  return {

    val, todos, addTodo,

    count,double,add,

    x,doubleX

  }

}

return的上下文太长了,我们可以使用vue3的setup script功能,把setup这个配置也优化掉,一个功能export一次

<script setup>

import useCounter from './counter'

import useMouse from './mouse'

import useTodo from './todos'



let { val, todos, addTodo } = useTodo()

export {val, todos, addTodo}



let {count,double,add} = useCounter()

export {count,double,add}



let {x, double:doubleX} = useMouse()

export {x,doubleX}

</script>

我的属性怎么就不响应了

下面是我第一次写的代码

setup({ foo, bar }) {

  watchEffect(() => {

    console.log(foo, bar) // foo,bar发生变化,也不会有输出

  })

}

props是一个Proxy对象,直接解构就失去了响应能力,所以对待props要温柔,不能动不动就劈开了

setup(props) {

  watchEffect(() => {

    console.log(props.foo, props.bar) // foo,bar发生变化,会有输出

  })

}

真想劈开也行,看你喜欢什么姿势了

setup(props) {

  const { foo, bar } = toRefs(props)

  watchEffect(() => {

    console.log(foo.value, bar.value) // foo,bar发生变化,会有输出

  })

}

怎么派发自定义事件?

突然之间没有this了之后,好像突然生活不能自理了,再也不能用this.$emit()派发事件了。

其实通过组件实例是可以派发事件的,比如:

setup() {

   getCurrentInstance().emit('ooxx') 

}

//但是这样比较麻烦,所以我们要用到setup函数的第二个参数:

setup(props, ctx) {

              ctx.emit('ooxx')

}

//当然,还能把emit直接解构出来使用更方便:

setup(props, { emit }) {

              emit('ooxx')

}

vue3获取当前页面路由

1.

import { getCurrentInstance } from 'vue'

setup() {
  const { ctx } = getCurrentInstance()
  console.log(ctx.$router.currentRoute.value)
}

2.

import { useRoute } from 'vue-router'
import { toRaw } from 'vue'

setup () {
  const route = useRoute()
  console.log(toRaw(route))
}

父组件调用子组件方法

//父组件
  <Listmodify :isShow="isShow" ref="child"></Listmodify>
  
  setup(props, context) {
        const child = ref();
        const handleEdit = () => {
          child.value.str();
        };
         return {  handleEdit,  child };
  }


//子组件
  setup(props, context) {
        const str = () => {
      console.log("我被父组件调用了");
    };
         return {  str };
  }
  

3.Teleport 传送

(解决样式等冲突问题不挂载到app下)

Teleport提供了一种方法,是我们可以控制要在Dom中哪个父对象下呈现HTML,而不必求助于全局状态或将其拆分为两个部分(有点像哆啦A梦的任意门,个人理解)

Teleport 的使用

示例一:

1、先搭建一个vue3的项目

2、开始使用 示例:

 <div id="app"></div>
 <div id="teleport-target"></div>
 <div id="modal-container"></div>
  <button @click="showToast" class="btn">打开 toast</button>
  <!-- to 属性就是目标位置 -->
  <teleport to="#teleport-target">
    <div v-if="visible" class="toast-wrap">
      <div class="toast-msg">我是一个 Toast 文案</div>
    </div>
  </teleport>
import { ref } from 'vue';
export default {
  setup() {
    // toast 的封装
    const visible = ref(false);
    let timer;
    const showToast = () => {
      visible.value = true;
      clearTimeout(timer);
      timer = setTimeout(() => {
        visible.value = false;
      }, 2000);
    }
    return {
      visible,
      showToast
    }
  }
}

3、与 Vue components 一起使用 —— modal封装的组件

<div id="app"></div>
  <div id="teleport-target"></div>
   <div id="modal-container"></div>
  <script type="module" src="/src/main.js"></script>
<teleport to="#modal-container">
    <!-- use the modal component, pass in the prop -->
    <modal :show="showModal" @close="showModal = false">
      <template #header>
        <h3>custom header</h3>
      </template>
    </modal>
  </teleport>
import { ref } from 'vue';
import Modal from './Modal.vue';
export default {
  components: {
    Modal
  },
  setup() {
    // modal 的封装
    const showModal = ref(false);
    return {
      showModal
    }
  }
}

在这种情况下,即使在不同的地方渲染 Modal,它仍将是当前组件(调用 Modal 的组件)的子级,并将从中接收 show prop

示例二:
  1. index.html 页面新加插入点(会挂载到 #headTitie DOM下)
<div id="headTitie"></div>

<div id="app"></div>
  1. 在components目录下新建 headTitle.vue
<template>
  <teleport to="#headTitie">
    <div class="head">
      <h1>{{ title }}</h1>
    </div>
  </teleport>
</template>

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

export default defineComponent({
  name: "headTitie",
  setup() {
    const title = ref("Vue3 新特性示例");
    return {
      title,
    };
  },
});
</script>
  1. 在 App.vue 加
<template>
  <headTitle />
  <router-view />
</template>

<script lang="ts">
import headTitle from "./components/headTitle.vue";
    
export default {
  name: "App",
  components: {
    headTitle,
  },
};

</script>

4.watch监听

从 vue 中引入 watch 可以参考 第4点 Reactivity(反应性)

  1. 监听 ref 声明的 反应性数据
  2. 监听 reactive 声明的反应性数据
  3. 监听 props 里面的数据
<script>
//一定使用之前引入
import {ref, watch} from 'vue'
setup(props) {
    //监听 ref 数据
    let count = ref(0)
    watch(count, (val, old) => {
        // 新数据、老数据
    })
    
    //监听 reactive 数据  对象
    let person = reactive({
        age: 18
    })
    watch(() => person.age, (val, old) => {
        //新数据、老数据
    })
    //监听 reactive 数据  数组
    let arr = reactive([1, 2, 3])
    watch(arr, (val, old) => {
        //新数据、老数据
    })
    
    //监听 props 的数据 加入props传了一个 testkey
    watch(() => props.testkey, () => {
    })
    
    //要注意 return
    return {
        count,
        person,
        arr
    }
}
</script>
<script lang="ts">
import { defineComponent, ref, reactive, toRefs, watch } from "vue";

export default defineComponent({
  name: "Home",
  components: {},
  setup() {
    const text = ref("测试单个值");
    const data = reactive({
      list: ["深圳", "北京", "上海"],
      name: "",
      show: (index: string) => {
        data.name = index;
      },
    });
    //watch(text, 单个用法,watch([text,()=>data.name], 多个用法,注:()=>data.name 为了兼容vue2
    watch([text,()=>data.name], (newValue, oldValue) => {
      console.log(`new--->${newValue}`);
      console.log(`old--->${oldValue}`);
    });
    const refData = toRefs(data);
    return {
      ...refData,
    };
  },
});
</script>

watchEffect和watch有啥不同?

这俩方法很相似,都是观察响应式数据,变化执行副作用函数,但有如下不同:

  • watch需要明确指定监视目标
watch(() => state.counter, (val, prevVal) => {})

//watchEffect不需要

watchEffect(() => {

console.log(state.counter)

})
  • watch可以获取变化前后的值
  • watch是懒执行的,它等效于vue2中的this.$watch(),watchEffect为了收集依赖,要立即执行一次

生命周期钩子能不能写多个?

当然可以写多个,最后它们会按注册顺序依次执行:

setup() {

  onMounted(() => {})

  onMounted(() => {})

  onMounted(() => {})

}

甚至还能拆分出多个相同生命周期钩子到独立函数中

function fun1() {

  // 这里可以用onMounted执行代码

  onMounted(() => {})

}

function fun2() {

  // 这里还可以用onMounted执行代码

  onMounted(() => {})

}

setup() {

  fun1()

  fun2()

  onMounted(() => {})

}

5. computed 计算属性

从 vue 中引入 computed

<script>
import {ref, computed} from 'vue'
setup(props) {
    let count = ref(0)
    
    //当前组件的计算属性
    let countCom = computed(() => {
        return count.value * 2
    })
    //props传进来的计算属性
    let countCom2 = computed(() => {
        return props.propsKey * 2
    })
}
</script>

扩展——

  • computed创建只读计算属性

computed() 传入一个函数,可以得到一个只读的计算属性:

const count = ref(1)

// 创建一个计算属性,使其值比 count 大 1
const bigCount = computed(() => count.value + 1)

console.log(bigCount.value) // 输出 2
bigCount.value++ // error 不可写
  • computed创建可读可写计算属性
const count = ref(1)

// 创建一个 computed 计算属性,传入一个对象
const bigCount = computed({
    // 取值函数
    get: () => (count.value + 1),
    // 赋值函数
    set: val => {
      count.value = val - 1
    }
})

// 给计算属性赋值的操作,会触发 set 函数
bigCount.value = 9
// 触发 set 函数后,count 的值会被更新
console.log(count.value) // 8

6. setup 生命周期钩子使用

  1. setup 的 onMounted 执行 在 整个组件的 mounted 之前
  2. setup 的 onMounted 执行 在 setup 同步组件执行之后
  3. setup 的 onMounted 里面以及 setup 不能使用this
<script>
import {onMounted} from 'vue'
mounted () {
    console.log(4)
},
setup () {
    onMounted(() => {
        console.log(1)
    })
    console.log(2)
    setTimeout(() => {
        console.log(3)
    }, 0)
    //打印顺序
    // 2 1 4 3
    
    
    onMounted(() => {
        console.log(this)
    })
    //打印
    //undefined
}
</script>

7.provide和inject

<!--一句话介绍:provide可以向所有子孙组件提供数据以及提供修改数据的方法,子孙组件用inject使用数据。-->

从 vue 中引入父组件 provide

<script>
import {provide} from 'vue'

export default {
    setup () {
        provide('person', {
            name: 'Bob',
            age: 18
        })
        provide('city', '杭州')
    }
}
</script>

子孙组件 inject

<script>
import {inject} from 'vue'

export default {
    setup () {
        //父辈组件 provide 的数据
        let person = inject('person')
        let city = inject('city')
        //父辈组件 没有提供的值,给个默认值 不给默认为为 undefined 而且会有Vue warn提醒
        let noProvide = inject('noProvide', 'noProvide hah')
    }
}
</script>

provide 带有反应性的数据 父组件值修改,子孙组件值会对应的修改

<script>
import {provide} from 'vue'

export default {
    setup () {
        let city = ref('滨江')
        provide('city', city)
    }
}
</script>

provide 一个 函数

<script>
import {provide} from 'vue'

export default {
    setup () {
        let provideFun = () => {
            console.log('provide fun')
        }
        provide('provideFun', provideFun)
    }
}
</script>
// 父组件
import { ref,provide } from 'vue';
// provide()接受两参数(name: string,data: 传递的数据)
setup(){
    let init = ref(1);
    function change(){
       init.value += 1
    }
    provide('init',init) 
    provide('update',change) // 数据响应式的关键,修改数据的方法也要一应传递下去,否则会不响应
    return {
       change,init
    }
}
// 子组件
import { inject,readonly } from 'vue';
// inject()接收两个参数(name: 要接受provide的name,data: 可不填,默认的数据)
setup(){
    const oneInit = inject('init') // 接收上方传递下来的数据
    const updateUser = inject('updatea1',readonly(oneInit))
    // 接收方法,readonly只读,以免外在原因修改了数据
    return {
            oneInit,
            updateUser
        }
}

8.Reactivity(反应性)

Vue最独特的功能之一是不引人注目的反应系统。

当您将纯JavaScript对象作为data选项传递给应用程序或组件实例时,Vue将遍历其所有属性,并使用带有getter和setter的处理程序将它们转换为Proxies
  • 跟踪更改它的函数:在代理的getter中进行此操作 effect
  • 触发函数,以便它可以更新最终值:在代理中的setter中进行操作 trigger
声明反应性数据

一般使用 ref、reactive、readonly 来声明数据

  • ref 声明基本类型
  • reactive 声明引用类型
  • readonly 声明只读引用类型
import { ref, reactive, readonly } from 'vue'

export default {
    setup () {
        // 一般用 ref 声明 基本 类型
        // 用 reactive 声明 引用 类型
        let count = ref(0) // 在 script 使用 count.value,在 template 使用 {{count}}
        let state = reactive({
            name: 'Bob'
        }) // 在 script 使用 state.name, 在 template 使用 {{state.name}}
        
        // 用 ref 声明 引用类型
        let obj1 = ref({
            count: 1
        })
        // 用 reactive 声明 基本 类型 警告⚠️ 可以正常使用,但是没有反应性
        let num1 = reactive(10) //value cannot be made reactive: 10
        
        // readonly
        // readonly 和 reactive 一样,但是声明的是只读数据 声明基本类型和 reactive 一样警告提醒
        let person = readonly({age: 18})
        setTimeout(() => {
            // 定时器 修改 警告 ⚠️
            person.age = 10 // Set operation on key "age" failed: target is readonly
        });
        
        // 一定要return 才可以在 template 和 script 其他地方使用
        return {
            count, 
            state,
            obj1
        }
    }
}

count打印出的数据

RefImpl {_rawValue: 0, _shallow: false, __v_isRef: true, _value: 0}
    __v_isRef: true
    _rawValue: 0
    _shallow: false
    _value: 0
    value: 0

state打印出的数据

Proxy {name: "Bob"}
    [[Handler]]: Object
        deleteProperty: ƒ deleteProperty(target, key)
        get: ƒ (target, key, receiver)
        has: ƒ has(target, key)
        ownKeys: ƒ ownKeys(target)
        set: ƒ (target, key, value, receiver)
    [[Target]]: Object
        name: "Bob"
    [[IsRevoked]]: false

obj1打印出的数据

RefImpl {_rawValue: {…}, _shallow: false, __v_isRef: true, _value: Proxy}
    __v_isRef: true
    _rawValue: {count: 1}
    _shallow: false
    _value: Proxy {count: 1}
    value: Proxy
        [[Handler]]: Object
        [[Target]]: Object
            count: 1
        [[IsRevoked]]: false

num1打印的数据

10
template中使用
<!-- 使用ref定义的基本类型直接使用 -->
<!-- 使用reactive定义的引用类型直接.对应的属性 -->
<div class="vue3_pro">
    count: {{count}}
    state: {{state.name}}
</div>

9. Vue3 生命周期函数用法

需要引入 (注:vue2 生命周期函数不影响)

<script lang="ts">

import { defineComponent,ref,reactive,toRefs,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onActivated,
onDeactivated,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} from "vue";

export default defineComponent({
  name: "Home",
  components: {},
  setup() {
    //setup() 开始创建组件之前,在beforeCreate和created之前执行。
    const data = reactive({
      list: ["深圳", "北京", "上海"]
    });
    onBeforeMount(()=>{
      //组件挂载到节点上之前执行的函数。
    })
    onMounted(()=>{
      //组件挂载完成后执行的函数。
    })
    onBeforeUpdate(()=>{
      //组件更新之前执行的函数
    })
    onUpdated(()=>{
      //组件更新完成之后执行的函数。
    })
    onBeforeUnmount(()=>{
      //组件卸载之前执行的函数。
    })
    onUnmounted(()=>{
      //组件卸载完成后执行的函数。
    })
    onActivated(()=>{
      //被包含在<keep-alive>中的组件,会多出两个生命周期钩子函数。被激活时执行。
    })
    onDeactivated(()=>{
      //比如从 A 组件,切换到 B 组件,A 组件消失时执行。
    })
    onErrorCaptured(()=>{
      //当捕获一个来自子孙组件的异常时激活钩子函数。
    })
    //< 调试用生命函数
    onRenderTracked((event)=>{
      //跟踪所有状态触发
      console.log(event);
    });

    onRenderTriggered((event) => {
      //跟踪当前状态触发
      console.log(event);

      //key 那边变量发生了变化

      //newValue 更新后变量的值

      //oldValue 更新前变量的值
        
      //target 目前页面中的响应变量和函数
    });
    // 调试用生命函数 />
    const refData = toRefs(data);
    return {
      ...refData
    };
  },
  mounted(){
    console.log("vue2 生命周期");
  }
});
</script>

10.Vue3 模块化重用功能 (优化 mixins)

1.新建useTime.ts文件

import { ref } from "vue";
const time = ref("00:00:00");
const getTime = () => {
    const now = new Date();
    const h= now.getHours() < 10 ? "0" + now.getHours() : now.getHours();
    const m = now.getMinutes() < 10 ? "0" + now.getMinutes() : now.getMinutes();
    const s= now.getSeconds() < 10 ? "0" + now.getSeconds() : now.getSeconds();
    time.value = h + ":" + m + ":" + s;
    setTimeout(getTime, 1000);
};
export { time, getTime }
          

2.引入

<template>
  <div class="home">
    <div>时间:{{time}} <button @click="startTime">开始</button></div>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
import { time, getTime } from './useTime';

export default defineComponent({
  name: "Home",
    
  components: {},
    
  setup() {
    const startTime = () => {
      getTime();
    };
    return {
      startTime,
      time
    };
  },
});
</script>

11.Suspense 异步请求组件

  1. 新建Demo.vue
<template>
  <div class="Demo">
    <div>名字:{{ name }}</div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  name: "Demo",
  components: {},
  setup() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        return resolve({ name: "我是 Suspense 异步请求组件" });
      }, 2100);
    });
  },
});

</script>
  1. 使用引入 home.vue
<template>
  <div class="home">
      
    <Suspense>
        
      <template #default>
        <Demo />
      </template>

      <template #fallback>
        <p>加载中...</p>
      </template>

    </Suspense>

  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Demo from "./Demo.vue";
export default defineComponent({
  name: "Home",
  components: {Demo}
});
</script>

Composition-Api:

12.ref()

ref() 函数可以根据给定的来创建一个响应式的数据对象,返回值是一个对象,且只包含一个 .value 属性。

  • 用 ref 创建响应式对象
// 引入 ref
import { ref } from '@vue/composition-api'

setup() {
    // 创建响应式对象
    const count = ref(0);

    return {
        count
    }
}
  • 使用响应式对象
<p>当前的count的值为:{{count}}</p>

<button @click="count++">点击增加count</button>

在JS中修改这个值要额外加上value

  • ref 的注意事项
  1. setup() 函数内,由 ref() 创建的响应式数据返回的是对象,所以需要用 .value 来访问;

    而在 setup() 函数外部则不需要 .value ,直接访问即可。

  2. 可以在 reactive 对象中访问 ref() 函数创建的响应式数据。
  3. 新的 ref() 会覆盖旧的 ref()
ref数据响应式监听,和react-hook好像差不多。ref 函数传入一个值作为参数,返回一个基于该值的响应式Ref对象,该对象中的值一旦被改变和访问,都会被跟踪到,就像我们改写后的示例代码一样,通过修改 count.value 的值,可以触发模板的重新渲染,显示最新的值

13.reactive()

reactive是用来定义更加复杂的数据类型,但是定义后里面的变量取出来就不在是响应式Ref对象数据了

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

在Vue2.x的版本中,我们只需要在 data() 中定义一个数据就能将它变为响应式数据,在 Vue3.0 中,需要用 reactive 函数或者 ref 来创建响应式数据。
  • 用reactive创建响应式对象
// 在组件库中引入 reactive
import { reactive } from '@vue/    composition-api'

setup() {
    // 创建响应式对象
    const state = reactive({
        count:0
    });

    // 将响应式对象return出去,暴露给模板使用
    return state;
}
  • 使用响应式对象
<p>当前的count的值为:{{count}}</p>

<button @click="count++">点击增加count<  button>

composition-api引入了独立的数据响应式方法reactive,它可以将传入的对象做响应式处理:

const state = reactive({

  foo: 'foo',

})

watchEffect(() => {

  console.log(state.foo)

})

state.foo = 'foooooo' // 输出 'foooooo'

这个方式类似于我们设置的data选项,能够解决我们大部分需求。但是也有以下问题:

  • 当我们直接导出这个state时,我们在模板中就要带上state这个前缀
setup() {

const state = reactive({})

return { state }

}

<div>{{ state.foo }}</div>


//为了解决这个问题又要引入toRefs

setup() {

const state = reactive({})

return { ...toRefs(state) }

}

<div>{{ foo }}</div>
ref和reactive之间的选择

如果是单值,建议ref,哪怕是个单值的对象也可以

一个业务关注点有多个值,建议reactive

14. readonly()

传入一个响应式对象、普通对象或 ref ,返回一个只读的对象代理。这个代理是深层次的,对象内部的数据也是只读的。

const state = reactive({ count: 0 })

const copy = readonly(state)

watchEffect(() => {
  // 依赖追踪
  console.log(copy.count)
})

// state 上的修改会触发 copy 上的侦听
state.count++

// 这里只读属性不能被修改
copy.count++ // warning!

15. watchEffect()

watchEffect() 会立即执行传入的函数,并响应式侦听其依赖,并在其依赖变更时重新运行该函数。

  • 基本用法
const count = ref(0)

// 初次直接执行,打印出 0
watchEffect(() => console.log(count.value))

setTimeout(() => {
  // 被侦听的数据发生变化,触发函数打印出 1
  count.value++
}, 1000)
  • 停止侦听

watchEffect() 使用时返回一个函数,当执行这个返回的函数时,就停止侦听。

const stop = watchEffect(() => {
  /* ... */
})

// 停止侦听
stop()

Composition-Api 依赖工具

1. isRef()

isRef() 顾名思义,是用来判断某个值是否为 ref() 创建出来的响应式的值。

当你需要展开某个可能为 ref() 创建的响应式的值的时候,会用到它:

import { isRef } from '@vue/composition-api'

const unwrapper = isRef(foo) ? foo.value : foo

2.toRefs()

toRefs() 可以将 reactive() 创建出来的响应式对象转换成内容为 ref 响应式的值的普通对象

在搞清楚 toRefs() 的用法之前,我们需要先了解一下用 reactive()ref() 创建出来的响应式对象的区别:

  1. reactive() 创建的响应式对象,整个对象是响应式的,而对象里的每一项都是普通的值。当你把它用展开运算符展开后,整个对象的普通值都不是响应式的;
  2. 而用 ref() 创建的响应式的值,本身就是响应式的,并不依赖于其他对象。

所以当你需要展开 reactive() 创建的响应式对象,又不想让他们失去响应式特点的时候,就需要用 toRefs() 将它进行转换:

mport { toRefs } from '@vue/composition-api'

setup() {
    // 定义响应式数据对象
    const state = reactive({
        count: 0
    })

    // 定义简单的函数,使count每次+1
    const add = () => {
        state.count++
    }

    // 将setup函数的内容return出去,供外界使用
    return {
        // 将 state 展开导出,同时将其属性都转化为 ref 形式的响应式数据
        ...toRefs(state),
        add
    }
}
<template>
    <div>
        <p>当前的count值为:{{count}}</p>
        <button @click="add">点击+1</button>
    </div>
</template>

vue3与vue2的变化

这都是自己试出来的坑

router-link

改进如下:

  • 删除 tag prop - 使用作用域插槽代替
  • 删除 event prop - 使用作用域插槽代替
  • 增加 scoped-slot API
  • 停止自动将 click 事件分配给内部锚点
  • 添加 custom prop 以完全支持自定义的 router-link 渲染

在 vue2-router 中,想要将 <roter-link> 渲染成某种标签,例如 <button>,需要这么做:

<router-link to="/" tag="button">按钮</router-link>
!-- 渲染结果 -->
<button>按钮</button>

以后可能需要这样做:

<router-link
  to="/foo"
  v-slot="{ href, route, navigate, isActive, isExactActive }"
>
  <li
    :class="[isActive && 'router-link-active', isExactActive && 'router-link-exact-active']"
  >
    <a :href="href" @click="navigate">{{ route.fullPath }}</a>
  </li>
</router-link>

插槽 prop 的对象包含下面几个属性:

1、href:解析后的 URL。将会作为一个 a 元素的 href attribute。
2、route:解析后的规范化的地址。
3、navigate:触发导航的函数。会在必要时自动阻止事件,和 router-link 同理。
4、isActive:如果需要应用激活的 class 则为 true。允许应用一个任意的 class。
5、isExactActive:如果需要应用精确激活的 class 则为 true。允许应用一个任意的 class。

API变更

关于 Composition API 的说明:

1、useRouter
2、useRoute
3、onBeforeRouteLeave
4、onBeforeRouteUpdate
5、useLink


Jreey
16 声望2 粉丝