Vue框架
Vue介绍
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
Vue官网https://cn.vuejs.org
在使用时我们可以下载vue.js,并在页面中引用
<script type="text/javascript" src="vue.js"></script>
声明式渲染
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统
<div id="app"> {{ message }} </div> <script> var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } }) </script>
当我们引入了Vue.js后,此时window下就挂载了一个全局的Vue函数,我们使用他来new一个对象,此对象包含一个参数,这个参数是json对象,期中包括el(找到对应的标签对象),data(数据属性)
此时我们就可以在标签中利用{{}}来渲染数据了
我们已经成功创建了第一个 Vue 应用!看起来这跟渲染一个字符串模板非常类似,但是 Vue 在背后做了大量工作。现在数据和 DOM 已经被建立了关联,所有东西都是响应式的。我们要怎么确认呢?打开你的浏览器的 JavaScript 控制台 (就在这个页面打开),并修改app.message
的值,你将看到上例相应地更新
除了文本插值,我们还可以像这样来绑定元素特性:
<div id="app-2"> <span v-bind:title="message"> 鼠标悬停几秒钟查看此处动态绑定的提示信息! </span> </div> var app2 = new Vue({ el: '#app-2', data: { message: '页面加载于 ' + new Date().toLocaleString() } })
这里我们遇到了一点新东西。你看到的 v-bind
特性被称为指令。指令带有前缀 v-
,以表示它们是 Vue 提供的特殊特性。可能你已经猜到了,它们会在渲染的 DOM 上应用特殊的响应式行为。在这里,该指令的意思是:“将这个元素节点的 title
特性和 Vue 实例的message
属性保持一致”。
如果你再次打开浏览器的 JavaScript 控制台,输入 app2.message = '新消息'
,就会再一次看到这个绑定了 title
特性的 HTML 已经进行了更新。
这里的v-bind等同于一个:号
条件渲染
v-if
在 Vue 中,我们使用 v-if
指令实现判断的功能
<h1 v-if="ok">Yes</h1>
也可以用 v-else
添加一个“else 块”:
<h1 v-if="ok">Yes</h1> <h1 v-else>No</h1>
其中ok的值我们可以在data数据属性中定义
v-else
元素必须紧跟在带 v-if
或者 v-else-if
的元素的后面,否则它将不会被识别
<div v-if="Math.random() > 0.5"> Now you see me </div> <div v-else> Now you don't </div>
v-else-if
,顾名思义,充当 v-if
的“else-if 块”,可以连续使用:
<div v-if="type === 'A'"> A </div> <div v-else-if="type === 'B'"> B </div> <div v-else-if="type === 'C'"> C </div> <div v-else> Not A/B/C </div>
类似于 v-else
,v-else-if
也必须紧跟在带 v-if
或者 v-else-if
的元素之后
v-show
另一个用于根据条件展示元素的选项是 v-show
指令。用法和v-if大致一样:
<h1 v-show="ok">Hello!</h1>
不同的是带有 v-show
的元素始终会被渲染并保留在 DOM 中。v-show
只是简单地切换元素的 CSS 属性 display
注意,v-show
不支持 <template>
元素,也不支持 v-else
v-if和v-show的区别
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
列表渲染
用 v-for
把一个数组对应为一组元素
我们用 v-for
指令根据一组数组的选项列表进行渲染。v-for
指令需要使用item in items
形式的特殊语法,items
是源数据数组并且 item
是数组元素迭代的别名
<ul id="example-1"> <li v-for="item in items"> {{ item.message }} </li> </ul> var example1 = new Vue({ el: '#example-1', data: { items: [ { message: 'Foo' }, { message: 'Bar' } ] } })
结果:
在 v-for
块中,我们拥有对父作用域属性的完全访问权限。v-for
还支持一个可选的第二个参数为当前项的索引。
<ul id="example-2"> <li v-for="(item, index) in items"> {{ parentMessage }} - {{ index }} - {{ item.message }} </li> </ul> var example2 = new Vue({ el: '#example-2', data: { parentMessage: 'Parent', items: [ { message: 'Foo' }, { message: 'Bar' } ] } })
结果:
你也可以用 of
替代 in
作为分隔符,因为它是最接近 JavaScript 迭代器的语法
一个对象的v-for
你也可以用 v-for
通过一个对象的属性来迭代
<ul id="v-for-object" class="demo"> <li v-for="value in object"> {{ value }} </li> </ul> new Vue({ el: '#v-for-object', data: { object: { firstName: 'John', lastName: 'Doe', age: 30 } } })
结果:
你也可以提供第二个的参数为键名:
<div v-for="(value, key) in object"> {{ key }}: {{ value }} </div>
第三个参数为索引:
<div v-for="(value, key, index) in object"> {{ index }}. {{ key }}: {{ value }} </div>
一段取值范围的 v-for
v-for
也可以取整数。在这种情况下,它将重复多次模板
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
结果:
v-for
on a <template>
类似于 v-if
,你也可以利用带有 v-for
的 <template>
渲染多个元素。比如
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider"></li>
</template>
</ul>
v-for
with v-if
当它们处于同一节点,v-for
的优先级比 v-if
更高,这意味着 v-if
将分别重复运行于每个 v-for
循环中。当你想为仅有的一些项渲染节点时,这种优先级的机制会十分有用,如下
<li v-for="todo in todos" v-if="!todo.isComplete"> {{ todo }} </li>
事件处理
监听事件
可以用 v-on
(等同于@)指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码
<div id="example-1"> <button v-on:click="counter += 1">Add 1</button> <p>The button above has been clicked {{ counter }} times.</p> </div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})
事件处理方法
然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on
指令中是不可行的。因此 v-on
还可以接收一个需要调用的方法名称
<div id="example-2"> <!-- `greet` 是在下面定义的方法名 --> <button v-on:click="greet">Greet</button> </div> var example2 = new Vue({ el: '#example-2', data: { name: 'Vue.js' }, // 在 `methods` 对象中定义方法 methods: { greet: function (event) { // `this` 在方法里指向当前 Vue 实例 alert('Hello ' + this.name + '!') // `event` 是原生 DOM 事件 if (event) { alert(event.target.tagName) } } } }) // 也可以用 JavaScript 直接调用方法 example2.greet() // => 'Hello Vue.js!'
方法写在methods中,绑定时如果方法没有参数则可以不加括号
事件修饰符
在事件处理程序中调用 event.preventDefault()
或 event.stopPropagation()
是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题,Vue.js 为 v-on
提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的
<!-- 阻止单击事件继续传播 --> <a v-on:click.stop="doThis"></a> <!-- 提交事件不再重载页面 --> <form v-on:submit.prevent="onSubmit"></form> <!-- 修饰符可以串联 --> <a v-on:click.stop.prevent="doThat"></a> <!-- 只有修饰符 --> <form v-on:submit.prevent></form> <!-- 添加事件监听器时使用事件捕获模式 --> <!-- 即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 --> <div v-on:click.capture="doThis">...</div> <!-- 只当在 event.target 是当前元素自身时触发处理函数 --> <!-- 即事件不是从内部元素触发的 --> <div v-on:click.self="doThat">...</div>
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用v-on:click.prevent.self
会阻止所有的点击,而 v-on:click.self.prevent
只会阻止对元素自身的点击
按键修饰符
在监听键盘事件时,我们经常需要检查常见的键值。Vue 允许为 v-on
在监听键盘事件时添加按键修饰符:
<!-- 只有在 `keyCode` 是 13 时调用 `vm.submit()` --> <input v-on:keyup.13="submit">
记住所有的 keyCode
比较困难,所以 Vue 为最常用的按键提供了别名:
<!-- 同上 --> <input v-on:keyup.enter="submit"> <!-- 缩写语法 --> <input @keyup.enter="submit">
系统修饰键
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
注意:在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。在其他特定键盘上,尤其在 MIT 和 Lisp 机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”
<!-- Alt + C --> <input @keyup.alt.67="clear"> <!-- Ctrl + Click --> <div @click.ctrl="doSomething">Do something</div>
鼠标按钮修饰符
这些修饰符会限制处理函数仅响应特定的鼠标按钮
一些例子
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> <style> .box{ width: 100px; height: 100px; background-color: red; } .box2{ width: 100px; height: 100px; background-color: green; } .box3{ width: 100px; height: 100px; background-color: yellow; } .box4{ background-color: blue; } </style> </head> <body> <div id="app"> <!-- 声明式的指令 --> <!--{{}} 插值语法 react{} angular{{}}--> <h2>{{msg}}</h2> <div class="box" v-if="isShow">我的第一个盒子</div> <button v-on:click="showHandler()">{{text}}</button> <div class="box2" v-show="show">我的第二个盒子</div> <button v-on:click="showHandler2()">隐藏</button> <div class="box3" v-bind:class="{box4:isBox4}"></div> <input type="button" value="切换" v-on:click="box4Handler()"> <a :href="url">百度一下</a> <a href="javascript:alert(1)">click</a> <a href="javascript:;">click</a> <!--阻止a标签的默认行为--> <a href="" @click="anchorHandler($event)">a1</a> <a href="#" @click.prevent="">a2</a> <!-- v-bind: === : , v-on: === @ --> <ul> <li v-for="item in lists">{{item}}</li> </ul> <ul> <li v-for="(item,index) in lists">{{index}}</li> </ul> <ul> <li v-for="(item,index) in lists"> <a :href="index">{{item}}</a> </li> </ul> </div> <script type="text/javascript" src="vue.js"></script> <script> var app = new Vue({ el:"#app", data:{ msg:"学习Vue", isShow:true, text:"隐藏", show:false, isBox4:false, url:"http://www.baidu.com", lists:["红烧肉","红烧茄子","红烧鱼","江小白"] }, methods:{ showHandler(){ if(this.isShow){ this.isShow = false; this.text = "显示"; }else{ this.isShow = true; this.text = "隐藏"; } }, box4Handler(){ this.isBox4 = !this.isBox4 }, anchorHandler(e){ e.preventDefault() // 阻止标签的默认行为 } } }); console.log(app); // Vue {_uid: 0, _isVue: true, $options: Object, _renderProxy: Proxy, _self: Vue…} console.log(app.msg); // 学习Vue console.log(app.$data.msg) // 学习Vue </script> </body> </html>
页面上的音乐播放
在h5中有一个用于播放音乐的标签audio
我们先在目录中导入我们提前准备好的音乐文件
然后写页面
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> <style> *{ padding: 0; margin: 0; } ul{list-style: none} li{ border-bottom: 1px solid red; } </style> </head> <body> <div id="music"> <audio :src="currentSong" autoplay controls autoloop @ended="nextSong()"></audio> <ul> <li v-for="(item,i) in songs" @click="currentHandler(item,i)"> <h3>作者:{{item.author}}</h3> <p>{{item.name}}</p> </li> </ul> <button>上一首</button> <button @click="nextSong()">下一首</button> </div> <script type="text/javascript" src="vue.js"></script> <script> var songs = [ {id:1,src:'./audios/1.mp3',name:"la la Land",author:'Ryan'}, {id:2,src:'./audios/2.mp3',name:"The Best of",author:'Skillof'}, {id:3,src:'./audios/3.mp3',name:"It My Life",author:'Bon'}, {id:4,src:'./audios/4.mp3',name:"Tender",author:'Blur'} ]; // MVVM Model==>数组 数据库 View==>document 标签 VM // 数据的双向绑定 m<=>v var music = new Vue({ el:"#music", data:{ // 数据属性 songs:songs, currentSong:"./audios/1.mp3", index:0 }, methods:{ currentHandler(item,i){ this.index = i; // this.currentSong = item.src; this.currentSong = this.songs[i].src }, nextSong(){ this.index++; this.currentSong = this.songs[this.index].src; // 判断数组越界,实现上一首 } } }) </script> </body> </html>
可以看到具体播放哪一首歌取决于audio标签的src属性
我们给每一个li标签绑定一个点击事件,点击时就将目前的index切换为该歌曲的index,再通过index取到songs数组中对应歌曲的src,赋值给currentSong,这样就完成了通过点击切换歌曲
下一首按钮同样绑定了点击事件,只要点击就将index加1,不过这里要考虑数组越界问题,需要添加判断
audio标签的autoplay属性表示自动播放,controls则表示控制台,可以有调节音量等按钮,autoloop表示自动循环,当一首歌播放完毕后,会触发@ended,这里我们给他也绑定了下一首的功能
最后效果:
计算属性
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:
<div id="example"> {{ message.split('').reverse().join('') }} </div>
在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message
的翻转字符串。当你想要在模板中多次引用此处的翻转字符串时,就会更加难以处理。
所以,对于任何复杂逻辑,你都应当使用计算属性
例子:
<div id="example"> <p>Original message: "{{ message }}"</p> <p>Computed reversed message: "{{ reversedMessage }}"</p> </div> var vm = new Vue({ el: '#example', data: { message: 'Hello' }, computed: { // 计算属性的 getter reversedMessage: function () { // `this` 指向 vm 实例 return this.message.split('').reverse().join('') } } })
结果:
Original message: "Hello"
Computed reversed message: "olleH"
这里我们声明了一个计算属性 reversedMessage
。我们提供的函数将用作属性vm.reversedMessage
的 getter 函数:
console.log(vm.reversedMessage) // => 'olleH' vm.message = 'Goodbye' console.log(vm.reversedMessage) // => 'eybdooG'
你可以打开浏览器的控制台,自行修改例子中的 vm。vm.reversedMessage
的值始终取决于 vm.message
的值。
你可以像绑定普通属性一样在模板中绑定计算属性。Vue 知道 vm.reversedMessage
依赖于vm.message
,因此当 vm.message
发生改变时,所有依赖 vm.reversedMessage
的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解
计算属性的 setter
计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter :
computed: { fullName: { // getter get: function () { return this.firstName + ' ' + this.lastName }, // setter set: function (newValue) { var names = newValue.split(' ') this.firstName = names[0] this.lastName = names[names.length - 1] } } }
现在再运行 vm.fullName = 'John Doe'
时,setter 会被调用,vm.firstName
和vm.lastName
也会相应地被更新
例子:
<div id="computed"> <h2>{{message}}</h2> <h4>{{getMessage}}</h4> <button @click="clickHandler()"></button> </div> <script type="text/javascript" src="vue.js"></script> <script> var com = new Vue({ el:"#computed", data:{ message:"luffycity" }, methods:{ clickHandler(){ // this.message = "youngman"; // set this.getMessage = "youngman"; } }, computed:{ // 计算属性默认只有getter 作用:实时监听我们的数据变化 // getMessage(){ // return this.message // } getMessage:{ get:function () { return this.message }, set:function (newValue) { this.message = newValue; } } } }) </script>
通过计算属性实现播放音乐
<div id="music"> <audio :src="currentSong" autoplay controls autoloop @ended="nextSong()"></audio> <ul> <li v-for="(item,i) in songs" @click="currentHandler(item,i)"> <h3>作者:{{item.author}}</h3> <p>{{item.name}}</p> </li> </ul> <button>上一首</button> <button @click="nextSong()">下一首</button> </div> <script type="text/javascript" src="vue.js"></script> <script> var songs = [ {id:1,src:'./audios/1.mp3',name:"la la Land",author:'Ryan'}, {id:2,src:'./audios/2.mp3',name:"The Best of",author:'Skillof'}, {id:3,src:'./audios/3.mp3',name:"It My Life",author:'Bon'}, {id:4,src:'./audios/4.mp3',name:"Tender",author:'Blur'} ]; // MVVM Model==>数组 数据库 View==>document 标签 VM // 数据的双向绑定 m<=>v var music = new Vue({ el:"#music", data:{ // 数据属性 songs:songs, // currentSong:"./audios/1.mp3", index:0 }, methods:{ currentHandler(item,i){ this.index = i; // this.currentSong = item.src; // this.currentSong = this.songs[i].src }, nextSong(){ this.index++; // this.currentSong = this.songs[this.index].src; // 判断数组越界,实现上一首 } }, computed:{ currentSong(){ return this.songs[this.index].src } } }) </script>
可以发现,只要修改了index,对应的计算属性currentSong就会自动更新
表单输入绑定
你可以用 v-model
指令在表单 <input>
及 <textarea>
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model
本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理
v-model
会忽略所有表单元素的 value
、checked
、selected
特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data
选项中声明初始值
<input v-model="message" placeholder="edit me"> <p>Message is: {{ message }}</p> <span>Multiline message is:</span> <p style="white-space: pre-line;">{{ message }}</p> <br> <textarea v-model="message" placeholder="add multiple lines"></textarea>
效果:
在文本区域插值 (<textarea></textarea>
) 并不会生效,应用 v-model
来代替
修饰符
在默认情况下,v-model
在每次 input
事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy
修饰符,从而转变为使用 change
事件进行同步:
<!-- 在“change”时而非“input”时更新 --> <input v-model.lazy="msg" >
如果想自动将用户的输入值转为数值类型,可以给 v-model
添加 number
修饰符:
<input v-model.number="age" type="number">
这通常很有用,因为即使在 type="number"
时,HTML 输入元素的值也总会返回字符串
如果要自动过滤用户输入的首尾空白字符,可以给 v-model
添加 trim
修饰符:
<input v-model.trim="msg">
相关软件介绍
webpack:打包机,将项目中的js,css,img,font,html等进行捆绑 编译成一个.js进行加载
nodejs: Node.js 的包管理器npm 一门后端语言 写我们服务器代码
npm: 好比python中的pip,node package manager,供所有的前端开发者使用的包都在这里面
babel: 将我们es6的代码在各种浏览器中兼容
npm相关命令
npm init --yes 初始化 不加--yes需要自己选择 npm install jquery --save 下载项目依赖时用--save 如果没有项目依赖则--save-dev npm install bootstrap@3 --save 下载指定版本的bootstrap npm uninstall jquery 卸载jquery
构建vue项目
安装nodejs
npm是nodejs的包管理工具,我们可以通过他来下载一些我们需要的包
首先我们要下载nodejs,官网为https://nodejs.org/en/
,下载完进行安装时需要注意,如果你没有安装在默认的路径下,需要将你的安装路径添加到环境变量,不然无法直接使用node和npm命令
安装完成后,我们就可以使用这两个命令了
安装Vue-cli
Vue 提供一个官方命令行工具,可用于快速搭建大型单页应用。该工具为现代化的前端开发工作流提供了开箱即用的构建配置。只需几分钟即可创建并启动一个带热重载、保存时静态检查以及可用于生产环境的构建配置的项目
# 全局安装 vue-cli npm install --global vue-cli
安装完成后我们就可以使用vue命令了
使用vue list可以查看一些可用的模板
其中pwa是用于移动端的,而webpack和webpack-simple就是我们马上要使用的
npm初始化工作目录
现在我们就可以创建一个工作目录,并在cmd中进入该目录进行初始化了
初始化完成后会在工作目录中生成一个package.json文件内容如上图所示
然后我们就可以使用npm install命令安装包了,安装完的包会放在工作目录下的node_modules目录下
搭建Vue项目
通过我们的vue-cli搭建我们的vue项目,首先创建一个目录并进入,然后执行vue init webpack-simple + 项目名,如果不加项目名会默认使用目录名
完成后就可以根据他的提示进行操作了,首先cd到项目目录中,然后使用npm install下载依赖包
可以看到此时的package.json文件为
{ "name": "my-project", "description": "A Vue.js project", "version": "1.0.0", "author": "", "license": "MIT", "private": true, "scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" }, "dependencies": { "vue": "^2.5.11" }, "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ], "devDependencies": { "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-preset-env": "^1.6.0", "babel-preset-stage-3": "^6.24.1", "cross-env": "^5.0.5", "css-loader": "^0.28.7", "file-loader": "^1.1.4", "vue-loader": "^13.0.5", "vue-template-compiler": "^2.4.4", "webpack": "^3.6.0", "webpack-dev-server": "^2.9.1" } }
dependencies就是项目依赖,而devDependencies是其它的一些依赖,由于这些依赖包一般都比较大,我们在上传项目到github上时是会忽略这些包的,当我们从github上下载了别人的项目要使用时可以先执行npm install,这样就会根据package.json文件自动下载依赖了
最后运行项目使用npm run dev,然后我们就可以通过http://localhost:8080/来访问我们的项目了
总结
生成vue的项目 模板: simple,webpack-simple,webpack(最后要使用的模板) vue init webpack-simple 项目名 根据提示 安装模板 不要安装sass cd 当前目录 npm install ===> vue.js 整个项目的依赖 npm run dev 启动我们的vue项目 vue is not a module如果出现这种错误表明未下载依赖
Vue项目中的组件和功能
项目的入口文件
上面我们已经创建了一个vue项目,现在我们可以看看这个项目的目录结构
这里的main.js就是项目的入口文件
这里要提到一些概念
webpack: entry 入口文件的地址 output 出口 loader babel-base-loader,vue-loader css-loader style-loader plugins js压缩 css压缩 html压缩 图片压缩等
我们可以看看main.js的内容
// es6的模块导入方式 // 所有的模块不要添加路径 // 第三方模块 只能引入名字 有import就有export(抛出) import Vue from 'vue' // 导入 import App from './App.vue' new Vue({ el: '#app', render: h => h(App) })
这里导入的vue就是vue模块,当导入第三方模块时直接使用名字就行了,而导入一个文件时,需要加路径,这里导入App时就是从文件中导入的
这里可以看到我们其实项目中只用到了一个Vue对象
import导入时,导入的模块中一定有export(抛出)
父组件
在main.js中直接导入的组件可以称为父组件
这里我们可以看看App.vue文件(这种.vue结尾的文件其实就是一个组件)中的内容
<!-- 组件都是.vue的文件 --> <!-- 写我们页面的结构 --> <template> <div id="app"> <h3>{{msg}}</h3> <ul> <li v-for="(item,index) in favs"> {{item}} </li> </ul> <!-- 插入子组件 --> <Vheader :hfavs="getAllDatas" @addHandler="add"></Vheader> <Vmarked></Vmarked> <!-- 如何从父组件传递数据到子组件,使用vue提供的一个属性props,props一定要验证自定义的属性 --> <!-- 子组件往父组件传值,使用自定义事件,使用$emit()来触发自定义的函数 --> </div> </template> <!-- 处理我们页面的业务逻辑 --> <script> // 导入子组件 import Vheader from "./Vheader" import Vmarked from "./Vmarked" export default{ name:"App", data(){ return { msg: "今天学习组件", favs: ["抽烟", "喝酒", "烫头"] } }, methods:{ add(a){ // alert(a) this.favs.push(a) } }, computed:{ getAllDatas(){ return this.favs } }, // 父组件插入子组件 挂载子组件 把子组件挂载到父组件上 components:{ Vheader: Vheader, Vmarked } } </script> <!-- 样式 --> <style></style>
这个文件中的内容我们重新写了一下,可以看到主要分为三个部分template部分写的是页面结构(html),script部分写的是页面的业务逻辑(js),style部分写的是样式(css)
在script中我们就要写exprot default,这里面包括组件的名字和数据属性等信息,需要注意的是,这里的写法和我们上面提到的有些不一样
子组件
在父组件中我们还可以导入子组件,当然首先需要写一个子组件的文件,比如Vheader.vue,然后在父组件中通过如下方式导入
<script> // 导入子组件 import Vheader from "./Vheader" import Vmarked from "./Vmarked" export default{ name:"App", data(){ return { msg: "今天学习组件", favs: ["抽烟", "喝酒", "烫头"] } }, methods:{ add(a){ // alert(a) this.favs.push(a) } }, computed:{ getAllDatas(){ return this.favs } }, // 父组件插入子组件 挂载子组件 把子组件挂载到父组件上 components:{ Vheader: Vheader, Vmarked } } </script>
这里的components就是用来挂载子组件的,在开头我们先通过import Vheader from "./Vheader"将写好的子组件导入,然后将这个子组件放到components中,最后在template中通过如下方式使用
<template> <div id="app"> <h3>{{msg}}</h3> <ul> <li v-for="(item,index) in favs"> {{item}} </li> </ul> <!-- 插入子组件 --> <Vheader></Vheader>
这样我们在页面上就能看到父组件和子组件的内容了
markdown功能
我们可以再写一个子组件,在页面上实现一个markdown功能
首先要下载marked模块
npm install marked --save
然后写一个新的组件Vmarked.vue
<template> <div class="mark"> <div class="markdown"> <textarea v-model="msg"> </textarea> </div> <div class="box" v-html="currentMarked"></div> </div> </template> <script> import Marked from "marked" export default{ name:"mark", data(){ return { msg:"" } }, methods:{ }, computed:{ currentMarked(){ // 默认只有getter return Marked(this.msg) } }, components:{ } } </script> <style scoped> .markdown,.box{ width:50%; height: 300px; border: 1px solid gray; float: left; box-sizing: border-box; } textarea{ width: 100%; height:300px; } </style>
在这个组件中我们让用户在textarea框中输入,然后通过计算属性,将输入的值通过marked转换为我们需要的格式,最后在页面上展示,但是需要注意的是,这里转换成的html代码无法直接在页面上显示,需要通过v-html才能展示
v-html
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用v-html
指令:
<p>Using mustaches: {{ rawHtml }}</p> <p>Using v-html directive: <span v-html="rawHtml"></span></p>
效果
在父组件中导入
完成子组件后只需要在父组件中导入
<!-- 组件都是.vue的文件 --> <!-- 写我们页面的结构 --> <template> <div id="app"> <h3>{{msg}}</h3> <ul> <li v-for="(item,index) in favs"> {{item}} </li> </ul> <!-- 插入子组件 --> <Vheader :hfavs="getAllDatas" @addHandler="add"></Vheader> <Vmarked></Vmarked> <!-- 如何从父组件传递数据到子组件,使用vue提供的一个属性props,props一定要验证自定义的属性 --> <!-- 子组件往父组件传值,使用自定义事件,使用$emit()来触发自定义的函数 --> </div> </template> <!-- 处理我们页面的业务逻辑 --> <script> // 导入子组件 import Vheader from "./Vheader" import Vmarked from "./Vmarked" export default{ name:"App", data(){ return { msg: "今天学习组件", favs: ["抽烟", "喝酒", "烫头"] } }, methods:{ add(a){ // alert(a) this.favs.push(a) } }, computed:{ getAllDatas(){ return this.favs } }, // 父组件插入子组件 挂载子组件 把子组件挂载到父组件上 components:{ Vheader: Vheader, Vmarked } } </script> <!-- 样式 --> <style></style>
组件的style
在style中可以写css样式,但是当父组件和子组件都有相同的标签时,可能造成css样式的混乱,一般情况下子组件会覆盖父组件的,这时我们可以使用下面的参数
<!-- scoped注入,当前的样式只对当前的html有用 --> <style scoped> </style>
scoped参数使当前的式样只对当前的html有用
父子组件通信
父子组件之间的数据传递是一种单向数据流,从父组件流向子组件
父传子
当子组件要用父组件的数据时,可以在template中插入子组件时,给子组件绑定属性,属性的值就是要传递的数据
<!-- 插入子组件 --> <Vheader :hfavs="getAllDatas" @addHandler="add"></Vheader>
子组件中使用props方法接收数据,接收到的数据一定要验证
<script> import url from "./assets/logo.png" export default{ name:"vheader", data(){ return { url: url } }, props:{ // 一定要验证 验证数据属性的类型 hfavs:Array }, methods:{ addOneFav(){ // 使用$emit()方法来触发自定义事件 // 第一个参数是自定义的函数名 this.$emit("addHandler",12323); // this.hfavs.push("泡妹") } } } </script>
验证完成后就可以在子组件中使用了
<template> <div class="vheader"> <img :src="url" alt=""> <ul> <li v-for="(item,index) in hfavs"> {{item}} </li> </ul> <button @click="addOneFav">添加</button> </div> </template>
props
我们可以为组件的 prop 指定验证规则。如果传入的数据不符合要求,Vue 会发出警告。这对于开发给他人使用的组件非常有用
type
可以是下面原生构造器:
- String
- Number
- Boolean
- Function
- Object
- Array
- Symbol
子传父
当子组件要向父组件传递数据时,我们要给template中插入的子组件自定义一个触发事件
<!-- 插入子组件 --> <Vheader :hfavs="getAllDatas" @addHandler="add"></Vheader>
这里我们定义了一个addHandler的触发事件,然后在子组件中我们使用this.$emit()触发这个自定义事件
<template> <div class="vheader"> <img :src="url" alt=""> <ul> <li v-for="(item,index) in hfavs"> {{item}} </li> </ul> <button @click="addOneFav">添加</button> </div> </template> <script> import url from "./assets/logo.png" export default{ name:"vheader", data(){ return { url: url } }, props:{ // 一定要验证 验证数据属性的类型 hfavs:Array }, methods:{ addOneFav(){ // 使用$emit()方法来触发自定义事件 // 第一个参数是自定义的函数名 this.$emit("addHandler",12323); // this.hfavs.push("泡妹") } } } </script>
这里$emit的第一个参数是我们自定义的触发事件的名字,第二个参数就是我们要传递的数据
当父组件的触发事件触发后执行相应的函数,这个函数可以接收一个参数,这个参数就是子组件传递的数据
<script> // 导入子组件 import Vheader from "./Vheader" import Vmarked from "./Vmarked" export default{ name:"App", data(){ return { msg: "今天学习组件", favs: ["抽烟", "喝酒", "烫头"] } }, methods:{ add(a){ // alert(a) this.favs.push(a) } }, computed:{ getAllDatas(){ return this.favs } }, // 父组件插入子组件 挂载子组件 把子组件挂载到父组件上 components:{ Vheader: Vheader, Vmarked } } </script>
图片当文件导入
在我们的子组件Vheader中有一个img标签,一开始我们写死了他的src属性,但是一般情况下我们不应该写死他,所以我们可以把这个图片当作一个模块导入,然后定义一个url来接收
<template> <div class="vheader"> <img :src="url" alt=""> <ul> <li v-for="(item,index) in hfavs"> {{item}} </li> </ul> <button @click="addOneFav">添加</button> </div> </template> <script> import url from "./assets/logo.png" export default{ name:"vheader", data(){ return { url: url } }, props:{ // 一定要验证 验证数据属性的类型 hfavs:Array }, methods:{ addOneFav(){ // 使用$emit()方法来触发自定义事件 // 第一个参数是自定义的函数名 this.$emit("addHandler",12323); // this.hfavs.push("泡妹") } } } </script>
vue-router
使用vue-router路由时需要先进行安装
npm install vue-router
安装完成后要在main.js中导入
import Vue from 'vue' import App from './App.vue' import VueRouter from 'vue-router' // 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter) // 1. 定义(路由)组件。 // 导入路由组件 import Index from './Index' import Luffy from './Luffy' Vue.use(VueRouter) // 创建 router 实例,然后传 `routes` 配置 const router = new VueRouter({ routes:[ { path: '/', component: Index }, { path: '/luffy', component: Luffy } ] }) new Vue({ el: '#app', router, render: h => h(App) })
可以看到导入后我们又导入了两个路由组件
index
<template> <div class="index"> <h3>我是首页</h3> </div> </template> <script> export default{ name:'index', data(){ return { } } } </script> <style> </style>
luffy
<template> <div class="luffy"> <h4>我是路飞</h4> </div> </template> <script> export default{ name:'luffy', data(){ return { } } } </script> <style> </style>
然后我们创建了一个router实例,实例中包含我们路径对应的组件,最后将router实例添加到Vue实例中
在App组件中使用时
<template> <div id="app"> <img src="./assets/logo.png"> <h1>{{ msg }}</h1> <h2>Essential Links</h2> <ul> <router-link v-for='(item,index) in urls' :to="item.href" :class='{active:currentIndex==index}' @click.native='clickHandler(index)' >{{item.name}}</router-link> <!-- 给router-link添加事件 会阻止click事件的触发,需要加上.navtive就可以了。加上.navtive 才是真正点击了a标签的click事件,在组件中不加.native 不会触发原生的事件。注意了注意了 --> <!-- <router-link to="/luffy">路飞学城</router-link> --> </ul> <!-- 路由出口 所有路由匹配的组件都会被渲染到这里 --> <router-view></router-view> </div> </template> <script> export default { name: 'app', data () { return { msg: 'Welcome to Your Vue.js App', urls:[ {href:'/',name:"首页"}, {href:'/luffy',name:"路飞学城"} ], currentIndex:0 } }, methods:{ clickHandler(index){ console.log(index) this.currentIndex = index; } } } </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; } h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } a.active{ color: yellow; } </style>
<router-view></router-view>是路由的出口,所有路由匹配的组件都会被渲染到这里
<router-link> 默认会被渲染成一个 `<a>` 标签,他的to对应的就是a标签的href属性,在给该标签绑定事件时需要注意,添加.native
创建webpack模板的项目
与上面创建项目一样,首先创建项目目录,然后执行初始化![](https://images2018.cnblogs.com/blog/1247576/201804/1247576-20180404145732376-925174402.png)
创建完成后使用npm run dev运行项目
使用webpack模板创建的项目目录结构和我们上面创建的项目有些区别
可以看到有专门放组件的目录,还有一个放路由模块的目录,看看main.js中的逻辑
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' new Vue({ el: '#app', router, components: { App }, template: '<App/>' })
这里直接导入了App组件和router路由,看看路由目录的index.js中抛出了什么
import Vue from 'vue' import Router from 'vue-router' // import HelloWorld from '@/components/HelloWorld' import Vmain from '@/components/Vmain' import Vmarked from '@/components/Vmarked' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Vmain', component: Vmain }, { path: '/marked', name: 'Vmarked', component: Vmarked } ] })
可以看到抛出的就是我们上面路由中需要的Router实例,这样就实现了分离,不用将路由的逻辑写到main.js中了
在路由中我们还定义了两个新的组件内容,分别是首页的内容和我的笔记内容,现在我们通过App,Vmain和Vmarked三个组件来构建我们的基本页面
App
<template> <div id="app"> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#"> <img src="./assets/logo.png" style="width: 50px;height: 50px;margin-top: -15px"> </a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li :class="{active:currentIndex==index}" v-for = "(item,index) in urls" @click='clickHandler(index)'> <router-link :to="item.url">{{item.name}}</router-link> </li> </ul> <form class="navbar-form navbar-right"> <div class="form-group"> <input type="text" class="form-control" placeholder="Search"> </div> <button type="submit" class="btn btn-default">Submit</button> </form> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <router-view></router-view> </div> </template> <script> import 'bootstrap/dist/css/bootstrap.min.css' var $ = require('jquery'); export default { name: 'App', data(){ return { urls:[ {url:'/',name:'首页'}, {url:'/marked',name:"我的笔记"} ], currentIndex:0 } }, methods:{ clickHandler(index){ this.currentIndex = index; } }, } </script> <style> </style>
这里我们使用到了bootstrap,需要先下载npm install bootstrap@3 --save,在导入bootstrap时,只需要直接import文件就行,但是需要注意,这里忽略了node_modules目录
然后在App中通过router-link和router-view来关联两个路由组件
Vmain
<template> <div class="main"> <div class="container"> <div class="row"> <div class="col-md-12"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">我的首页</h3> </div> <div class="panel-body"> <ul> <li>我的笔记</li> <li>我的博客</li> </ul> </div> </div> </div> </div> </div> </div> </template> <script> export default{ name:'Vmain', data(){ return { } }, methods:{ }, components:{ }, computed:{ } } </script> <style> </style>
Vmarked
<template> <div class="marked"> <div class="container"> <div class="row"> <div class="col-md-12"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">我的markdown</h3> </div> <div class="panel-body"> <!-- 左边: 笔记列表 --> <div class="col-md-3"> <Vnotelist></Vnotelist> </div> <!-- 右边:markdown编辑器 --> <div class="col-md-9"> <Vmarkdown></Vmarkdown> </div> </div> </div> </div> </div> </div> </div> </template> <script> import Vnotelist from './Vnotelist' import Vmarkdown from './Vmarkdown' export default{ name:'Vmarked', data(){ return { } }, methods:{ }, components:{ Vnotelist, Vmarkdown }, computed:{ } } </script> <style> </style>
在Vmarked中我们又引入了两个子组件,分别是左侧的笔记列表和右侧的markdown内容
分别来看看这两个子组件的内容,先看Vnotelist
<template> <div class="notelist"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">我的笔记列表</h3> </div> <div class="panel-body"> <ul> <!-- <li v-for='(item,index) in getAllDatas'> {{item.content}} </li> --> <!-- <VnoteItem v-for='(item,index) in getAllDatas' :item = 'item'></VnoteItem> --> <VnoteItem :getAllDatas = 'getAllDatas'></VnoteItem> </ul> </div> </div> </div> </div> </template> <script> import VnoteItem from './VnoteItem' export default{ name:'Vnotelist', data(){ return { } }, methods:{ }, components:{ VnoteItem }, computed:{ getAllDatas(){ return this.$store.state.allNotes; } } } </script> <style> </style>
这里我们看到在这个组件中我们需要使用到后端传来的数据,这个数据应该在页面加载完就从后端请求来,所以我们需要在App组件中需要在载入完成后向后台发送请求,这里我们要知道vue的声明周期中有一个钩子函数mounted,在页面加载完后就会执行这个函数
App.vue
<script> import 'bootstrap/dist/css/bootstrap.min.css' var $ = require('jquery'); export default { name: 'App', data(){ return { urls:[ {url:'/',name:'首页'}, {url:'/marked',name:"我的笔记"} ], currentIndex:0 } }, methods:{ clickHandler(index){ this.currentIndex = index; } }, mounted(){ // console.log('当前组件加载完成了'); // axios vue推荐咱们使用这个插件发起ajax console.log($) var _this = this; $.ajax({ url:'http://127.0.0.1:7428/api/comments', type:'get', success:function(allNotes) { console.log(_this.$store); _this.$store.state.allNotes = allNotes; }, error:function(err) { console.log(err); } }) } } </script>
首先我们导入jquery,需要先下载npm install jquery --save,然后我们在mounted中向后台发送ajax请求,获得数据,但是这里的数据是在App中,而需要使用数据的组件Vnodelist和App没有关联,要怎么拿到数据能,这里就要引入我们的新知识vuex了
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能
要使用他我们需要先下载npm install vuex --save,然后我们在main.js中导入并使用他
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' // 使用vuex 先下载再导入 import Vuex from 'vuex' // 确保下面这句代码一定要写 Vue.use(Vuex) Vue.config.productionTip = false import $ from 'jquery' // 创建store 商店 const store = new Vuex.Store({ // 状态 state: { // 数据属性 allNotes: [], note:{ id:'', title:'', date:'', content:'', markdown:'' } }, // 修改状态唯一的方法就是提交mutations mutations: { // 同步操作 // 起名字 ADDONENOTE:function(state,newAllDatas){ state.allNotes = newAllDatas; } }, actions:{ // 异步操作 addOneNote:function(context,json) { // 发起ajax请求 往后台 添加一条笔记 // $.ajax({ url:"http://127.0.0.1:7428/api/comments", type:'post', data:json, success:function(newAllDatas) { context.commit('ADDONENOTE',newAllDatas) } }) } } }) /* eslint-disable no-new */ new Vue({ el: '#app', router, store, components: { App }, template: '<App/>' })
首先导入vuex,然后使用vue.use使用他,最后使用Vuex.Store创建一个store实例对象,在这个对象里,有state,这就是我们的数据属性,不论在哪个组件中我们都可以通过this.$store.state取到这个状态中的值,所以我们在App的mounted中发送ajax请求获取到值后直接更新这个store中的数据就行了
mounted(){ // console.log('当前组件加载完成了'); // axios vue推荐咱们使用这个插件发起ajax console.log($) var _this = this; $.ajax({ url:'http://127.0.0.1:7428/api/comments', type:'get', success:function(allNotes) { console.log(_this.$store); _this.$store.state.allNotes = allNotes; }, error:function(err) { console.log(err); } }) }
这里需要注意一下,ajax中的this并不是我们要的,可以使用箭头函数或者上面的方式获取,有了这个以后我们在Vnodelist中就可以通过计算属性来监听他了
computed:{ getAllDatas(){ return this.$store.state.allNotes; } }
在Vnodelist中我们还有一个子组件,就是每一个列表的item,这里我们有很多中方法把数据传给他,也可以直接在子组件中通过vuex获取数据
<template> <div class="notelist"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">我的笔记列表</h3> </div> <div class="panel-body"> <ul> <!-- <li v-for='(item,index) in getAllDatas'> {{item.content}} </li> --> <!-- <VnoteItem v-for='(item,index) in getAllDatas' :item = 'item'></VnoteItem> --> <VnoteItem :getAllDatas = 'getAllDatas'></VnoteItem> </ul> </div> </div> </div> </div> </template>
VnoteItem
<template> <div> <li v-for='(item,index) in getAllDatas'> <!-- { "id": 1492672679281, "title": "vue的核心用法3", "date": "2017-4-23", "content": "计算属性", "markdown": "### 计算属性" } --> <h4>{{item.title}}</h4> <p>{{item.content}}</p> <button class="btn btn-danger">删除</button> </li> </div> </template> <script> export default{ name:'VnoteItem', data(){ return { } }, props:{ getAllDatas:Array }, computed:{ } } </script> <style> </style>
我们从后台获取的数据其实就是一个数组中包含多个对象的形式,对象的内容如上面的注释,这样我们就可以在页面上展示我们从数据库获得的数据了
到这里Vmarked的左侧部分就完成了,现在我们来看看右侧的markdown内容
Vmarkdown
<!-- 页面的结构 --> <template> <div class="mark"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">我的markdown</h3> 我的标题: <input type="text" v-model='titleHandler'/> <button class="btn btn-success" @click='addOneNote'>提交</button> </div> <div class="panel-body"> <div class="markdown"> <textarea v-model='msgHandler'> </textarea> </div> <div class="box" v-html='currentMarked'> </div> </div> </div> </div> </template> <!-- 页面的业务逻辑 --> <script> import Marked from 'marked' export default{ name:'Vmarkdown', data(){ return { msg:'' } }, methods:{ /* { "id": 1492672679281, "title": "vue的核心用法3", "date": "2017-4-23", "content": "计算属性", "markdown": "### 计算属性" } */ // 最小值 到最大值 (max-min) + Math.random()*min 100---300 // 添加一条笔记 addOneNote(){ var content = document.getElementsByClassName('box')[0].innerText; var json = { id:Math.random(), title:this.titleHandler, date: (new Date()).toLocaleString(), content:content, markdown:this.msgHandler } console.log(json) this.$store.dispatch('addOneNote',json) // success:function(){ // } } }, computed:{ currentMarked(){ // 默认只有getter return Marked( this.$store.state.note.markdown) }, getDatas(){ return this.$store.state.allNotes; }, titleHandler:{ get:function() { return this.$store.state.note.title }, set:function(newValue) { console.log(newValue) this.$store.state.note.title = newValue; } }, msgHandler:{ get:function() { return this.$store.state.note.markdown; }, set:function(newValue) { this.$store.state.note.markdown = newValue } } }, components:{ } } </script> <!-- 样式 --> <style scoped> .markdown,.box{ width: 50%; height: 300px; border:1px solid gray; float: left; box-sizing: border-box; } textarea{ width:100%; height: 100%; border:none; } </style>
这里的功能和我们上面写的markdown差不多,我们还增加了标题的输入框以及一个提交按钮,当点击提交时,需要将新写的数据提交到数据库,并在左侧的列表中显示,这里的标题input框我们使用v-model来取值,他的值我们利用数据属性来监听,当用户输入时,相当于触发了数据属性的set功能,在这里我们在vuex的store中定义一个空的对象来存放值,同理textarea框中的内容也这样存放
// 创建store 商店 const store = new Vuex.Store({ // 状态 state: { // 数据属性 allNotes: [], note:{ id:'', title:'', date:'', content:'', markdown:'' } }
这样标题栏中输入的值一直会实时更新进去,当输入完所有内容后,点击提交时,我们要触发一个事件,要向后台提交数据,并且要将新的数据在store中更改,使得左侧展示的数据能实时更新,点击提交后,我们先取到所有的内容生成一个对象,然后将这个对象传给后台,这里又要用到新的只是,在vuex的store中有两个方法Mutations和actions
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数
const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { // 变更状态 state.count++ } } })
你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 increment
的 mutation 时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法:
store.commit('increment')
这里的store我们在组件中使用时就是this.$store,从这里可以看出我们上面在更改store中state状态时其实是有问题的,没有使用mutation
action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
让我们来注册一个简单的 action:
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } } })
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit
提交一个 mutation,或者通过 context.state
和 context.getters
来获取 state 和 getters。当我们在之后介绍到 Modules时,你就知道 context 对象为什么不是 store 实例本身了。
实践中,我们会经常用到 ES2015 的参数解构来简化代码(特别是我们需要调用 commit
很多次的时候):
actions: { increment ({ commit }) { commit('increment') } }
Action 通过 store.dispatch
方法触发:
store.dispatch('increment')
通过上面的介绍,我们发现当我们获得了新的数据对象后其实就是要实现一个action方法来发送ajax请求,然后将获得的新内容通过mutation来更新到store中,具体内容如下
methods:{ /* { "id": 1492672679281, "title": "vue的核心用法3", "date": "2017-4-23", "content": "计算属性", "markdown": "### 计算属性" } */ // 最小值 到最大值 (max-min) + Math.random()*min 100---300 // 添加一条笔记 addOneNote(){ var content = document.getElementsByClassName('box')[0].innerText; var json = { id:Math.random(), title:this.titleHandler, date: (new Date()).toLocaleString(), content:content, markdown:this.msgHandler } console.log(json) this.$store.dispatch('addOneNote',json) // success:function(){ // } } }
在main.js中我们定义store中mutation和action
// 创建store 商店 const store = new Vuex.Store({ // 状态 state: { // 数据属性 allNotes: [], note:{ id:'', title:'', date:'', content:'', markdown:'' } }, // 修改状态唯一的方法就是提交mutations mutations: { // 同步操作 // 起名字 ADDONENOTE:function(state,newAllDatas){ state.allNotes = newAllDatas; } }, actions:{ // 异步操作 addOneNote:function(context,json) { // 发起ajax请求 往后台 添加一条笔记 // $.ajax({ url:"http://127.0.0.1:7428/api/comments", type:'post', data:json, success:function(newAllDatas) { context.commit('ADDONENOTE',newAllDatas) } }) } } })
这样就实现了一个简单的新增数据功能