JS框架 - Vue基础
-
引入Vue
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
-
声明式渲染
- 文本插值
数据和DOM已经被建立了关联,所有东西都是响应式的
<div id="app"> {{message}} </div> <script type="text/javascript"> var app=new Vue({ el:'#app', ///注意是字母l,不是1 data:{ message:'Hello Vue!' } }) </script>
- 绑定元素特性
<div id="app-2"> <span v-bind:title="message"> 鼠标悬停几秒钟查看此处动态绑定的提示信息! </span> </div> <script type="text/javascript"> var app2 = new Vue({ el: '#app-2', data: { message: '页面加载于 ' + new Date().toLocaleString() } }) </script>
- 文本插值
-
条件和循环
-
v-if
<div id="app-3"> <p v-if="seen">现在你看到我了</p> </div> <script type="text/javascript"> var app3=new Vue({ el:"#app-3", data:{ seen:true } }) </script>
-
v-for
v-for 指令可以绑定数组的数据来渲染一个项目列表
<div id="app-4"> <ol> <li v-for="todo in todos"> {{todo.text}} </li> </ol> </div> <script type="text/javascript"> var app4=new Vue({ el: '#app-4', data: { todos: [ { text: '学习 JavaScript' }, { text: '学习 Vue' }, { text: '整个牛项目' } ] } }) app4.todos.push({ text: '新项目' }) </script>
-
-
用户交互
-
v-on:click 添加一个事件监听器
<div id="app-5"> <p>{{message}}</p> <button v-on:click="reverseMessage">逆转消息</button> </div> <script type="text/javascript"> var app5=new Vue({ el:'#app-5', data:{ message:'Hello Vue.js' }, methods:{ reverseMessage:function () { this.message=this.message.split('').reverse().join('') } } }) </script>
-
v-model
<div id="app-6"> <p>{{message}}</p> <input v-model="message"> </div> <script type="text/javascript"> var app6=new Vue({ el:'#app-6', data:{ message:'Hello Vue!' } }) </script>
-
v-text
-
v-html
-
v-bind:attr
插值表达式
<li v-for="item in todos" v-text="item.text" v-bind:id="item.id"></li> <li v-for="item in todos" v-html="item.text"></li>
上面两条语句表示绑定文本和结构语言。
你的站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。
<button v-on:click="showMessage()" v-bind:disabled="isButtonDisabled">click me...</button> <a v-bind:href="url"></a>
缩写:
<!--属性缩写--> <a v-bind:href="url"></a> <a :href="url"></a> <!--事件缩写--> <a v-on:click="doSomething"></a> <a @click="doSomething"></a>
-
-
组件
- 注册组件
<ol> <todo-item></todo-item> </ol> <script type="text/javascript"> Vue.component('todo-item',{ template:'<li>这是个待办项</li>' }) </script>
- 循环输出
<div id="app-7"> <ol> <todo-item v-for="item in groceryList" v-bind:todo="item" v-bind:key="item.id"> </todo-item> </ol> </div> <script type="text/javascript"> Vue.component('todo-item', { props:['todo'], template: '<li>{{todo.text}}</li>' }) var app7=new Vue({ el:'#app-7', data:{ groceryList:[ { id: 0, text: '蔬菜' }, { id: 1, text: '奶酪' }, { id: 2, text: '随便其它什么人吃的东西' } ] } }) </script>
- 引用模板
将组件元素内容放到模板<template>中并引用其id
<template id="wbs"> <div> <h3>{{msg}}</h3> <ul> <li v-for="value in arr">{{value}}</li> </ul> </div> </template> components:{ 'my-hello':{ template:'#wbs', } }
这个组件需要传递两个参数,调用方式为
<my-hello :msg='var' :arr='array'></my-hello>
- 动态组件
组件:多个组件使用同一个挂载点,然后动态的在它们之间切换
组件,使用缓存数据,适用于非活动组件
<keep-alive> <component :is="flag"></component>//使该组件不每次请求,采用缓存数据 <keep-alive>
- 父子组件
在一个组件内部定义另一个组件,称为父子组件,子组件只能在父组件内部使用
默认情况下,子组件无法访问父组件中的数据,每个组件实例的作用域是独立的
var vm = new Vue({ el:'#d1', data:{msg:'hello'}, components:{ 'my-world':{ template:'#fatherComp', components:{ 'my-hello':{ template:'#sonComp' } }, } } }) <template id='fatherComp'><div><h1>父组件</h1><my-hello></my-hello></div></template> <template id='sonComp'><div><h2>子组件</h2></div></template>
-
组件间数据传递
-
子组件访问父组件的数据
在调用子组件时,绑定想要获取的父组件中的数据
在子组件内部,使用props选项声明获取的数据,及接收来自父组件的数据
注:props除了字符串数组之外,也可以采用对象类型,允许配置高级设置,如类型判断、数据校验、设置默认值
props:{ message:String, name:{ type:String, required:true }, age:{ type:Number, default:18, validator:function(value){ return value>=0; } }, user:{ type:Object, default:function(){ //对象或数组的默认值必须使用函数的形式来返回 return {id:3306,username:'秋香'}; } } },
-
父组件访问子组件的数据
a) 在子组件中使用vm.$emit(事件名,数据)触发一个自定义事件,事件名自定义
//子组件内部定义发送方法 methods:{ send(){ this.$emit('e-hello',this.sname,this.sage) } }
b) 父组件在使用子组件的地方监听子组件触发的事件,并在父组件中定义方法,用来获取数据
<template id='fatherComp'> <div> <h1>父组件数据{{fname}},获取子组件数据{{fsname}}-{{fsage}}</h1> <my-hello :name=fname :age=fage @e-hello="getdata"></my-hello> </div> </template>
总结:子组件通过events给父组件发送消息,实际上就是子组件把自己的数据发送到父组件
-
单向数据流
props是单向绑定的,当父组件的属性变化时,将传导给子组件,但是不会反过来
而且不允许子组件直接修改父组件中的数据,会报错
解决方式1:如果子组件想把它作为局部数据来使用,可以将数据存入另一个变量中再操作,不影响父组件中的数据
解决方式2:如果子组件想修改数据并且同步更新到父组件,两个方法:
a.使用.sync(1.0版本中支持,2.0版本中不支持,2.3版本又开始支持),需要显式地触发一个更新事件 b.可以将父组件中的数据包装成对象,然后在子组件中修改对象的属性(因为对象是引用类型,指向同一个内存空间),推荐
-
非父子组件间的通信
非父子组件间的通信,可以通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件
var Event=new Vue(); //发送数据组件 var A={ template:'#a', data(){ return { name:'tom' } }, methods:{ send(){ Event.$emit('data-a',this.name); } } } //接收数据组件 var C={ template:'#c', data(){ return { name:'', age:'' } }, mounted(){ //在模板编译完成后执行 Event.$on('data-a',name => { this.name=name; // console.log(this); }); Event.$on('data-b',age => { this.age=age; }); } }
-
- 注册组件
-
数据与方法
只有当实例被创建时 data 中存在的属性才是响应式的。也就是说如果你添加一个新的属性,比如:
vm.b = 'hi'
那么对 b 的改动将不会触发任何视图的更新。如果你知道你会在晚些时候需要一个属性,但是一开始它为空或不存在,那么你仅需要设置一些初始值。比如:
data: { newTodoText: '', visitCount: 0, hideCompletedTodos: false, todos: [], error: null }
这里唯一的例外是使用 Object.freeze(),这会阻止修改现有的属性,也意味着响应系统无法再追踪变化。
var obj = { foo: 'bar' } Object.freeze(obj) new Vue({ el: '#app', data: obj }) <div id="app"> <p>{{ foo }}</p> <!-- 这里的 `foo` 不会更新! --> <button v-on:click="foo = 'baz'">Change it</button> </div>
除了数据属性,Vue 实例还暴露了一些有用的实例属性与方法。它们都有前缀 $,以便与用户定义的属性区分开来。例如:
app.$data.title='VUE.JS..........' // $watch 是一个实例方法 vm.$watch('a', function (newValue, oldValue) { // 这个回调将在 `vm.a` 改变后调用 })
-
生命周期钩子
-
created
created: function () {}
-
mounted
-
updated
-
destroyed
-
-
插值
- v-once指令 :当数据改变时,插值处的内容不会更新。
-
计算属性和侦听器
-
计算属性
<div id="example"> <p>Original message: "{{ message }}"</p> <p>Computed reversed message: "{{ reversedMessage }}"</p> </div> <script type="text/javascript"> var vm=new Vue({ el:'#example', data:{ message:'Hello' }, computed:{ reversedMessage:function () { return this.message.split('').reverse().join('') } } }) </script>
同样的效果可以在方法中实现,如:
<p>Reversed message: "{{ reversedMessage() }}"</p> // 在组件中 methods: { reversedMessage: function () { return this.message.split('').reverse().join('') } }
不同:计算属性是基于它们的依赖进行缓存的。只在相关依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:
computed: { now: function () { return Date.now() } }
-
侦听属性
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
<div id="watch-example"> <p> Adk a yes/no question: <input v-model="question"> </p> <p>{{answer}}</p> </div> <!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 --> <!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 --> <script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script> <script type="text/javascript"> var watchExampleVM=new Vue({ el:'#watch-example', data:{ question:'', answer:'I cannot give you an answer until you ask a question!' }, watch:{ // 如果 `question` 发生改变,这个函数就会运行 question:function (newQuestion,oldQuestion) { this.answer='Waiting for you to stop typing...'; this.debouncedGetAnswer() } }, created:function () { //当调用函数0.5秒后,才会执行该动作,若在这0.5秒内又调用该函数则将取消前一次并重新计算执行时间 this.debouncedGetAnswer=_.debounce(this.getAnswer,500) }, methods:{ getAnswer:function () { if(this.question.indexOf('?')===-1){ this.answer='Questions usually contain a question mark. ;-)' return } this.answer='Thinking...'; var vm=this; //异步,ajax,路由地址 axios.get('https://yesno.wtf/api') .then(function (response){ vm.answer=_.capitalize(response.data.answer) }) .catch(function (error) { vm.answer = 'Error! Could not reach the API. ' + error }) } } }) </script>
深度检测
-
第一个是hanndler:其值是一个回调函数,即监听到变化时应该执行的函数.
-
第二个是deep:其值是true或flase;确认是否深入监听.(一般监听时是不能监听到对象属性值的变化的,数组的值变化可以听到.)
-
第三个是immediate:其值是true或flase;确认是否以当前的初始值执行handler的函数
watch:{ //深度watcher //c:{ // 'a':1, // 'b':2, // 'e':[1,2,3] //} //正常情况下取不到e里的值,想取得时候用deep c:{ handler:function(val,oldVal){ /*...*/ }, deep:true } //该回调将会在侦听开始之后被立即调用 d:{ handler:function(val,oldVal){ /*...*/ }, immediate:true } }
methods是事件来触发的;computed是用于数据计算,加工处理的;watch是监听数据变化的
-
-
阻止默认事件
<a href="#"> baidu</a> <script> var link=document.querySelector('a'); link.onclick=function (e) { this.innerText='百度'; e.preventDefault(); } </script>
-
Class与Style绑定
- 动态的绑定属性
<style> .button{ width:300px; height:40px; } .button-color{ background:red; } .button-border{ border:solid 1px black; } </style> <div class="container" id="myapp"> <div class="row"> <button class="button" v-bind:class="{button-color:flag,button-border:!flag}" :style="{color:fontcolor}" @click="change">login...</button> <button :style='buttonclass'></button> </div> </div> <script type="text/javascript"> var app=new Vue({ el:'#myapp', data:{ flag:false, fontcolor:'red', buttonclass:{ width:'300px', height:'40px', } }, methods:{ change:function(){ this.flag=true; } } }) </script>
- 用在组件上
<div class="container" id="myapp"> <div class="row"> <ul class="nav nav-tabs"> <li role="presentation" :class="{active:index====1}" @click="index=1"><a href="#">Home</a></li> <li role="presentation" :class="{active:index====2}" @click="index=2"><a href="#">Profile</a></li> <li role="presentation" :class="{active:index====3}" @click="index=3"><a href="#">Messages</a></li> </ul> </div> </div> <script type="text/javascript"> var app=new Vue({ el:'#myapp', data:{ index:1, } }) </script>
-
条件渲染
-
v-if v-else
<div class="container" id="myapp"> <div class="row"> <h1 v-if="flag">if</h1> <h1 v-else>else</h1> </div> </div> <script type="text/javascript"> var app=new Vue({ el:'#myapp', data:{ flag="true" //显示if } }) </script>
在 元素上使用 v-if 条件渲染分组
因为 v-if 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 元素。
<template v-if="ok"> <h1>Title</h1> <p>Paragraph 1</p> <p>Paragraph 2</p> </template>
用key管理可复用的元素
如果你允许用户在不同的登录方式之间切换:
<template v-if="loginType === 'username'"> <label>Username</label> <input placeholder="Enter your username"> </template> <template v-else> <label>Email</label> <input placeholder="Enter your email address"> </template>
那么在上面的代码中切换 loginType 将不会清除用户已经输入的内容。因为两个模板使用了相同的元素, 不会被替换掉——仅仅是替换了它的 placeholder。
这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key 属性即可:
<template v-if="loginType === 'username'"> <label>Username</label> <input placeholder="Enter your username" key="username-input"> </template> <template v-else> <label>Email</label> <input placeholder="Enter your email address" key="email-input"> </template>
现在,每次切换时,输入框都将被重新渲染。注意,
-
v-show:根据条件展示元素
<h1 v-show="ok">Hello!</h1>
v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 display。
注意,v-show 不支持 元素,也不支持 v-else。
-
v-if 和 v-show 比较
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
-
-
列表渲染
-
索引
<div id="v-for-object" class="demo"> <div v-for="(value, key, index) in object"> {{ index }}. {{ key }}: {{ value }} </div> </div> new Vue({ el: '#v-for-object', data: { object: { firstName: 'John', lastName: 'Doe', age: 30 } } })
-
key
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。
<div v-for="item in items" :key="item.id"> <!-- 内容 --> </div>
-
数组更新检查
Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新。这些方法如下:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
例:
this.jobs[3].pop();
-