发布时间:2024-11-08 10:01
可以把一整块的代码块放入template中,这样就可以方便以后调用。
<div id="app">
{{message}}
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
}
})
script>
以上即为一个模板,把它复制后进入设置,找到editor->live templates->vue,再点击右侧加号,把内容复制进去,同时给这个模板取个名字。记得要在界面下方的这里设置勾选html,再点击apply即可。
如何把data,methods中的文本数据,插入到HTML代码中?我们可以用到mustache语法,也就是这样双大括号的形式:
<div id="app">
{{message}}
div>
这样的数据是响应式的。在mustache里还可以进行字符串的拼接,数值的计算等操作。
要注意的是,mustache语法是写在内容中的,而不能写在标签里。
如上的数据是响应式的,但是有时,我们不希望数据随着用户输入而改变。这时,就需要在代码里加入“v-once”。如下:
<div id="app">
<h2 v-once>
{{message}}
h2>
div>
有时,我们从服务器请求到的数据是html代码,这时候用mustache语法解析会把html代码输出(比如下图里会把a标签也展示出来),但是我们希望得到的是按照html格式来进行解析的内容,这时,就要用到v-html。如下:
<div>
<h2 v-html="url">
{{message}}
h2>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
url:'xxx'
}
})
script>
<div>
<h2 v-text="message">h2>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
url:'xxx'
}
})
script>
展示效果与mustache相同,但是一般不用,不如mustache灵活,无法进行拼接。
v-pre类似于
标签,里面的东西可以原封不动地表现出来,用于跳过这个元素和这个元素里的内容。如,我们想在页面上显示一个双括号:
<div>
<h2 v-pre>{{message}}h2>
div>
html的代码是从上至下解析的,所以当之后的js代码卡住时,前面未被js解释的mustache语法就会显示出来。一般情况下,我们不希望用户看到这种信息,所以就有了v-cloak,用法如下:
<div id="app" v-cloak>
{{message}}
div>
我们可以把v-cloak当成一个属性,加入到div里。在vue解析之前,div里有一个属性v-cloak,解析之后就没了。所以,可以根据有无v-cloak属性来判断js代码是否执行了。我们用style来实现这个功能:
<style>
[v-cloak] {
display:none;
}
style>
网页中某些属性我们希望动态决定,如商城里的商品轮播图等。一些网址等,在开发中也很少会写死,大多都是从服务器请求得来的。我们把这样的数据在vue里暂存中转,在html里利用v-bind来实现动态绑定。
<div id="app">
<img v-bind:src="imgURL">
<a v-bind:href="ahref">a>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
imgURL:'website1,website2……'
ahref:'website1,website2……'
}
})
script>
v-bind有个语法糖(简写),只留下一个冒号,格式如下:
<div id="app">
<img :src="imgURL">
<a :href="ahref">a>
div>
v-bind和class的对象用法如下:
<style>
.active {
color:red;
}
style>
<div id="app">
<h2 :class="active">
{{message}}
h2>
<h2 v-bind:class="{key1:value1, key2:value2}">
{{message}}
h2>
<h2 v-bind:class="{类名1:boolean, 类名2:boolean……}">
{{message}}
h2>
<h2 v-bind:class="{active:isActive, line:isLine}">
{{message}}
h2>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
active:'active'
isActive:true,
isLine:true,
}
})
script>
假设有这样的一个需求:有一个button,按下后使文字变成红色,再按一下又变回黑色,如此往复,怎么实现?
<style>
.active {
color:red;
}
style>
<div>
<h2 v-bind:class="{active:isActive, line:isLine}">
{{message}}
h2>
<button v-on:click="Click">button>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
active:'active'
isActive:true,
isLine:true,
}
methods:{
Click: function(){
this.isActive=!this.isActive
}
}
})
script>
如上代码,我们可以在button里定义一个点击事件click=”Click“,再在vue里定义方法Click。这样,每当我们按下button,就等于在vue里让isActive的值取反,从而实现message反复拥有、不拥有active类——即颜色变化的效果。
此外,v-bind决定的class不与直接定义的class(固定的)冲突,可以共存。如:
<h2 class="title" v-bind:class="{active:isActive, line:isLine}">
{{message}}
h2>
这里,title类是固定不变的,其余两个active和line类都是可变的。
v-bind和class的数组用法如下(不常用):
<h2 class="title" v-bind:class="['active','line']">
{{message}}
h2>
什么时候会用到动态绑定style?比如说,我们在做网站时,我们会把一个搜索栏做成一个组件。而这个搜索栏再主页、分页里样式不同,所以需要动态绑定。
v-bind和style的对象用法如下:
<div id="app">
<h2 :style="{key(css属性名):value(属性值)}">
{{message}}
h2>
<h2 :style="{fontSize:'50px'}">
{{message}}
h2>
<h2 :style="{fontSize: finalSize}">
{{message}}
h2>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
finalSize: '100px'
}
})
script>
<h2 :style="[baseStyle,baseStyle2]">
{{message}}
h2>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
baseStyle:{backgroundColor:'red'},
baseStyle:{fontSize:'100px'}
}
})
script>
当我们需要对data里的数据进行处理并显示时,需要用到computed(计算属性),它虽然在vue里通过函数实现,但在html里要当作属性来使用(不用加括号)。
计算属性的基本使用:
<h2>
{{getFullName()}}
{{fullName}}
h2>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
firstName:'Lebron',
lastName:'James'
},
methods:{
getFullName(){
return this.firstName+' '+this.lastName
}
},
computed:{<!--按照属性名取函数的名字-->
fullName: function(){
return this.firstName+' '+this.lastName
}
}
})
script>
computed还可以实现一些不用计算属性,只用mustache语法难以实现的功能:
<h2>
{{totalPrice}}
h2>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
books:[
{id:100,name:'unix',price:120},
{id:101,name:'c++',price:110},
{id:102,name:'java',price:100},
]
},
computed:{
totalPrice: function(){
let result=0;
for(let i=0;i<this.books.length;i++)
{
result+=books[i].price
}
return result
}
}
})
script>
每个计算属性都包含一个getter和setter方法。这是计算属性的完整版本(5.1里是简写版本)。一般情况下,我们不用写set方法。
<div id="app">
<h2>
{{fullName}}
h2>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
firstName:'Kobe',
lastName:'Bryant'
},
computed:{
fullName:{
set:function(newValue){
const names = newValue.split(' ');
this.firstName=names[0];
this.lastName=names[1];
},
get:function(){
return this.firstName+' '+this.lastName
}
}
}
})
script>
为什么我们更多使用计算属性而不是methods方法?因为当我们使用(调用)属性时,methods每次调用属性都要调用methods方法,而计算属性只调用一次。所以计算属性性能更好。
<h2>
{{getFullName()}}
{{fullName}}
h2>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
firstName:'Lebron',
lastName:'James'
},
methods:{
getFullName(){
return this.firstName+' '+this.lastName
}
},
computed:{<!--按照属性名取函数的名字-->
fullName: function(){
return this.firstName+' '+this.lastName
}
}
})
script>
变量作用域:变量在什么范围内可用。
ES5之前,由于if和for都没有块级作用域,必须借助function来解决引用外部变量的问题。ES6中加入了let,它拥有if和for块级作用域。
下面是var和let的区别,方便我们理解块级作用域:
const btns = document.getElementsByTagName('button')
for(var i = 0; i < btns.length; i++){
btns[i].addEventListener('click',function(){
console.log("第"+i+"个元素被打印");
})
}
for(let i = 0; i < btns.length; i++){
btns[i].addEventListener('click',function(){
console.log("第"+i+"个元素被打印");
})
}
当我们用var指定i时,点击第一个按键,console会打印“第5个元素被打印”,这是因为在执行console.log语句之前,由于var没有for的块级作用域,var已经被加到了5,再去进入执行console.log语句,而let则不会。let每个循环语句里有独立的i,var的情况下,每当进入下一个循环,由于没有块级作用域,改变i值(i++)会把i赋给之前所有的循环里的i,所以最终所有的i都变成了最后一个i值。当我们点击时,回调console.log,自然得不到正确的结果。
const用以保证数据安全性。在开发中,优先使用const,只在需要改变标识符时才使用let.
需要注意的是:const指向的对象不能修改,但是其内部属性可以。如:
const obj = {
name:'fyk'
}
obj.name = 'james';
这时,obj对象的属性已被改变。
什么是字面量?当我们创建一个对象时,可以new一个对象,也可以用大括号代替。这就是字面量。
const obj = new Object();
const obj = {}
字面量的增强写法是一个十分方便的功能。在ES5及之前的版本里,当我们想要对一个对象的属性赋以一个对象外的值时,我们需要这样操作:
const name= 'fyk';
const height= 1.83
const obj = {
name: name,
height: height
}
ES6里可以采取增强写法:
const obj = {
name,
height
}
除了对属性的增强写法,我们还可以对函数进行增强写法。下面是对比:
const obj = {
run : function(){
}
}
const obj = {
run(){
}
}
前端开发需要经常和用户交互。这时就要监听用户事件,比如点击、拖拽等。在vue中,我们使用v-on指令来监听事件。
<div id="app">
<h2>
{{counter}}
h2>
<button v-on:click="increasement">
+
button>
<button v-on:click="sub">
-
button>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
counter: 0
},
methods: {
increasement(){
this.counter++
},
sub(){
this.counter--
}
}
})
script>
v-on的语法糖:
@click
当事件监听时使用的方法没有参数,那么不用加括号,直接写方法名调用即可。
但是除这种情况外,还有很多情况:如有参数但无括号
<div id="app">
<button v-on:click="Click1">
按钮1
button>
<button v-on:click="Click2(123,$event)">
按钮2
button>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
counter: 0
},
methods: {
Click1(event){
console.log(event);
} ,
Click2(abc,event){
}
})
script>
<div @click="divClick">
<button @click="buttonClick">
按钮
button>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
counter: 0
},
methods: {
divClick(){
},
buttonClick(){
}
})
script>
当上述代码块执行后,若我们点击div块(非按钮区域),则divClick和buttonClick都会被执行。有时,我们在点击一块区域时并不想让其中的子区域的点击事件也被触发,那么我们就可以使用stop修饰符,如:
<div @click="divClick">
<button @click.stop="buttonClick">
按钮
button>
div>
当我们想要监听某个键盘的键的键入时,我们可以用修饰符来帮助我们:
<div>
<input type="text" @keyup.enter="keyUp">
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
counter: 0
},
methods: {
keyUp(){
}
})
script>
上述代码块执行时,当我们按下并松开键盘的键,不会执行keyUp方法,但是当我们按下并松开了回车键,就会执行keyUp方法。在keyup或keydown后加特定的修饰符可以用来监听特定按键的键入。
当我们想要根据一些条件来判断是否显示一些内容时,可以使用v-if.
<div>
<h2 v-if="isShow">
abc
h2>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
isShow: true
}
})
script>
<div>
<h2 v-if="isShow">
abc
h2>
<h1 v-else>
isShow为false时显示此句
h1>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
isShow: true
}
})
script>
<div id="app">
<h2 v-if="score>=90">
优秀
h2>
<h2 v-else-if="score>=80">
良好
h2>
<h2 v-else-if="score>=60">
及格
h2>
<h2 v-else>
不及格
h2>
<h1>
{{result}}
h1>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
score:99
},
computed:{ <!--复杂情况更推荐写在计算属性里-->
result(){
let show = '';
if(this.score>=90){
show='优秀'
}
else if(...)
return show
}
}
})
script>
下面是依靠v-if实现的一个登录切换的小案例:
<div id="app">
<span v-if="isUser">
用户账号
<input type="text" id="username">
span>
<span v-else>
用户邮箱
<input type="text" id="email">
span>
<button @click="change">
切换登录类型
button>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
isUser: true
},
methods:{
change(){
this.isUser=!this.isUser
}
}
})
script>
这里有个小问题:如果我们在有输入内容的情况下,切换了类型,我们会发现此时文本框内依然存在着我们之前输入的内容。但是按理来说,我们应该切换到另一个input元素中了。而在另一个input元素中,我们并没有输入内容,为什么会出现这个问题?
这是因为vue的虚拟dom出于性能考虑,遇到互斥条件时,会复用input。为了解决这个问题,可以在input里加入key,当key值不同时,不会复用:
<div id="app">
<span v-if="isUser">
用户账号
<input type="text" id="username" key="username">
span>
<span v-else>
用户邮箱
<input type="text" id="email" key="email">
span>
<button @click="change">
切换登录类型
button>
div>
v-show和v-if很类似,都能决定元素是否显示。区别在于条件为false时,v-if修饰的元素根本就不会存在dom中,而v-show只是新增一个行内样式display,并把display属性设为none.条件为true时,v-if会再创建一个元素,v-show只用把display属性删去即可。
开发时,当需要频繁切换时,使用v-show更好。反之,v-if常用。
v-for遍历数组:
<div id="app">
<ul>
<li v-for="item in names">{{item}}li>
ul>
<ul>
<li v-for="(item,index) in names">{{index}}.{{item}}li>
ul>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
names:['harden','westbrook','tucker']
},
})
script>
v-for遍历对象:
<div id="app">
<ul>
<li v-for="item in info">{{item}}li>
ul>
<ul>
<li v-for="(value, key) in info">{{key}}.{{value}}li>
ul>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
info:{
name:'kevin',
height:1.83,
weight:63
}
},
})
script>
<div id="app">
<ul>
<li v-for="item in letters" :key="item">{{item}}li>
ul>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
letters:['a','b','c']
},
})
script>
push():从末尾加入数组元素(可一次加入多个元素)
pop():删除数组末尾元素
shift():删除数组第一个元素
unshift():从最前面加入数组元素(可一次加入多个元素)
splice(arg1,arg2,…):删除、插入、替换元素
删除:从第arg1位置开始,删除arg2个参数。如果第二个参数未定义,则默认从arg1开始全部删除。
插入:arg2=0,从arg1位置开始,插入n个元素(第三个参数及以后)
替换:从arg1位置开始,删除arg2个参数,再加入n个元素(第三个参数及以后)
sort():排序
reverse():反转数组
这些方法都是响应式的方法。
要注意的是,直接通过索引值改变数组元素不是响应式的。要改动指定位置的元素,可以使用splice()来实现。也可以使用Vue的set方法。
Vue.set(this.letters,0,'abc')
以下是一个购物车的案例,综合运用了v-if,v-for,v-bind,v-on等知识,同时用到了过滤器。目标是这样的:
<head>
...
<link rel="stylesheet" href="style.css">
head>
<body>
<div id="app">
<div v-if="books.length">
<table>
<thead>
<tr>
<th>书籍名称th>
<th>出版日期th>
<th>价格th>
<th>购买数量th>
<th>操作th>
tr>
thead>
<tbody>
<tr v-for="(item,index) in books">
<td>{{item.id}}td>
<td>{{item.name}}td>
<td>{{item.date}}td>
<td>{{item.price|showPrice}}td>
<td>
<button @click="increasment(index)">
+
button>
{{item.quantity}}
<button @click="sub(index)" v-bind:disabled="item.count<=1">
-
button>
td>
<td>
<button @click="remove(index)">
移除
button>
td>
tr>
tbody>
table>
<h2>
{{totalPrice|showPrice}}
h2>
div>
<h2 v-else>
购物车为空
h2>
div>
<script src="vue.js">script>
<script src="main.js">script>
body>
const app = new Vue({
el:'#app',
data:{
books:[
{
id:1,
name:'algorithm introduction',
date:'2006-9',
price:85.00,
count:1
},
{
id:2,
name:'unix',
date:'2006-2',
price:59.00,
count:1
}
]
}
methods:{
increasment(index){
this.books[index].count++
},
sub(index){
this.books[index].count--
},
remove(index){
this.books.splice(index,1)
}
}
computed:{
totalPrice(){<!--利用计算属性计算总价格-->
let totalPrice=0;
for(let i=0;i<this.books.length;i++){
totalPrice+=this.books.[i].price*this.books[i].quantity;
}
return totalPrice
}
}
filters:{
showPrice(price){
return '$'+price.toFixed(2);<!--保留两位小数,使用过滤器,可以自动传参,比methods方便-->
}
}
})
table{
border:1px solid #e9e9e9;
border-collapse: collapse;
border-spacing :0;
}
th, td{
padding: 8px 16px;
border:1px solid #e9e9e9;
text-align: left
}
th{
background-color:;
color:;
font-weight:;
}
filter需要回调函数,它要求必须返回一个布尔值。当返回的是true,函数内部会自动把这次回调的n(数组里的每一个值或对象)加入到一个新的数组里。当返回的是false,函数内部不会把n加入新数组(过滤)。如果我们想把一个数组里小于100的值提取出来,可以利用filter这样写,比写for循环方便许多:
const nums = [10,20,50,110,220]
let newNums = nums.filter(function(n)({
return n < 100
}))
如果我们想对数组里的每一个元素进行操作,可以使用map函数。map函数也需要一个回调函数,与filter类似,它会把每一个回调的n按照我们的指令进行操作并加入到新的数组里。如果我们想要把上一个例子里的数组的元素值都乘以两倍,那么可以利用map这样写,比for循环方便许多:
let newNums2 = newNums.map(function(n){
return n * 2
})
reduce函数的作用是对数组中的所有内容进行汇总。
a.reduce(arg1,arg2)
a.reduce(function(preValue,n){
return 1
},0)
let total = newNums2.reduce(function(preValue,n){
return preValue+n
},0)
let total = this.books.reduce(function(preValue,book){
return preValue+book.price*book.quantity
},0)
这几个函数,就体现了函数式编程的思想。
v-model用以实现表单元素和数据的双向绑定。如下的代码运行后,我们可以在网页中看到,虽然我们没有输入内容,但此时表单里已经有默认的值”hello“,当我们改动data里的message,表单里的内容也会随着改变。同时,当我们改动表单里的内容(输入我们想输入的内容),data里的message也会随之改变。这就是双向绑定,而不是v-bind那种单向绑定——单向绑定的情况下,只有改动data里的数据才会让网页元素改变,改变网页元素无法改变data里的数据。
<div id="app">
<input type="text" v-model="message">
{{message}}
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
}
})
script>
v-model其实就是这两个语法的结合:1.v-bind绑定一个value属性。2.v-on指令给当前元素绑定input事件
<div id="app">
<input type="text" :value="message" @input="valueChange">
{{message}}
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
},
methods:{
valueChange(event){
this.message = event.target.value
}
}
})
script>
当我们在radio里想要让选项互斥,就要把input的name属性设置为相同的。而如果我们只是单纯地设两个相同的name,我们就无法获取其值(后端要用),所以就需要再在input标签里加入value属性。v-model就可以把name和value放到一起,比较便捷。
<div id="app">
<label for="male">
<input type="radio" id="male" v-model="sex">男
label>
<label for="female">
<input type="radio" id="female" v-model="sex">女
label>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
sex:'男'<!--默认选择是男-->
}
})
script>
checkbox分为单选框和多选框。我们分别举例:
单选框,比如说同意协议,且同意了才能进行下一步:(v-model具备了获得input的value属性的能力,理解时要注意这个)
<div id="app">
<label for="agree">
<input type="checkbox" id="agree" v-model="isAgree">
label>
<button :disabled="!isAgree">
next step
button>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
isAgree:false//单选框对应布尔类型
}
})
script>
多选框:
<div id="app">
<input type="checkbox" value="basketball" v-model="hobbies">basketball
<input type="checkbox" value="football" v-model="hobbies">football
<input type="checkbox" value="badminton" v-model="hobbies">badminton
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
hobbies:[]//多选框对应数组类型
}
})
script>
select下拉选择框也分为单选和多选。我们分别举例:
单选:
<div id="app">
<select v-model="fruit">
<option value="apple">appleoption>
<option value="orange">orangeoption>
<option value="banana">bananaoption>
select>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
fruit:'apple'
}
})
script>
多选:
<div id="app">
<select v-model="fruit" mutiple>
<option value="apple">appleoption>
<option value="orange">orangeoption>
<option value="banana">bananaoption>
select>
div>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
fruit:[]
}
})
script>
默认情况下,v-model是在input事件中同步输入框的数据的。一旦有数据改变,data里的数据就会自动随之改变。但有时,我们不想让data里的数据这么快改变,那么就可以添加lazy修饰符,从而让数据在失去焦点(点击其他地方)或回车时才会更新。
<div id="app">
<input type="text" v-model.lazy="message">
div>
当我们往input里输入数字,Vue会自动把这些数字当成是字符串。如果想要以数字的格式存储它,那就要添加number修饰符。
<div id="app">
<input type="text" v-model.number="age">
div>
当我们往input里输入内容时,有时会输入一些空格。当我们想要删去其中的空格时,就要添加trim修饰符。
<div id="app">
<input type="text" v-model.trim="message">
div>
如果我们把一个页面里所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,且不利于后期的管理、扩展。但如果我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
组件使用分成三个步骤:
1.创建组件构造器 Vue.extend()
2.注册组件 Vue.component()
3.使用组件
首先创建组件构造器对象(注意这里template用的是tab键上方的引号来包含字符串):
<script src="../vue.js">script>
<script>
const cpnConstructor = Vue.extend({
template:`
`
})
script>
然后注册组件:
Vue.component('my_cpn',cpnConstructor)
注册完成之后,我们就可以这样使用组件:
<div id="app">
<my_cpn>my_cpn>
div>
像12.2里的组件注册完成后就是全局组件。局部组件定义如下,它只能在对应的实例下(下例中的app)起作用:
<script src="../vue.js">script>
<script>
const app = new Vue({
el:'#app',
components:{
cpn:cpnConstructor<!--组件名:构造器名-->
}
})
script>
在开发里,局部组件比较常用。
我们可以把一个组件b放到另一个组件a的构造器里去,这样组件a在构造时同时会构造组件b。而如果要使用这个组件,只需要注册一个组件a即可,因为组件b已被包含在其中。
但是要注意的是,如果这里我们在id为app的div下使用,网页会报错并不予显示。这是因为,一个组件要想被使用,要么是一个全局组件(在全局被注册),要么在局部的Vue里被注册。父组件里的子组件无法直接使用,要想使用可以在局部Vue的components里再加入子组件的注册。
<div id="app">
<cpn2>cpn2>
div>
<script src="../vue.js">script>
<script>
const cpnC1 = Vue.extends({
template:`
headline
`
})
const cpnC2 = Vue.extend({
template:`
headline
`,
components:{
cpn1:cpnC1
}
})
const app = new Vue({
el:'#app',
components:{
cpn2:cpnC2
}
})
script>
注册组件的过程可能有些繁琐,为了简化这个过程,Vue提供了注册的语法糖,省去了Vue.extend()的步骤,直接用一个对象来代替。
<script src="../vue.js">script>
<script>
const cpnC1 = Vue.extend({
template:`
headline
`
})
Vue.components('cpn1',cpnC1)<!--普通写法-->
Vue.components('cpn1',{
template:`
headline
`
})<!--全局组件语法糖,省去extend语句,把template直接写到第二个参数的位置,即直接把构造器的内容写到构造器的位置处-->
const app = new Vue({
el:'#app',
components:{
cpn2: {
template:`
headline
`
}
}
})<!--局部组件的语法糖-->
script>
每次在Vue里写模板很麻烦,所以可以把模板抽离出去写,再在全局组件处注册即可,不过记得挂载template
<script type="text/x-template" id="cpn">
<div>
...
</div>
script>
<script src="../vue.js">script>
<script>
Vue.component('cpn',{
template:'#cpn'
})
script>
<template id="cpn">
<div>
...
div>
template>
<script src="../vue.js">script>
<script>
Vue.component('cpn',{
template:'#cpn'
})
script>
组件是一个单独功能模块的封装,这个模块有它自己的html模板,也有自己的data属性,组件中是不能直接访问Vue实例中(如挂载app的Vue)的data的。
其实,组件对象也有一个data属性,(也有methods等属性)只是它的data属性是个函数,而且这个函数返回一个对象,对象的内部保存着我们组件里要用到的数据。
<template id="cpn">
<div>
{{message}}
div>
template>
<script src="../vue.js">script>
<script>
Vue.component('cpn',{
template:'#cpn',
data(){
return {
message:'hello'
}
}
})
script>
为什么组件里的data必须是个函数呢?因为如果data是一个对象,那么每次调用组件,这些组件实例的数据就会相互影响,因为事实上这些实例的data对象都是同一个。改动一个组件实例里的数据会让其他组件实例里的数据跟着改变。而如果我们使用的是函数,函数每次返回一个对象,实质上是在每次调用这个data函数的时候,data函数都会创建一个新的对象并返回,所以实例之间相互不会影响。
子组件不能引用父组件或者Vue实例的数据。但是在开发中,往往一些数据确实需要从上层传递到下层。比如说在一个页面中,我们从服务器请求到了很多数据,其中一部分书不是由大组件来展示,而是由小组件展示(比如淘宝的商品列表等),这时,不会让子组件再次发送请求,而是让大组件(父组件)把数据传递给小组件(子组件)。
父子组件间的通信方式有两种:一是父组件通过props向子组件传递,二是子组件通过(自定义)事件向父组件发送消息。
下面是父组件向子组件通过props传递数据的一个例子:
<div id="app">
<cpn :childrenmovies="movies">cpn>
div>
<template id="cpn">
<div>
{{childrenmovies}}
div>
template>
<script src="../vue.js">script>
<script>
Vue.component('cpn',{
template:'#cpn',
props:['childrenmovies']<!--2.再在子组件里加入props属性,变量名字自定义,这里设置为字符串数组,方便传入多个父组件的data-->
data(){
return {
message:'hello'
}
}
})
const app = new Vue({
el:'#app',
data:{
movies:['海贼王','火影忍者']
}<!--1.先在父组件里写好要传送的数据,注意Vue实例可以算是所有组件的父组件(root)-->
})
script>
props写成字符串形式可能有些复杂,我们还有更清晰的对象写法:
<script>
Vue.component('cpn',{
template:'#cpn',
props:['childrenmessages']<!--第一种写法,字符串形式-->
props:{
childrenmessages:{
type:String,
default:'aaa',
required:true<!--在使用子组件时必传的值,不传会报错-->
}
childrenmovies:{
type:Array,
default(){
return []
}
}<!--当传递的类型时数组或者对象时,我们的默认值不能直接写成数组或是对象,而是要写一个函数,返回对应的类型-->
}<!--第二种形式,对象形式,在对象里我们还能定义传入数据的类型和默认值,比较常用-->
data(){
return {
message:'hello'
}
}
})
script>
要注意的是,我们在写子组件的props的属性时,不能直接用驼峰标识的形式写,而是要转换成”-“。比如:childMessage要写成child-message.
<div id="app">
<cpn>cpn>
div>
<template id="cpn">
<div>
<button @click="btnClick">
button1
button>
div>
template>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
},
components:{
cpn:{<!--子组件-->
template:'#cpn',
methods:{
btnClick(){
console.log(this.$parent);
console.log(this.$root);<!--直接访问根组件-->
}
}
}
}
})
script>
为每一次组件的使用定义更多的拓展性(每一次使用组件都在组件template的模板上拥有自己独特的地方)。
<div id="app">
<cpn><button>按钮button>cpn>
<cpn>cpn>
div>
<template id="cpn">
<div>
<h2>
h2>
div>
<slot>slot>
<slot><span>默认值span>slot>
template>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
},
components:{
cpn:{
template:'#cpn'
}
}
})
script>
比如,我们在写网页的导航栏时,通常把导航栏封装成为一个组件。但是,每个网页的导航栏又有所不同。这时,我们只需要把这个导航栏组件分成几个插槽即可。如下图:
总的来说,就是把共性的东西写入组件,不同的东西所在的位置,就预留为插槽。
<div id="app">
<cpn><span slot="left">左span>cpn>
<cpn><button slot="middle">中button>cpn>
div>
<template id="cpn">
<slot name="left">slot>
<slot name="center">slot>
<slot name="right">slot>
template>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
},
components:{
cpn:{
template:'#cpn'
}
}
})
script>
<div id="app">
<cpn v-show="isShow">cpn>
div>
<template id="cpn">
<p>
abc
p>
<span v-show="isShow">defspan>
template>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
isShow:true<!--父级作用域-->
},
components:{
cpn:{
template:'#cpn',
data(){
return{
isShow:false
}
}
}
}
})
script>
只能在自己的作用域里查找变量名。
一句话总结作用域插槽:父组件替换插槽标签,内容由子组件决定。
我们可以看这个例子:子组件里有一个Language属性,子组件中默认以列表形式展示这个数组。但是当我们想要在父组件(大的div里)换一种方式展示数组时,我们不能直接用另一种方式替换slot,因为父组件无法直接使用子组件数据。所以要把子组件的数据绑定一个属性A(下面这个属性命名为data),然后再在父组件里用slot-scope这个属性,写{{slot.A}}就可以使用到子组件的数据。
<div id="app">
<cpn>
<template slot-scope="slot">
<span>{{slot.data.join('*')}}span>
template>
cpn>
div>
<template id="cpn">
<div>
<slot :data="Languages">
<ul>
<li v-for="item in Languages">{{item}}li>
ul>
slot>
div>
template>
<script src="...">script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
},
components:{
cpn:{
template:'#cpn',
data(){
return{
Languages:['js','c++','python']
}
}
}
}
})
script>
编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。
而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数和变量。这个方式也称为模块模式(module pattern):
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter
的变量和名为 changeBy
的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。
这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域,它们都可以访问 privateCounter
变量和 changeBy
函数。
我们可以把它看成类似于Java的语法。Counter是一个类,而三个公共函数是它的private方法,privateCounter是它的private属性。
由上面一小节可知,我们可以使用模块化来避免变量的冲突,具体操作如下:
var moduleA = (function(){
var age = 20
var flag = true
function sum(num1,num2){
return num1 + num2
}
var obj = {}//1.创建一个对象
obj.age=age//2.把这个模块里的属性和方法(函数)都赋值给这个对象
obj.flag=flag
obj.sum=sum
return obj//3.将这个对象返回,赋值给一个var变量moduleA,此时moduleA就是obj的一个引用,属性完全相同
})()
console.log(moduleA.age)//20
console.log(moduleA.sum(10,20))//30
幸运的是,前端模块化开发已经有了许多的规范以及对应的实现方案。比如commonJS、AMD、CMD、ES6的Modules
ES6中,最简单的导入导出这样实现:
假设同一个html文件有多个js文件需要引入:
html文件:
<script src="a.js" type="module">script>
<script src="b.js" type="module">script>
js文件:
//a.js
var flag = true
function sum(num1,num2){
return num1 + num2
}
//第一种导出方式
export{ //export用以导出
flag,sum
}
//第二种导出方式,定义时直接导出
export var name = 'james';
export function mul(num1,num2){
return num1 * num2
}
export class Person(){
run(){
...
}
}
//b.js
import {flag} from "./a.js" //import用以导入
import {Person} from "./a.js"
import * as aa from "./a.js"//当需要导入的东西很多时,可以使用*,并为它取个名字(aa),这里其实实质上是把这些属性统合成了一个对象,aa就是它的名字,当需要用到属性的时候,用aa.+属性名就可以调用了
console.log(aa.name)
if(flag){
...
}
const p = new Person()
p.run()
Webpack简单来说,就是把我们项目里一些浏览器无法识别的文件(比如commonJS)打包,转换成浏览器可以识别的文件。
我们项目的文件夹下有两个子文件夹:src(源码)和dist(发布),我们把使用模块化开发的js文件写好后,不能直接发布,因为浏览器无法解析我们的模块化(比如commonJS)。
所以,要通过
webpack ./src/main.js ./dist/bundle.js
这条命令,把src下的js文件打包成为dist下的bundle.js文件,然后再在html文件里引入bundle.js,即可实现模块化开发。
要注意的是,webpack会自动识别js文件之间的依赖,所以我们不必把每一个js文件都打包。(比如上面的这行命令,main.js里引入了a.js的导出,但我们不必在命令里写上a.js)。
在创建完src、dist、index.html后,再要写一个webpack.config.js文件,最后通过npm init 命令创建package.json
webpack.config.js文件内容如下:
const path = require('path')//commonJS的语法
module.exports={
entry:'./src/main.js'
output:{
path:path.resolve(__dirname,'dist')//dirname是node里的一个全局变量
filename:'bundle.js'
}
}
这样,我们就不用每次都在命令里指定输出的文件,只要用webpack这一个简单的命令或者”npm run build“(项目中更常用,在package.json里的script里加一个build:webpack)就可以实现打包。
如果要开发大型项目,那么必须要用到Vue CLI
使用脚手架,要先安装node、npm、webpack
创建项目:vue create 项目名称
目录结构:
node modules:存放node的一些包(通过npm安装的)
public:类似于VueCLI2里的static,最后会原封不动地放入dist文件夹
src:源代码
跑项目:npm run serve
前端路由中,整个项目只有一个index.html,通过router来决定选择哪些组件渲染,就不用写多个html文件,直接在index后加上/…即可。
前端路由的核心就是:改变url,但是页面不进行整体的刷新。
用以访问设定路径,将路径和组件映射起来。在vue-router的单页面应用中,页面路径的改变就是组件的切换。
安装vue-router: npm install vue-router --save
我们在脚手架中可以勾选router,这样在创建完项目后自动会生成一个router文件夹(里面有一个index.js)
为了后续方便自己加入组件,我们要自己学会配置router(在index.js里):
import VueRouter from 'vue-router'
import Vue from 'vue'
//配置路由相关信息
Vue.use(VueRouter)//1.通过Vue.use(插件)来安装插件,这里的VueRouter是一个插件
//2.创建VueRouter对象
const routes=[
{
},
]
const router = new VueRouter({
routes
})//配置路由和组件之间的关系
首先要知道,写路由对应的组件,都是通过.vue文件来写的:
import VueRouter from 'vue-router'
import Vue from 'vue'
import Home from '../components/Home'
import Home from '../components/About'
Vue.use(VueRouter)//1.通过Vue.use(插件)来安装插件,这里的VueRouter是一个插件
const routes=[
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
}
]//2.建立路径和组件联系
const router = new VueRouter({
routes
})//3.创建router实例
自己创建.vue文件(模板参见Helloworld.vue),比如这个Home.vue:
homepage
这样,我们就把router和对应的组件联系起来了。那么怎么跳转到对应的url,从而显示不同的组件呢?我们需要在App.vue里写上以下内容:
首页
关于
总结来说,使用vue-router的步骤有三步:
第一步:创建路由组件(.vue)
第二步:配置路由映射(组件和路径)(index.js)
第三步:使用路由:通过和
默认情况下,我们刚进入一个网站时,总是希望显示网站首页,但是按照之前的写法,还得再点击一个a标签才行,默认没有显示首页组件。所以,我们需要重定向:
import VueRouter from 'vue-router'
import Vue from 'vue'
import Home from '../components/Home'
import About from '../components/About'
Vue.use(VueRouter)
const routes=[
{
path:'/',//path配置的是根路径: /
redirect:'/home'
},//redirect就是指:当进入这个网页时,默认重定向至某某组件
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
}
]
const router = new VueRouter({
routes
})
前端需要发送网络请求到服务器请求后端数据,这时就需要用到axios框架。
首先,在vue项目里安装axios:npm install axios --save
然后,在main.js中,引入axios:
import axios from 'axios'
new Vue({...})
引入axios后,就可以直接引用它。只要往axios里传入相关的网络配置即可,而config一般是对象类型:
axios(config)
axios({
url:'http://39.97.183.73:8000/home/data'//默认get方式,也可以用methods来换其他方式(下图)
}).then(res => {
console.log(res)
})//单一参数函数的简便写法,相当于function(res){console.log(res)}
axios.request(config)
axios.get(url[,config])
axios.delete(url[,config])
axios.head(url[,config])
axios.post(url[,data[,config]])
使用axios.all,可以放入多个请求的数组。axios.all([])返回的是一个数组,使用axios.spread可以把[res1,res2]展开为res1,res2
axios.all([axios({
url:''
}),axios({
url:''
})]).then(results => {
console.log(results[0]);
console.log(results[1])
})//第一种写法
axios.all([axios({
url:''
}),axios({
url:''
})]).then(axios.spread((res1,res2) =>{
console.log(res1);
console.log(res2)
}))//第一种写法
在工作中,一个项目的不同页面往往被放在不同的服务器上。所以,使用全局的axios会有所不便。这时就需要用到axios的实例:
const instance1 = axios.create({
baseURL:'',
timeout:5000
})
const instance2 = axios.create({
baseURL:'',
timeout:1000
})//不同的实例可以设置独立的配置
instance1({
url:'/home'
}).then(res=>{
console.log(res)
})
instance({
url:''
}).then(res=>{
console.log(res)
})
当我们的组件(.vue)文件多起来后,我们最好不要在每个.vue文件里都import axios,这样对第三方框架的依赖太大了——当这个框架不维护后,我们要一个个文件改动,很麻烦。
为了解决这个可能出现的问题,我们不妨在项目的src文件夹里新创建一个network文件夹,并在里面新建一个request.js,用来当作框架和我们项目的一个“中介”,内容如下:
import axios from 'axios'
export function request(config){
const instance = axios.create({//1.创建axios实例
baseURL:'',
timeout:1000
})
//2.发送网络请求
return instance(config)
}
在.vue文件里这样写:
import {request} from './network/request'
request({
url:'/home',
}).then(res=>{
...
})//把res返回到要处理的地方
用于每次发送请求或得到响应后,做出相应处理。
instance.interceptors.request.use(config=>{//请求拦截
console.log()
return config//必须返回,不然就彻底拦下来了传不下去了,我们只是为了处理
},err=>{
})
instance.interceptors.reponse.use(config=>{//响应拦截
console.log()
},err=>{
})
new Vue({...})
引入axios后,就可以直接引用它。只要往axios里传入相关的网络配置即可,而config一般是对象类型:
axios(config)
axios({
url:'http://39.97.183.73:8000/home/data'//默认get方式,也可以用methods来换其他方式(下图)
}).then(res => {
console.log(res)
})//单一参数函数的简便写法,相当于function(res){console.log(res)}
axios.request(config)
axios.get(url[,config])
axios.delete(url[,config])
axios.head(url[,config])
axios.post(url[,data[,config]])