第二天 TODOMVC

一 、准备工作

  1. vue-TodoMVC

  2. 演示效果 :

    当前任务:敲代码、视频、游戏

  3. 下载模板地址

    git clone https://github.com/tastejs/todomvc-app-template.git

  4. 安装依赖包 : npm i


二、配置 vue

  1. 安装 vue : npm i vue

  2. 导入 vue : <script src="./node_modules//vue/dist/vue.js"></script>

    在 index.html里的app.js 导入之前导入,因为 app.js 里 就要用到 vue 了

  3. 实例化 vue :在app.js中创建 vue 示例,并设置好边界 el:'#app'

    找到 index.html ,给 section 标签添加一个 id

  4. 测试 vue :

    data 中随便来一个 msg 看能不能显示到视图中


三、列表渲染

  1. todoList 数组里面都是对象 id name done

    id : (唯一标识 ) 删除需要
    name : (名称) 展示需要
    done : (是否完成任务) 选中状态需要

  2. 去掉线 : :class="{ completed : item.done }"

  3. 多选框选中状态 : v-model='item.done'

# 列表展示
1. 数据 数组 => list
2. 遍历 v-for='item in list' :key='item.id'
3. 处理任务名称 : {{ item.name }}
4. 处理选中状态 <input  v-model='item.done' >
5. 横线 :class='{ completed : item.done }'

四、添加任务

# 添加任务
- 拿到数据
1. 回车 注册键盘事件 @keyup='addTodo'
2. 拿到数据 input <===> todoName , this.todoName
3. if(e.keyCode===13) { 按回车键了 }

- 添加任务
1. 判断文本框不能为空
2. 处理 id , 数组里最后一个元素的 id+1
   const id =
   this.list.length === 0 ? 1 : this.list[this.list.length - 1].id + 1
3. 添加任务
4. 添加任务完清空内容
// 添加任务
addTodo( e ) {
    console.log('添加任务',this.todoName);

    // 0. 判断不能为空
    if (this.todoName.trim() === '') {
        return;
    }

    // 1. 添加任务
    if (e.keyCode === 13) {
        this.todoList.push({
            id : 4,
            name : this.todoName,
            done:false
        })
        //2. 清空文本框内容
        this.todoName = ''
    }

}

五 、删除任务

传 id,过滤不是 id 的

this.todoList = this.todoList.filter(item => item.id != id)
# 删除任务
1. 注册点击事件
2. 拿到 id
3. 根据 id 删除任务
   > 过滤出来不等于当前 id 的
   > this.list = this.list.filter( item => item.id != id)

六、编辑任务 (难点)

画图

三步 :

  1. 在 data 中存一个数据 : editId, 记录选中的文本框
  2. :class = {editing: item.id === editId } 判断 选中的是哪一个,是选一个都返回true, 显示编辑状态
  3. 双击 : @dblclick="editTodo(item.id)"
  4. 在editTodo 函数里 保存选中的id this.editId = id
  5. 回车 : 编辑状态消除 : this.editId = -1
  6. Vue中数据更新的特点:
    只要Vue中的数据发生改变,页面中所有的指令和表达式都会被重新计算一次
# 编辑任务
1. 显示编辑状态 (难点)
- 第一步 : data 里新加一个 editId : -1
- 第二步 : item.id == editId
- 第三步 : 双击 => 拿到双击任务的 id => 赋值 editId this.editId = id

> vue 中, data 里的数据,一旦发生了变化,,当前页面的指令和表达式会重新计算

2. 修改任务 v-model='item.name'
3. 隐藏编辑状态
   `this.editId = -1`


七、事件修饰符

01-事件修饰符.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .fa {
            width: 70px;
            height: 70px;
            margin-bottom: 10px;
            background: green;
            text-align: center;
        }
        
        button {
            margin-top: 20px;
            text-align: center;
        }
    </style>
</head>

<body>
    <!-- 
       事件修饰符 
       1.  事件.prevent: 阻止默认行为  
       2.  事件.stop: 阻止冒泡 

       3.  事件.capture: 捕获
       4.  事件.self: 点击自己才会被触发
       5.  事件.once: 点击只触发一次
       6.  事件.passive: 移动端提高性能
     -->

    <div id="app">
        <a href="https://baidu.com" @click.prevent="fn0">链接</a>
        <hr>
        <!-- 点击按钮,输出fn2,说明阻止了冒泡。如果去掉stop,则输出fn2、fn1。 -->
        <div class="fa" @click="fn1">
            <button class="btn" @click.stop='fn2'>按钮</button>
        </div>

        <!-- 点击按钮,先后输出fn2、fn1,说明先后执行fn2、fn1 -->
        <div class="fa" @click="fn1">
            <button @click.capture='fn2'>按钮</button>
        </div>

        <!-- 点击按钮,先后输出fn1、fn2,说明先后执行fn1、fn2 -->
        <div class="fa" @click.capture="fn1">
            <button @click='fn2'>按钮</button>
        </div>

        <div class="fa" @click.self="fn1">
            <button @click='fn2'>按钮</button>
        </div>

        <div class="fa" @click="fn1">
            <button @click.once='fn2'>按钮</button>
        </div>
    </div>

    <script src="./vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                msg: ''
            },
            methods: {
                fn0() {
                    console.log('fn0');
                },
                fn1() {
                    console.log('fn1');
                },
                fn2() {
                    console.log('fn2');
                }
            }
        })
    </script>

</body>

</html>

八、按键修饰符

记住 keyCode 太麻烦了

  1. 只有在键盘事件中生活效, (keydown、keypress、 keyup)
  2. 语法 : @keyup.enter='事件函数'
    .enter 就是一个按键修饰符, 意思就是当按回车的时候, 事件才会被触发
  3. @keyup.13 也可以, 但是 keyCode 也是要记住的
  4. 完善 TodoMVC + 按键修饰符
# 按键修饰符
keyup/keydown/keypress

> 判断回车的例子
> 第一个版本 : @keyup='addTodo' ==> if(e.keyCode==13) { }
> 第二个版本 : @keyup.13='addTodo' => 不要判断了
> 第三个版本 : @keyup.enter='addTodo' 按键修饰符

```js
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
```
<button @click="delTodo(item.id)" class="destroy"></button>

// 删除任务
delTodo(id) {
    // console.log(id)
    // 拿到id, 直接删除不好删, 过滤, 过滤出来不等于当前id的元素, 新的数组, 把之前的list覆盖掉就可以了
    this.list = this.list.filter(item => item.id != id)
}


九、v-if 和 v-show

  1. 代码

  2. <h1 v-if='isShow'>我是h1 v-if</h1>
    
    <h1 v-show='isShow'>我是h1 v-show</h1>
    
    
  3. 异同点

  4. v-if 和 v-show 的异同点
    
    1. 相同点 : 可以切换元素的显示与隐藏
    
    2. 不同点 : 切换显示和隐藏的实现不同
    
        v-if :  显示:创建节点  隐藏: 删除节点
    
        v-show : 显示: display:block  隐藏 : display:none
    
    3. 使用场景 :
    
        v-if因为要不断的创建和删除来切换显示与隐藏 ,所以性能不高
    
        v-if : 切换次数不频繁的时候,
    
            v-show : 切换次数频繁的时候
    
    
    
  5. 完善 TodoMVC + v-show


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>

</head>

<body>

    <!-- 
     v-if 和 v-show 切换元素的显示和隐藏
     格式 v-if/v-show='布尔'  true => 显示  false=> 隐藏

     相同点 : v-if 和 v-show 都能够切换元素的显示和隐藏
     不同点 : 实现的方式不一样
       v-if  显示:创建节点  隐藏:删除节点
       v-show  显示:display:block  隐藏:display:none

       因为 v-if 是通过不断的创建和删除节点来切换显示和隐藏的,所以性能不好
       所以要决定使用哪个,看切换的频繁与否
       切换的频繁 : v-show 【切换的不频繁,也可以使用v-show。】
       切换的不频繁 : v-if 

   -->

    <div id="app">

        <!-- <h1 v-if='isShow'>测试 v-if </h1> -->

        <h1 v-show='isShow'>测试 v-show </h1>

    </div>
    <script src="./vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                isShow: false
            }
        })
    </script>
</body>

</html>


十、Footer 的显示与隐藏

  1. 直接写
    v-show="todoList.length > 0"

  2. 封装到一个函数里

  3. 组件 : v-show="isFooter()"

  4. // 代码显示
    isFooterShow() {
        // 只要 vue里的数据发生了变化, 页面中所有的指令和表达式都会重新计算
        // 所以只要文本框里的内容发生改变, todoName也会发送改变, 这里就会不断的打印,性能不好
        console.log('改变了');
    	return this.todoList.length > 0
    }
    
    
    // 底部的显示与隐藏 【这种方式,只要输入框的值一改变,todoName就会改变,页面的指令、表达式就会重新计算。】 【每输入一个字符,都会输出 '重新计算了'。】
    isFooterShow1() {
     console.log('重新计算了')
        return this.list.length > 0
    }
    
    
  5. 有问题

    5.1 只要 vue 里的data数据发生了变化, 页面中所有的指令和表达式都会重新计算
    5.2 所以只要文本框里的内容发生改变,todoName 也会发送改变,这里就会不断的打印, 性能不好
    5.3 我们要做的是 只要数组列表的个数改变才会影响底部的变化, 文本不管内容文字再多都不应该影响底部的变化
    5.4 需要 计算属性

# 底部的显示与隐藏
> 方式 1 : 表达式 v-show='list.length > 0'
> 问题 : 代码过多, 直接在这里写就很不方便了

-----

> 方式 2 : 函数 v-show='isFooterShow1()'
> isFooterShow1(){
    return this.list.length > 0
}

> 问题 : 与数组毫无管理的数据(比如data 里的 todoName), 只要随意改变, 就会重新计算, 性能不好
> 原因 : vue 中, data 里的数据todoName,一旦发生了变化, 当前页面的指令(v-show)和表达式会重新计算

----

> 方式 3 : 计算属性 computed (重要 死了都要会)


十一、计算属性 computed

计算属性其实就是一个属性

  • 说明 : 计算属性只跟随相关的数据变化而变化 ,解决底部显示隐藏问题,

  • 怎么使用?

    • 在 computed 里面
    • 写起来像一个方法
    • 用起来像一个属性
  • 特点 :

    • 计算属性一定要有返回值, 返回的值,就会标签要展示的内容

    • 计算属性可以使用data里之前已知的值

    • (重要) 只要 计算属性 相关的数据 发生了变化,计算属性会 重新计算

    • (说一下 ???? num1就是totalNum计算属性的相关属性,所以num1变了,计算属性会重新计算,

      ​ 但是num2不是相关的属性,所以不管你num2怎么变,计算属性都不会重新计算

  • 注意点 :

    • 4.1. 一定要有返回值
    • 4.2. 用起来的时候,不能当方法用,因为它本来就是一个属性
    • 4.3. 计算属性(computed里面的属性) 不能和 data里的属性重名
  • 什么时候使用 计算属性?

    • 根据已知的值,得到一个新值
    • 并且 , 新值只想跟相关的数据(已知的值)的变化而变化 (实时更新)
  • 案例 : 计算器

num1 <input type="text" v-model="num1" /> + 
num2 <input type="text" v-model="num2" /> = <span>{{ num3 }}</span>
      <hr />
<input type="text" v-model="test" />

  • 完善 TodoMVC + 计算属性 + 底部显示与隐藏 / 左边的剩余未完成数 / 右边清除完成按钮显示与隐藏

05-计算属性的基本使用.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <title>Document</title>
</head>

<body>
    <!-- 
     计算属性  computed 
     1. 说明 : 计算属性就是一种属性 【Vue实例对象的属性。】
     2. 如何使用 ? 
      - 写在 computed 里面 
      - 写起来像一个方法/函数
      - 用起来像一个属性

     3. 特点 : 
        - 一定要有返回值, 返回值就是计算属性得到的值  
        - 可以使用data里存在的值
        - ** 计算属性只会随着相关的数据发生变化而变化, 只要相关的数据发生了变化, 计算属性会重新计算 ** 
     
     4. 注意点
        - 一定要有返回值
        - 不能当方法用  【否则报错:函数未定义。】 
        - 不能和data里的数据重名 【否则报错:变量已声明。】 

      5. 以后何时使用计算属性 ? 
          - 根据已知值(data里值), 想得到一个新值  
          - 想得到一个效果 : 新值只和相关的数据变化有关, 相关的数据变化, 新值会随着变化, 其他值不受影响 【相关的数据变化,只会影响新值,不会影响别的值,新值也不受不相关的数据的影响。】
   -->

    <div id="app">
        <h1>{{ totalNum }}</h1>
    </div>

    <script src="./vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                num1: 100,
                num2: 200
            },
            computed: {
                totalNum() {
                    console.log('重新计算了')
                    return this.num1 + 20
                },
                num3() {
                    return this.num1 * this.num2
                }
            }
        })
    </script>
</body>

</html>


06-计算属性小案例.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <title>Document</title>
</head>

<body>
    <div id="app">
        num1 : <input type="text" v-model="num1" /> + num2 :
        <input type="text" v-model="num2" /> =
        <span>{{ totalNum }}</span>
        <hr />
        <input type="text" v-model="num3" />
    </div>
    <script src="./vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                num1: '',
                num2: '',
                num3: ''
            },
            computed: {
                // totalNum 只跟num1 、num2有关,跟num3无关,不管num3如何变化。
                totalNum() {
                    // 诸如'111'的数字类型的字符串,前面加上 加号+ ,就转换为数字类型。
                    return +this.num1 + +this.num2
                }
            }
        })
    </script>
</body>

</html>


十二、key

  • 说明 :
    • Vue 中推荐, 在使用 v-for 的时候,添加 key 属性

看官网

  • 介绍 就地复用,原地打滚

    默认跟着索引走。

<!-- 显示组件 -->
<p v-for="(item,index) in list" :key="index">
	{{ item.name}} <input type="text" />
</p>
<!-- 数据 -->
data: { list : [ 
            { id : 1, name : '老罗' },
            { id : 2, name : '涛涛' }, 
            { id : 3, name : '聪聪' } 
			]}

<!-- 演示  -->
vm.list.unshift({id:4,name:'马哥'})

  • 怎么使用 key
    • 如果数组的元素是一个对象 : 使用对象里固定属性,唯一
    • 一般情况下,对象里都有 id, 99%都是取 item.id
    • 如果数组的元素是一个简单类型,不是一个对象, 就可以取索引作为 key
    • 语法 : :key='item.id'
    • 以后,写了v-for之后,立马写好 :key
  • 完善 TodoMVC + key

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <!-- 
        key 
        1. vue官网推荐, 我们以后使用v-for的时候, 加上key
        2. 如果我们不加key的话, 会出现`就地复用`的策略 (了解) 【默认值是索引。】
        3. 解决就地复用的问题 : 添加一个key属性,并且给key一个正确的值
        4. 如何给key赋值呢?? 
          1. 如果数组里的元素是一个对象(90%), key取对象里的属性(固定、唯一), :key='item.id'
          2. 如果数组里的元素不是一个对象, 真的很简单, :key='index' (不要让顺序发生了改变)
      -->
    <div id="app">

        <p v-for='(item,index) in list' :key='item.name'>
            {{ item.id }} {{ item.name }} <input type="text">
        </p>
    </div>

    <script src="./vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                list1: ['张三', '李四'],
                list: [{
                    id: 1,
                    name: '聪聪',
                    done: false
                }, {
                    id: 2,
                    name: '浪涛',
                    done: false
                }, {
                    id: 3,
                    name: '傻春',
                    done: true
                }]
            }
        })
    </script>
</body>

</html>


十三、条件渲染指令 v-else 和 v-else-if

     条件渲染指令 
     if(){   // v-if

     }else if(){ // v-else-if

     }else {}  // v-else

  1. v-else : 两种情况的
<h1 v-if="num > 40">第一个</h1>
<h1 v-else>第二个</h1>

  1. v-else-if : 三种以上情况
<h1 v-if="num >= 40">第一个</h1>
<h1 v-else-if="num >= 30 && num < 40">第二个</h1>
<h1 v-else>第三个</h1>


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>

</head>

<body>
    <!-- 
     条件渲染指令 
     if(){   // v-if

     }else if(){ // v-else-if

     }else {}  // v-else
   -->

    <div id="app">
        <!-- 
       只显示一种
     -->
        <h1 v-if='length >= 18'>特别长</h1>
        <h1 v-else-if='length >= 12'>很长</h1>
        <h1 v-else>一般般</h1>

    </div>
    <script src="./vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                length: 25
            }
        })
    </script>
</body>

</html>