基本结构
首先,我们需要理解
setup(props, context) { // ... }
props : 是一个包含了传入组件所有 prop 的对象。context : 是一个包含了组件的许多有用的属性和方法的对象,如attrs 、emit 等。
返回值
在
import { ref, computed } from 'vue'; export default { setup() { const count = ref(0); // 使用 ref 创建响应式数据 const doubled = computed(() => count.value * 2); // 使用 computed 创建计算属性 return { count, doubled }; } }
在模板中访问从
在
import { ref, computed } from 'vue'; export default { setup() { const count = ref(0); const doubled = computed(() => count.value * 2); return { increment: () => count.value++ // 这个方法会自动成为组件实例上的 increment 方法 }; } }
除了返回一个对象,还可以返回一个渲染函数,此时在渲染函数中可以直接使用在同一作用域下声明的响应式状态:
import { h, ref } from 'vue' export default { setup() { const count = ref(0) return () => h('div', count.value) } }
返回一个渲染函数将会阻止我们返回其他东西。对于组件内部来说,这样没有问题,但如果我们想通过模板引用将这个组件的方法暴露给父组件,那就有问题了。
我们可以通过调用 expose() 解决这个问题(这个函数下面会讲到):
import { h, ref } from 'vue' export default { setup(props, { expose }) { const count = ref(0) const increment = () => ++count.value expose({ increment }) return () => h('div', count.value) } }
此时父组件可以通过模板引用来访问这个
第一个参数props
先定义一个子组件如下
let template = ` <div>{{mes}}</div> ` export default { props: { mes: String }, setup: function (props) { let { mes } = props return { mes } }, template }
再定义一个父组件如下
import { ref } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js' import PropsC from "./components/PropsC.js" let template = ` <PropsC v-on:click='changeMes' :mes='mes'/> ` export default { components: { PropsC, }, setup: function () { let mes = ref('收到请回复') let changeMes = () => mes.value += '!' return { mes, changeMes } }, template }
父组件引入子组件并注册使用,再定义一个响应式数据mes,并把它传递给子组件,另外还提供了一个方法用于改变mes,用于模拟传递给子组件的数据的变化。
当我们点击组件上的文字的时候,未能看到视图的刷新变化。
所以我们推荐通过
import { toRefs} from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js' let template = ` <div>{{mes}}</div> ` export default { props: { mes: String }, setup: function (props) { let { mes } = toRefs(props) return { mes } }, template }
如果使用toRef():
import { toRef} from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js' let mes = toRef(props, 'mes')
通过上面提到的三种方式改写,我们就能看到页面的变化了。
第二个参数context
Vue 3 中的
attrs : 是一个包含了当前组件上所有属性的对象,这些属性未在props 中声明。
setup(props, { attrs }) { // ... }
slots : 是一个包含了当前组件的所有插槽的对象。
setup(props, { slots }) { // ... }
vue官网中描述到:”
- “attrs 和 slots总是会随着组件自身的更新而更新”:这意味着,当组件的状态或属性发生变化并导致组件重新渲染时,
attrs 和slots 也会相应地更新。这是因为它们与组件的状态紧密相关。 - “attrs 和 slots 的属性都不是响应式的”:这意味着,与
props 不同,attrs 和slots 的属性值不会自动响应其值的变化。如果你更改了attrs 或slots 的某个属性,Vue.js 不会自动检测到这些变化并更新 DOM。
这两句话并不矛盾。第一句描述了
我们引用上面props的代码实例,并改写进而讲解attrs。其中父组件没有丝毫改变
- 当子组件改写成这样(注意,下面的mes我并没有声明为是一个props):
let template = ` <div>{{attrs.mes}}</div> ` export default { setup: function (props, { attrs }) { return { attrs } }, template }
当点击组件组件上的文字时,我们是能看到页面有所变化的,这也照应了“始终通过
- 接下来我们看看解构attrs会是什么情况,当子组件改写成这样:
let template = ` <div>{{mes}}</div> ` export default { setup: function (props, { attrs }) { let { mes } = attrs return { mes } }, template }
当点击组件组件上的文字时,页面是没有任何变化的,这就是为什么“你应当避免解构它们”。
- “想要基于
attrs 或slots 的改变来执行副作用,那么你应该在onBeforeUpdate 生命周期钩子中编写相关逻辑”
import { onBeforeUpdate } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js' let template = ` <div>{{attrs.mes}}</div> ` export default { setup: function (props, { attrs }) { onBeforeUpdate(()=>{ console.log(attrs.mes) }) return { attrs } }, template }
当点击组件组件上的文字时,控制台会打印父组件传来的新的mes值。
其实如果要执行副作用,还可以用watch侦听attrs的变化,进而在回调函数执行副作用,但watch需要一个响应式的对象作为参数,所以你要先把attrs变为响应式的。但这种方式相对复杂。
- 继续,“和
props 不同,attrs 和slots 的属性都不是响应式的”,这好办,既然不是响应式的,那我就把你变为响应式的
import { reactive } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js' let template = ` <div>{{attrs_copy.mes}}</div> ` export default { setup: function (props, { attrs }) { let attrs_copy = reactive(attrs) return { attrs_copy } }, template }
这样改写也能令页面有变化。
- 既然能在onBeforeUpdate钩子里写相关逻辑,那么我们还可以使用ref
import { ref, reactive, toRef, toRefs, watch, onBeforeUpdate } from 'https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.esm-browser.js' let template = ` <div>{{mes}}</div> ` export default { setup: function (props, { attrs }) { let mes = ref(attrs.mes) onBeforeUpdate(() => { mes.value = attrs.mes }) return { mes } }, template }
这里的意思是创建一个响应式的mes,然后在每次组件更新时将新的attrs中的mes内容赋值给响应式的mes,然后模板使用的是这个响应式的mes。
emit : 是一个用于派发事件的方法,可以在子组件中触发自定义事件并传递数据给父组件。
setup(props, { emit }) { const handleClick = () => { emit('custom-event', 'Hello from child component!'); // 触发一个名为 'custom-event' 的自定义事件,并传递一个字符串参数给父组件 }; // ... }
expose : 是一个用于暴露公共属性的函数,可以将一些数据或方法暴露给父组件或其他组件使用。
setup(props, { expose }) { expose({ myProp: 'Hello from child component!' }); // 将一个名为 'myProp' 的公共属性暴露给父组件或其他组件使用 // ... }
结束语
由于