Vue 3.0 新特性与使用 二

image

setUp 执行时机

执行时机css

  • setup:执行,组件实例还没有被建立
  • beforeCreate:表示组件刚刚被建立出来,组件的 data 和 methods 尚未初始化好
  • created:表示组件刚刚被建立出来,而且组件的 data 和 methods 已经初始化好

setup 注意点html

  • 因为在执行 setup 函数的时候,尚未执行 beforeCreate、created 生命周期方法,因此在 setup 函数中,是没法使用 data 和 methods
  • 因为咱们不能在 setup 函数中使用 data 和 methods,因此 Vue 为了不咱们错误的使用,它直接将 setup 函数中 this 修改为了 undefined
  • setup 函数只能同步而不能异步的

组合 Api 本质

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

reactive 理解

  1. 什么是 reactive?
  • reactive 是 Vue3 中提供的实现响应式数据的方法
  • 在 Vue2 中响应式数据是经过 defineProperty 来实现的,而在 Vue3 中响应式数据是经过 ES6 的 Proxy 来实现的
  1. reactive 注意点:
  • reactive 参数必须是对象(json/arr)
  • 若是给 rective 传递了其余对象
    • 默认状况下使用该对象的方法来修改对象,界面不会自动更新
    • 若是想更新,能够经过从新赋值的方式

例如使用了 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}
    }
}
复制代码

ref 理解

  1. 什么是 ref?
  • ref 和 reactive 同样,也是用来实现响应式数据的方法
  • 因为 reactive 必须传递一个对象,因此致使在企业开发中,若是咱们只想让某个变量实现响应式的时候会很是麻烦,因此 Vue3 就给咱们提供了 ref 方法,实现对简单值的监听
  1. ref 本质:
  • ref 在源码中会对传入的数据进行类型判断,若是判断为对象数据类型会使用 reactive 去进行响应式分装的;
  • 对于非对象类型 ref 底层的会 new 一个 RefImpl 对象,该对象会定义 get 和 set 方法去取值赋值监听,这点相似于 Vue2 的 Object.definePropert
  1. ref 注意点:
  • 在 Vue template 中使用 ref 的值不用经过 value 获取,由于 Vue 会在编译 {{ age }} 的时候自动加上变成 {{ age.value }}
  • 在 JS 中使用 ref 的值必须经过 value 获取,由于 ref 传入的值会报存在 RefImpl 对象的 _value 和 value 中

ref 和 reactive 的区别

  1. ref 和 reactive 区别:
  • 若是在 template 里使用的是 ref 类型的数据,那么 Vue 会自动帮咱们添加 .value
  • 若是在 template 里使用的是 reactive 类型的数据,那么 Vue 不会自动帮咱们添加 .value
  1. Vue 是如何肯定是否须要添加 .value 的?
  • Vue 在解析数据以前,会自动判断这个数据是不是 ref 数据,若是是就自动添加 .value, 若是不是就不自动添加 .value。
  • 那 Vue 是如何判断当前的数据是否为 ref 类型的?,Vue 是经过当前数据的 __v_ref 类判断的,若是有这个私有的属性,而且取值为 true,那么就表明是一个 ref 类型的数据
  1. ref 和 reactive 输出
  • ref 是一个 RefImpl 对象,包含了__value 和 __v_isRef
  • reactive 是一个 Proxy 对象
  1. Vue 提供了 2 个方法来判断 Ref 和 Reactive
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

image

变动后:api

image

image

上述代码在执行 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>
复制代码

读者可自行拷贝代码进行演示

shallowRef 本质

ref -> refImpl(若是传入 object -> reactive)
ref(10) -> refImpl{value: 10}
shallowRef -> shallowReactive
shallowRef(10) -> refImpl(若是传入 object -> shallowReactive)
复制代码

左边标识咱们在使用 API 的样子

右边为 Vue3 实际使用的样子

toRaw

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';
    }
}
复制代码

markRaw

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 进行响应式数据的封装将不在其做用了。

toRef

能够用来为源响应式对象上的 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 属性传下去
  }
}
复制代码

toRefs

将响应式对象转换为普通对象,其中结果对象的每一个 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
    }
  }
}
复制代码

customRef

建立一个自定义的 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')
    }
  }
}
复制代码

ref 获取元素

<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 变量。

readonly

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
复制代码

shallowReadonly

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});
复制代码
  • argu_1 控制这该变量不可变动,可是能够变动里面的属性
  • argu_2 控制着每一层属性都不可变动,可是 argu_2 能够从新赋值
  • argu_3 控制着第一层属性不可变动,深层的属性能够变动,好比 sex 是能够变动的,argu_3 能够从新赋值

emits 定义自定义事件

这个语法相似于 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 })
    }
  }
})
复制代码

script scoped 支持全局规则或只针对插槽内容的规则

deep 选择器

<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())伪元素。

scoped 状况下定义全局样式

<style scoped>
    /* one-off global rule */
    ::v-global(.foo) {}
    /* shorthand */
    :global(.foo) {}
</style>
复制代码

当前,要添加全局CSS规则,咱们须要使用单独的无做用域块。咱们将引入一个新的:: v-global()(简写为:: global())伪元素,以用于一次性全局规则。

实验状态的特性

script setup

<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 属性

script vars

支持将组件状态驱动的 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);
}
复制代码

参考文献

系列

相关文章
相关标签/搜索