参考https://blog.csdn.net/yfm120750310/article/details/111251300
参考https://pro.antdv.com/docs/getting-started
参考http://t.zoukankan.com/freely-p-10874297.html
04 | 第一个Vue程序
官方文档https://v2.cn.vuejs.org/v2/guide/conditional.html
4.0 总结
①插值 {{}} 其内只能使用表达式,不能用语句。微信小程序的原生组件也有这个数据绑定方式,来源是mustache模板引擎。
②v-bind:绑定动态的值
③v-if、v-else:可用来做条件渲染
④v-for:可用来列表循环渲染
4.1 变量默认用{{message}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">{{message}}</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
message:'hello world'
}
})
</script>
</body>
</html>
4.2 变量可在console中修改
vm.message=‘hello vue’
4.3 变量仅支持表达式,不支持指令语句
<div id="app">{{message}}{{message + message}}</div>
4.4 指令是个标志位,如v-bind
<body>
<div id="app">{{message}}{{message + message}}
<div v-bind:id="message"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
message:'hello world'
}
})
</script>
</body>
4.5 v-if/v-else
<body>
<div id="app">{{message}}{{message + message}}
<div v-bind:id="message"></div>
<ul>
<li>
<span v-if="!item.del">{{item.title}}</span>
<span v-else style="text-decoration: line-through">{{item.title}}</span>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
message:'hello world',
item:{
title:'课程1',
del:false,
}
}
})
</script>
</body>
4.6 v-show显示与否,但挂在BOM节点上
<body>
<div id="app">{{message}}{{message + message}}
<div v-bind:id="message"></div>
<ul>
<li>
<span v-if="!item.del">{{item.title}}</span>
<span v-else style="text-decoration: line-through">{{item.title}}</span>
<button v-show="!item.del">删除</button>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
message:'hello world',
item:{
title:'课程1',
del:false,
}
}
})
</script>
</body>
4.7 v-for循环
<body>
<div id="app">{{message}}{{message + message}}
<div v-bind:id="message"></div>
<ul>
<li v-for="item in list">
<span v-if="!item.del">{{item.title}}</span>
<span v-else style="text-decoration: line-through">{{item.title}}</span>
<button v-show="!item.del">删除</button>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
message:'hello world',
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
}
})
</script>
</body>
05 | 组件基础及组件注册
官方文档https://v2.cn.vuejs.org/v2/guide/components.html
总结
组件就是为了复用。
组件的名字必须是全局唯一的。
5.1 vue.component
vue.component需定义data、template、methods、props
Vue.component('todo-item',{
props:{},
template:``,
data:function(){
return {}
},
methods:{
},
})
5.2 建立todo-item组件
建立todo-item组件
<body>
<div id="app">{{message}}{{message + message}}
<div v-bind:id="message"></div>
<ul>
<!-- <li v-for="item in list">
<span v-if="!item.del">{{item.title}}</span>
<span v-else style="text-decoration: line-through">{{item.title}}</span>
<button v-show="!item.del">删除</button>
</li> -->
<todo-item v-for="item in list" :title="item.title" :del="item.del"></todo-item>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
Vue.component('todo-item',{
props:{
title: String,
del:{
type: Boolean,
default: false,
},
},
template:`
<li>
<span v-if="!del">{{title}}</span>
<span v-else style="text-decoration: line-through">{{title}}</span>
<button v-show="!del">删除</button>
</li>`,
data:function(){
return {}
},
methods:{
},
})
var vm = new Vue({
el:'#app',
data:{
message:'hello world',
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
}
})
</script>
</body>
5.3 建立todo-list组件
todo-list组件就是进一步复用。
<body>
<div id="app">{{message}}{{message + message}}
<div v-bind:id="message"></div>
<!-- <ul>
<todo-item v-for="item in list" :title="item.title" :del="item.del"></todo-item>
</ul> -->
<todo-list></todo-list>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
Vue.component('todo-item',{
props:{
title: String,
del:{
type: Boolean,
default: false,
},
},
template:`
<li>
<span v-if="!del">{{title}}</span>
<span v-else style="text-decoration: line-through">{{title}}</span>
<button v-show="!del">删除</button>
</li>`,
data:function(){
return {}
},
methods:{
},
})
Vue.component('todo-list',{
template: `
<ul>
<todo-item v-for="item in list" :title="item.title" :del="item.del"></todo-item>
</ul>
`,
data:function(){
return {
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
}
}
})
var vm = new Vue({
el:'#app',
data:{
message:'hello world',
}
})
</script>
</body>
06 | Vue组件的核心概念:事件
官方文档https://v2.cn.vuejs.org/v2/guide/events.html
6.1 总结
总结:子组件通过事件声明的方式( $emit),将数据传递给父组件进行处理,父组件通过注册子组件声明的事件,并指定函数来完成接收并处理。
6.2 定义handleClick和handleDelete事件
- todo-item组件
@click=“handleClick”
methods中定义handleClick()方法
Vue.component('todo-item',{
props:{
title: String,
del:{
type: Boolean,
default: false,
},
},
template:`
<li>
<span v-if="!del">{{title}}</span>
<span v-else style="text-decoration: line-through">{{title}}</span>
<button v-show="!del" @click="handleClick">删除</button>
</li>`,
data:function(){
return {}
},
methods:{
handleClick(){
console.log('点击删除按钮')
this.$emit('delete',this.title)
}
},
})
- todo-list组件
@click=“handleDelete”
methods中定义handleDelete( )方法
Vue.component('todo-list',{
template: `
<ul>
<todo-item @delete="handleDelete" v-for="item in list" :title="item.title" :del="item.del"></todo-item>
</ul>
`,
data:function(){
return {
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
}
},
methods:{
handleDelete(val){
console.log('handleDelete', val)
}
},
})
-
手动触发delete取到val
this.$emit(‘delete’,this.title) -
汇总代码
4:10那段话记住
<body>
<div id="app">
{{message}} {{message + message}}
<div :id="message"></div>
<!-- <ul>
<todo-item v-for="item in list" :title="item.title" :del="item.del"></todo-item>
</ul> -->
<todo-list></todo-list>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('todo-item', {
props: {
title: String,
del: {
type: Boolean,
default: false,
},
},
template: `
<li>
<span v-if="!del">{{title}}</span>
<span v-else style="text-decoration: line-through">{{title}}</span>
<button v-show="!del" @click="handleClick">删除</button>
</li>
`,
data: function() {
return {}
},
methods: {
handleClick(e) {
console.log('点击删除按钮')
this.$emit('delete', this.title)
}
},
})
Vue.component('todo-list', {
template: `
<ul>
<todo-item @delete="handleDelete" v-for="item in list" :title="item.title" :del="item.del"></todo-item>
</ul>
`,
data: function() {
return {
list: [{
title: '课程1',
del: false
}, {
title: '课程2',
del: true
}],
}
},
methods: {
handleDelete(val) {
console.log('handleDelete', val)
}
}
})
var vm = new Vue({
el: '#app',
data: {
message: 'hello world',
}
})
</script>
</body>
07 | Vue组件的核心概:插槽
官方文档https://v2.cn.vuejs.org/v2/guide/components-slots.html
7.1 总结
插槽是一种传递复杂属性的API方式。
插槽(slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。https://blog.csdn.net/qq_57587705/article/details/124527016
插槽有点像django模板中的变量{{}}。
插槽可以由默认值。
7.2 默认插槽
todo-item不要写死在todo-list里面。
< slot >< / slot >标签是为了放置<todo-item @delete="handleDelete" v-for="item in list" :title="item.title" :del="item.del"></todo-item>
如果没有< slot >< / slot >标签,< todo-item >< / todo-item >不知道挂在到哪个BOM上。
<body>
<div id="app">{{message}}{{message + message}}
<div v-bind:id="message"></div>
<todo-list>
<todo-item @delete="handleDelete" v-for="item in list" :title="item.title" :del="item.del"></todo-item>
</todo-list>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
Vue.component('todo-item',{
props:{
title: String,
del:{
type: Boolean,
default: false,
},
},
template:`
<li>
<span v-if="!del">{{title}}</span>
<span v-else style="text-decoration: line-through">{{title}}</span>
<button v-show="!del" @click="handleClick">删除</button>
</li>`,
data:function(){
return {}
},
methods:{
handleClick(){
console.log('点击删除按钮')
this.$emit('delete',this.title)
}
},
})
Vue.component('todo-list',{
template: `
<ul>
<slot></slot>
</ul>
`,
data:function(){
return {
}
},
})
var vm = new Vue({
el:'#app',
data:{
message:'hello world',
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
},
methods:{
handleDelete(val){
console.log('handleDelete', val)
}
},
})
</script>
</body>
7.3 具名插槽(前后置图标)-Vue2.5语法
todo-list组件通过slot来放置todo-item,todo-item组件通过slot来放置【前置图标】和【后置图标】的span。
<body>
<div id="app">{{message}}{{message + message}}
<div v-bind:id="message"></div>
<todo-list>
<todo-item @delete="handleDelete" v-for="item in list" :title="item.title" :del="item.del">
<span slot="pre-icon">前置图标</span>
<span slot="suf-icon">后置图标</span>
</todo-item>
</todo-list>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
Vue.component('todo-item',{
props:{
title: String,
del:{
type: Boolean,
default: false,
},
},
template:`
<li>
<slot name="pre-icon"><slot>
<span v-if="!del">{{title}}</span>
<span v-else style="text-decoration: line-through">{{title}}</span>
<slot name="suf-icon"><slot>
<button v-show="!del" @click="handleClick">删除</button>
</li>`,
data:function(){
return {}
},
methods:{
handleClick(){
console.log('点击删除按钮')
this.$emit('delete',this.title)
}
},
})
Vue.component('todo-list',{
template: `
<ul>
<slot></slot>
</ul>
`,
data:function(){
return {
}
},
})
var vm = new Vue({
el:'#app',
data:{
message:'hello world',
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
},
methods:{
handleDelete(val){
console.log('handleDelete', val)
}
},
})
</script>
</body>
7.4 具名插槽(前后置图标)-Vue2.6之后的语法
插槽采取template的标签<template v-slot:suf-icon><span>后置图标</span></template>
<body>
<div id="app">{{message}}{{message + message}}
<div v-bind:id="message"></div>
<todo-list>
<todo-item @delete="handleDelete" v-for="item in list" :title="item.title" :del="item.del">
<!-- <span slot="pre-icon">前置图标</span>
<span slot="suf-icon">后置图标</span> -->
<template v-slot:pre-icon>
<span>前置图标</span>
</template>
<template v-slot:suf-icon>
<span>后置图标</span>
</template>
</todo-item>
</todo-list>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
Vue.component('todo-item',{
props:{
title: String,
del:{
type: Boolean,
default: false,
},
},
template:`
<li>
<slot name="pre-icon"></slot>
<span v-if="!del">{{title}}</span>
<span v-else style="text-decoration: line-through">{{title}}</span>
<slot name="suf-icon"></slot>
<button v-show="!del" @click="handleClick">删除</button>
</li>`,
data:function(){
return {}
},
methods:{
handleClick(){
console.log('点击删除按钮')
this.$emit('delete',this.title)
}
},
})
Vue.component('todo-list',{
template: `
<ul>
<slot></slot>
</ul>
`,
data:function(){
return {
}
},
})
var vm = new Vue({
el:'#app',
data:{
message:'hello world',
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
},
methods:{
handleDelete(val){
console.log('handleDelete', val)
}
},
})
</script>
</body>
7.5 作用域插槽
作用域插槽可以接收子组件传递的值。根据子组件传递值的不同,作用域插槽返回不同的内容。
value的传递过程:
- data: function() {
return {
value: Math.random()
}
}, <slot name="pre-icon" :value="value"></slot>
<template v-slot:pre-icon="{value}"><span>前置图标 {{value}}</span> </template>
<body>
<div id="app">{{message}}{{message + message}}
<div v-bind:id="message"></div>
<todo-list>
<todo-item @delete="handleDelete" v-for="item in list" :title="item.title" :del="item.del">
<!-- <span slot="pre-icon">前置图标</span>
<span slot="suf-icon">后置图标</span> -->
<template v-slot:pre-icon="{value}">
<span>前置图标{{value}}</span>
</template>
<template v-slot:suf-icon="{value}">
<span>后置图标{{value}}</span>
</template>
</todo-item>
</todo-list>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
Vue.component('todo-item',{
props:{
title: String,
del:{
type: Boolean,
default: false,
},
},
template:`
<li>
<slot name="pre-icon" :value="value"></slot>
<span v-if="!del">{{title}}</span>
<span v-else style="text-decoration: line-through">{{title}}</span>
<slot name="suf-icon"></slot>
<button v-show="!del" @click="handleClick">删除</button>
</li>`,
data:function(){
return {
value: Math.random()
}
},
methods:{
handleClick(){
console.log('点击删除按钮')
this.$emit('delete',this.title)
}
},
})
Vue.component('todo-list',{
template: `
<ul>
<slot></slot>
</ul>
`,
data:function(){
return {
}
},
})
var vm = new Vue({
el:'#app',
data:{
message:'hello world',
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
},
methods:{
handleDelete(val){
console.log('handleDelete', val)
}
},
})
</script>
</body>
7.6 插槽默认值
把<template v-slot:suf-icon="{value}"><span>后置图标{{value}}</span></template>
删掉。
直接修改template的<slot name="suf-icon"></slot>
,添加默认值。可以使插槽没有传入值时,使用默认值。
<body>
<div id="app">{{message}}{{message + message}}
<div v-bind:id="message"></div>
<todo-list>
<todo-item @delete="handleDelete" v-for="item in list" :title="item.title" :del="item.del">
<!-- <span slot="pre-icon">前置图标</span>
<span slot="suf-icon">后置图标</span> -->
<template v-slot:pre-icon="{value}">
<span>前置图标{{value}}</span>
</template>
</todo-item>
</todo-list>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
Vue.component('todo-item',{
props:{
title: String,
del:{
type: Boolean,
default: false,
},
},
template:`
<li>
<slot name="pre-icon" :value="value"></slot>
<span v-if="!del">{{title}}</span>
<span v-else style="text-decoration: line-through">{{title}}</span>
<slot name="suf-icon">😄</slot>
<button v-show="!del" @click="handleClick">删除</button>
</li>`,
data:function(){
return {
value: Math.random()
}
},
methods:{
handleClick(){
console.log('点击删除按钮')
this.$emit('delete',this.title)
}
},
})
Vue.component('todo-list',{
template: `
<ul>
<slot></slot>
</ul>
`,
data:function(){
return {
}
},
})
var vm = new Vue({
el:'#app',
data:{
message:'hello world',
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
},
methods:{
handleDelete(val){
console.log('handleDelete', val)
}
},
})
</script>
</body>
08 | 理解单文件组件
官方文档https://v2.cn.vuejs.org/v2/guide/single-file-components.html
8.1 安装npm和vue-cli
- 安装npm请到官方网站下载Node的文件https://nodejs.org/en/
npm install -g @vue/cli
安装脚手架,如果失败则用npm install vue-cli -g --force
- npm install vue安装vue
- vue --version查看版本
- 淘宝镜像npm config set registry https://registry.npm.taobao.org --global
8.2 vue create vue-demo1
vue create vue-demo1
vue create vue-demo1
Vue CLI v5.0.8
✨ Creating project in C:\Users\Z13073219\vue-demo1.
⚙️ Installing CLI plugins. This might take a while...
added 847 packages in 2m
🚀 Invoking generators...
📦 Installing additional dependencies...
added 86 packages in 17s
⚓ Running completion hooks...
📄 Generating README.md...
🎉 Successfully created project vue-demo1.
👉 Get started with the following commands:
$ cd vue-demo1
$ npm run serve
8.3 运行服务
npm run serve
访问http://localhost:8080/可以打开默认网页。
8.4 main.js
main.js整列式写法,相当于index.html的el代码。
- main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
- index.html
var vm = new Vue({
el:'#app',
data:{
message:'hello world',
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
},
methods:{
handleDelete(val){
console.log('handleDelete', val)
}
},
})
8.5 App.vue
main.js用import App from './App.vue'
,进入引入【App.vue】单文件组件。
将index.html中的el部分的data和methods,转化为App.vue的data和methods。
最大的区别是data在el中只是一个对象,在App.vue中是一个方法返回一个对象。
- index.html
var vm = new Vue({
el:'#app',
data:{
message:'hello world',
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
},
methods:{
handleDelete(val){
console.log('handleDelete', val)
}
},
})
- App.vue增加data和methods
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
},
data(){
return {
message:'hello world',
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
}
},
methods:{
handleDelete(val){
console.log('handleDelete', val)
}
},
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
8.6 TodoItem.vue
将index.html中的todo-item组件,转化为TodoItem.vue。
- index.html中的todo-item组件
Vue.component('todo-item',{
props:{
title: String,
del:{
type: Boolean,
default: false,
},
},
template:`
<li>
<slot name="pre-icon" :value="value"></slot>
<span v-if="!del">{{title}}</span>
<span v-else style="text-decoration: line-through">{{title}}</span>
<slot name="suf-icon">😄</slot>
<button v-show="!del" @click="handleClick">删除</button>
</li>`,
data:function(){
return {
value: Math.random()
}
},
methods:{
handleClick(){
console.log('点击删除按钮')
this.$emit('delete',this.title)
}
},
})
- TodoItem.vue直接拷贝index的todo-item组件内容,并把template置顶。
<template>
<li>
<slot name="pre-icon" :value="value"></slot>
<span v-if="!del">{{title}}</span>
<span v-else style="text-decoration: line-through">{{title}}</span>
<slot name="suf-icon">😄</slot>
<button v-show="!del" @click="handleClick">删除</button>
</li>
</template>
<script>
export default {
props:{
title: String,
del:{
type: Boolean,
default: false,
},
},
data:function(){
return {
value: Math.random()
}
},
methods:{
handleClick(){
console.log('点击删除按钮')
this.$emit('delete',this.title)
}
},
}
</script>
8.7 TodoList.vue
将index.html中的todo-list组件,转化为TodoList.vue。
- index.html中的todo-list组件
Vue.component('todo-list',{
template: `
<ul>
<slot></slot>
</ul>
`,
data:function(){
return {
}
},
})
- TodoList.vue
<template>
<ul>
<slot></slot>
</ul>
</template>
<script>
export default {
data:function(){
return {
}
},
}
</script>
<style></style>
8.8 引入单文件组件
- App.VUE的< script>增加下列import
import TodoItem from ‘./components/TodoItem.vue’
import TodoList from ‘./components/TodoList.vue’
8.9 debug:error Custom elements in iteration require ‘v-bind:key’ directives vue/valid-v-for
- App.VUE template增加:key=“item.title”
<template>
<div id="app">{{message}}{{message + message}}
<div v-bind:id="message"></div>
<todo-list>
<todo-item @delete="handleDelete" v-for="item in list" :title="item.title" :del="item.del">
<!-- <span slot="pre-icon">前置图标</span>
<span slot="suf-icon">后置图标</span> -->
<template v-slot:pre-icon="{value}">
<span>前置图标{{value}}</span>
</template>
</todo-item>
</todo-list>
</div>
</template>
<script>
import TodoItem from './components/TodoItem.vue'
import TodoList from './components/TodoList.vue'
export default {
name: 'App',
components: {
TodoItem,
TodoList
},
data(){
return {
message:'hello world',
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
}
},
methods:{
handleDelete(val){
console.log('handleDelete', val)
}
},
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
8.10 全局注册
- main.js 中全局注册TodoList
import TodoList from './components/TodoList.vue'
,并用Vue.component('todo-list',TodoList)
import Vue from 'vue'
import App from './App.vue'
import TodoList from './components/TodoList.vue'
Vue.component('todo-list',TodoList)
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
8.11 当前组件的CSS不被污染
在单文件组件中的style中增加scoped属性。
<style scoped>
.red{
color:red;
}
</style>
- TodoItem.vue
<template>
<li>
<slot name="pre-icon" :value="value"></slot>
<span class='red' v-if="!del">{{title}}</span>
<span v-else style="text-decoration: line-through">{{title}}</span>
<slot name="suf-icon">😄</slot>
<button v-show="!del" @click="handleClick">删除</button>
</li>
</template>
<script>
export default {
props:{
title: String,
del:{
type: Boolean,
default: false,
},
},
data:function(){
return {
value: Math.random()
}
},
methods:{
handleClick(){
console.log('点击删除按钮')
this.$emit('delete',this.title)
}
},
}
</script>
<style scoped>
.red{
color:red;
}
</style>
09 | 双向绑定和单向数据流不冲突
9.1 双向绑定的意思
视图更新后,数据也会更新;数据更新后,视图也会更新。
9.2 v-model创建双向绑定。
<input v-model="message">
- App.VUE中的template
<template>
<div id="app">
<input v-model="message">
{{message}}{{message + message}}
<div v-bind:id="message"></div>
<todo-list>
<todo-item @delete="handleDelete" v-for="item in list" :key="item.title" :title="item.title" :del="item.del">
<!-- <span slot="pre-icon">前置图标</span>
<span slot="suf-icon">后置图标</span> -->
<template v-slot:pre-icon="{value}">
<span>前置图标{{value}}</span>
</template>
</todo-item>
</todo-list>
</div>
</template>
9.3 v-model仅仅是语法糖,可以通过:value来绑定值和@input来触发事件来更新值。
<input :value="message" @input="handleChange">
methods:{
handleChange(e){
this.message = e.target.value
},
}
- App.vue
<template>
<div id="app">
<input v-model="message">
<input :value="message" @input="handleChange">
{{message}}{{message + message}}
<div v-bind:id="message"></div>
<todo-list>
<todo-item @delete="handleDelete" v-for="item in list" :key="item.title" :title="item.title" :del="item.del">
<!-- <span slot="pre-icon">前置图标</span>
<span slot="suf-icon">后置图标</span> -->
<template v-slot:pre-icon="{value}">
<span>前置图标{{value}}</span>
</template>
</todo-item>
</todo-list>
</div>
</template>
<script>
import TodoItem from './components/TodoItem.vue'
import TodoList from './components/TodoList.vue'
export default {
name: 'App',
components: {
TodoItem,
TodoList
},
data(){
return {
message:'hello world',
list:[{
title:'课程1',
del:false,
},
{
title:'课程2',
del:true,
},
]
}
},
methods:{
handleChange(e){
this.message = e.target.value
},
handleDelete(val){
console.log('handleDelete', val)
}
},
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
9.4 v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件。
9.5 .sync来绑定多个属性(略)
https://v2.cn.vuejs.org/v2/guide/components-custom-events.html#sync-%E4%BF%AE%E9%A5%B0%E7%AC%A6
10 | 理解虚拟DOM及key属性的作用
10.1 虚拟DOM==>如何移动节点
10.2 说明key属性的重要性,否则可能会报错
debug:error Custom elements in iteration require ‘v-bind:key’ directives vue/valid-v-for
做法1:App.VUE template增加:key=“item.title”
做法2:v-for=“(item,index) in list” :key=“index”。如果下属节点删除,可能导致index错乱。
11 | 如何触发组件的更新
11.1 组件更新
我们都知道vue是数据驱动的,只有在数据改变的时候,我们的视图才会改变。所以任何直接修改dom的行为都是在作死。
数据来源(单向的):
- 来自父元素的属性props
- 来自组件自身的状态data
- 来自状态管理器,vuex,Vue.observable
11.2 状态data与属性props
- 状态时组件自身的数据
- 属性时来自父组件的数据
- 状态的改变未必会触发更新
- 属性的改变未必会触发更新
11.3 vue的响应式更新
用到的数据放在Watcher里面,如果没有用到就不放到Watcher里面。
如果Watcher用到了,下次更新时setter就会通知Watcher更新数据。
如果Watcher没有用到,即使setter更新了也不会通知Watcher。
11.4 代码说明:vue的响应式更新
1)非响应式更新
原因:name没有做响应式。响应式只存在info里面,还不存在info下面。
2)响应式更新
做法:name放到return里面;info提前声明number。
12 | 合理应用计算属性和侦听器
12.1 计算属性computed
1)作用
- 减少模板中的计算逻辑
- 数据缓存
- 依赖固定的数据类型(响应式数据)
2)代码:ComputedDo.vue
message有变化时:而当我们在输入框中输入数据时,reversedMessage1和reversedMessage2会同时被执行。
message无变化时:我们通过this.$forceUpdate()刷新数据,我们点击按钮的时候,可以看到只有reversedMessage2方法被调用了。
<template>
<div>
<p>Reversed message1: "{{ reversedMessage1 }}"</p>
<p>Reversed message2: "{{ reversedMessage2() }}"</p>
<p>{{ now }}</p>
<button @click="() => $forceUpdate()">forceUpdate</button>
<br />
<input v-model="message" />
</div>
</template>
<script>
export default {
data() {
return {
message: "hello vue"
};
},
computed: {
// 计算属性的 getter
reversedMessage1: function() {
console.log("执行reversedMessage1");
return this.message
.split("")
.reverse()
.join("");
},
now: function() {
return Date.now();
}
},
methods: {
reversedMessage2: function() {
console.log("执行reversedMessage2");
return this.message
.split("")
.reverse()
.join("");
}
}
};
</script>
12.2 侦听器watch
1)作用
- 更加灵活、通用
- watch中可以执行任何逻辑,如函数节流,ajax异步获取数据,甚至操作DOM
2)代码:ComputedDo.vue
watch监听了a,当a有变化时,就会执行this.b.c += 1和console.log(“new: %s, old: %s”, val, oldVal)。
这里涉及到一个嵌套监听的概念,注意:如果在e中设置deep为false,那么对b.d的监听中更改e值不会涉及到e的handler事件的触发。
<template>
<div>
{{ $data }}
<br />
<button @click="() => (a += 1)">a+1</button>
</div>
</template>
<script>
export default {
data: function() {
return {
a: 1,
b: { c: 2, d: 3 },
e: {
f: {
g: 4
}
},
h: []
};
},
watch: {
a: function(val, oldVal) {
this.b.c += 1;
console.log("new: %s, old: %s", val, oldVal);
},
"b.c": function(val, oldVal) {
this.b.d += 1;
console.log("new: %s, old: %s", val, oldVal);
},
"b.d": function(val, oldVal) {
this.e.f.g += 1;
console.log("new: %s, old: %s", val, oldVal);
},
e: {
handler: function(val, oldVal) {
this.h.push("😄");
console.log("new: %s, old: %s", val, oldVal);
},
deep: true
},
h(val, oldVal) {
console.log("new: %s, old: %s", val, oldVal);
}
}
};
</script>
12.3 computed vs watch
1)区别
- computed 能做的,watch都能做,反之则不行
- 能用computed的尽量用computed
2)功能相同,用computed API写的逻辑
<template>
<div>
{{ fullName }}
<div>firstName: <input v-model="firstName" /></div>
<div>lastName: <input v-model="lastName" /></div>
</div>
</template>
<script>
export default {
data: function() {
return {
firstName: "Foo",
lastName: "Bar"
};
},
computed: {
fullName: function() {
return this.firstName + " " + this.lastName;
}
},
watch: {
fullName: function(val, oldVal) {
console.log("new: %s, old: %s", val, oldVal);
}
}
};
</script>
3)功能相同,用watch API写的逻辑
<template>
<div>
{{ fullName }}
<div>firstName: <input v-model="firstName" /></div>
<div>lastName: <input v-model="lastName" /></div>
</div>
</template>
<script>
export default {
data: function() {
return {
firstName: "Foo",
lastName: "Bar",
fullName: "Foo Bar"
};
},
watch: {
firstName: function(val) {
this.fullName = val + " " + this.lastName;
},
lastName: function(val) {
this.fullName = this.firstName + " " + val;
}
}
};
</script>
13 | 生命周期的应用场景和函数式组件
13.1 生命周期
13.2 函数式组件(难)
- 作用
- fuctional: true
- 无状态、无实例、没有this上下文、无生命周期
- 是一个简单的方法
2)示例通过函数式组件实现临时变量的功能(虽然计算属性能帮我们搞定大部分的问题,但是计算属性更多的时针对一些被监听的变量的,有时候还是要用到临时变量。)
TempVar为一个函数式组件。
- TempVar.js
export default {
functional: true,
render: (h, ctx) => {
return ctx.scopedSlots.default && ctx.scopedSlots.default(ctx.props || {});
}
};
- index.vue
<template>
<div>
<a-tabs>
<a-tab-pane key="clock" tab="时钟">
<button @click="destroyClock = !destroyClock">
{{ destroyClock ? "加载时钟" : "销毁时钟" }}
</button>
<Clock v-if="!destroyClock" />
</a-tab-pane>
<a-tab-pane key="Functional" tab="函数式组件">
<Functional :name="name" />
<TempVar
:var1="`hello ${name}`"
:var2="destroyClock ? 'hello vue' : 'hello world'"
>
<template v-slot="{ var1, var2 }">
{{ var1 }}
{{ var2 }}
</template>
</TempVar>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script>
import Clock from "./Clock";
import Functional from "./Functional";
import TempVar from "./TempVar";
export default {
components: {
Clock,
Functional,
TempVar
},
data() {
return {
destroyClock: false,
name: "vue"
};
}
};
</script>
14 | 指令的本质是什么
14.1 内置指令
14.2 自定义指令
- CustomerDirectives.vue
这个自定义组件appendText被v-append-text引用。
(vue-append: like v-html directive, but it can call javascript function)
<template>
<div>
<button @click="show = !show">
销毁
</button>
<button v-if="show" v-append-text="`hello ${number}`" @click="number++">
按钮
</button>
</div>
</template>
<script>
export default {
directives: {
appendText: {
bind() {
console.log("bind");
},
inserted(el, binding) {
el.appendChild(document.createTextNode(binding.value));
console.log("inserted", el, binding);
},
update() {
console.log("update");
},
componentUpdated(el, binding) {
el.removeChild(el.childNodes[el.childNodes.length - 1]);
el.appendChild(document.createTextNode(binding.value));
console.log("componentUpdated");
},
unbind() {
console.log("unbind");
}
}
},
data() {
return {
number: 1,
show: true
};
}
};
</script>
这个append-text指令实现的是往当前的结点文本内容之后插入内容的指令,看下运行效果图:
15 | 常用高级特性provide/inject(难)
15.1 provide/inject
https://blog.csdn.net/weixin_54757930/article/details/121761669
provider和inject是为了解决组件通信时层层需要传递的问题。
15.2 代码
1.上图展示了各个节点之间的层级,我们按照这个层级书写代码:
2.看下A节点的代码:A节点通过provide提供了一个theme属性。
<template>
<div class="border">
<h1>A 结点</h1>
<button @click="() => changeColor()">改变color</button>
<ChildrenB />
<ChildrenC />
<ChildrenD />
</div>
</template>
<script>
import ChildrenB from "./ChildrenB";
import ChildrenC from "./ChildrenC";
import ChildrenD from "./ChildrenD";
export default {
components: {
ChildrenB,
ChildrenC,
ChildrenD
},
provide() {
return {
theme: {
color: this.color
}
};
},
// provide() {
// return {
// theme: this
// };
// },
data() {
return {
color: "blue"
};
},
methods: {
changeColor(color) {
if (color) {
this.color = color;
} else {
this.color = this.color === "blue" ? "red" : "blue";
}
}
}
};
</script>
3.E节点代码:E节点通过inject注入theme。
<template>
<div class="border2">
<h3 :style="{ color: theme.color }">E 结点</h3>
<button @click="handleClick">改变color为green</button>
</div>
</template>
<script>
export default {
components: {},
inject: {
theme: {
default: () => ({})
}
},
methods: {
handleClick() {
if (this.theme.changeColor) {
this.theme.changeColor("green");
}
}
}
};
</script>
4.F节点(运用别名):
<template>
<div class="border2">
<h3 :style="{ color: theme1.color }">F 结点</h3>
</div>
</template>
<script>
export default {
components: {},
inject: {
theme1: {
from: "theme",
default: () => ({})
}
}
};
</script>
5.I节点(函数式组件):
<template functional>
<div class="border2">
<h3 :style="{ color: injections.theme.color }">I 结点</h3>
</div>
</template>
<script>
export default {
inject: {
theme: {
default: () => ({})
}
}
};
</script>
16 | 跨层级组件实例
16.1 ref获取组件实例
我们可以通过ref获取组件实例:
https://www.51sjk.com/b166b135671/
16.2 callback ref(难)
但是当我们层级多的时候就显得不合适了,递归查找的代码会繁琐,性能会低效。我们看一下callback ref的概念:
如果e节点更新以后能够调用a节点的钩子函数,主动通知a节点,那么层级的调用就显得容易很多了。
看一下实现的形式,依旧是沿用上一次的节点结构:
<template>
<div class="border">
<h1>A 结点</h1>
<button @click="getEH3Ref">获取E h3 Ref</button>
<ChildrenB />
<ChildrenC />
<ChildrenD />
</div>
</template>
<script>
import ChildrenB from "./ChildrenB";
import ChildrenC from "./ChildrenC";
import ChildrenD from "./ChildrenD";
export default {
components: {
ChildrenB,
ChildrenC,
ChildrenD
},
provide() {
return {
setChildrenRef: (name, ref) => {
this[name] = ref;
},
getChildrenRef: name => {
return this[name];
},
getRef: () => {
return this;
}
};
},
data() {
return {
color: "blue"
};
},
methods: {
getEH3Ref() {
console.log(this.childrenE);
}
}
};
</script>
A节点通过provide提供主动获取通知的钩子函数。
子节点D:
<template>
<div class="border1">
<h2>D 结点</h2>
<ChildrenG />
<ChildrenH v-ant-ref="c => setChildrenRef('childrenH', c)" />
<ChildrenI />
</div>
</template>
<script>
import ChildrenG from "./ChildrenG";
import ChildrenH from "./ChildrenH";
import ChildrenI from "./ChildrenI";
export default {
components: {
ChildrenG,
ChildrenH,
ChildrenI
},
inject: {
setChildrenRef: {
default: () => {}
}
}
};
</script>
17 | template和JSX的对比以及它们的本质
17.1 template和JSX的对比