在Vue2.x
中,编写组件的方式是使用Options API
,它的特点是在对应的属性中编写对应的功能模块。
比如data
中定义数据、methods
中定义方法、computed
中定义计算属性、watch
中监听属性改变。
生命周期钩子也属于Options API
(选项式API
)。
当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中。
当组件变得很大、很复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散。
特别是对于那些一开始没有编写这些组件的人(阅读组件的人)来说,这个组件的代码是难以阅读和理解的。
碎片化的代码使用理解和维护这个复杂的组件变得非常困难,很容易隐藏了潜在的逻辑问题。
当处理单个逻辑关注点时,需要不断的跳到相应的代码块中,增加了代码维护的成本。
如果能将同一个逻辑关注点相关的代码收集在一起会更好一些,这就是Composition API
想 要做的事情。
Vue Composition API
可以简称为VCA
。
Vue3.x
提供了setup
函数来编写各种组合式API
。
setup
其实也是组件的另外一个选项,只不过这个选项可以用来替代大部分其他选项式API
选项。
比如methods
、computed
、watch
、data
、各种生命周期钩子函数等等。
setup函数主要由两个参数:
props
:父组件传递过来的属性会被放到props
对象中
props
的类型,和选项式API
中的的规则一样,在props选项中定义props
的使用,在template
中依然是可以把props
中的属性当成普通Vue
变量去使用。setup
函数中用props
,不可以通过this
去获取context
:也称之为SetupContext
,是setup
函数的上下文对象,它主要由三个属性
attrs
:所有的非props
的的属性slots
:父组件传递过来的插槽,在以渲染函数返回时会有作用emit
:当组件内部需要发出事件时会用到emitsetup
函数中不能访问this
,所以无法通过this.$emit
发出事件setup
的返回值可以来替代data
选项,因此setup
的返回值可以在模板template
中使用。
<script>
import { ref } from 'vue'
export default {
setup() {
// 定义counter的内容
// 默认定义的数据都不是响应式数据,使用ref可以让数据变成响应式的
let counter = ref(100)
return {counter}
}
}
</script>
setup
的返回值可以来替代methods
中定义的方法
<script>
import { ref } from 'vue'
export default {
setup() {
// 定义counter的内容
// 默认定义的数据都不是响应式数据,使用ref可以让数据变成响应式的
let counter = ref(100)
const increment = () => {
counter.value ++
console.log(counter.value)
}
return {counter, increment}
}
}
</script>
代码示例:
<template>
<div>
<h2>账号: {{ account.username }}</h2>
<h2>密码: {{ account.password }}</h2>
<!-- 没使用reactive函数时,点击此按钮,能够修改数据,但是却不能渲染到页面上 -->
<button @click="changeAccount">修改账号</button>
</div>
</template>
<script>
// 1.引入reactive函数
import { reactive } from 'vue'
export default {
setup() {
// 2.使用reactive函数: 定义复杂类型的响应式数据数据
const account = reactive({
username: "张三",
password: "666666"
})
function changeAccount() {
account.username = "kobe"
}
// 3.返回定义的数据
return {account, changeAccount}
}
}
</script>
由代码示例可知,使用reactive函数注意好三步即可:
Vue
中引入reactive
函数reactive
函数,reactive
函数的返回值就是响应式数据setup
函数中返回才可以在模板上使用为什么加了reactive函数后数据就变成了响应式的:
当我们使用reactive
函数处理我们的数据之后,数据再次被使用时就会进行依赖收集。
当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作。
选项式API
中的data
选项中的复杂数据,在底层也是交给了reactive
函数将其编程响应式对象的。
reactive
函数对传入的类型是有限制的,它要求必须传入的是一个对象或者数组类型。
如果传入一个基本数据类型,比如String
、Number
、Boolean
等等就会报出一个警告:
value cannot be made reactive : xxx
Vue3
给我们提供了另外一个ref
函数。
ref
函数返回可变的响应式对象,该对象作为一个 响应式的引用维护着它内部的值,这也是ref
名称的来源。
它内部的值是在ref
的value
属性中被维护的,因此使用ref
函数定义的变量,使用时需要从value
属性取值。
代码示例:
<template>
<div>
<!-- 默认情况下在template中使用ref时, vue会自动对其进行解包(取出其中value) -->
<h2>当前计数: {{ counter }}</h2>
<button @click="increment">+1</button>
<button @click="counter++">+1</button>
</div>
</template>
<script>
// 1.引入ref函数
import { ref } from 'vue'
export default {
setup() {
// 2.ref函数: 主要定义简单类型的数据(也可以定义复杂类型的数据)
const counter = ref(0)
function increment() {
counter.value++
}
// 3.返回定义的数据
return {counter, increment}
}
}
</script>
<template>
<div>
<!--ref 浅层解包 (意思就是把ref变量放别的对象中时就会有下面两个情况)-->
<!-- 使用的时候不需要写.value -->
<h2>当前计数: {{ info.counter }}</h2>
<!-- 修改的时候需要写.value -->
<button @click="info.counter.value++">+1</button>
</div>
</template>
<script>
// 1.引入ref函数
import { ref } from 'vue'
export default {
setup() {
// 2.ref函数: 主要定义简单类型的数据(也可以定义复杂类型的数据)
const counter = ref(0)
const info = {counter}
// 3.返回定义的数据
return {counter}
}
}
</script>
注意点:
在模板中引入ref
函数定义的值时,Vue
会自动帮助我们进行解包操作
解包就是Vue
自动取出ref
函数所定义的变量中的value
值
所以使用者并不需要在模板中通过ref.value
的方式来使用
在 setup
函数内部,ref
函数定义的值是一个ref
引用, 所以需要使用ref.value
的方式去操作
关于ref
的浅层解包
template
中使用ref
变量,会进行自动解包ref
变量放在一个对象当中会有下面的情况
value
属性去获取值reactive
函数应用于本地的数据的定义,本地数据代表这个数据并不是来源于服务器
reactive
函数定义的复杂数据的多个属性之间是有一定联系的
import {reactive} from 'vue'
const user = reactive({
username: '张三',
password: '123456'
})
例如收集一个表单中的数据,就是很适合使用reactive函数的场景
定义本地的简单类型的数据
import {ref} from 'vue'
const message = ref('hello ref')
定义从服务器中获取的数据
import {ref} from 'vue'
const music = ref([]) // 定义一个本地数组
const serverMusicList = ['小星星', '虫儿飞'] // 模拟从服务器获取到的数据
music.value = serverMusicList // 把数据赋值给music变量
这是经验之谈,很多优秀的开源项目中也都是这么取做的,文档中并没有明确说明什么情况使用什么
这其实也是程序员本身应该自己去考虑的问题
父组件传递给子组件的对象数据,子组件只能使用,不允许修改。
虽然是能修改的,但是违反了单向数据流的原则。
如果真的想修改这个数据,应该发出事件,让父组件监听这个事件,然后在父组件中进行修改。
今后项目中可能会有很多子组件,父组件的一个数据可能会传递给多个子组件。
由于传递的是数据对象的引用,因此其中一个子组件中修改了数据,会导致全部引用它的地方,全部受到影响。
而且维护时,难以知道是哪个位置修改的这个数据。
其它地方也有很多类似的概念规范:
react
中
react
有一个重要的原则:任何一个组件都应该像纯函数一样,不能修改传入的props
js
中
js
中也有类似原则,熟悉js
的可能会知道一个纯函数的概念。这些规范如果不遵守,代码确实也能实现功能。但是将来维护性将会非常差,谁试谁知道。
在给组件传递数据时,如果希望组件使用传递的内容。但是不允许它们修改时,就可以使用readonly
了。
因为单向数据流原则在Vue2.x
的时候时没法从代码上避免的。如果一个程序员根本不知道这个原则。
就有可能写出难以维护的代码。
而Vue3.x
则提供了readonly
函数从编码层面去避免这个问题。
<template>
<!-- 给组件home传递一个只读数据 -->
<home :info="readonlyInfo" />
</template>
<script>
import { readonly } from 'vue'
setup() {
// 本地定义多个需要传递给子组件的数据
const info = reactive({
name: "张三",
age: 25,
height: 1.78
})
// 使用readOnly包裹info
const roInfo = readonly(info)
return {roInfo}
}
</script>
readonly
返回的对象都是不允许修改的;但是经过readonly
处理的原来的对象是允许被修改的。
比如 对于语句const info = readonly(obj)
,info
对象是不允许被修改的。
当obj
被修改时,readonly
返回的info
对象也会被修改;
但是我们不能去修改readonly
返回的对象info
;
如果代码本身就非常遵守单向数据流的原则,那么也可以不使用
readonly的其它注意点:
readonly
会返回原始对象的只读代理
也就是它依然是一个Proxy
,这是一个proxy
的set
方法被劫持,并且不能对其进行修改
在开发中常见的readonly
方法会传入三个类型的参数:
类型一:普通对象
类型二:reactive
返回的对象
类型三:ref
的对象
isProxy
:检查对象是否是由 reactive
或 readonly
创建的 proxy
isReactive
: 检查对象是否是由reactive
创建的响应式代理
如果该代理是 readonly
建的,但包裹了由 reactive
创建的另一个代理,它也会返回 true
isReadonly
:检查对象是否是由 readonly
创建的只读代理。
toRaw
:返回reactive
或readonly
代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)
shallowReactive
: 创建一个响应式代理,它跟踪其自身property
的响应性
但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。
shallowReadonly
: 创建一个proxy
,使其自身的property
为只读
但不执行嵌套对象的深度只读转换(深层还是可读、可写的)
这些用的可能不会很多,但是也要了解
使用ES6
的解构语法,对reactive
返回的对象进行解构获取值,数据将不再是响应式的。
const info = reactive({
name: "张三",
age: 25,
height: 1.78
})
ES6解构:
const {name, age, height} = info;
数据失去响应式
toRefs解构:
const {name, age, height} = toRefs(info);
解构后数据仍然保持响应式
用于获取一个ref
引用中的value
。
ref
,则返回内部值,否则返回参数本身val = isRef(val) ? val.value : val
的语法糖函数判断值是否是一个ref对象。
创建一个浅层的ref
对象。
手动触发和shallowRef
创建对象的响应式。