随书的源码
https://github.com/icarusion/vue-book
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>示例</title>
</head>
<body>
<div id="app">
<input type="text" v-model="name" placeholder="用户名">
<h1>你好,{{name}}</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
name:'',
}
})
</script>
</body>
</html>
上面,el用于指定一个页面中已存在的DOM元素来挂载Vue实例。
每个Vue实例创建时,都会经历一系列的初始化过程,同时也会调用相应的生命周期钩子。
vue常用的生命周期钩子有
- created:实例创建完成后调用,此阶段完成了数据的观测等,但尚未挂载,$el还不可用,需要初始化处理一些数据时会比较有用
- **mounted :**el挂载到实例上后调用,一般我们的第一个业务逻辑会在这里
- **beforeDestroy:**实例销毁之前调用。主要解绑一些使用addEventListener监听的事件。
在{{}}
插值的尾部添加一个管道符(|)
对数据进行过滤,常用于格式化文本
Vue实例—— 实时显示当前的时间 2.1.2-showCurrentData
- 实现简单文本插值。
- 动态设置元素的样式名称class和内联样式style
计算属性是基于它的依赖缓存的,一个计算属性所依赖的数据发生变化时,它才会重新取值,所以当遍历大数组和做大量计算时,应当使用计算属性
<div id="app">
<div :class="classes">
绑定class的几种方式
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
isActive:true,
error:null
},
computed:{
classes:function(){
return {
active:this.isActive && !this.error,
'text-fail':this.error && this.error.type === 'fail'
}
}
}
})
</script>
给class绑定一个数组
<div id="app">
<div :class="[activeCls,errorCls]">
需要多个class
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
activeCls:'active',
errorCls:'error',
}
})
可以使用计算属性给元素动态设置类名
<div id="app">
<div :class="classes">
动态设置类名
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
size:'large',
disabled:true,
},
computed:{
classes:function(){
return [
'btn',
{
['btn-'+this.size]:this.size!== '',
['btn-disabled']:this.disabled
}
]
}
}
})
</script>
对于简单项目可以解决初始化慢导致页面闪的问题。
工程化项目里通过路由去挂载不同组件,不需要v-cloak
定义它的元素或组件只渲染一次,包括元素或组件的所有子节点。首次渲染后,不再随数据的变化重新渲染,将被视为静态内容。(少用。进一步优化性能时可能用到
v-if 若表达式初值为false,则一开始元素就不会被渲染,条件为真才渲染。
v-show 只是简单的CSS属性切换,无论条件真或否,都会被编译。
v-if更适合条件不经常改变的场景,因为其切换开销相对较大,v-show适用于频繁切换条件
v-for可以遍历
- 数据
- 对象的属性(没用过!?
- 还可以迭代整数
例如,给示例的数组books添加一项
改变原数组的情况
使用push为数组添加一项
不改变原数组的情况
有些方法不会改变原数组,例如 filter()
,concat()
,`slice(),他们返回一个新数组。
不想改变原数组,想通过一个数组的副本来做过滤或排序的显示时,可以使用计算属性来返回过滤或排序后的数组
https://blog.csdn.net/sinat_38368658/article/details/107997407
Vue提供了一个特殊变量$event
,用于访问原生DOM事件。例如下面的实例可以阻止链接打开
<div id="app">
<a href="www.baidu.com" @click="handleClick('禁止打开',$event)">打开链接</a>
</div>
<script>
var app = new Vue({
el:'#app',
methods:{
handleClick:function(message,event){
event.preventDefault();
window.alert(message);
}
}
});
</script>
上述使用的 event.preventDefault()
也可以用Vue事件的修饰符实现。
Vue支持一下修饰符
- .stop
- .prevent
- .capture
- .self
- .once
练习题 购物车https://blog.csdn.net/Clara_G/article/details/89840364
.number 将输入转换为Number类型,否则虽然你输入的是数字,但它的类型其实是string,用于数字输入框
.trim 过滤首尾空格
注意,Vue组件的模板在某些情况下会受到HTML的限制,比如<table>
内规定只允许是<tr>,<td><th>
等这些表格元素,所以在<table>
内直接使用组件是无效的。在这种情况下,可以使用is属性来挂载组件
<div id="app">
<table>
<tbody is="my-component"></tbody>
</table>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component',{
template:'<div>这里是我自定义组件的内容</div>'
})
var app = new Vue({
el:'#app',
});
</script>
常见的限制元素还有ul ol select
【注】如果使用的是字符串模板,是不受限制的,比如vue单文件用法
父组件中包含子组件,父组件要正向地向子组件传递数据或参数,这个过程就是通过props实现。
在组件中,使用选项props声明需要从父级接受的数据,props的值可以是两种,字符串数组或对象。
以数组用法为例,构造一个数组,接受一个来自父级的数据mesage,并把它在组件模板中渲染。
<div id="app">
<my-component message="来自父组件的数据" warning-text="提示信息"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component',{
props:['message','warningText'],
template:'<div>{{message}} | {{warningText}}</div>'
})
var app = new Vue({
el:'#app',
});
</script>
![image-20200814172940373](《Vue.js 实战》读书笔记.assets/image-20200814172940373.png)
当传递的数据不是写死,而是来自父级的动态数据,可以使用v-bind来动态绑定props的值,当父组件的数据发生变化时,也会传递给子组件
<div id="app">
<input type="text" v-model="parentMessage">
<my-component :message="parentMessage"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component',{
props:['message'],
template:'<div>来自父组件的消息:{{message}}</div>'
})
var app = new Vue({
el:'#app',
data:{
parentMessage:''
}
});
</script>
【注意】:如果要传递数字,布尔值,数组,对象。如果不使用v-bind,传递的仅仅是字符串
<div id="app">
<!--未使用v-bind 传递的时字符串-->
<my-component message="[1,2,3]"></my-component>
<!--使用v-bind 传递的才是数组-->
<my-component :message="[1,2,3]"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component',{
props:['message'],
template:'<div>{{message.length}}</div>'
})
var app = new Vue({
el:'#app',
});
</script>
![image-20200814210925375](《Vue.js 实战》读书笔记.assets/image-20200814210925375.png)
props传递数据是单向,对于需要改变props的情况
第一种是父组件传初始值,子组件将其作为初始值保存起来,在自己的作用域下可以随意修改和使用
这种情况可以在组件data内再声明一个数据,引用父组件的prop
<div id="app">
<my-component :init-count="1"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component',{
props:['initCount'],
template:'<div>{{count}}</div>',
data(){
return{
count:this.initCount
}
}
})
var app = new Vue({
el:'#app',
});
</script>
![image-20200814214054591](《Vue.js 实战》读书笔记.assets/image-20200814214054591.png)
组件中声明了数据count,它在组件初始化事会获取来自父组件的initCount,之后就与之无关了,指用维护count。这样就可以避免直接操作initCount
另一种情况是prop作为需要被转变的原始值传入,这种情况使用计算属性就可以
<div id="app">
<my-component :width="100"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component',{
props:['width'],
template:'<div :style="style">子组件内容</div>',
computed:{
style:function(){
return {
width:this.width+'px'
}
}
}
})
var app = new Vue({
el:'#app',
});
</script>
![image-20200814214409130](《Vue.js 实战》读书笔记.assets/image-20200814214409130.png)
【注意】JS中对象和数组是引用类型,指向同一个内存空间,所以props是对象和数组时,在子组件内改变是会影响父组件的。
上面的例子props的值都是数组,除了数组外,可以是对象,当prop需要验证时,就需要对象写法
一般当你的组件提供给别人使用时,推荐都进行数据验证。比如某个数据必须是数字类型,如果传入字符串,就会在控制台弹出警告
Vue.component('my-component',{
props:{
//必须是数字类型
propA:Number,
//必须是字符串或数字类型
propB:[String,Number],
//布尔值,如果没有定义,默认值就是true
propC:{
type:Boolean,
default:true
},
//数字,而且是必传
propD:{
type:Number,
required:true
},
//如果是数组或对象,默认值必须是一个函数来返回
propE:{
type:Array,
default:function(){
return [];
}
},
//自定义一个验证函数
propF:{
validator:function(value){
return val>10;
}
}
}
});
当prop验证失败时,开发版本下会在控制台抛出一条警告
子组件$emit()
触发事件,父组件$on()
监听子组件
父组件也可以直接在子组件的自定义标签上使用v-on
来监听子组件出发的自定义事件
<div id="app">
<p>总数:{{total}}</p>
<!--语法糖写法:v-on:increase-->
<my-component
@increase="handleGetTotal"
@reduce="handleGetTotal"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component',{
template:'\
<div>\
<button @click="handleIncrease">+1</button>\
<button @click="handleReduce">-1</button>\
</div>',
data:function(){
return {
counter:0
}
},
methods:{
handleIncrease:function(){
this.counter++;
this.$emit('increase',this.counter);
},
handleReduce:function(){
this.counter--;
this.$emit('reduce',this.counter);
}
}
})
var app = new Vue({
el:'#app',
data:{
total:0,
},
methods:{
handleGetTotal:function(total){
this.total = total;
}
}
});
</script>
</body>
</html>
![image-20200818113302429](《Vue.js 实战》读书笔记.assets/image-20200818113302429.png)
一个语法糖,直接使用V-model绑定数据total,这里组件$emit()的事件名时特殊的input。
<div id="app">
<p>总数:{{total}}</p>
<!--语法糖写法:v-on:increase-->
<my-component v-model="total"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component',{
template:'\
<div>\
<button @click="handleReduce">-1</button>\
</div>',
data:function(){
return {
counter:0
}
},
methods:{
handleReduce:function(){
this.counter--;
this.$emit('input',this.counter);
}
}
})
var app = new Vue({
el:'#app',
data:{
total:0,
},
});
</script>
</body>
</html>
v-model还可以用来创建自定义的表单输入组件,进行数据双向绑定
<div id="app">
<p>总数:{{total}}</p>
<my-component v-model="total"></my-component>
<button @click="handleReduce">-1</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component',{
template:'<input :value="value" @input="updateValue">',
methods:{
updateValue:function(event){
this.$emit('input',event.target.value);
}
}
})
var app = new Vue({
el:'#app',
data:{
total:0,
},
methods:{
handleReduce:function(){
this.total--;
}
}
});
</script>
![image-20200818115329186](《Vue.js 实战》读书笔记.assets/image-20200818115329186.png)
实现这样一个具有双向绑定的v-model组件要满足下面两个要求
- 接受一个value属性
- 在有新的value时触发input事件
非父子组件一般有两种,兄弟组件和跨多级组件。
在Vue2.x中**,推荐使用一个空的Vue实例作为中央事件总线,也就是一个中介。**这种方法巧妙而轻量地实现了任何组件间的通信,包括父子,兄弟,跨级。
<div id="app">
{{message}}
<component-a></component-a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var bus = new Vue();
Vue.component('component-a',{
template:'<button @click="handleEvent">传递事件</button>',
methods:{
handleEvent:function(){
bus.$emit('on-message','来自组件component-a的内容');
}
}
})
var app = new Vue({
el:'#app',
data:{
message:'',
},
mounted:function(){
var _this = this;
//在实例初始化时,监听来自bus实例的事件
bus.$on('on-message',function(msg){
_this.message = msg;
})
}
});
</script>
点击后
props传递数据,events触发事件和slot内容分发就构成了Vue组件的3个API来源,再复杂的组件也是由这三部分组成的。
父组件模板的内容是在父组件作用域内编译,子组件模板的内容实在子组件作用域内编译
showChild绑定父组件的数据
<div id="app">
<child-component v-show="showChild"></child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var bus = new Vue();
Vue.component('child-component',{
template:'<div>子组件</div>',
});
var app = new Vue({
el:'#app',
data:{
showChild:true
}
});
</script>
showChild绑定子组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>示例</title>
</head>
<body>
<div id="app">
<child-component></child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var bus = new Vue();
Vue.component('child-component',{
template:'<div v-show="showChild">子组件</div>',
data:function(){
return {
showChild:true
}
}
});
var app = new Vue({
el:'#app',
});
</script>
</body>
</html>
![image-20200818200316770](《Vue.js 实战》读书笔记.assets/image-20200818200316770.png)
slot分发的内容,作用域实在父组件上的
单个slot
<div id="app">
<child-component>
<p>分发内容</p>
<p>分发更多的内容</p>
</child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var bus = new Vue();
Vue.component('child-component',{
template:'\
<div>\
<slot>\
<p>如果父件没有插入内容,我将默认出现</p>\
</slot>\
</div>',
});
var app = new Vue({
el:'#app',
});
</script>
具名slot
给<slot>
指定name后可以分发多个内容,具名slot可以和单个slot共存
<div id="app">
<child-component>
<h2 slot="header">标题</h2>
<p>内容</p>
<p>更多正文内容</p>
<div slot="footer">底部信息</div>
<p>试试</p>
</child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child-component',{
template:'\
<div class="container">\
<div class="header">\
<slot name="header"></slot>\
</div>\
<div class="main">\
<slot></slot>\
</div>\
<div class="footer">\
<slot name="footer"></slot>\
</div>\
</div>'
});
var app = new Vue({
el:'#app',
});
</script>
作用域插槽是一种特殊的slot,使用一个可以复用的模板替换已渲染元素
<div id="app">
<child-component>
<template scope="props">
<p>来自父组件的内容</p>
<p>{{props.msg}}</p>
</template>
</child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var bus = new Vue();
Vue.component('child-component',{
template:'\
<div class="container">\
<slot msg="来自子组件的内容"></slot>\
</div>'
});
var app = new Vue({
el:'#app',
});
</script>
<template scope="props">
,template内可以通过临时变量props访问来自子组件插槽的数据msg
$slots
用来访问被slot分发的内容的方法。
<div id="app">
<child-component>
<h2 slot="header">标题</h2>
<p>内容</p>
<p>更多正文内容</p>
<div slot="footer">底部信息</div>
</child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child-component',{
template:'\
<div class="container">\
<div class="header">\
<slot name="header"></slot>\
</div>\
<div class="main">\
<slot></slot>\
</div>\
<div class="footer">\
<slot name="footer"></slot>\
</div>\
</div>',
mounted:function(){
var header = this.$slots.header;
var main = this.$slots.default;
var footer = this.$slots.footer;
console.log(footer);
console.log(footer[0].elm.innerHTML);
}
});
var app = new Vue({
el:'#app',
});
</script>
![image-20200818203921784](《Vue.js 实战》读书笔记.assets/image-20200818203921784.png)
组件在它的模板内可以递归的调用自己,只要给组件设置name就可以了。
组件递归使用可以用来开发一些具有未知层级关系的独立组件,比如级联选择器和属性控件等
给组件标签使用inline-template
特性,组件就会把它的内容当作模板,而不是当作内容分发。不建议使用
元素<component>
来动态挂载不同的组件,使用is来选择要挂载的组件。
<div id="app">
<component :is="currentView"></component>
<button @click="handleChangeView('A')">切换到组件A</button>
<button @click="handleChangeView('B')">切换到组件B</button>
<button @click="handleChangeView('C')">切换到组件C</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el:'#app',
components:{
comA:{
template:'<div>组件A</div>'
},
comB:{
template:'<div>组件B</div>'
},
comC:{
template:'<div>组件C</div>'
},
},
data:{
currentView:'comA',
},
methods:{
handleChangeView:function(component){
this.currentView = 'com'+component;
}
}
});
</script>
![image-20200818204923401](《Vue.js 实战》读书笔记.assets/image-20200818204923401.png)
Vue.js允许将组件定义为一个工厂函数,动态地解析组件。Vue.js只在组件需要渲染时触发工厂函数,并把结果缓存起来,用于后面的再次渲染
<div id="app">
<child-component></child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child-component',function(resolve,reject){
window.setTimeout(function(){
resolve({
template:'<div>我是异步渲染的</div>'
})
})
});
var app = new Vue({
el:'#app',
});
</script>
后面使用webpack更优雅地实现异步组件
一个重要概念:异步更新队列:Vue在观察到数据变化时并不直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的所有数据改变,在缓冲时会去除重复数据,从而避免比不要的计算和DOM操作。然后,在下一个事件循环tick中,Vue刷新队列并执行实际(已去重的)工作。
<div id="app">
<div id="div" v-if="showDiv">这是一段文本</div>
<button @click="getText">获取div内容</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
showDiv:false
},
methods:{
getText:function(){
this.showDiv = true;
this.$nextTick(function(){
var text = document.getElementById('div').innerHTML;
console.log(text);
});
}
}
})
</script>
自定义指令的注册方式分为全局注册和局部注册
自定义指令的选项由几个钩子函数组成,每个都是可选的
- bind
- inserted
- update
- componentUpdated
- unbind
打开页面,input输入框就自动获得了焦点,成为可输入状态
<div id="app">
<input type="text" v-focus>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//这种是全局注册方式
Vue.directive('focus',{
inserted:function(el){
el.focus();
}
})
var app = new Vue({
el:'#app',
});
</script>
![image-20200826210806631](《Vue.js 实战》读书笔记.assets/image-20200826210806631.png)
使用的不多,业务上更需要的不是自定义指令,而是组件。
查看前端demo里面
Vue.js 2.x使用了Virtual Dom(虚拟DOM)来更新DOM节点,提升渲染性能
React和Vue2都使用了Virtual Dom技术,并不是真正意义上的DOM,而是一个轻量级的Javascript对象。在状态发生变化时,virtual Dom会进行Diff运算,来更新只需要被替换的DOM,而不是全部重绘。
Virtual Dom运行过程
![image-20200827212547179](《Vue.js 实战》读书笔记.assets/image-20200827212547179.png)
用virtualDom创建的JavaScript对象一般是这样的:
var vNode = {
tag:'div',
attributes:{
id:'main'
},
children:[
//p节点
]
在Vue2中,virtual Dom是通过一种VNode类表达的,每个DOM元素或组件都对应一个VNode对象。
VNode可以分为如下几类:
![image-20200828091327405](《Vue.js 实战》读书笔记.assets/image-20200828091327405.png)
很多网站将标题做成了锚点,点击会将内容加在网址后面,以#分割
Render函数通过createElement参数来创建Virtual Dom,结构比使用template精简了许多。
<div id="app">
<anchor :level="2" title="character">特性</anchor>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('anchor',{
props:{
level:{
type:Number,
required:true
},
title:{
type:String,
default:'',
}
},
render:function(createElement){
return createElement(
'h'+this.level,
[
createElement(
'a',
{
domProps:{
href:'#' + this.title
}
},
this.$slots.default//= undefined 说明父组件中没有定义slot
)
]
)
}
});
var app = new Vue({
el:'#app',
});
</script>
![image-20200828093719973](《Vue.js 实战》读书笔记.assets/image-20200828093719973.png)
Vue.js提供一个functional的布尔选项,设置为true可以使组件无状态或无实例,也就是没有data和this上下文,这样用render函数返回虚拟节点可以更容易渲染,因为函数化组件只是一个函数。渲染开销要小很多。
使用函数化组件时,Render函数提供了第二个参数context
来提供临时上下文。组件需要的dara,props,slots,children,parent
都是通过这个上下文来传递的,比如this.level
要改写为context.props.level
,this.$slots.default改写问context.children
示例:使用函数化组件展示了一个根据数据智能选择不同组件的场景
看代码![image-20200828104422520](《Vue.js 实战》读书笔记.assets/image-20200828104422520.png)
![image-20200828104517527](《Vue.js 实战》读书笔记.assets/image-20200828104517527.png)
![image-20200828104523153](《Vue.js 实战》读书笔记.assets/image-20200828104523153.png)
![image-20200828104528584](《Vue.js 实战》读书笔记.assets/image-20200828104528584.png)
函数化组件在业务中并不是很常用,上述示例也可以用组件的is特性来动态加载。
总之,函数化组件主要适用于以下两个场景
- 程序化地在多个组件中选择一个
- 在将children,props,data传递给子组件之前操作它们。
为了让Render函数更好的书写和阅读,Vue.js提供了插件babel-plugin-transform-vue-jsx来支持JSX语法
见前端demo
webpack:热门的JS应用的模块打包工具
通常,前端自动化(半自动化)工程主要解决以下问题:
- Js,css代码的合并和压缩
- CSS预处理:Less,Sass,Stylus的编译
- 生成雪碧图(CSS Sprite)
- ES6转ES5
- 模块化
webpack模块化示意图:
![image-20200903094222108](《Vue.js 实战》读书笔记.assets/image-20200903094222108.png "webpack模块化示意图")
左边是业务中写的各种格式的文件,这些格式的文件通过特定的加载器(Loader)编译后,最终统一生成.js,.css,.png等静态资源文件。在webpack中,一张图片,一个css甚至一个字体,都成为模块(Module),彼此存在依赖关系,webpack就是来处理模块间的依赖关系的,并把它们进行打包。
这块是实践内容
书上说的挺好的,有时间可以回来康康
前后端分裂的开发模式,后端只提供API来返回数据,前端通过Ajax获取数据后,再用一定的方式渲染到页面里
在实际业务中,经常有跨组件共享数据的需求,因此Vuex的设计就是用来统一管理组件状态的