Vue瞎j学 组件
Vue学过但也没学过,主要把不清楚的地方记一记。这篇主要记录组件相关的内容。
组件Prop的Attribute继承
单根组件
单根组件,外部设置的属性会被设置到组件根元素上。
<div id="app">
<custom-component style="background-color: black;"></custom-component>
</div>
<script>
// ...
app.component('custom-component', {
template: `<header></header>`
});
</script>
这个设置到custom-component
上的style属性会被设置到渲染后的header
上。
如果你不想要这个默认行为,可以设置inheritAttrs
为false。
app.component('custom-component', {
inheritAttrs: false,
template: `<header></header>`
});
多根组件
对于多根组件,默认不会将属性绑定到任何根子元素上,而且会在控制台输出警告
<div id="app">
<custom-layout style="background-color: black;"></custom-layout>
</div>
<script>
// ...
app.component('custom-layout', {
template: `
<header></header>
<main></main>
<footer></footer>
`
});
</script>
如果想将属性绑定到某个子根元素上,可以使用
app.component('custom-layout', {
template: `
<header v-bind="$attrs"></header>
<main v-bind="$attrs"></main>
<footer></footer>
`
});
emit验证
在子组件中向父组件发射事件时,可以使用emits
进行验证
<template id="custom-form">
<form>
Email:
<input type="text" v-model="email"> <br>
Password:
<input type="text" v-model="password"> <br>
<button @click.prevent="submitForm(email, password)">提交</button>
</form>
</template>
<script>
app.component('custom-form', {
emits: {
submit: ({ email, password }) => {
return email && password && email.length>0 && password.length>0;
}
},
data () {
return {
email: '',
password: ''
}
},
methods: {
submitForm (email, password) {
this.$emit('submit', { email, password });
}
},
template: '#custom-form'
});
</script>
这里验证了事件发生时email
和password
的长度并且,除了控制台多出了一条warning外,我没看出有什么区别。
组件中的v-model
组件中的子元素没法直接通过v-model
和父级进行双向数据绑定。但是,v-model
不过是v-bind
和事件组合使用的语法糖。
这两条代码,一个是通过v-model
写的,一个是通过v-bind
和事件组合编写的,其效果一致。
message: {{message}}
<input type="text" v-model="message">
<input type="text" :value="message" @input="message=$event.target.value">
所以在组件中也可以按照这种办法恢复v-model
的使用权。
组件中的v-model:属性名="data中的变量"
,实际上是把这个变量以prop的形式传递到组件中,prop的名字就是冒号后面的属性名。所以我们可以通过v-bind
的方式将用户通过v-model
传入的prop绑定到对应位置,再通过监听不同的事件,向父组件发送数据已被更改的事件即可。
<template id="name-editor">
<div>
firstName:
<input type="text" :value="firstName"
@input="$emit('update:firstName', $event.target.value)"> <br>
lastName:
<input type="text" :value="lastName"
@input="$emit('update:lastName', $event.target.value)">
</div>
</template>
app.component('name-editor', {
template: '#name-editor',
props: ['firstName', 'lastName']
});
然而默认情况下,如果你不给v-model
传递参数,它会绑定到modelValue
prop上,你要通过update:modelValue
更新它。
这段代码是Vue3的,Vue2中应该不是这样写,不过原理一致
Vue2中的这里我虽然能写出来,但一直迷糊,可能是因为Vue2默认情况下的处理太让人迷惑了,Vue3很清晰。
v-model修饰符
对于传递到子组件中的v-model
修饰符,会被注入到名字Modifiers
中。
例如给firstName添加一个Modifiers,用于指定该字段中的所有字符都变成大写。
app.component('name-editor', {
template: '#name-editor',
props: ['firstName', 'lastName', 'firstNameModifiers'],
methods: {
emitValue (e) {
let value = e.target.value;
if(this.firstNameModifiers.capitalize) {
value = value.toUpperCase();
}
this.$emit('update:firstName', value);
}
}
});
<name-editor v-model:first-name.capitalize="firstName"
v-model:last-name="lastName"></name-editor>
对于没有参数的v-model
,修饰符会被注入到名字为modelModifiers
的prop中。
始终记住,要使用这些功能,请先在props中声明
插槽渲染作用域
<template id="component">
<div>
<slot>Default Text</slot>
</div>
</template>
<div id='app'>
<component>
ABCDEFG {{message}}
</component>
</div>
这个message
一定要定义在和引用组件同级别的作用域里,在这个例子中就是#app
中。不能这样访问定义在组件data
中的数据。
具名插槽
有时候组件中需要由多个插槽,这时候就要给插槽命名。
<template id="ui-container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</template>
在使用时,需要使用一个template
并加上v-slot
来指定使用哪一个插槽。
<div id='app'>
<ui-container>
<template v-slot:header>
<h1>Header</h1>
</template>
<template v-slot:default>
<p>Content</p>
</template>
<template v-slot:footer>
<p>Footer</p>
</template>
</ui-container>
</div>
作用域插槽
有时候还需要在父组件的插槽中引用子组件中定义的数据。比如这里,我想把列表项结构的选择权留给外部,而不是定义在组件内部。
app.component('todo-list', {
data () {
return {
todoList: [
{id: 0, todo: '洗脸'},
{id: 1, todo: '刷牙'},
{id: 2, todo: '上厕所'},
]
}
},
template: `
<ul>
<li v-for="item in todoList">
<slot></slot>
</li>
</ul>
`
});
可以通过给slot
绑定属性
template: `
<ul>
<li v-for="item in todoList">
<slot :todo-item="item"></slot>
</li>
</ul>
`
在外部通过一个template
选择插槽并指定插槽属性要设置到的对象名,Vue会创建这个对象,你就可以使用这些数据了
<todo-list>
<template v-slot:default="slotProps">
<h1>{{slotProps.todoItem.todo}}</h1>
</template>
</todo-list>
由于只有一个插槽,可以简写成如下形式
<todo-list v-slot="slotProps">
<h1>{{ slotProps.todoItem.todo }}</h1>
</todo-list>
如果有多个插槽,请始终使用完整的template
写法。
这有些骚操作可以对这个插槽prop解构
Provide/Inject
这好像是Vue3才有的新功能,我在Vue2文档里和学校的课程里都没听说过。
早先在开发嵌套层级很深的组件,传值时很费劲,需要使用props逐级传递。
其实仔细考虑下,子组件依赖层级很高的祖先组件中的状态,可能有不少时候是设计失误。
不管咋的Provide/Inject
提供了这么个功能,让子组件和祖先组件不管中间跨越了多少组件都可以直接传值。
app.component('grand-child', {
template: 'rootValue:{{valueFromRoot}}',
inject: ['valueFromRoot']
});
app.component('child', {
template: '<grand-child></grand-child>'
});
app.component('root', {
template: '<child></child>',
provide: {
valueFromRoot: 666
}
});
这里孙子组件跨越了子组件直接访问了根组件中的属性。
有一个问题是,provide
中的this并访问不到组件data
中的数据。这是一个错误的例子
app.component('root', {
template: '<child></child>',
data () {
return {
value: 666
}
},
provide: {
valueFromRoot: this.value
}
});
如果想要引用data
中的数据,请将provide
转换为方法。
app.component('root', {
template: '<child></child>',
data () {
return {
value: 666
}
},
provide () {
return {
valueFromRoot: this.value
}
}
});
我感觉,一直使用方法不也挺好吗,至少和data
的写法保持一致。
处理响应性
不过这样的话,就相当于在provide
中直接将valueFromRoot
设置成this.value
,只是一次简单的赋值,并没有响应式。
可以使用computed
provide () {
return {
valueFromRoot: Vue.computed(() => this.value)
}
}
这是Vue3的组合式API,我还不太明白,以后再说。
keep-alive
这是一个简单的用Vue写的分页
<div id='app'>
<div class="btn-group">
<button v-for="page in pages" @click="currentPage=page.component">
{{page.pageName}}
</button>
<component :is="currentPage"></component>
</div>
</div>
关键是组件的状态不会被记录,像这样
加上keepalive,就可以记录状态了。
<keep-alive>
<component :is="currentPage"></component>
</keep-alive>
异步组件
const asyncComponent = Vue.defineAsyncComponent(
() => new Promise((resolve, reject) => {
setTimeout(function(){
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
)
app.component('async-component', asyncComponent);