Vue入门——模板/事件绑定/v-bind/v-model/computed/watch/v-if/show/for/todolist

324 阅读3分钟

大纲

基础知识→案例实践→TodoList→Vue-cli→TodoList

VUE基础知识点

使用VUE不会有任何DOM的操作,着重于数据的编写。vue是数据驱动的框架。

VUE引入 VUE挂载点、模板与实例绑定方式

  • 引入:复制vue.js脚本,放到vue项目下命名为vue.js。
  • 挂载点:div标签即为vue实例的挂载点,vue只会处理挂载点内的内容。
  • 模板:挂载点内部的内容叫做模板内容,模板不仅可以写在挂载点内,也可以放在vue实例里的template属性里。
  • 实例:实例里指定挂载点和模板,vue会自动根据你的模板和数据生成最终要展示的内容。

VUE实例中的数据、事件与方法

模板内容+数据

  • {{}}这种语法叫做插值表达式,<h1>{{number}}</h1>
  • <h1 v-text="number"></h1> 会转义
  • <h1 v-html="number"></h1> 不会转义

事件的绑定

在想要改变的标签上,要写一个绑定事件。 事件写在VUE实例的methods对象里面。

<body>
    <div id="root">
        <div v-on:click="handleClick">{{content}}</div>
        <!-- 给标签绑定一个click事件-->
    </div>
    <script>
        new Vue({ //新建一个VUE实例
            el:"#root",//挂载点 指定让VUE接管页面上的哪一个元素
            data: {//数据放到data里
                msg:"hello, world!",
                content:"hello"
            },
            methods: {
                handleClick:function(){
                    this.content="world"//this.content指的是VUE实例下的data里的vue变量
                }
            },
        })
    </script>
</body>

事件绑定的简写v-on:click@click

VUE中的属性绑定和双向绑定语法

在VUE里要想改变数据的显示,不用去改变DOM,直接去改变数据。

属性的绑定

  • 让标签里的属性等于实例里数据的某个值:
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>属性绑定和双向绑定语法</title>
        <script src="./vue.js"></script>
    </head>
    <body>
        <div id="root">
            <div v-bind:title="'dell li '+ title">hello world</div>
        </div>
    
        <script>
            new Vue({
                el:"#root",
                data:{
                    title:"this is hello world"
                }
            })
        </script>
    </body>  
    

属性绑定的简写v-bind:title:bind

数据的双向绑定

数据能决定页面的显示,那么如何让页面上的操作去修改数据呢?

  • 学习一个input框和div双向绑定的实例。v-model
    <body>
        <div id="root">
            <div :title="'dell li '+ title">hello world</div>
            <input v-model="content"/>
            <div>{{content}}</div>
        </div>
    
        <script>
            new Vue({
                el:"#root",
                data:{
                    title:"this is hello world",
                    content:"this is content"
                }
            })
        </script>
    </body>
    
    image.png

Vue 中的计算属性 computed 和和侦听器 watch

计算属性

什么是计算属性? 根据其他属性计算出来的新的结果。
特点:计算属性自带缓存机制,语法较简单。

  • fullName这个属性是由firstName和lastName属性计算得到的,需要计算属性computed。(和React中的reselect库特别像)

    <body>
        <div id="root">
            姓:<input v-model="firstName">
            名:<input v-model="lastName">
            <div>{{fullName}}</div>
        </div>
        <script>
            new Vue({
                el: "#root",
                data:{
                    firstName:'',
                    lastName:''
                },
                computed:{
                    fullName: function(){//fullNAme是一个计算属性 它的值是一个函数
                        return this.firstName+' '+this.lastName
                    }
                }
            })
        </script>
    

    image.png

  • 计算属性是内置缓存的,当fullName依赖的值firstName和lastName没变的时候,fullName会使用上一次的缓存值,当其中一个属性发生改变的时候,fullName会重新计算。

  • 利用方法methods也可以实现上述功能。但不如计算属性有效,没有内置缓存机制,性能没有计算属性高。

     <div>{{fullName()}}</div>
    
    methods:{
        fullName: function(){
            return this.firstName+' '+this.lastName
        }
    
  • 侦听器也可以实现这个功能,侦听first和lastname的变化,加一个冗余的data:fullName ,性能还可以,因为也有缓存的功能。但是watch的语法复杂了很多,所以一个功能优先使用computed8L0@P_Q7QB8R$ZB4JHFQC~P.png

侦听器

功能:监测某个数据的变化,一旦某个数据发生变化,就在侦听器里去做自己的业务逻辑。

<body>
   <div id="root">
       姓:<input v-model="firstName">
       名:<input v-model="lastName">
       <div>{{fullName}}</div>
       <div>{{count}}</div>
   </div>
   <script>
       new Vue({
           el: "#root",
           data:{
               firstName:'',
               lastName:'',
               count:0
           },
           computed:{
               fullName: function(){//fullNAme是一个计算属性
                   return this.firstName+' '+this.lastName
               }
           },
           watch:{//侦听器
               fullName:function(){
                   this.count++
               }
           }
       })
   </script>
</body>

Vue中常见的三个指令——v-if、v-show、v-for

v-if 条件渲染

  • v-if后跟的表达式的返回值,决定了这个标签是否真正地被挂载到页面之上。

    body>
        <div id="root">
            <div v-if="show">hello world</div> 
            <!-- v-if的值是false的时候div里的东西就不存在 -->
            <button @click="handleClick">toggle</button>
        </div>
        <script>
            new Vue({
                el:"#root",
                data:{
                    show:false
                },
                methods: {
                    handleClick:function(){
                        this.show=!this.show; //每次点一次按钮 show都取反一次
                    }
                },
            })
        </script>
    </body>
    

    image.png

  • v-if还能和else紧贴着一起,当show值改变成true的时候,会显示Hello world! 1636547214.jpg

  • v-if v-else-if v-else 90BK5)2YJ_QQ8W)%~N(SWE0.png

  • Vue在重新渲染页面的时候,会尽量地复用页面上已经存在的DOM。
    当你打开网页时,显示的是邮箱名:input,你在input中输入自己的邮箱名,然后在调试工具里把show改成true,你会发现邮箱名变成了用户名,但是input还是那个input,也没有被清空。 image.png

    • 解决方法:要有个key值 1636548028(1).jpg

v-show

  • 已经渲染到页面上了。只不过加了一个display:none;的形式。比v-if性能更高一些。
    <div id="root">
        <div v-show="show">hello world</div> 
        <!-- v-show的值是false的时候div加一个属性 -->
        <button @click="handleClick">toggle</button>
    </div>
    
    1636430530(1).png

v-for

列表循环

  • 把数据中列表的数据循环展示到一个ul中,v-for使用时,加一个key属性,提升每一项渲染的效率,要求每一个值都不同。

    <div id="root">
            <ul>
                <!-- 遍历list这个数据项 -->
                <li v-for="item of list" :key="item">{{item}}</li>
            </ul>
        </div>
        <script>
            new Vue({
                el:"#root",
                data:{
                    list:[1,2,3]
                }
            })
        </script>
    

    image.png

  • key 的作用详解

    v-for 中 key 的作用是什么?如果不添加 key 程序会不会报错?为了搞清楚这个问题,我们需要先了解什么是虚拟 DOM 以及 diff 算法。

    • 虚拟 DOM

      虚拟 DOM 简称 VNode ,是一棵以 JavaScript 对象作为基础的树, 是对真实 DOM 的抽象。虚拟 DOM 经过一系列转换可以变成真实 DOM 并渲染到页面上。我们可以用虚拟 DOM 来描述一个简单的 vue 组件,如下所示:

      <template>
        <span class="demo" v-show="isShow"> This is a span. </span>
      </template>
      

      对应的 VNode:

      {
          tag: 'span',
          data: {
              /* 指令集合数组 */
              directives: [
                  {
                      /* v-show指令 */
                      rawName: 'v-show',
                      expression: 'isShow',
                      name: 'show',
                      value: true
                  }
              ],
              /* 静态class */
              staticClass: 'demo'
          },
          text: undefined,
          children: [
              /* 子节点是一个文本VNode节点 */
              {
                  tag: undefined,
                  data: undefined,
                  text: 'This is a span.',
                  children: undefined
              }
          ]
      }
      
    • diff 算法

      假设现在我们已经有一棵 VNode 树,现在又有一棵 VNode 新树,现在我们需要把最新的这棵树更新上去要怎么操作呢? 最简单粗暴的方式就是直接拿最新的树把之前的树替换掉,然后页面重新渲染即可,这样确实可以解决问题,但是肯定不是最佳解决方案。我们肯定是想可不可以只更新那些变化的地方,那些没有变化的元素不更新,这样不就更高效吗? 要想实现这种高效的更新,就需要我们在两棵树中找出变化的地方从而进行更新。而 diff 算法就是专门来干这件事情的一种算法。 接下来我们再来说说 v-for 中的 key。不使用 key 的一段代码:

      <template>
        <ul>
          <li v-for="v in arr">{{v}}</li>
        </ul>
      </template>
      <script>
        export default {
          data() {
            arr: ["A", "B", "C"];
          },
          create() {
            setTimeout(() => {
              this.arr.splice(1, 0, "D"); // [A,D,B,C]
            }, 2000);
          },
        };
      </script>
      

      上面的代码把一个数组 [A,B,C] 变成 [A,D,B,C] 同时页面也更新,一共做了两次更新一次插入操作。(diff过程在下边)
      有 key 的情况:

      <template>
        <ul>
          <li v-for="v in arr" :key="v">{{v}}</li>
        </ul>
      </template>
      <script>
        export default {
          data() {
            arr: ["A", "B", "C"];
          },
          create() {
            setTimeout(() => {
              this.arr.splice(1, 0, "D"); // [A,D,B,C]
            }, 2000);
          },
        };
      </script>
      

      diff 过程如下所示,在有 key 的情况下,只是执行了一次插入操作。 image.png

      v-for 中 key 的作用就是让每个被循环元素有一个唯一的身份标识,这样 Vue 就可以更加精准的追踪到每个元素,从而更加高效的更新页面。当然如果没有 key 程序也不会报错,只不过此时的程序变得非常的“笨”。

  • 列表渲染进阶key参考TodoList
    把index作为key值,频繁操作DOM元素相对应的数据的时候,还是费性能的,无法充分复用DOM节点。那么用什么呢? 后端返给前端数据的时候,一般会携带一个唯一数据条目的标识符id和数据内容: image.png 使用:key=item.id就可以了。 image.png

  • 通过数组变异方法改变数组push pop shift unshift splice sort reverse去改变数组!!

  • 通过改变数据的引用地址去改变数组image.png

  • template模板占位符可以用来包裹一些元素,但是在循环的过程中并不会真正被渲染到页面上 image.png

  • set方法 image.png

对象循环

image.png image.png image.png set方法改变对象元素 image.png

Vue中的组件

组件与实例的关系

一个组件就是一个实例,任何VUE项目都是由千千万万个实例组成。在TodoList案例中,把li标签作为组件,组件又是实例。根组件若你不定义模板,则要去他的挂载点下找模板。 父组件到子组件传值,是通过属性的形式传递的。

TodoList

TodoList的个功能

  • 实现输入inputValue显示到ul的li里面,需要监听button的点击事件,点击的时候,触发并把输入的push到数据的list里,模板要接受list,写一个v-for
  • VUE不是操作DOM而是操作数据
  • 需求:点击li删除该li
  • 步骤:
    • 1:写最外层父组件(实例),它的模板在挂载点下。
    • 2:写最外层实例的模板
    • 3:写子组件
    • 4:实现父子组件之间的传值,父组件通过属性传值给子组件,子组件通过向外触发一个事件传给父组件。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TodoList</title>
    <script src="./vue.js"></script>
</head>
<body>
    <!-- 2. 最外层实例的模板 使用组件-->
    <div id="root">
        <div>
            <input v-model="inputValue" />
            <button @click="handleClick">Submit</button>
        </div>
        <ul>
            <!-- <li v-for="(item,index) of list" :key="index">
                {{item}}
            </li> -->
            <!-- <li>2</li> -->
            <todo-item 
                v-for="(item, index) of list"
                :key="index"
                :content="item"
                :index="index"
                @delete="handleDelete"
                > <!--传参 用content属性 把item传递给todo-item组件-->
            </todo-item>
        </ul>
    </div>
    
    
    <script>
        // 3.定义一个全局组件 不需要声明注册
        Vue.component('todo-item', {
            props:['content','index'],//接受从外部传递过来的一个名字叫content的属性
            template:'<li @click="handleClick">{{content}}{{index}}</li>',
            methods: {
                handleClick:function(){
                    this.$emit('delete',this.index)//向外触发(发布)一个事件,告诉父组件要删除我自己 触发一个自定义事件delete 传递一个index值
   
                }
            }
        })
        //如果定义局部组件的话,需要做一个组件的注册(声明)如下行
        // var TodoItem = {
        //     template:'<li>item</li>'
        // }
        
        
        
        //1.最外层父组件:VUE实例 它有自己的模板是挂载点下的内容
        new Vue({
            el:"#root",
            // components:{
            //     'todo-item':TodoItem//如果定义的是局部组件,需要做一个声明(注册)
            // },
            data:{
                inputValue:'hello',
                list:[]
            },
            methods: {
                handleClick:function(){
                    this.list.push(this.inputValue)
                    this.inputValue=''
                },
                handleDelete:function(index){
                    this.list.splice(index,1)
                }
            }
        })
    </script>
</body>
</html>

Vue-cli的简介与使用

快速构建一个VUE项目,同时自带了webpack各种配置,迅速上手工程级别VUE项目的开发。

node -v
npm -v
vue install --gloabl vue-cli
vue init webpack my-project
cd my-project
npm run dev
  • build:webpack配置文件
  • config:针对开发环境和线上环境的配置文件
  • node_moudles 项目依赖
  • src 源码 主要编写
    • main.js 整个项目的入口文件
  • static 静态资源
  • babel编译 浏览器配置 eslint检测规则。
  • index.html整个网页最外层的html文件。

App.vue单文件组件 :一个.vue文件里包含了一个组件里必须的所有内容

  • template 模板
  • script 逻辑
  • css 样式

以前会把html和各种逻辑耦合在一起进行编码,用了vue-cli提供了一种单文件组件编码的形式,一个vue问价里就是一个完整的组件。

全局样式和局部样式

scoped