vue详解

文章目录

Vue

Vue 是什么?

  • Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架
  • vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合

使用Vue将helloworld 渲染到页面上

指令

  • 本质就是自定义属性
  • Vue中指定都是以 v- 开头

v-cloak

  • 防止页面加载时出现闪烁问题:当使用异步请求,请求数据后,将数据熏染到页面中,有的浏览器会出现页面闪烁的问题。

     <style type="text/css">
      /* 
        1、通过属性选择器 选择到 带有属性 v-cloak的标签  让他隐藏
     */
      [v-cloak]{
        /* 元素隐藏    */
        display: none;
      }
      </style>
    <body>
      <div id="app">
        <!-- 2、 让带有插值 语法的   添加 v-cloak 属性 
             在 数据渲染完场之后,v-cloak 属性会被自动去除,
             v-cloak一旦移除也就是没有这个属性了  属性选择器就选择不到该标签
    		 也就是对应的标签会变为可见
        -->
        <div  v-cloak  >{{msg}}</div>
      </div>
      <script type="text/javascript" src="js/vue.js"></script>
      <script type="text/javascript">
        var vm = new Vue({
          //  el   指定元素 id 是 app 的元素  
          el: '#app',
          //  data  里面存储的是数据
          data: {
            msg: 'Hello Vue'
          }
        });
    </script>
    </body>
    </html>
    

v-text

  • v-text指令用于将数据填充到标签中,作用于插值表达式类似,但是没有闪动问题
  • 如果数据中有HTML标签会将html标签一并输出
  • 注意:此处为单向绑定,数据对象上的值改变,插值会发生变化;但是当插值发生变化并不会影响数据对象的值
<div id="app">
    <!--  
		注意:在指令中不要写插值语法  直接写对应的变量名称 
        在 v-text 中 赋值的时候不要在写 插值语法
		一般属性中不加 {{}}  直接写 对应 的数据名 
	-->
    <p v-text="msg"></p>
    <p>
        <!-- Vue  中只有在标签的 内容中 才用插值语法 -->
        {{msg}}
    </p>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue.js'
        }
    });
</script>

v-html

  • 用法和v-text 相似 但是他可以将HTML片段填充到标签中

  • 可能有安全问题, 一般只在可信任内容上使用 v-html永不用在用户提交的内容上

  • 它与v-text区别在于v-text输出的是纯文本,浏览器不会对其再进行html解析,但v-html会将其当html标签解析后输出。

    <div id="app">
      <p v-html="html"></p> <!-- 输出:html标签在渲染的时候被解析 -->
        
        <p>{{message}}</p> <!-- 输出:<span>通过双括号绑定</span> -->
        
      <p v-text="text"></p> <!-- 输出:<span>html标签在渲染的时候被源码输出</span> -->
    </div>
    <script>
      let app = new Vue({
      el: "#app",
      data: {
        message: "<span>通过双括号绑定</span>",
        html: "<span>html标签在渲染的时候被解析</span>",
        text: "<span>html标签在渲染的时候被源码输出</span>",
      }
     });
    </script>
    

v-pre

  • 显示原始信息跳过编译过程
  • 跳过这个元素和它的子元素的编译过程。
  • 一些静态的内容不需要编译加这个指令可以加快渲染
    <span v-pre>{{ this will not be compiled }}</span>    
	<!--  显示的是{{ this will not be compiled }}  -->
	<span v-pre>{{msg}}</span>  
     <!--   即使data里面定义了msg这里仍然是显示的{{msg}}  -->
<script>
    new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue.js'
        }
    });
</script>

v-once

  • 执行一次性的插值【当数据改变时,插值处的内容不会继续更新】
  <!-- 即使data里面定义了msg 后期我们修改了 仍然显示的是第一次data里面存储的数据即 Hello Vue.js  -->
     <span v-once>{{ msg}}</span>    
<script>
    new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue.js'
        }
    });
</script>

v-rules

表单验证

<form :rules="rules">     # 绑定formRules验证信息
    <label prop="userName">用户名:</label>      # 验证userName字段
    <input style="width:300px" maxlength="50"/>
</form>
<script>
    new Vue({
        el: '#app',
        data: {
            // 省略别的数据定义
            ... 
            // 表单验证
            rules: {      
                userName: [           // 表示失去焦点时触发此验证
                    {required: true,message: "请输入用户名称",trigger: "blur"}
                ]
            }
        }
    });
</script>

双向数据绑定

  • 当数据发生变化的时候,视图也就发生变化
  • 当视图发生变化的时候,数据也会跟着同步变化
v-model
  • v-model是一个指令,限制在 <input>、<select>、<textarea>、components中使用
 <div id="app">
      <div>{{msg}}</div>
      <div>
          当输入框中内容改变的时候,  页面上的msg  会自动更新
        <input type="text" v-model='msg'>
      </div>
  </div>
获取单选框中的值
  • 通过v-model
 	<!-- 
		1、 两个单选框需要同时通过v-model 双向绑定 一个值 
        2、 每一个单选框必须要有value属性  且value 值不能一样 
		3、 当某一个单选框选中的时候 v-model  会将当前的 value值 改变 data 中的 数据

		gender 的值就是选中的值,我们只需要实时监控他的值就可以了
	-->
<div id="app">
   <input type="radio" id="male" value="1" v-model='gender'>
   <label for="male"></label>

   <input type="radio" id="female" value="2" v-model='gender'>
   <label for="female"></label>
</div>
<script>
    new Vue({
         el:"#app",
         data: {
             // 默认会让当前的 value 值为 2 的单选框选中
                gender: 2,  
            },
    })

</script>
获取复选框中的值
  • 通过v-model
  • 和获取单选框中的值一样
  • 复选框 checkbox 这种的组合时 data 中的 hobby 我们要定义成数组 否则无法实现多选
	<!-- 
		1、 复选框需要同时通过v-model 双向绑定 一个值 
        2、 每一个复选框必须要有value属性  且value 值不能一样 
		3、 当某一个单选框选中的时候 v-model  会将当前的 value值 改变 data 中的 数据

		hobby 的值就是选中的值,我们只需要实时监控他的值就可以了
	-->

<div id="app">
   <span>爱好:</span>
   <input type="checkbox" id="ball" value="1" v-model='hobby'>
   <label for="ball">篮球</label>
   <input type="checkbox" id="sing" value="2" v-model='hobby'>
   <label for="sing">唱歌</label>
   <input type="checkbox" id="code" value="3" v-model='hobby'>
   <label for="code">写代码</label>
 </div>
<script>
    new Vue({
         el:"#app",
         data: {
                // 默认会让当前的 value 值为 2 和 3 的复选框选中
                hobby: ['2', '3'],
            },
    })
</script>
获取下拉框和文本框中的值
  • 通过v-model
<div id="app">
    <span>职业:</span>
    <!--
         1、 需要给select  通过v-model 双向绑定 一个值
         2、 每一个option  必须要有value属性  且value 值不能一样
         3、 当某一个option选中的时候 v-model  会将当前的 value值 改变 data 中的 数据
          occupation 的值就是选中的值,我们只需要实时监控他的值就可以了
     -->
    <!-- multiple  多选 -->
    <select v-model='occupation' multiple>
        <option value="0">请选择职业...</option>
        <option value="1">教师</option>
        <option value="2">软件工程师</option>
        <option value="3">律师</option>
    </select>
    <!-- textarea 是 一个双标签   不需要绑定value 属性的  -->
    <textarea v-model='desc'></textarea>
</div>
<script>
    new Vue({
        el:"#app",
        data: {
            // 默认会让当前的 value 值为 2 和 3 的下拉框选中
            occupation: ['2', '3','1'],
            desc: 'nihao'
        },
    })
</script>
表单修饰符
  • .number 转换为数值

    • 当开始输入非数字的字符串时,因为Vue无法将字符串转换成数值,所以属性值将实时更新成相同的字符串。即使后面输入数字,也将被视作字符串。
  • .trim 自动过滤用户输入的首尾空白字符

    • 只能去掉首尾的 不能去除中间的空格
  • .lazy 将input事件切换成change事件

    • .lazy 修饰符延迟了同步更新属性值的时机。即将原本绑定在 input 事件的同步逻辑转变为绑定在 change 事件上
  • 在失去焦点 或者 按下回车键时才更新

    <!-- 自动将用户的输入值转为数值类型 -->
    <input v-model.number="age" type="number">
    
    <!--自动过滤用户输入的首尾空白字符   -->
    <input v-model.trim="msg">
    
    <!-- 在“change”时而非“input”时更新 -->
    <input v-model.lazy="msg" >
    

mvvm
  • MVC 是后端的分层开发概念; MVVM是前端视图层的概念,主要关注于 视图层分离,也就是说:MVVM把前端的视图层,分为了 三部分 Model, View , VM ViewModel
  • m model
    • 数据层 Vue 中 数据层 都放在 data 里面
  • v view 视图
    • Vue 中 view 即 我们的HTML页面
  • vm (view-model) 控制器 将数据和视图层建立联系
    • vm 即 Vue 的实例 就是 vm

v-on

  • 用来绑定事件的
  • 形式如:v-on:click 缩写为 @click;
v-on事件函数中传入参数

<body>
    <div id="app">
        <div>{{num}}</div>
        <div>
            <!-- 如果事件直接绑定函数名称,那么默认会传递事件对象作为事件函数的第一个参数 -->
            <button v-on:click='handle1'>点击1</button>
            <!-- 2、如果事件绑定函数调用,那么事件对象必须作为最后一个参数显示传递,
                 并且事件对象的名称必须是$event -->
            <button v-on:click='handle2(123, 456, $event)'>点击2</button>
        </div>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                num: 0
            },
            methods: {
                handle1: function(event) {
                    console.log(event.target.innerHTML)
                },
                handle2: function(p, p1, event) {
                    console.log(p, p1)
                    console.log(event.target.innerHTML)
                    this.num++;
                }
            }
        });
    </script>
事件修饰符
  • 在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求。
  • Vue 不推荐我们操作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>

<!-- 只当在 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">

<!-- -当点击enter 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">

<!--当点击enter或者space时  时调用 `vm.alertMe()`   -->
<input type="text" v-on:keyup.enter.space="alertMe" >

<!--常用的按键修饰符
.enter 	=>  enter键
.tab 	=> 	tab键
.delete (捕获“删除”和“退格”按键) =>  删除键
.esc 	=>	取消键
.space	=>  空格键
.up		=>  上
.down 	=>  下
.left 	=>  左
.right 	=>  右-->

<script>
    var vm = new Vue({
        el:"#app",
        methods: {
            submit:function(){},
            alertMe:function(){},
        }
    })
</script>
自定义按键修饰符别名
  • 在Vue中可以通过config.keyCodes自定义按键修饰符别名
<div id="app">
    <!--预先定义了keycode 116(即F5)的别名为f5,因此在文字输入框中按下F5,会触发prompt方法-->
    <input type="text" v-on:keydown.f5="prompt()">
</div>

<script>
    Vue.config.keyCodes.f5 = 116;

    let app = new Vue({
        el: '#app',
        methods: {
            prompt: function() {
                alert('我是 F5!');
            }
        }
    });
</script>

v-bind

  • v-bind 指令被用来响应地更新 HTML 属性
  • v-bind:href 可以缩写为 :href;
<!-- 绑定一个属性 -->
<img v-bind:src="imageSrc">

<!-- 缩写 -->
<img :src="imageSrc">
绑定对象
  • 我们可以给v-bind:class 一个对象,以动态地切换class。
  • 注意:v-bind:class指令可以与普通的class特性共存
1、 v-bind 中支持绑定一个对象 
	如果绑定的是一个对象 则 键为 对应的类名  值 为对应data中的数据 
<!-- 
	HTML最终渲染为 <ul class="box textColor textSize"></ul>
	注意:
		textColor,textSize  对应的渲染到页面上的CSS类名	
		isColor,isSize  对应vue data中的数据  如果为true 则对应的类名 渲染到页面上 

		当 isColor 和 isSize 变化时,class列表将相应的更新,
		例如,将isSize改成false,
		class列表将变为 <ul class="box textColor"></ul>
-->

<ul class="box" v-bind:class="{textColor:isColor, textSize:isSize}">
    <li>学习Vue</li>
    <li>学习Node</li>
    <li>学习React</li>
</ul>
  <div v-bind:style="{color:activeColor,fontSize:activeSize}">对象语法</div>

<script>
var vm= new Vue({
    el:'.box',
    data:{
        isColor:true,
        isSize:true,
    	activeColor:"red",
        activeSize:"25px",
    }
})
</script>
<style>

    .box{
        border:1px dashed #f0f;
    }
    .textColor{
        color:#f00;
        background-color:#eef;
    }
    .textSize{
        font-size:30px;
        font-weight:bold;
    }
</style>
绑定class
2、  v-bind 中支持绑定一个数组    数组中classA和 classB 对应为data中的数据

这里的classA  对用data 中的  classA
这里的classB  对用data 中的  classB
<ul class="box" :class="[classA, classB]">
    <li>学习Vue</li>
    <li>学习Node</li>
    <li>学习React</li>
</ul>
<script>
var vm= new Vue({
    el:'.box',
    data:{
        classA:‘textColor‘,
        classB:‘textSize‘
    }
})
</script>
<style>
    .box{
        border:1px dashed #f0f;
    }
    .textColor{
        color:#f00;
        background-color:#eef;
    }
    .textSize{
        font-size:30px;
        font-weight:bold;
    }
</style>
绑定对象和绑定数组 的区别
  • 绑定对象的时候 对象的属性 即要渲染的类名 对象的属性值对应的是 data 中的数据
  • 绑定数组的时候数组里面存的是data 中的数据
绑定style
 <div v-bind:style="styleObject">绑定样式对象</div>'
 
<!-- CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用单引号括起来)    -->
 <div v-bind:style="{ color: activeColor, fontSize: fontSize,background:'red' }">内联样式</div>

<!--组语法可以将多个样式对象应用到同一个元素 -->
<div v-bind:style="[styleObj1, styleObj2]"></div>


<script>
	new Vue({
      el: '#app',
      data: {
        styleObject: {
          color: 'green',
          fontSize: '30px',
          background:'red'
        },
        activeColor: 'green',
   		fontSize: "30px"
      },
      styleObj1: {
             color: 'red'
       },
       styleObj2: {
            fontSize: '30px'
       }

</script>

分支结构

v-if 使用场景
  • 1- 多个元素 通过条件判断展示或者隐藏某个元素。或者多个元素
  • 2- 进行两个视图之间的切换
<div id="app">
        <!--  判断是否加载,如果为真,就加载,否则不加载-->
        <span v-if="flag">
           如果flag为true则显示,false不显示!
        </span>
</div>

<script>
    var vm = new Vue({
        el:"#app",
        data:{
            flag:true
        }
    })
</script>

----------------------------------------------------------

    <div v-if="type === 'A'">
       A
    </div>
  <!-- v-else-if紧跟在v-if或v-else-if之后   表示v-if条件不成立时执行-->
    <div v-else-if="type === 'B'">
       B
    </div>
    <div v-else-if="type === 'C'">
       C
    </div>
  <!-- v-else紧跟在v-if或v-else-if之后-->
    <div v-else>
       Not A/B/C
    </div>

<script>
    new Vue({
      el: '#app',
      data: {
        type: 'C'
      }
    })
</script>
v-show 和 v-if的区别
  • v-show本质就是标签display设置为none,控制隐藏
    • v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,故v-show性能更好一点。
  • v-if是动态的向DOM树内添加或者删除DOM元素
    • v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件

循环结构

v-for
  • 用于循环的数组里面的值可以是对象,也可以是普通元素
<ul id="example-1">
    <!-- 循环结构-遍历数组  
item 是我们自己定义的一个名字  代表数组里面的每一项  
items对应的是 data中的数组-->
    <li v-for="item in items">
        {{ item.message }}
    </li> 

</ul>
<script>
    new Vue({
        el: '#example-1',
        data: {
            items: [
                { message: 'Foo' },
                { message: 'Bar' }
            ]}
    })
</script>
  • 不推荐同时使用 v-ifv-for
  • v-ifv-for 一起使用时,v-for 具有比 v-if 更高的优先级。
   <!--  循环结构-遍历对象
		v 代表   对象的value
		k  代表对象的 键 
		i  代表索引	
	---> 
     <div v-if='v==13' v-for='(v,k,i) in obj'>{{v + '---' + k + '---' + i}}</div>

<script>
 new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ],
    obj: {
        uname: 'zhangsan',
        age: 13,
        gender: 'female'
    }
  }
})
</script>
  • key 的作用
    • key来给每个节点做一个唯一标识
    • key的作用主要是为了高效的更新虚拟DOM
<ul>
  <li v-for="item in items" :key="item.id">...</li>
</ul>

案例选项卡

1、 HTML 结构
<div id="app">
    <div class="tab">
        <!--  tab栏  -->
        <ul>
            <li class="active">apple</li>
            <li class="">orange</li>
            <li class="">lemon</li>
        </ul>
        <!--  对应显示的图片 -->
        <div class="current"><img src="img/apple.png"></div>
        <div class=""><img src="img/orange.png"></div>
        <div class=""><img src="img/lemon.png"></div>
    </div>
</div>
2、 提供的数据
list: [{
    id: 1,
    title: 'apple',
    path: 'img/apple.png'
}, {
    id: 2,
    title: 'orange',
    path: 'img/orange.png'
}, {
    id: 3,
    title: 'lemon',
    path: 'img/lemon.png'
}]
3、 把数据渲染到页面
  • 把tab栏 中的数替换到页面上

    • 把 data 中 title 利用 v-for 循环渲染到页面上
    • 把 data 中 path利用 v-for 循环渲染到页面上
    <div id="app">
        <div class="tab">  
            <ul>
    <!--  
    1、绑定key的作用 提高Vue的性能 
    2、 key 需要是唯一的标识 所以需要使用id, 也可以使用index ,
    index 也是唯一的 
    3、 item 是 数组中对应的每一项  
    4、 index 是 每一项的 索引
    -->
                <li :key='item.id' v-for='(item,index) in list'>{{item.title}}</li>
            </ul>
            <div  :key='item.id' v-for='(item, index) in list'>
                <!-- :  是 v-bind 的简写   绑定属性使用 v-bind -->
                <img :src="item.path">
            </div>
        </div>
    </div>
    <script>
        new  Vue({
            //  指定 操作元素 是 id 为app 的 
            el: '#app',
            data: {
                list: [{
                    id: 1,
                    title: 'apple',
                    path: 'img/apple.png'
                }, {
                    id: 2,
                    title: 'orange',
                    path: 'img/orange.png'
                }, {
                    id: 3,
                    title: 'lemon',
                    path: 'img/lemon.png'
                }]
            }
        })
    </script>
    
4、 给每一个tab栏添加事件,并让选中的高亮
  • 4.1 、让默认的第一项tab栏高亮

    • tab栏高亮 通过添加类名active 来实现 (CSS active 的样式已经提前写好)
      • 在data 中定义一个 默认的 索引 currentIndex 为 0
      • 给第一个li 添加 active 的类名
        • 通过动态绑定class 来实现 第一个li 的索引为 0 和 currentIndex 的值刚好相等
        • currentIndex === index 如果相等 则添加类名 active 否则 添加 空类名
  • 4.2 、让默认的第一项tab栏对应的div 显示

    • 实现思路 和 第一个 tab 实现思路一样 只不过 这里控制第一个div 显示的类名是 current
<ul>
    <!-- 动态绑定class   有 active   类名高亮  无 active   不高亮-->
    <li  :class='currentIndex==index?"active":""'
        :key='item.id' v-for='(item,index) in list'
        >{{item.title}}</li>
</ul>
<!-- 动态绑定class   有 current  类名显示  无 current  隐藏-->
<div :class='currentIndex==index?"current":""' 

     :key='item.id' v-for='(item, index) in list'>
    <!-- :  是 v-bind 的简写   绑定属性使用 v-bind -->
    <img :src="item.path">
</div>

<script>
    new  Vue({
        el: '#app',
        data: {
            currentIndex: 0, // 选项卡当前的索引  默认为 0  
            list: [{
                id: 1,
                title: 'apple',
                path: 'img/apple.png'
            }, {
                id: 2,
                title: 'orange',
                path: 'img/orange.png'
            }, {
                id: 3,
                title: 'lemon',
                path: 'img/lemon.png'
            }]
        }
    })
</script>
  • 4.3 、点击每一个tab栏 当前的高亮 其他的取消高亮

    • 给每一个li添加点击事件

    • 让当前的索引 index 和 当前 currentIndex 的 值 进项比较

    • 如果相等 则当前li 添加active 类名 当前的 li 高亮 当前对应索引的 div 添加 current 当前div 显示 其他隐藏

<div id="app">
    <div class="tab">
        <ul>
            <!--  通过v-on 添加点击事件   需要把当前li 的索引传过去 -->
            <li v-on:click='change(index)'		           			
                :class='currentIndex==index?"active":""'                   
                :key='item.id' 
                v-for='(item,index) in list'>{{item.title}}</li>
        </ul>
        <div :class='currentIndex==index?"current":""' 
             :key='item.id' v-for='(item, index) in list'>
            <img :src="item.path">
        </div>
    </div>
</div>

<script>
    new  Vue({
        el: '#app',
        data: {
            currentIndex: 0, // 选项卡当前的索引  默认为 0  
            list: [{
                id: 1,
                title: 'apple',
                path: 'img/apple.png'
            }, {
                id: 2,
                title: 'orange',
                path: 'img/orange.png'
            }, {
                id: 3,
                title: 'lemon',
                path: 'img/lemon.png'
            }]
        },
        methods: {
            change: function(index) {
                // 通过传入过来的索引来让当前的  currentIndex  和点击的index 值 相等 
                //  从而实现 控制类名    
                this.currentIndex = index;
            }
        }
    })
</script>

自定义指令

  • 内置指令不能满足我们特殊的需求,Vue允许我们自定义指令

Vue.directive 注册全局指令

<div id="app">
    <!--
      使用自定义的指令,只需在对用的元素中,加上'v-'的前缀形成类似于内部指令'v-if','v-text'的形式。
    -->
    <input type="text" v-focus>
</div>
<script>
    // 注意点:
    //   1、 在自定义指令中  如果以驼峰命名的方式定义 如  Vue.directive('focusA',function(){})
    //   2、 在HTML中使用的时候 只能通过 v-focus-a 来使用

    // 注册一个全局自定义指令 v-focus
    Vue.directive('focus', {
        // 当绑定元素插入到 DOM 中。 其中 el为dom元素
        inserted: function (el) {
            // 聚焦元素
            el.focus();
        }
    });
    new Vue({
        el:'#app'
    });
</script>

Vue.directive 注册全局指令带参数

<div id="app">
    <input type="text" v-color='msg'>
</div>
<script type="text/javascript">
    /*
      自定义指令-带参数
      bind - 只调用一次,在指令第一次绑定到元素上时候调用

    */
    Vue.directive('color', {
        // bind声明周期, 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
        // el 为当前自定义指令的DOM元素
        // binding 为自定义的函数形参   通过自定义属性传递过来的值 存在 binding.value 里面
        bind: function (el, binding) {
            // 根据指令的参数设置背景色
            // console.log(binding.value.color)
            el.style.backgroundColor = binding.value.color;
        }
    });
    
    
    
    
    var vm = new Vue({
        el: '#app',
        data: {
            msg: {
                color: 'blue'
            }
        }
    });
</script>

自定义指令局部指令

  • 局部指令,需要定义在 directives 的选项 用法和全局用法一样
  • 局部指令只能在当前组件里面使用
  • 当全局指令和局部指令同名时以局部指令为准
<div id="app">
    <input type="text" v-color='msg'>
    <input type="text" v-focus>
</div>
<script type="text/javascript">
    /*
      自定义指令-局部指令
    */
    var vm = new Vue({
        el: '#app',
        data: {
            msg: {
                color: 'red'
            }
        },
        //局部指令,需要定义在  directives 的选项
        directives: {
            color: {
                bind: function (el, binding) {
                    el.style.backgroundColor = binding.value.color;
                }
            },
            focus: {
                inserted: function (el) {
                    el.focus();
                }
            }
        }
    });
</script>

组件

组件可以扩展 HTML 元素,封装可重用的代码

全局注册

  • Vue.component('组件名称', { }) 第1个参数是标签名称,第2个参数是一个选项对象
  • 全局组件注册后,任何vue实例都可以用

局部注册

  • 只能在当前注册它的vue实例中使用
<div id="app">
    <my-component></my-component>
</div>

<script>
    // 定义组件的模板
    var Child = {
        template: '<div>A custom component!</div>'
    }
    new Vue({
        //局部注册组件  
        components: {
            // <my-component> 将只在父模板可用  一定要在实例上注册了才能在html文件中使用
            'my-component': Child
        }
    })
</script>

组件注意事项

  • 组件参数的data值必须是函数同时这个函数要求返回一个对象
  • 组件模板必须是单个根元素
  • 组件模板的内容可以是模板字符串

组件的基本使用

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Vue组件</title>
    </head>
    <body>
        <div id="app">
            <!--单标签会出现bug,组件必须使用双标签-->
            <!-- 使用Vue.extend创建的vue组件 -->
            <com></com>

            <!-- 使用对象字面量的方式创建的vue组件-->
            <login></login>

            <!--使用简写方式创建的组件-->
            <com2></com2>
            <login2></login2>

            <!--使用template标签调用的组件-->
            <tmp></tmp>
        </div>
        <template id="tmp">
            <div>
                <h1>这个组件是template标签创建组件,msg={{ msg }}</h1>
            </div>
        </template>

        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script type="text/javascript">
            /* 1、使用Vue.extend来创建Vue组件模板 */
            let com = Vue.extend({
                template: '<h3>这个是使用Vue.extend创建的vue组件</h3>'
            })

            /* 2、通过对象字面量的方式,创建一个vue组件模板*/
            let login = {template: '<h3>我是通过对象字面量的方式创建的vue组件</h3>'}

            /* 3、Vue.component注册全局组件 */
            Vue.component("com", com)
            Vue.component("login", login)
            /** 简写
                Vue.component("com2",Vue.extend({
                    template:'<h3>这个是使用Vue.extend创建的vue组件</h3>'
                }))
                
  			  Vue.component("login2",{template: '<h3>我是通过对象字面量的方式创建的vue组件</h3>'})
  			**/

            /*创建vue实例,并挂载到#app节点上,注意*/
            new Vue({
                el: '#app',
                components:{		  /* 注册私有子组件(局部注册)*/
                    com2,login2,
                    tmp:{
                        template:'#tmp',
                        data() {        /* 组件的data属性,必须进行return,否则无法使用 */
                            return {
                                msg : "我是组件的data属性"
                            };
                        }
                    }
                }
            })
        </script>
    </body>
</html>

父组件向子组件传值

通过绑定父组件的变量,与子组件的变量,达到传值的效果

  • 父组件发送的形式是以属性的形式绑定值到子组件身上。
  • 然后子组件用属性props接收
  • 在props中使用驼峰形式,模板中需要使用短横线的形式字符串形式的模板中没有这个限制
<div id="app">
    <div>{{pmsg}}</div>
    <!--1、menu-item  在 APP中嵌套着 故 menu-item   为  子组件      -->
    <!-- 给子组件传入一个静态的值 -->
    <menu-item title='来自父组件的值'></menu-item>
    <!-- 
    2、 需要动态的数据的时候 需要属性绑定的形式设置 此时 ptitle  来自父组件data 中的数据 . 
    传的值可以是数字、对象、数组等等
    -->
    <menu-item :title='ptitle' content='hello'></menu-item>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    Vue.component('menu-item', {
        // 3、 子组件用属性props接收父组件传递过来的数据  
        props: ['title', 'content'],
        data: function() {
            return {
                msg: '子组件本身的数据'
            }
        },
        template: '<div>{{msg + "----" + title + "-----" + content}}</div>'
    });
    var vm = new Vue({
        el: '#app',
        data: {
            pmsg: '父组件中内容',
            ptitle: '动态绑定属性'
        }
    });
</script>

子组件向父组件传值

通过子组件注册自定义事件,绑定父组件方法,达到传值的效果,

  • 子组件用$emit()触发事件
  • $emit() 第一个参数为 自定义的事件名称 第二个参数为需要传递的数据
  • 父组件用v-on 监听子组件的事件
<div id="app">
    <!-- 子组件自定义funchild事件,来触发父组件的funFather方法-->
    <!--  @子组件自定义事件="父组件方法 ,注意:子组件事件名必须为小写-->
    <tmp @funchild="funFather"></tmp><br>
    子组件传递的参数1为:{{msg1}} <br>
    子组件传递的参数2为:{{msg2}}
</div>

<template id="tmp">
    <!--点击按钮,则会向父组件传值-->
    <button @click="btnClick">向父组件传递参数</button>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    let vm = new Vue({
        el: '#app',
        data: {
            msg1: null,     /* 父组件变量*/
            msg2: null
        },
        components: {/*注册私有子组件*/
            tmp: {
                template: '#tmp',
                methods: {
                    btnClick: function (){
                        /* 子组件触发自定义事件,并向父组件传递两个参数 */
                        this.$emit("funchild", "你好父组件","你是父组件")
                    }
                }
            }
        },
        methods: {
            /*子组件自定义事件,触发父组件方法,将传递的值保存到父组件的data属性的变量中*/
            funFather: function (value,value2, $event) {
                this.msg1 = value;
                this.msg2 = value2;
            }
        }
    })
</script>

兄弟组件之间的传值

通过另外一个Vue实例达到传值效果

  • 兄弟之间传递数据需要借助于事件中心,通过事件中心传递数据
    • 提供事件中心 var hub = new Vue()
  • 传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)
  • 接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名
  • 销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据
<div id="app">
    <div>父组件</div>
    <div>
        <button @click='handle'>销毁事件</button>
    </div>
    <test-tom></test-tom>
    <test-jerry></test-jerry>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
    /*兄弟组件之间数据传递*/
    
    //1、 提供事件中心
    var hub = new Vue();

    Vue.component('test-tom', {
        data: function(){
            return {
                num: 0
            }
        },
      template: `
        <div>
          <div>TOM:{{num}}</div>
          <div>
            <button @click='handle'>点击</button>
          </div>
        </div>
      `,
        methods: {
            handle: function(){
                //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件
                hub.$emit('jerry-event', 2);
            }
        },
        mounted: function() {
            // 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on(方法名
            hub.$on('tom-event', (val) => {
                this.num += val;
            });
        }
    });
    Vue.component('test-jerry', {
        data: function(){
            return {
                num: 0
            }
        },
      template: `
        <div>
          <div>JERRY:{{num}}</div>
          <div>
            <button @click='handle'>点击</button>
          </div>
        </div>
      `,
        methods: {
            handle: function(){
                //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件
                hub.$emit('tom-event', 1);
            }
        },
        mounted: function() {
            // 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on()方法名
            hub.$on('jerry-event', (val) => {
                this.num += val;
            });
        }
    });
    var vm = new Vue({
        el: '#app',
        data: {

        },
        methods: {
            handle: function(){
                //4、销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据  
                hub.$off('tom-event');
                hub.$off('jerry-event');
            }
        }
    });
</script>

component标签实现多个组件的切换

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>component标签实现多个组件的切换</title>
    </head>
    <body>
        <div id="app">
            <button @click="toggleView">切换</button>
            <!-- :is 可以根据指定的组件名显示,指定的组件-->
            <component :is="comName"></component>
        </div>

        <!-- 定义组件left-->
        <template id="left">
            <h3>left组件</h3>
        </template>

        <!-- 定义组件right-->
        <template id="right">
            <h3>right组件</h3>
        </template>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script type="text/javascript">
            new Vue({
                el:'#app',
                data:{
                    comName:'left'
                },
                methods:{
                    toggleView:function () {  // 切换组件的逻辑
                        if (this.comName == 'left')
                            this.comName = 'right'
                        else
                            this.comName = 'left'
                    }
                },
                components:{ // 注册组件
                    left:{
                        template:'#left'
                    },
                    right:{
                        template: '#right'
                    }
                }
            })
        </script>
    </body>
</html>

render实现熏染组件

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>render实现熏染组件</title>
    </head>
    <body>
        <div id="app">
            123456
        </div>

        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script type="text/javascript">
            /* 创建组件模板 */
            let login = {
                template:"<h3>我是render方法熏染的组件</h3>"
            }

            /*创建Vue实例*/
            new Vue({
                el:'#app',      /* 挂载HTML中的节点*/
                render:function (createdElements) {
                   /*createdElements是一个方法,调用它,能够将指定的组件模板,熏染为HTML结构,并替换掉el属性中挂载的节点*/
                    return createdElements(login)
                    /*注意:这里return的结果,会替换页面中el挂载的那个节点*/
                }
            })
        </script>
    </body>
</html>

组件插槽

组件的最大特性就是复用性,而用好插槽能大大提高组件的可复用能力

匿名插槽
<div id="app">
    <!-- 这里的所有组件标签中嵌套的内容会替换掉slot  如果不传值 则使用 slot 中的默认值  -->  
    <alert-box>有bug发生</alert-box>
    <alert-box>有一个警告</alert-box>
    <alert-box></alert-box>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    /*
      组件插槽:父组件向子组件传递内容
    */
    Vue.component('alert-box', {
      /**当组件渲染的时候,这个 <slot> 元素将会被替换为“组件标签中嵌套的内容”。
		插槽内可以包含任何模板代码,包括 HTML**/
      template: `
        <div>
          <strong>ERROR:</strong>
          <slot>默认内容</slot>
        </div>
      `	
    });
    new Vue({
        el: '#app',
        data: {}
    });
</script>
具名插槽
  • 具有名字的插槽
  • 使用 中的 “name” 属性绑定元素
<div id="app">
    <base-layout>
        <!-- 2、 通过slot属性来指定, 这个slot的值必须和下面slot组件得name值对应上
		如果没有匹配到 则放到匿名的插槽中   --> 
        <p slot='header'>标题信息</p>
        <p>主要内容1</p>
        <p>主要内容2</p>
        <p slot='footer'>底部信息信息</p>
    </base-layout>
	<hr/>
    <base-layout>
        <!-- 注意点:template临时的包裹标签最终不会渲染到页面上     -->  
        <template slot='header'>
            <p>标题信息1</p>
            <p>标题信息2</p>
        </template>
        <p>主要内容1</p>
        <p>主要内容2</p>
        <template slot='footer'>
            <p>底部信息信息1</p>
            <p>底部信息信息2</p>
        </template>
    </base-layout>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    /*
      具名插槽
    */
    Vue.component('base-layout', {
        /** 1、 使用 <slot> 中的 "name" 属性绑定元素 指定当前插槽的名字 
        	注意点:具名插槽的渲染顺序,完全取决于模板,而不是取决于父组件中元素的顺序**/
		template: `
        <div>
          <header>
            <slot name='header'></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
            <slot name='footer'></slot>
          </footer>
        </div>
      `
    });
    new Vue({
        el: '#app',
        data: {}
    });
</script>
作用域插槽
  • 父组件对子组件加工处理
  • 既可以复用子组件的slot,又可以使slot内容不一致
<div id="app">
    <!-- 
    1、当我们希望li 的样式由外部使用组件的地方定义,因为可能有多种地方要使用该组件,
    但样式希望不一样 这个时候我们需要使用作用域插槽 
    -->  
    <fruit-list :list='list'>
        <!-- 
		2、 父组件中使用了<template>元素,而且包含scope="slotProps",
        slotProps在这里只是临时变量   
        ---> 	
        <template slot-scope='slotProps'>
            <strong v-if='slotProps.info.id==3' class="current">
                {{slotProps.info.name}}		         
            </strong>
            <span v-else>{{slotProps.info.name}}</span>
        </template>
    </fruit-list>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
    /* 作用域插槽 */
    Vue.component('fruit-list', {
        props: ['list'],
        /**3、 在子组件模板中,<slot>元素上有一个类似props传递数据给组件的写法msg="xxx",
			插槽可以提供一个默认内容,如果如果父组件没有为这个插槽提供了内容,会显示默认的内容。
			如果父组件为这个插槽提供了内容,则默认的内容会被替换掉 **/
	    template: `
        <div>
          <li :key='item.id' v-for='item in list'>
            <slot :info='item'>{{item.name}}</slot>
          </li>
        </div>
      `
    });
    new Vue({
        el: '#app',
        data: {
            list: [{id: 1,name: 'apple'},
                   {id: 2,name: 'orange'},
                   {id: 3,name: 'banana'}]
        }
    });
</script>

购物车案例

1. 实现组件化布局
  • 把静态页面转换成组件化模式
  • 把组件渲染到页面上
 <div id="app">
    <div class="container">
      <!-- 2、把组件渲染到页面上 --> 
      <my-cart></my-cart>
    </div>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    # 1、 把静态页面转换成组件化模式
    # 1.1  标题组件 
    var CartTitle = {
      template: `
        <div class="title">我的商品</div>
      `
    }
    # 1.2  商品列表组件 
    var CartList = {
      #  注意点 :  组件模板必须是单个根元素  
      template: `
        <div>
          <div class="item">
            <img src="img/a.jpg"/>
            <div class="name"></div>
            <div class="change">
              <a href="">-</a>
              <input type="text" class="num" />
              <a href="">+</a>
            </div>
            <div class="del">×</div>
          </div>
          <div class="item">
            <img src="img/b.jpg"/>
            <div class="name"></div>
            <div class="change">
              <a href="">-</a>
              <input type="text" class="num" />
              <a href="">+</a>
            </div>
            <div class="del">×</div>
          </div>
          <div class="item">
            <img src="img/c.jpg"/>
            <div class="name"></div>
            <div class="change">
              <a href="">-</a>
              <input type="text" class="num" />
              <a href="">+</a>
            </div>
            <div class="del">×</div>
          </div>
          <div class="item">
            <img src="img/d.jpg"/>
            <div class="name"></div>
            <div class="change">
              <a href="">-</a>
              <input type="text" class="num" />
              <a href="">+</a>
            </div>
            <div class="del">×</div>
          </div>
          <div class="item">
            <img src="img/e.jpg"/>
            <div class="name"></div>
            <div class="change">
              <a href="">-</a>
              <input type="text" class="num" />
              <a href="">+</a>
            </div>
            <div class="del">×</div>
          </div>
        </div>
      `
    }
    # 1.3  商品结算组件 
    var CartTotal = {
      template: `
        <div class="total">
          <span>总价:123</span>
          <button>结算</button>
        </div>
      `
    }
    ## 1.4  定义一个全局组件 my-cart
    Vue.component('my-cart',{
      ##  1.6 引入子组件  
      template: `
        <div class='cart'>
          <cart-title></cart-title>
          <cart-list></cart-list>
          <cart-total></cart-total>
        </div>
      `,
      # 1.5  注册子组件   
      components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {

      }
    });

  </script>
2、实现 标题和结算功能组件
  • 标题组件实现动态渲染
    • 从父组件把标题数据传递过来 即 父向子组件传值
    • 把传递过来的数据渲染到页面上
  • 结算功能组件
    • 从父组件把商品列表list 数据传递过来 即 父向子组件传值
    • 把传递过来的数据计算最终价格渲染到页面上
 <div id="app">
    <div class="container">
      <my-cart></my-cart>
    </div>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
     # 2.2  标题组件     子组件通过props形式接收父组件传递过来的uname数据
    var CartTitle = {
      props: ['uname'],
      template: `
        <div class="title">{{uname}}的商品</div>
      `
    }
	# 2.3  商品结算组件  子组件通过props形式接收父组件传递过来的list数据   
    var CartTotal = {
      props: ['list'],
      template: `
        <div class="total">
          <span>总价:{{total}}</span>
          <button>结算</button>
        </div>
      `,
      computed: {
        # 2.4    计算商品的总价  并渲染到页面上 
        total: function() {
          var t = 0;
          this.list.forEach(item => {
            t += item.price * item.num;
          });
          return t;
        }
      }
    }
    Vue.component('my-cart',{
      data: function() {
        return {
          uname: '张三',
          list: [{
            id: 1,
            name: 'TCL彩电',
            price: 1000,
            num: 1,
            img: 'img/a.jpg'
          },{
            id: 2,
            name: '机顶盒',
            price: 1000,
            num: 1,
            img: 'img/b.jpg'
          },{
            id: 3,
            name: '海尔冰箱',
            price: 1000,
            num: 1,
            img: 'img/c.jpg'
          },{
            id: 4,
            name: '小米手机',
            price: 1000,
            num: 1,
            img: 'img/d.jpg'
          },{
            id: 5,
            name: 'PPTV电视',
            price: 1000,
            num: 2,
            img: 'img/e.jpg'
          }]
        }
      },
      #  2.1  父组件向子组件以属性传递的形式 传递数据
      #   向 标题组件传递 uname 属性   向 商品结算组件传递 list  属性  
      template: `
        <div class='cart'>
          <cart-title :uname='uname'></cart-title>
          <cart-list></cart-list>
          <cart-total :list='list'></cart-total>
        </div>
      `,
      components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {

      }
    });

  </script>

3. 实现列表组件删除功能
  • 从父组件把商品列表list 数据传递过来 即 父向子组件传值
  • 把传递过来的数据渲染到页面上
  • 点击删除按钮的时候删除对应的数据
    • 给按钮添加点击事件把需要删除的id传递过来
      • 子组件中不推荐操作父组件的数据有可能多个子组件使用父组件的数据 我们需要把数据传递给父组件让父组件操作数据
      • 父组件删除对应的数据
 <div id="app">
    <div class="container">
      <my-cart></my-cart>
    </div>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    
    var CartTitle = {
      props: ['uname'],
      template: `
        <div class="title">{{uname}}的商品</div>
      `
    }
    #  3.2 把列表数据动态渲染到页面上  
    var CartList = {
      props: ['list'],
      template: `
        <div>
          <div :key='item.id' v-for='item in list' class="item">
            <img :src="item.img"/>
            <div class="name">{{item.name}}</div>
            <div class="change">
              <a href="">-</a>
              <input type="text" class="num" />
              <a href="">+</a>
            </div>
			# 3.3  给按钮添加点击事件把需要删除的id传递过来
            <div class="del" @click='del(item.id)'>×</div>
          </div>
        </div>
      `,
      methods: {
        del: function(id){
           # 3.4 子组件中不推荐操作父组件的数据有可能多个子组件使用父组件的数据 
          # 	  我们需要把数据传递给父组件 让父组件操作数据 
          this.$emit('cart-del', id);
        }
      }
    }
    var CartTotal = {
      props: ['list'],
      template: `
        <div class="total">
          <span>总价:{{total}}</span>
          <button>结算</button>
        </div>
      `,
      computed: {
        total: function() {
          // 计算商品的总价
          var t = 0;
          this.list.forEach(item => {
            t += item.price * item.num;
          });
          return t;
        }
      }
    }
    Vue.component('my-cart',{
      data: function() {
        return {
          uname: '张三',
          list: [{
            id: 1,
            name: 'TCL彩电',
            price: 1000,
            num: 1,
            img: 'img/a.jpg'
          },{
            id: 2,
            name: '机顶盒',
            price: 1000,
            num: 1,
            img: 'img/b.jpg'
          },{
            id: 3,
            name: '海尔冰箱',
            price: 1000,
            num: 1,
            img: 'img/c.jpg'
          },{
            id: 4,
            name: '小米手机',
            price: 1000,
            num: 1,
            img: 'img/d.jpg'
          },{
            id: 5,
            name: 'PPTV电视',
            price: 1000,
            num: 2,
            img: 'img/e.jpg'
          }]
        }
      },
      # 3.1 从父组件把商品列表list 数据传递过来 即 父向子组件传值  
      template: `
        <div class='cart'>
          <cart-title :uname='uname'></cart-title>
		  #  3.5  父组件通过事件绑定 接收子组件传递过来的数据 
          <cart-list :list='list' @cart-del='delCart($event)'></cart-list>
          <cart-total :list='list'></cart-total>
        </div>
      `,
      components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
      },
      methods: {
        # 3.6    根据id删除list中对应的数据        
        delCart: function(id) {
          // 1、找到id所对应数据的索引
          var index = this.list.findIndex(item=>{
            return item.id == id;
          });
          // 2、根据索引删除对应数据
          this.list.splice(index, 1);
        }
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {

      }
    });

  </script>
</body>
</html>
4. 实现组件更新数据功能 上
  • 将输入框中的默认数据动态渲染出来
  • 输入框失去焦点的时候 更改商品的数量
  • 子组件中不推荐操作数据 把这些数据传递给父组件 让父组件处理这些数据
  • 父组件中接收子组件传递过来的数据并处理
 <div id="app">
    <div class="container">
      <my-cart></my-cart>
    </div>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    
    var CartTitle = {
      props: ['uname'],
      template: `
        <div class="title">{{uname}}的商品</div>
      `
    }
    var CartList = {
      props: ['list'],
      template: `
        <div>
          <div :key='item.id' v-for='item in list' class="item">
            <img :src="item.img"/>
            <div class="name">{{item.name}}</div>
            <div class="change">
              <a href="">-</a>
				# 1. 将输入框中的默认数据动态渲染出来
				# 2. 输入框失去焦点的时候 更改商品的数量  需要将当前商品的id 传递过来
              <input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/>
              <a href="">+</a>
            </div>
            <div class="del" @click='del(item.id)'>×</div>
          </div>
        </div>
      `,
      methods: {
        changeNum: function(id, event){
          # 3 子组件中不推荐操作数据  因为别的组件可能也引用了这些数据
          #  把这些数据传递给父组件 让父组件处理这些数据
          this.$emit('change-num', {
            id: id,
            num: event.target.value
          });
        },
        del: function(id){
          // 把id传递给父组件
          this.$emit('cart-del', id);
        }
      }
    }
    var CartTotal = {
      props: ['list'],
      template: `
        <div class="total">
          <span>总价:{{total}}</span>
          <button>结算</button>
        </div>
      `,
      computed: {
        total: function() {
          // 计算商品的总价
          var t = 0;
          this.list.forEach(item => {
            t += item.price * item.num;
          });
          return t;
        }
      }
    }
    Vue.component('my-cart',{
      data: function() {
        return {
          uname: '张三',
          list: [{
            id: 1,
            name: 'TCL彩电',
            price: 1000,
            num: 1,
            img: 'img/a.jpg'
          }]
      },
      template: `
        <div class='cart'>
          <cart-title :uname='uname'></cart-title>
			# 4  父组件中接收子组件传递过来的数据 
          <cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list>
          <cart-total :list='list'></cart-total>
        </div>
      `,
      components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
      },
      methods: {
        changeNum: function(val) {
          //4.1 根据子组件传递过来的数据,跟新list中对应的数据
          this.list.some(item=>{
            if(item.id == val.id) {
              item.num = val.num;
              // 终止遍历
              return true;
            }
          });
        },
        delCart: function(id) {
          // 根据id删除list中对应的数据
          // 1、找到id所对应数据的索引
          var index = this.list.findIndex(item=>{
            return item.id == id;
          });
          // 2、根据索引删除对应数据
          this.list.splice(index, 1);
        }
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {

      }
    });

  </script>
5. 实现组件更新数据功能 下
  • 子组件通过一个标识符来标记对用的用户点击 + - 或者输入框输入的内容
  • 父组件拿到标识符更新对应的组件
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style type="text/css">
    .container {
    }
    .container .cart {
      width: 300px;
      margin: auto;
    }
    .container .title {
      background-color: lightblue;
      height: 40px;
      line-height: 40px;
      text-align: center;
      /*color: #fff;*/  
    }
    .container .total {
      background-color: #FFCE46;
      height: 50px;
      line-height: 50px;
      text-align: right;
    }
    .container .total button {
      margin: 0 10px;
      background-color: #DC4C40;
      height: 35px;
      width: 80px;
      border: 0;
    }
    .container .total span {
      color: red;
      font-weight: bold;
    }
    .container .item {
      height: 55px;
      line-height: 55px;
      position: relative;
      border-top: 1px solid #ADD8E6;
    }
    .container .item img {
      width: 45px;
      height: 45px;
      margin: 5px;
    }
    .container .item .name {
      position: absolute;
      width: 90px;
      top: 0;left: 55px;
      font-size: 16px;
    }

    .container .item .change {
      width: 100px;
      position: absolute;
      top: 0;
      right: 50px;
    }
    .container .item .change a {
      font-size: 20px;
      width: 30px;
      text-decoration:none;
      background-color: lightgray;
      vertical-align: middle;
    }
    .container .item .change .num {
      width: 40px;
      height: 25px;
    }
    .container .item .del {
      position: absolute;
      top: 0;
      right: 0px;
      width: 40px;
      text-align: center;
      font-size: 40px;
      cursor: pointer;
      color: red;
    }
    .container .item .del:hover {
      background-color: orange;
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="container">
      <my-cart></my-cart>
    </div>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    
    var CartTitle = {
      props: ['uname'],
      template: `
        <div class="title">{{uname}}的商品</div>
      `
    }
    var CartList = {
      props: ['list'],
      template: `
        <div>
          <div :key='item.id' v-for='item in list' class="item">
            <img :src="item.img"/>
            <div class="name">{{item.name}}</div>
            <div class="change">
			  # 1.  + - 按钮绑定事件 
              <a href="" @click.prevent='sub(item.id)'>-</a>
              <input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/>
              <a href="" @click.prevent='add(item.id)'>+</a>
            </div>
            <div class="del" @click='del(item.id)'>×</div>
          </div>
        </div>
      `,
      methods: {
        changeNum: function(id, event){
          this.$emit('change-num', {
            id: id,
            type: 'change',
            num: event.target.value
          });
        },
        sub: function(id){
          # 2 数量的增加和减少通过父组件来计算   每次都是加1 和 减1 不需要传递数量   父组件需要一个类型来判断 是 加一 还是减1  以及是输入框输入的数据  我们通过type 标识符来标记 不同的操作   
          this.$emit('change-num', {
            id: id,
            type: 'sub'
          });
        },
        add: function(id){
         # 2 数量的增加和减少通过父组件来计算   每次都是加1 和 减1 不需要传递数量   父组件需要一个类型来判断 是 加一 还是减1  以及是输入框输入的数据  我们通过type 标识符来标记 不同的操作
          this.$emit('change-num', {
            id: id,
            type: 'add'
          });
        },
        del: function(id){
          // 把id传递给父组件
          this.$emit('cart-del', id);
        }
      }
    }
    var CartTotal = {
      props: ['list'],
      template: `
        <div class="total">
          <span>总价:{{total}}</span>
          <button>结算</button>
        </div>
      `,
      computed: {
        total: function() {
          // 计算商品的总价
          var t = 0;
          this.list.forEach(item => {
            t += item.price * item.num;
          });
          return t;
        }
      }
    }
    Vue.component('my-cart',{
      data: function() {
        return {
          uname: '张三',
          list: [{
            id: 1,
            name: 'TCL彩电',
            price: 1000,
            num: 1,
            img: 'img/a.jpg'
          },{
            id: 2,
            name: '机顶盒',
            price: 1000,
            num: 1,
            img: 'img/b.jpg'
          },{
            id: 3,
            name: '海尔冰箱',
            price: 1000,
            num: 1,
            img: 'img/c.jpg'
          },{
            id: 4,
            name: '小米手机',
            price: 1000,
            num: 1,
            img: 'img/d.jpg'
          },{
            id: 5,
            name: 'PPTV电视',
            price: 1000,
            num: 2,
            img: 'img/e.jpg'
          }]
        }
      },
      template: `
        <div class='cart'>
          <cart-title :uname='uname'></cart-title>	
		# 3 父组件通过事件监听   接收子组件的数据  
          <cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list>
          <cart-total :list='list'></cart-total>
        </div>
      `,
      components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
      },
      methods: {
        changeNum: function(val) {
          #4 分为三种情况:输入框变更、加号变更、减号变更
          if(val.type=='change') {
            // 根据子组件传递过来的数据,跟新list中对应的数据
            this.list.some(item=>{
              if(item.id == val.id) {
                item.num = val.num;
                // 终止遍历
                return true;
              }
            });
          }else if(val.type=='sub'){
            // 减一操作
            this.list.some(item=>{
              if(item.id == val.id) {
                item.num -= 1;
                // 终止遍历
                return true;
              }
            });
          }else if(val.type=='add'){
            // 加一操作
            this.list.some(item=>{
              if(item.id == val.id) {
                item.num += 1;
                // 终止遍历
                return true;
              }
            });
          }
        }
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {

      }
    });

  </script>
</body>
</html>

路由

计算属性 computed

  • 模板中放入太多的逻辑会让模板过重且难以维护 使用计算属性可以让模板更加的简洁
  • 计算属性是基于它们的响应式依赖进行缓存的
  • computed比较适合对多个变量或者对象进行处理后返回一个结果值,也就是数多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化

image-20201129162124761

 <div id="app">
     <!--  
        当多次调用 reverseString  的时候 
        只要里面的 num 值不改变 他会把第一次计算的结果直接返回
		直到data 中的num值改变 计算属性才会重新发生计算
     -->
    <div>{{reverseString}}</div>
    <div>{{reverseString}}</div>
     <!-- 调用methods中的方法的时候  他每次会重新调用 -->
    <div>{{reverseMessage()}}</div>
    <div>{{reverseMessage()}}</div>
  </div>
  <script type="text/javascript">
    /*
      计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存
    */
    var vm = new Vue({
      el: '#app',
      data: {
        msg: 'hello',
        num: 100
      },
      methods: {
        reverseMessage: function(){
          console.log('methods')
          return this.msg.split('').reverse().join('');
        }
      },
      //computed  属性 定义 和 data 以及 methods 平级 
      computed: {
        //  reverseString   这个是我们自己定义的名字 
        reverseString: function(){
          console.log('computed')
          var total = 0;
          //  当data 中的 num 的值改变的时候  reverseString  会自动发生计算  
          for(var i=0;i<=this.num;i++){
            total += i;
          }
          // 这里一定要有return 否则 调用 reverseString 的 时候无法拿到结果    
          return total;
        }
      }
    });
  </script>

监听器 watch

  • 使用watch来响应数据的变化
  • 一般用于异步或者开销较大的操作
  • watch 中的属性 一定是data 中 已经存在的数据
  • 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听

watch 监听data数据的改变

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>watch 监听data数据的改变</title>
    </head>
    <body>
        <div id="app">
            <button @click="b = b +2">改变数据</button>
            {{msg}}
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script type="text/javascript">
            new Vue({
                el:'#app',
                data: {
                    b:0,
                    msg:null
                },
                watch:{
                    /* 监听变量b,变量b改变后,会调用次方法*/
                    b:function (newVal,val) {
                        this.msg = `新的数据 = ${newVal},旧的数据= ${val}`
                    }
                }
            })
        </script>
    </body>
</html>

watch 监听路由地址的改变

image-20201129162107054

过滤器

  • Vue.js允许自定义过滤器,可被用于一些常见的文本格式化。
  • 过滤器可以用在两个地方:双花括号插值和v-bind表达式。
  • 过滤器应该被添加在JavaScript表达式的尾部,由“管道”符号指示
  • 支持级联操作
  • 过滤器不改变真正的data,而只是改变渲染的结果,并返回过滤后的版本
  • 全局注册时是filter,没有s的。而局部过滤器是filters,是有s的
<div id="app">
    <input type="text" v-model='msg'>
    <!-- upper 被定义为接收单个参数的过滤器函数,表达式  msg  的值将作为参数传入到函数中进行处理 -->
    <div>{{msg | upper}}</div>
    <!--  
        支持级联操作
        将msg的值,传入upper过滤器中,然后将upper过滤后的结果传递给lower过滤器进行处理
	-->
    <div>{{msg | upper | lower}}</div>
    <div :abc='msg | upper'>测试数据</div>
</div>

<script type="text/javascript">
    //  lower  为全局过滤器     
    Vue.filter('lower', function(val) {
        return val.charAt(0).toLowerCase() + val.slice(1);
    });
    
    var vm = new Vue({
        el: '#app',
        data: {
            msg: ''
        },
        //filters  属性 定义 和 data 已经 methods 平级 
        //  定义filters 中的过滤器为局部过滤器 
        filters: {
            //   upper  自定义的过滤器名字 
            //    upper 被定义为接收单个参数的过滤器函数,表达式  msg  的值将作为参数传入到函数中
            upper: function(val) {
                //  过滤器中一定要有返回值 这样外界使用过滤器的时候才能拿到结果
                return val.charAt(0).toUpperCase() + val.slice(1);
            }
        }
    });
</script>

过滤器中传递参数

    <div id="box">
        <!--
			filterA 被定义为接收三个参数的过滤器函数。
  			其中 message 的值作为第一个参数,
			普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。
		-->
        {{ message | filterA('arg1', 'arg2') }}
    </div>
    <script>
        // 在过滤器中 第一个参数 对应的是  管道符前面的数据   n  此时对应 message
        // 第2个参数  a 对应 实参  arg1 字符串
        // 第3个参数  b 对应 实参  arg2 字符串
        Vue.filter('filterA',function(n,a,b){
            if(n<10){
                return n+a;
            }else{
                return n+b;
            }
        });
        
        new Vue({
            el:"#box",
            data:{
                message: "哈哈哈"
            }
        })

    </script>

Vue生命周期

  • 事物从出生到死亡的过程
  • Vue实例从创建 到销毁的过程 ,这些过程中会伴随着一些函数的自调用。我们称这些函数为钩子函数
函数效果
beforeCreate在实例初始化之后,数据观测和事件配置之前被调用 此时data 和 methods 以及页面的DOM结构都没有初始化 什么都做不了
created在实例创建完成后被立即调用,此时data 和 methods已经可以使用 但是页面还没有渲染出来
beforeMount在挂载开始之前被调用 此时页面上还看不到真实数据 只是一个模板页面而已
mountedel被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。 数据已经真实渲染到页面上,注意如果要操作页面上的DOM节点最早也要在此方法中进行操作
beforeUpdate当data中的数据进行改变时,触发此方法,此时data数据已经改变,但是页面还没有进行更新
updated当执行到此方法是,页面已经进行更新,(页面和data数据已经保存同步了)
beforeDestroy实例销毁之前调用
destroyed实例销毁后调用
new Vue({
    data:{},		// vue实例对象 的绑定参数
    methods: {},	// Vue实例对象 的私有方法
    components:{},	// vue实例对象 的私有的组件
    beforeCreate() {},      // Vue实例创建前执行,此时data,和methods还没有初始化
    created() {},           // 在此方法执行时,Vue实例的date(参数)和methods(方法)已经被初始化

    beforeMount() {},       // 表示Vue实例已经编译完成,但是还没有把实例放入页面,页面数据还没有进行更新
    mounted() {},     		// 表示已经将Vue实例数据放入页面中,此方法执行完表示Vue实例已经完全创建好了,注意如果要操作页面上的DOM节点最早也要在此方法中进行操作

    beforeUpdate(){},		// 当data中的数据进行改变时,触发此方法,此时data数据已经改变,但是页面还没有进行更新
    updated(){},			// 当执行到此方法是,页面已经进行更新,(页面和data数据已经保存同步了)
    beforeDestroy(){},		// vue实例销毁前执行
    destroyed(){}			// Vue实例完全销毁
})

数组变异方法

  • 在 Vue 中,直接修改对象属性的值无法触发响应式。当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变
  • 变异数组方法即保持数组方法原有功能不变的前提下对其进行功能拓展
push()往数组最后面添加一个元素,成功返回当前数组的长度
pop()删除数组的最后一个元素,成功返回删除元素的值
shift()删除数组的第一个元素,成功返回删除元素的值
unshift()往数组最前面添加一个元素,成功返回当前数组的长度
splice()有三个参数,第一个是想要删除的元素的下标(必选),第二个是想要删除的个数(必选),第三个是删除 后想要在原位置替换的值
sort()sort() 使数组按照字符编码默认从小到大排序,成功返回排序后的数组
reverse()reverse() 将数组倒序,成功返回倒序后的数组

替换数组

  • 不会改变原始数组,但总是返回一个新数组
filterfilter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
concatconcat() 方法用于连接两个或多个数组。该方法不会改变现有的数组
sliceslice() 方法可从已有的数组中返回选定的元素。该方法并不会修改数组,而是返回一个子数组

动态数组响应式数据

  • Vue.set(a,b,c) 让 触发视图重新更新一遍,数据动态起来
  • a是要更改的数据 、 b是数据的第几项、 c是更改后的数据

图书列表案例

  • 静态列表效果
  • 基于数据实现模板效果
  • 处理每行的操作按钮
1、 提供的静态数据
  • 数据存放在vue 中 data 属性中
var vm = new Vue({
    el: '#app',
    data: {
        books: [{id: 1,name: '三国演义',date: ''},
                {id: 2,name: '水浒传',date: ''},
                {id: 3,name: '红楼梦',date: ''},
                {id: 4,name: '西游记',date: ''}]},
}); 
2、 把提供好的数据渲染到页面上
  • 利用 v-for循环 遍历 books 将每一项数据渲染到对应的数据中
<style type="text/css">
    .grid {
        margin: auto;
        width: 500px;
        text-align: center;
    }
    .grid table {
        width: 100%;
        border-collapse: collapse;
    }
    .grid th,td {
        padding: 10;
        border: 1px dashed orange;
        height: 35px;
        line-height: 35px;
    }
    .grid th {
        background-color: orange;
    }
</style>

<div id="app">
    <div class="grid">
        <table>
            <thead>
                <tr>
                    <th>编号</th>
                    <th>名称</th>
                    <th>时间</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <tr :key='item.id' v-for='item in books'>
                    <td>{{item.id}}</td>
                    <td>{{item.name}}</td>
                    <td>{{item.date}}</td>
                    <td>
                        <a href="" @click.prevent>修改</a>
                        <span>|</span>
                        <a href="" @click.prevent>删除</a>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</div>
3、 添加图书
  • 通过双向绑定获取到输入框中的输入内容
  • 给按钮添加点击事件
  • 把输入框中的数据存储到 data 中的 books 里面
<style type="text/css">
    .grid {
        margin: auto;
        width: 530px;
        text-align: center;
    }
    .grid table {
        border-top: 1px solid #C2D89A;
        width: 100%;
        border-collapse: collapse;
    }
    .grid th,td {
        padding: 10px;
        border: 1px dashed #F3DCAB;
        height: 35px;
        line-height: 35px;
    }
    .grid th {
        background-color: #F3DCAB;
    }
    .grid .book {
        padding-bottom: 10px;
        padding-top: 5px;
        background-color: #F3DCAB;
    }
</style>


<div id="app">
    <div class="grid">
        <div>
            <h1>图书管理</h1>
            <div class="book">
                <div>
                    <label for="id">编号:</label>
                    <input type="text" id="id" v-model='id'>
                    <label for="name">名称:</label>
                    <input type="text" id="name" v-model='name'>
                    <button @click='handle'>提交</button>
                </div>
            </div>
        </div>
        <table>
            <thead>
                <tr>
                    <th>编号</th>
                    <th>名称</th>
                    <th>时间</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <tr :key='item.id' v-for='item in books'>
                    <td>{{item.id}}</td>
                    <td>{{item.name}}</td>
                    <td>{{item.date}}</td>
                    <td>
                        <a href="" @click.prevent>修改</a>
                        <span>|</span>
                        <a href="" @click.prevent>删除</a>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</div>
<script type="text/javascript">
    /*
      图书管理
    */
    var vm = new Vue({
        el: '#app',
        data: {
            id: '',
            name: '',
            books: [{id: 1,name: '三国演义',date: ''},
                    {id: 2,name: '水浒传',date: ''},
                    {id: 3,name: '红楼梦',date: ''},
                    {id: 4,name: '西游记',date: ''}]
        },
        methods: {
            handle: function(){
                // 添加图书
                var book = {};
                book.id = this.id;
                book.name = this.name;
                book.date = '';
                this.books.push(book);
                // 清空表单
                this.id = '';
                this.name = '';
            }
        }
    });
</script>

4、修改图书-上
  • 点击修改按钮的时候 获取到要修改的书籍名单
    • 4.1 给修改按钮添加点击事件, 需要把当前的图书的id 传递过去 这样才知道需要修改的是哪一本书籍
  • 把需要修改的书籍名单填充到表单里面
    • 4.2 根据传递过来的id 查出books 中 对应书籍的详细信息
    • 4.3 把获取到的信息填充到表单
<div id="app">
    <div class="grid">
        <div>
            <h1>图书管理</h1>
            <div class="book">
                <div>
                    <label for="id">
                        编号:
                    </label>
                    <input type="text" id="id" v-model='id' :disabled="flag">
                    <label for="name">
                        名称:
                    </label>
                    <input type="text" id="name" v-model='name'>
                    <button @click='handle'>提交</button>
                </div>
            </div>
        </div>
        <table>
            <thead>
                <tr>
                    <th>编号</th>
                    <th>名称</th>
                    <th>时间</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <tr :key='item.id' v-for='item in books'>
                    <td>{{item.id}}</td>
                    <td>{{item.name}}</td>
                    <td>{{item.date}}</td>
                    <td>
                        <!--- 
4.1  给修改按钮添加点击事件,  需要把当前的图书的id 传递过去 
这样才知道需要修改的是哪一本书籍
--->  
                        <a href="" @click.prevent='toEdit(item.id)'>修改</a>
                        <span>|</span>
                        <a href="" @click.prevent>删除</a>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</div>
<script type="text/javascript">
    /*
      图书管理
    */
    var vm = new Vue({
        el: '#app',
        data: {
            flag: false,
            id: '',
            name: '',
            books: [{id: 1,name: '三国演义',date: ''},
                    {id: 2,name: '水浒传',date: ''},
                    {id: 3,name: '红楼梦',date: ''},
                    {id: 4,name: '西游记',date: ''}]
        },
        methods: {
            handle: function(){
                // 3.4 定义一个新的对象book 存储 获取到输入框中 书 的id和名字 
                var book = {};
                book.id = this.id;
                book.name = this.name;
                book.date = '';
                // 3.5 把book  通过数组的变异方法 push 放到    books 里面
                this.books.push(book);
                //3.6 清空输入框
                this.id = '';
                this.name = '';
            },
            toEdit: function(id){
                console.log(id)
                //4.2  根据传递过来的id 查出books 中 对应书籍的详细信息
                var book = this.books.filter(function(item){
                    return item.id == id;
                });
                console.log(book)
                //4.3 把获取到的信息填充到表单
                // this.id   和  this.name 通过双向绑定 绑定到了表单中  一旦数据改变视图自动更新
                this.id = book[0].id;
                this.name = book[0].name;
            }
        }
    });
</script>
5、修改图书-下
  • 5.1 定义一个标识符, 主要是控制 编辑状态下当前编辑书籍的id 不能被修改 即 处于编辑状态下 当前控制书籍编号的输入框禁用
  • 5.2 通过属性绑定给书籍编号的 绑定 disabled 的属性 flag 为 true 即为禁用
  • 5.3 flag 默认值为false 处于编辑状态 要把 flag 改为true 即当前表单为禁用
  • 5.4 复用添加方法 用户点击提交的时候依然执行 handle 中的逻辑如果 flag为true 即 表单处于不可输入状态 此时执行的用户编辑数据数据
<div id="app">
    <div class="grid">
      <div>
        <h1>图书管理</h1>
        <div class="book">
          <div>
            <label for="id">
              编号:
            </label>
              <!-- 5.2 通过属性绑定 绑定 disabled 的属性  flag 为 true 即为禁用      -->
            <input type="text" id="id" v-model='id' :disabled="flag">
            <label for="name">
              名称:
            </label>
            <input type="text" id="name" v-model='name'>
            <button @click='handle'>提交</button>
          </div>
        </div>
      </div>
      <table>
        <thead>
          <tr>
            <th>编号</th>
            <th>名称</th>
            <th>时间</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr :key='item.id' v-for='item in books'>
            <td>{{item.id}}</td>
            <td>{{item.name}}</td>
            <td>{{item.date}}</td>
            <td>
              <a href="" @click.prevent='toEdit(item.id)'>修改</a>
              <span>|</span>
              <a href="" @click.prevent>删除</a>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>   
<script type="text/javascript">
        /*图书管理-添加图书*/
        var vm = new Vue({
            el: '#app',
            data: {
                // 5.1  定义一个标识符, 主要是控制 编辑状态下当前编辑书籍的id 不能被修改 
                // 即 处于编辑状态下 当前控制书籍编号的输入框禁用 
                flag: false,
                id: '',
                name: '',
              
            },
            methods: {
                handle: function() {
                   /*
                     5.4  复用添加方法   用户点击提交的时候依然执行 handle 中的逻辑
                 		 如果 flag为true 即 表单处于不可输入状态 此时执行的用户编辑数据数据	
                   */ 
                    if (this.flag) {
                        // 编辑图书
                        // 5.5  根据当前的ID去更新数组中对应的数据  
                        this.books.some((item) => {
                            if (item.id == this.id) {
                                // 箭头函数中 this 指向父级作用域的this 
                                item.name = this.name;
                                // 完成更新操作之后,需要终止循环
                                return true;
                            }
                        });
                        // 5.6 编辑完数据后表单要处以可以输入的状态
                        this.flag = false;
                    //  5.7  如果 flag为false  表单处于输入状态 此时执行的用户添加数据    
                    } else { 
                        var book = {};
                        book.id = this.id;
                        book.name = this.name;
                        book.date = '';
                        this.books.push(book);
                        // 清空表单
                        this.id = '';
                        this.name = '';
                    }
                    // 清空表单
                    this.id = '';
                    this.name = '';
                },
                toEdit: function(id) {
                     /*
                     5.3  flag 默认值为false   处于编辑状态 要把 flag 改为true 即当前表单为禁					  用 
                     */ 
                    this.flag = true;
                    console.log(id)
                    var book = this.books.filter(function(item) {
                        return item.id == id;
                    });
                    console.log(book)
                    this.id = book[0].id;
                    this.name = book[0].name;
                }
            }
        });
    </script>
6、删除图书
  • 6.1 给删除按钮添加事件 把当前需要删除的书籍id 传递过来
  • 6.2 根据id从数组中查找元素的索引
  • 6.3 根据索引删除数组元素
<div id="app">
    <div class="grid">
        <div>
            <h1>图书管理</h1>
            <div class="book">
                <div>
                    <label for="id">编号:</label>
                    <input type="text" id="id" v-model='id' :disabled="flag">
                    <label for="name">名称:</label>
                    <input type="text" id="name" v-model='name'>
                    <button @click='handle'>提交</button>
                </div>
            </div>
        </div>
        <table border="1">
            <thead>
                <tr>
                    <th>编号</th>
                    <th>名称</th>
                    <th>时间</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <tr :key='item.id' v-for='item in books'>
                    <td>{{item.id}}</td>
                    <td>{{item.name}}</td>
                    <td>{{item.date}}</td>
                    <td>
                        <a href="" @click.prevent='toEdit(item.id)'>修改</a>
                        <span>|</span>
                        <a href="" @click.prevent='deleteBook(item.id)'>删除</a>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">

    /* 日期格式化 */
    Date.prototype.format = function (fmt) {
        let  o = {
            "M+": this.getMonth() + 1,                 //月份
            "d+": this.getDate(),                    //日
            "h+": this.getHours(),                   //小时
            "m+": this.getMinutes(),                 //分
            "s+": this.getSeconds(),                 //秒
            "q+": Math.floor((this.getMonth() + 3) / 3), //季度
            "S": this.getMilliseconds()             //毫秒
        };
        if (/(y+)/.test(fmt)) {
            fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
        }
        for (let k in o) {
            if (new RegExp("(" + k + ")").test(fmt)) {
                fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
            }
        }
        return fmt;
    }

    /*
      图书管理-添加图书
    */
    var vm = new Vue({
        el: '#app',
        data: {
            flag: false,
            id: '',
            name: '',
            books: [{id: 1, name: '三国演义', date: new Date().format("yyyy-MM-dd hh:mm:ss")},
                    {id: 2, name: '水浒传', date: new Date().format("yyyy-MM-dd hh:mm:ss")},
                    {id: 3, name: '红楼梦', date: new Date().format("yyyy-MM-dd hh:mm:ss")},
                    {id: 4, name: '西游记', date: new Date().format("yyyy-MM-dd hh:mm:ss")}]
        },
        methods: {
            handle: function () {
                if (this.flag) {
                    // 编辑图书
                    // 就是根据当前的ID去更新数组中对应的数据
                    this.books.some((item) => {
                        if (item.id == this.id) {
                            item.name = this.name;
                            item.date = new Date().format("yyyy-MM-dd hh:mm:ss")
                            // 完成更新操作之后,需要终止循环
                            return true;
                        }
                    });
                    this.flag = false;
                } else {
                    // 添加图书
                    var book = {};
                    book.id = this.id;
                    book.name = this.name;
                    book.date = new Date().format("yyyy-MM-dd hh:mm:ss");
                    this.books.push(book);
                }
                // 清空表单
                this.id = '';
                this.name = '';
            },
            toEdit: function (id) {
                // 禁止修改ID
                this.flag = true;
                console.log(id)
                // 根据ID查询出要编辑的数据
                var book = this.books.filter(function (item) {
                    return item.id == id;
                });
                console.log(book)
                // 把获取到的信息填充到表单
                this.id = book[0].id;
                this.name = book[0].name;
            },
            deleteBook: function (id) {
                // 删除图书
                // 根据id从数组中查找元素的索引
                // var index = this.books.findIndex(function(item){
                //   return item.id == id;
                // });
                // 根据索引删除数组元素
                // this.books.splice(index, 1);
                // -------------------------
                // 方法二:通过filter方法进行删除
                this.books = this.books.filter(function (item) {
                    return item.id != id;
                });
            }
        }
    });
</script>

常用特性应用场景

1 过滤器

  • Vue.filter 定义一个全局过滤器
 <tr :key='item.id' v-for='item in books'>
            <td>{{item.id}}</td>
            <td>{{item.name}}</td>
     		<!-- 1.3  调用过滤器 -->
            <td>{{item.date | format('yyyy-MM-dd hh:mm:ss')}}</td>
            <td>
              <a href="" @click.prevent='toEdit(item.id)'>修改</a>
              <span>|</span>
              <a href="" @click.prevent='deleteBook(item.id)'>删除</a>
            </td>
</tr>

<script>
    	#1.1  Vue.filter  定义一个全局过滤器
	    Vue.filter('format', function(value, arg) {
              function dateFormat(date, format) {
                if (typeof date === "string") {
                  var mts = date.match(/(\/Date\((\d+)\)\/)/);
                  if (mts && mts.length >= 3) {
                    date = parseInt(mts[2]);
                  }
                }
                date = new Date(date);
                if (!date || date.toUTCString() == "Invalid Date") {
                  return "";
                }
                var map = {
                  "M": date.getMonth() + 1, //月份 
                  "d": date.getDate(), //日 
                  "h": date.getHours(), //小时 
                  "m": date.getMinutes(), //分 
                  "s": date.getSeconds(), //秒 
                  "q": Math.floor((date.getMonth() + 3) / 3), //季度 
                  "S": date.getMilliseconds() //毫秒 
                };
                format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
                  var v = map[t];
                  if (v !== undefined) {
                    if (all.length > 1) {
                      v = '0' + v;
                      v = v.substr(v.length - 2);
                    }
                    return v;
                  } else if (t === 'y') {
                    return (date.getFullYear() + '').substr(4 - all.length);
                  }
                  return all;
                });
                return format;
              }
              return dateFormat(value, arg);
            })
#1.2  提供的数据 包含一个时间戳   为毫秒数
   [{
          id: 1,
          name: '三国演义',
          date: 2525609975000
        },{
          id: 2,
          name: '水浒传',
          date: 2525609975000
        },{
          id: 3,
          name: '红楼梦',
          date: 2525609975000
        },{
          id: 4,
          name: '西游记',
          date: 2525609975000
        }];
</script>

2 自定义指令

  • 让表单自动获取焦点
  • 通过Vue.directive 自定义指定
<!-- 2.2  通过v-自定义属性名 调用自定义指令 -->
<input type="text" id="id" v-model='id' :disabled="flag" v-focus>

<script>
    # 2.1   通过Vue.directive 自定义指定
	Vue.directive('focus', {
      inserted: function (el) {
        el.focus();
      }
    });

</script>

3 计算属性

  • 通过计算属性计算图书的总数
    • 图书的总数就是计算数组的长度
 <div class="total">
        <span>图书总数:</span>
     	<!-- 3.2 在页面上 展示出来 -->
        <span>{{total}}</span>
</div>

  <script type="text/javascript">
    /*
      计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存
    */
    var vm = new Vue({
      data: {
        flag: false,
        submitFlag: false,
        id: '',
        name: '',
        books: []
      },
      computed: {
        total: function(){
          // 3.1  计算图书的总数
          return this.books.length;
        }
      },
    });
  </script>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值