执行时机css
setup 注意点html
export default {
props: {
title: String
},
data() {
return {
name: 'Benson'
}
},
methods: {
setName() {
console.log(this.name);
}
}
setup(props) {
const sex = '男';
function setSex() {
console.log(this.sex);
}
return { sex, setSex };
}
}
复制代码
composition Api 实际上最终会把 setup return 的变量和方法会放到和 option Api 的 data 和 methods 同样的地方,因此咱们能后直接 this.xx 去使用。vue
例如使用了 data 对象:react
import { reactive } from 'vue';
export default {
name: 'App',
setup(props, context) {
// 建立一个响应式数据
// 本质:就是将传入的数据包装成一个 Propxy 对象
const state = reactive({
time: new Date();
})
function myFn() {
// 直接修改之前的,界面是不会更新
state.time.setDate(state.time.getDate() + 1);
// 从新赋值方式才能响应式变动界面
const newTime = new Date(state.time.getTime());
newTime.setDate(state.time.getDate() + 1);
state.time = newTime;
console.log(state.time);
}
return {state, myFn}
}
}
复制代码
import {ref, reactive, isRef, isReactive} from 'vue';
export default {
name: 'App',
setup() {
const age = ref(18);
const state = reactive({name: 'Benson'});
console.log(isRef(age));
console.log(isReactive(state));
return {age, state};
}
}
复制代码
<template>
<div>
<p>State1: {{ state1 }}</p>
<p>State2: {{ state2 }}</p>
<button @click="fn">变动</button>
</div>
</template>
<script>
import { ref, reactive } from 'vue';
export default {
name: 'App',
setup() {
let state1 = reactive({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd',
},
},
},
});
let state2 = ref({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd',
},
},
},
});
function fn() {
state1.a = '1';
state1.gf.b = '2';
state1.gf.f.c = '3';
state1.gf.f.s.d = '4';
console.log('-----------State1-----------');
console.log(state1);
console.log(state1.gf);
console.log(state1.gf.f);
console.log(state1.gf.f.s);
state2.value.a = '1';
state2.value.gf.b = '2';
state2.value.gf.f.c = '3';
state2.value.gf.f.s.d = '4';
console.log('-----------State2-----------');
console.log(state2.value);
console.log(state2.value.gf);
console.log(state2.value.gf.f);
console.log(state2.value.gf.f.s);
}
return { state1, state2, fn };
},
};
</script>
复制代码
变动前:json
变动后:api
上述代码在执行 fn 方法的时候,会发现页面同时发生了变化,而且在查看控制台的时候,你会发现输出来的全是进行过 Proxy 封装过的数据。数组
这就是 Vue3 的递归监听,reactive 和 ref 会递归循环数据,为每一层数据进行 Proxy 封装。浏览器
默认状况下,不管是经过 ref 仍是 reactive 都是递归监听的。markdown
递归监听存在必定的问题,若是数据量比较大,是很是消耗性能的app
通常状况下咱们使用 ref 和 reactive 便可
只有在须要监听的数据量比较大的时候,咱们才使用 shallowRef /shallowReactive
<template>
<div>
<p>State1: {{ state1 }}</p>
<p>State2: {{ state2 }}</p>
<button @click="fn">变动</button>
</div>
</template>
<script>
import { shallowRef, shallowReactive, triggerRef } from 'vue';
export default {
name: 'App',
setup() {
const data = {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd',
},
},
},
};
// 注意点:shallowReactive 建立的数据,只进行第一层的数据监听
let state1 = shallowReactive(JSON.parse(JSON.stringify(data)));
// shallowRef 建立的数据,只进行第一层的数据监听
let state2 = shallowRef(JSON.parse(JSON.stringify(data)));
function fn() {
// 只有修改这个第一层才能监听到数据变动UI
// state1.a = '1';
state1.gf.b = '2';
state1.gf.f.c = '3';
state1.gf.f.s.d = '4';
console.log('-----------State1-----------');
console.log(state1);
console.log(state1.gf);
console.log(state1.gf.f);
console.log(state1.gf.f.s);
// 只有修改这个第一层才能监听到数据变动UI
// state2.value = JSON.parse(JSON.stringify(data));
state2.value.a = '1';
state2.value.gf.b = '2';
state2.value.gf.f.c = '3';
state2.value.gf.f.s.d = '4';
// Vue3 只提供了 triggerRef 方法,没有提供 triggerReactive 方法
// 因此若是是 reative 类型的数据,那么是没法主动触发界面更新的
triggerRef(state2);
console.log('-----------State2-----------');
console.log(state2);
console.log(state2.value);
console.log(state2.value.gf);
console.log(state2.value.gf.f);
console.log(state2.value.gf.f.s);
}
return { state1, state2, fn };
},
};
</script>
<style></style>
复制代码
读者可自行拷贝代码进行演示
ref -> refImpl(若是传入 object -> reactive)
ref(10) -> refImpl{value: 10}
shallowRef -> shallowReactive
shallowRef(10) -> refImpl(若是传入 object -> shallowReactive)
复制代码
左边标识咱们在使用 API 的样子
右边为 Vue3 实际使用的样子
toRaw 从 Reactive 或 Ref 中获得原始数据
toRaw 做用就是作一些不想被监听的事情(提高性能)
setup() {
let obj = {name: 'Benson', age 18};
/**
* ref / reactive 数据类型的特色
* 每次修改都会被追踪,都会更新 UI 界面,可是这样实际上是很是消耗性能的
* 全部若是咱们有一些操做不须要追踪,不须要更新 UI 界面,那么这个时候,
* 咱们就能够经过 toRaw 方法拿到它的原始数据,对原始数据进行修改
* 这样就不会被追踪,这样就不会更新 UI 界面,这样性能就行了
*/
let state = reactive(obj);
let obj2 = toRaw(state);
console.log(obj === obj2); // true
console.log(obj === state); // false
// state 和 obj 的关系:
// 引用关系,state 的本质是一个 Proxy 对象,在这个 Proxy 对象引用了 obj
function fn() {
// 若是直接修改 obj,那么是没法触发界面更新的
// 只有经过包装后的 Proxy 对象来修,才会触发界面的更新
obj.name = 'HAHA';
}
}
复制代码
import { reactive, markRaw } from 'vue';
export defalut {
name: 'App',
setup() {
let obj = {name: 'Benson', age: 18};
obj = markRow(obj);
let state = reactive(obj);
function fn() {
state.name = 'zs';
}
return { state, fn }
}
}
复制代码
markRaw 的做用就是禁止源数据不能被用于监听,通过上述处理后,reactive 处理 obj 进行响应式数据的封装将不在其做用了。
能够用来为源响应式对象上的 property 性建立一个 ref。而后能够将 ref 传递出去,从而保持对其源 property 的响应式链接。
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
// 往 fooRef property 找,fooRef property 指向 state property 的 ref
fooRef.value++;
console.log(state.foo) // 2
state.foo++;
console.log(fooRef.value) // 3
复制代码
当您要将 prop 的 ref 传递给复合函数时,toRef 颇有用:
export default {
setup(props) {
useSomeFeature(toRef(props, 'foo')); // 将 foo 属性传下去
}
}
复制代码
将响应式对象转换为普通对象,其中结果对象的每一个 property 都是指向原始对象相应 property 的 ref。
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
Type of stateAsRefs:
{
foo: Ref<number>,
bar: Ref<number>
}
*/
// ref 和 原始property “连接”
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
复制代码
当从合成函数返回响应式对象时,toRefs 很是有用,这样消费组件就能够在不丢失响应性的状况下对返回的对象进行分解/扩散:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
// 逻辑运行状态
// 返回时转换为ref
return toRefs(state)
}
export default {
setup() {
// 能够在不失去响应性的状况下破坏结构
const { foo, bar } = useFeatureX()
return {
foo,
bar
}
}
}
复制代码
建立一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它须要一个工厂函数,该函数接收 track 和 trigger 函数做为参数,并应返回一个带有 get 和 set 的对象。
使用 v-model 使用自定义 ref 实现 debounce 的示例:
Html
<input v-model="text" />
复制代码
Js
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
export default {
setup() {
return {
text: useDebouncedRef('hello')
}
}
}
复制代码
<template>
<div ref="root">This is a root element</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// DOM元素将在初始渲染后分配给ref
console.log(root.value) // <div>这是根元素</div>
})
return { root }
}
}
</script>
复制代码
setup
return
响应式变量 root,template 中,ref="root"
在 onMounted
挂载以后就会将相应的元素或者组件实例将分配给该 root 变量。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 适用于响应性追踪
console.log(copy.count)
})
// 变动 original 会触发 watchEffect
original.count++
// 变动副本将失败并致使警告
copy.count++ // 警告!
// 经过 isReadonly 判断是否为 readonly 对象
console.log(isReadonly(copy)); // true
console.log(isReadonly(original)); // false
复制代码
const state = shallowReadonly({
foo: 1,
nested: {
bar: 2
}
});
// 改变状态自己的 property 将失败
state.foo++;
// ...但适用于嵌套对象
isReadonly(state.nested); // false
state.nested.bar++; // 适用
复制代码
只读效果,仅仅控制在 第一层,深层的属性仍是能够变动新的
还有一个注意点,ES6 的 const
也能控制只读,但请看下面例子:
const argu_1 = {name: '小明', attrs: {sex: 18}};
const argu_2 = readonly({name: '小红'}, attrs: {sex: 16});
const argu_3 = shallowReadonly({name: '小夏'}, attrs: {sex: 17});
复制代码
sex
是能够变动的,argu_3 能够从新赋值这个语法相似于 vue 组件中使用 props 校验传入的参数。
例子:
app.component('custom-form', {
// 数组方式, 只对自定义事件名称校验
emits: ['click', 'submit'],
// 对象方式
emits: {
// 没有验证
click: null,
// 验证submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
},
methods: {
submitForm() {
this.$emit('submit', { email, password })
}
}
})
复制代码
<style scoped>
/* deep selectors */
::v-deep(.foo) {}
/* shorthand */
:deep(.foo) {}
</style>
复制代码
最初,支持>>>组合器以使选择器为“ deep”。可是,某些CSS预处理器(例如SASS)在解析它时会遇到问题,由于这不是官方的CSS组合器。
后来切换到/ deep /,这曾经是CSS的实际建议添加(甚至是Chrome自己提供的),但后来删除了。这引发了一些用户的困惑,由于他们担忧在Vue SFC中使用/ deep /会致使在删除该功能的浏览器中不支持其代码。可是,就像>>>同样,/ deep /仅由Vue的SFC编译器用做编译时提示以重写选择器,并在最终CSS中被删除。
为了不丢失的/ deep /组合器形成混乱,引入了另外一个自定义组合器:: v-deep,此次更加明确地代表这是Vue特定的扩展,并使用伪元素语法,以便任何-处理器应该可以解析它。
因为当前Vue 2 SFC编译器的兼容性缘由,仍支持深度组合器的先前版本,这又可能使用户感到困惑。在v3中,咱们再也不支持>>>和/ deep /。
在研究适用于v3的新SFC编译器时,咱们注意到CSS伪元素实际上在语义上不是组合符。伪元素改成接受参数,这与惯用CSS更加一致,所以,咱们也使:: v-deep()那样工做。若是您不关心显式的v-前缀,也可使用更短的:deep()变体,其工做原理彻底相同。
当前仍支持:: v-deep做为组合器,但已将其弃用,并会发出警告。
<style scoped>
/* targeting slot content */
::v-slotted(.foo) {}
/* shorthand */
:slotted(.foo) {}
</style>
复制代码
当前,从父级传入的 slot 内容受父级的做用域样式和子级的做用域样式的影响。没法编写仅明确指定 slot 内容或不影响 slot 内容的规则。
在v3中,咱们打算默认使子范围的样式不影响 slot 的内容。为了显式地指定插槽内容,可使用:: v-slotted()(简写为:: slotted())伪元素。
<style scoped>
/* one-off global rule */
::v-global(.foo) {}
/* shorthand */
:global(.foo) {}
</style>
复制代码
当前,要添加全局CSS规则,咱们须要使用单独的无做用域块。咱们将引入一个新的:: v-global()(简写为:: global())伪元素,以用于一次性全局规则。
<template>
<button @click="inc">{{ count }}</button>
</template>
<script setup="props, { emit }">
import { ref, onMounted } from 'vue'
export const count = ref(0)
export const inc = () => count.value++
onMounted(() => {
emit('foo');
)
</script>
复制代码
最新方案:
import { useContext, defineProps, defineEmit } from 'vue'
const emit = defineEmit(['onClick'])
const ctx = useContext()
const props = defineProps({
msg: String
})
复制代码
其中 ctx
包含了 attrs、emit、props、slots、expose
属性
支持将组件状态驱动的 CSS 变量注入到“单个文件组件”样式中。
案例:
<template>
<div class="text">hello</div>
</template>
<script>
export default {
data() {
return {
color: 'red'
}
}
}
</script>
<style vars="{ color }">
.text {
color: var(--color);
}
</style>
复制代码
注意的一点,目前 Vue SFC 样式提供了直接的 CS 配置和封装,可是它是纯静态的-这意味着到目前为止,尚没法根据组件的状态在运行时动态更新样式。
当样式存在 scoped 局部样式和又要使用全局 var 的状况:
<style scoped vars="{ color }">
h1 {
color: var(--color);
font-size: var(--global:fontSize);
}
</style>
复制代码
这里的 fontSize 是全局的 var css 变量
通过 compiles 后:
h1 {
color: var(--6b53742-color);
font-size: var(--fontSize);
}
复制代码