Vue 四天课程学习笔记_第3天
课程内容概要:
1. 介绍browser-sync安装和配置使用 (强烈推荐!从此告别手动刷新F5)
2. 灵活运用 数组中的 every 和 some 和 filter 方法 (来实现数据的过滤)
3. Sublime的代码片段.sublime-snippet (类似于XCode里的Code Snippet)
4. 不可见元素template的使用
5. v-for 与 v-show 与 v-if 与 v-else-if 与 v-else 指令
6. v-on 与 v-bind 与 v-model 与 v-cloak 与 v-once 与 v-pre 指令
7. 使用v-text 或 v-cloak命令,解决 Mustache的闪烁问题(高频刷新时)
8. 按键捕获 (包括各种修饰符) 与 指令总结
9. 计算属性(getter/setter) 和 v-model指令 联合使用
10. 观察者 watch指令 与 数据持久化到localStorage
11. 通过计算属性,对列表进行按路由条件 (全部/已完成/未完成) 进行过滤
12. 使用 自定义指令(及5个钩子函数) 操作DOM(获取焦点,自动聚焦)
13. 使用 自定义指令(及5个钩子函数) 模拟实现v-show、v-bind指令 的效果
14. 通过上述知识点,实现一个完整的todoMVC项目
插一句:
从win10开始 出现了异常强大的 窗口分栏功能
其快捷键是 win + 上/下/左/右 (真的吗???)
再插一句:
Vue.config是一个对象,包含Vue的全局配置
可以在启动应用之前 设置以下属性:
例如:
// 取消所有日志与警告的输出
Vue.config.silent = true;
再插一句:
提到了项目管理软件 worktile trello Teambition
下面开始 安装 浏览器自动同步刷新神器 browser-sync
第1步,下载browser-sync,官网:browsersync.io
注意: –save-dev 参数表示 这个browser-sync的依赖 并不是必须的! (可有可无)
有了这个-dev的参数, 最后这个browser-sync依赖 会被写到package.json文件的 devDependencies节点
(插入一句说明: npm install 命令 会同时安装 dependencies节点和devDependencies节点)
(插入一句说明: 如果不想安装package.json文件中的devDependencies节点,而只想安装dependencies节点的话,可以使用命令npm install –production)
第2步,打开package.json进行配置
如图所示:
配置scripts节点就是为了方便他人运行该项目(而无需输入具体的.js名字)
第3步: 启动
npm start
经过上面的配置后, 其实就等价于 npm run dev
效果如下: 以后每次改动html文件或css文件,只要一保存,页面就会同步更新了,赞喔~
注意: browser-sync 有自动同步表单的功能 (幽灵输入) ???Excuse Me???
在这儿扩展一下这个scripts节点的相关知识点
举个例子, 以前我们都是在命令行中直接使用 node node_62_index.js 运行这个js文件
这儿是写死了 node_62_index.js这个名字
如果我们配置了scripts,例如
“scripts” : {
“beyond” : “node node_62_index.js”,
“start” : “npm run beyond”
}
那么我们就可以有3种方式运行node_62_index.js了
(即便别人不知道node_62_index.js这个具体的名字,也能够运行这个文件了)
第1种: 最原始的 node node_62_index.js
第2种: npm run beyond
第3种: npm start
或者: npm run start
而且 这个 start 比较特殊, 它是可以省略中间的 run的
如图所示:
可能会问 凭啥 start 就比较特殊呀, 就可以省略中间的 run呀
下面的图片给出了答案
下面是关于数组every方法 和 some方法的介绍
如图所示:
下面是数组的filter方法介绍 (特别是在用于数据过滤时,非常好用,比如清除所有已完成的item)
关于Sublime, 这儿插一句:
sublime 的快捷键 Ctrl + T 直接搜索文件 (@btnClick,如果前面加@ 还可以直接搜索方法Function)
如图所示:
下面介绍 Sublime里的.sublime-snippet
先看一张效果图:
创建.sublime-snippet片段的步骤如下:
第1步: 点击 菜单「Tools」–>「New Snippet…」
第2步, 弹出一个新的文本,如图所示:
简单说明一下:
1. tabTrigger标签里的内容 就是我们平常内容时的 触发器 (可以理解为代码片段的 快捷方式! 例如上面gif动图里面的 未闻花名)
2. ${1:beyond} ${2:面码} 代表这些两个是默认的内容,是可以被替换的,并且是能够 使用tab键 上蹿下跳的
3. 注意: 如果模板代码里面有 $ 符号, 则需要 使用 \ 进行转义 如: \$mount(‘#id_div_container’)
再比如这个:
第3步, 编辑好内容 和 trigger触发器之后, 按 Alt + S 弹出保存对话框
注意: 1. 必须以.sublime-snippet结尾
2. 必须保存在这个Packages目录下的User路径:
/Users/beyond/Library/Application Support/Sublime Text 3/Packages/User
3. 可以通过Preference菜单–>Browser Packages菜单,快速进入到Packages目录(下的User目录)
最终效果如下:
v-if 是真正的 条件渲染
因为它会确保在切换过程中,条件块内的事件监听器 和 子组件 相应地被创建和销毁
v-if 是惰性的:
如果在初始渲染时,条件为false,则什么也不做
一直到 条件第一次为true时, 才会开始渲染 条件块
对比一下:
v-show 元素总会被渲染, 并且只是简单地进行CSS样式(display: none/block;)的切换
总结就是:
v-if 每次根据条件 创建和销毁,会有更大的开销
v-show 则每次只是初始渲染的高开销
如果,需要频繁地 切换, 推荐使用v-show
如果,运行时,条件极少变化, 推荐使用v-if
注意: 当 v-if 与 v-for 一起使用时
v-for 有更高的优先级 (见列表渲染指南)
v-for 预期: Array | Object | number | string
数组用法 :
v-for=”girl in girlArr”
v-for=”(girl,index) in girlArr”
对象用法 :
v-for=”(value,key) in girl”
v-for=(value,key,index) in girl””
注意: v-for 默认行为 不改变整体
如果要 重新排序 , 则要提供一个key的特殊属性???Excuse Me??? 为啥没效果
<div v-for=”girl in girlArr” :key=”girl.girlAge” >
{{ girl.girlAge }}
</div>
如图所示: (为啥会没有效果???)
列表渲染之条件渲染
v-for可以把一个(过滤后的)数组 对应生成 一组元素
有时候,我们想要显示一个数组的排序副本或者过滤之后的数组,
而不实际改变或重置原始的数组
在这种情况下, 我们就可以创建一个 能够返回 过滤或排序后的数组 的计算属性
例如: 过滤出 loli
<li v-for=”loli in loliArr”>
{{ loli }}
</li>
computed: {
loliArr: {
get: function(){
return this.girlArr.filter(loli =>
loli.girlAge > 8 && loli.girlAge < 14
)
}
}
}
另一种情况:
在计算属性 不适用的情况下(例如 嵌套v-for时),
你可以使用一个method方法
<li v-for=”loli in findLoliArrFunction(girlArr)”> {{ loli.girlName }}
</li>
v-show: 显示不显示 (无论如何都会进行渲染)
v-if指令可以决定哪个DOM元素,是否需要进行渲染
假如有许多个DOM元素都需同时进行判断是否需要进行渲染,而且判断的条件还是同一个的话
那么, 如果每一个都写v-if的话,这样就很容易造成代码的重复和冗余
那么, 如果在外面套一层多余的div的话,又会增加不必要的层级
此时,我们可以把这些DOM元素,全部放进一个虚拟的template标签里,
该template标签 会根据v-if的值,决定其内部的所有子元素是否进行渲染,
如果需要渲染,则最终template标签 在完成任务后,会奇迹般地消失
例如:
<template v-if=”isNeed”>
<h1>动漫: 未闻花名</h1>
<p>女主: 面码</p>
<p>年代: 2011年</p>
</template>
data: {
isNeeded: true/false;
}
vue_18.html代码如下:
效果如下:
右键,审查元素,我们可以发现, 虚拟元素<template>奇迹般地完成其历史使命后消失了,仿佛从来没有出现过一样(好神奇)
再啰嗦一下:
在虚拟元素<template>上使用v-if进行条件渲染
因为我们知道,v-if指令必须加到一个HTML元素上,才能生效
但是,如果要同时根据同一条件去判断是否渲染多个HTML元素呢?
此时最好的办法就是: 把一个虚拟元素<template>作为他们的容器(根节点)
这时候,只要在虚拟元素<template>上使用v-if指令即可
最终,渲染出来的页面上,不会存在虚拟元素<template>,但其子元素都可以正常展示
下面简单演示一下v-if与v-else-if与v-else指令的使用
vue_19.html代码如下:
效果如下:
v-on 缩写 @
预期: Function | Inline Statement | Object
参数: event
修饰符:
.stop 调用event.stopPropagation()
.prevent 调用event.preventDefault()
.capture 添加事件侦听器时使用 capture模式
.self 只有事件是从侦听器绑定的元素上 触发时,才回调
.native 监听组件根元素的原生事件
.once 只触发一次回调
.passive 以passive模式 添加侦听器
.{keyCode | keyAlias} 只有事件是从特定的按键触发时,才回调
.left 只有点击鼠标左键时,才回调
.right 只有点击鼠标右键时,才回调
.middle 只有点击鼠标中键时,才回调
用法:
绑定事件监听器, 事件类型由 参数 决定, 修饰符为可选
支持 不带参数 绑定一个事件/监听器 键值对的对象 ???Excuse Me???(该情形不支持修饰符)
v-bind 缩写 :
预期: any(带参数) | Object(不带参数)
参数: attrOrProp (可选)
修饰符:
.prop 被用于 绑定DOM 属性
.camel 将kebab-case 转换成 camelCase
.sync 语法糖,会扩展成一个更新父组件绑定值的 v-on 侦听器???Excuse Me???
用法:
动态地绑定一个或多个属性 或一个组件 prop 到 表达式
在绑定class或sytle属性时, 支持其他类型(如数组或对象)
在绑定prop时,prop必须在子组件中声明(可用修饰符指定不同绑定类型)
没有参数时, 可以绑定到一个 包含 键值对的对象(该情况下class和style不再支持数组和对象) ???Excuse Me???
v-model 指令
只用于表单控件(input select textarea 和 components)
修饰符:
.lazy 只会在change事件时,执行回调
.number 自动将输入的字符串转成数字
.trim 自动过滤输入的首尾空格
v-cloak 指令 (不需要表达式)
用法:
这个指令保持在元素上 直到关联的实例 结束编译时,该指令被移除
常与下面的css样式 一起使用(消除 闪烁, 因为可以隐藏未编译状态下的Mustache指令)
[v-cloak] {
display: none;
}
使用:
<div id=”id_div_container” v-cloak>
{{ girlName }}
</div>
v-once 指令 (不需要表达式)
用法: 只渲染元素一次. 随后的渲染,都将视该元素/组件及其所有子节点 为 静态内容
作用: 优化性能
<div v-once>
<h1> 未闻花名 </h1>
<p> {{ girlName }} </p>
</div>
v-pre (不需要表达式)
用法:
比v-once更生猛,该指令直接跳过所在元素及其子元素的编译过程
因此,可以用来显示 大量 原始的 Mustache标签,
自动跳过大量没有 指令的 元素(如文章详情), 加快编译
<span v-pre>
{{ 这个是文章详情, 大量图文, 没有指令, 无需参与编译 }}
</span>
效果如下:
下面介绍, 使用v-text指令, 解决 Mustache语法的 闪烁小bug
当我们在使用Mustache语法时, 其实是有一个小bug的
那就是在频繁刷新渲染界面时,会在页面上看到渲染数据之前的占位符{{ girlName }}
因为 浏览器在渲染的时候, 不认Mustache语法, 例如: {{ girlName }}
所以,就会直接将 {{ girlName }}显示到界面上,
等Vue将这个Mustache语法中的girlName 替换之后, 会重新渲染绑定 的值
因此, 就会出现界面的闪烁~
为此,我们可以v-text指令避免这个问题
解决办法之一是: 使用v-text指令 修复刷新时的小bug
<span>{{ girlName }}</span>
<span v-text=”girlName”></span>
// 两者最终的渲染是完全一样的, (唯一不同的是: v-text不会闪烁, 而Mustache 可以进行部分渲染)
v-text之所不闪烁,是因为在Vue插手之前, 浏览器 不认识v-text属性, 所以什么也不会显示, 一片空白
等到Vue进行绑定数据时, 它会将 绑定数据后的DOM元素,替换掉原来的DOM
但是, 由于v-text也有先天不足, 无法部分数据绑定, 并且在很多个html元素上同时使用 v-text 会比较 繁琐和冗余
这儿,我们强烈推荐 终极解决方案: v-cloak指令
第1步: 我们在被Vue管理的Root Element 节点上, 使用 v-cloak指令
第2步: 并且定义一个css样式如下: (属性名为v-cloak的元素, 不显示)
[v-cloak] {
display: none;
}
html元素如下:
<div id=”id_div_container” v-cloak>
<h1> hello {{ girlName }} </h1>
</div>
这样一来, 一开始 DOM在渲染时,由于 div 拥有 v-cloak属性, 所以是display: none; 是隐藏的 (自然也就看不到{{ girlName }})
当等到Vue完成数组绑定时,重新渲染DOM时, 会奇迹般自动把div上的v-cloak属性移除,
没有了v-cloak属性了, 自然样式display:none;也就不生效了 (赞~~)
下面开始详细介绍 事件处理 之 捕获按键
按键修饰符
v-on:keydown.enter=”xxxFunction”
.once 修饰符, 让点击事件只触发一次
<a v-on:click.once=”anchorClickFunction”>点我试试</a>
在监听键盘事件时,我们经常需要检查用户按下的键值
可以在v-on:keyup事件中添加 修饰符
例如:
<input v-on:keyup.13=”xxxFunction” />
上面代码,只有在用户按下 keyCode为 13的键时,才调用处理函数xxxFunction
另外,Vue提供了常见的按键别名, 例如
<input v-on:keyup.enter=”xxxFunction” />
或者 使用缩写
<input @keyup.enter=”xxxFunction” />
上面代码的意思是,当用户按上并弹起回车键时, 执行处理函数xxxFunction
全部的按键别名如下:
.enter
.tab
.delete (包括 删除 和 退格)
.esc
.space (空格键)
以及四个方向键
.up
.down
.left
.right
可以通过全局 Vue.config.keyCodes 对象, 自己添加 按键别名
// 左边是自定义别名, 右边是 keyCode
Vue.config.keyCodes.f1 = 112
系统修饰键
.ctrl
.alt
.shift
.meta (Mac上为command键(⌘), Win上为 徽标键(⊞), 在Sun上为 实心宝石键(◆))
例如:
// ctrl + c 复制
<input @keyup.ctrl.67=”copyFunction” />
// alt + c 取消
<input @keyup.alt.67=”cancelFunction” />
// ctrl + click
<input @click.ctrl=”xxxFunction” />
vue_20.html代码如下:
效果如下:
指令部分总结
下面开始 对 指令部分,作一个 全面细致的总结:
1. v-text
1.1 跟 Mustache {{ }} 一样
1.2 Mustache {{ }} 会造成闪烁问题
1.3 v-text 没有闪烁问题, 但只能完全匹配
1.4 最终解决方案: 使用v-cloak指令 与 CSS 样式结合
2. v-html
3. v-show
3.1 条件 显示和隐藏
3.2 无论条件真假, 都会渲染到DOM
3.3 true时, display: block;
3.4 false时, display: none;
3.5 适用于 运行期间,需频繁地切换显示和隐藏 的场景
4. v-if
4.1 真正的条件渲染
4.2 true时, 渲染此DOM
4.3 false时, 不渲染并移除此DOM
4.4 适用于 只是一次 显示或隐藏
4.5 总结
v-if 因为每次切换都要 添加和移除DOM,所以有更高的开销
v-show 则有更高的初始渲染开销
v-show 适用于 频繁地切换 显示或隐藏
v-if 适用于 运行期间极少改变的情形
5. v-else v-else-if
6. v-for
7. v-on
8. v-bind
9. v-model
10. v-pre
11. v-cloak
11.1 使用v-clock,即可保留{{ }},又无闪烁
11.2 head中加入css样式 [v-cloak]{display:none;}
11.3 在Vue管理的节点上, 添加v-cloak属性
11.4 原理解析: 一开始 DOM是隐藏的; 当Vue渲染完之后,Vue会将v-cloak属性移除,故DOM开始显示
12. 自定义指令
发生在需要手动操作DOM的情形下
计算属性与观察者
虽然模板内的Mustache非常地便利,
但是仅适用于 无重复 且 运算简单的情况
如果模板内的Mustache运算异常复杂 且 大量重复的时候,
模板会变得繁重 和 冗余 且 难以维护
例如: 下面的就是模板的 逻辑过重
<div id=”id_div_container”>
{{ kayak.split(”).reverse().join(”) }}
</div>
在上面的这个例子当中, 模板不再简单地一目了然
而是要过老半天,才知道这是在将字符串翻转呀
如果你在多处都要用上面的这个模板的话,将显示更加冗余和难以维护
因此, 这儿我们隆重介绍 计算属性
例如: 计算数组中的loli数目
第1种: 计算属性的完整写法 (对象)
computed: {
loliCount: {
get: function(){
return girlArr.filter(girl => {
girl.girlAge > 8 && girl.girlAge < 14
}).length
}
}
}
第2种: 计算属性的简写 (伪方法,像方法但不是方法,只是属性)
computed:{
loliCount(){
return girlArr.filter(girl => {
girl.girlAge > 8 && girl.girlAge < 14
}).length
}
}
使用 计算属性 (再次强调, 计算属性不是方法)
{{ loliCount }}
计算属性的setter
默认的计算属性只有getter, 但是也可以自己手动实现一个setter
使用有一个checkbox, 功能是选择全部/取消全部选择
<!– 全部看过 / 取消全部
方式1: 在这儿监听 change 事件
v-on:change=”chooseAllOrNotFunction”
方式2: MVVM 高级玩法
使用计算属性chooseAllOrNot作为v-model 实现双向绑定 到checkbox
因为checkbox既有初始值, 又要勾选设置值,所以要双向绑定
所以,当初始化checkbox时, 会调用计算属性的getter方法
而当你勾选checkbox的时候,就会自动调用计算属性的 setter方法
–>
<input type=”checkbox” v-model=”chooseAllOrNot” />
那么可以使用v-model 将checkbox 与 计算属性 进行双向绑定
computed: {
chooseAllOrNot: {
get: function(){
// 当数组中每一个 anime 都 have seen的时候, 自动勾选 checkbox
// 当数组中 只要有一个anime 不是have seen时, 取消勾选 checkbox
// 注意: 计算属性知道自己依赖了 数组, 所以,当 数组 变化时, 计算属性也会重新计算
return animeArr.every(anime =>
anime.animeHaveSeen === true
)
},
/*
只要用户 勾选或取消 绑定了此计算属性的checkbox, 就会来到setter方法 (因为有 设置值)
数组animeArr中的所有animeHaveSeen的状态也就都会跟着联动
*/
set: function() {
// 0. 非常巧妙! 先通过计算属性的getter方法,获取当前的 勾选 状态
// 1. 先通过getter方法,获取当前的 勾选 状态
var isChecked = this.chooseAllOrNot
// 2. 然后选择相反的状态
var newIsChecked = !isChecked
// 3. 同步更新所有数组中的成员
this.animeArr.forEach(anime =>
anime.animeHaveSeen = newIsChecked
)
}
}
}
运行时, 只要勾选或取消了checkbox, 数组animeArr中的所有animeHaveSeen的状态也都会跟着联动
效果如下:
下面开始介绍如何实现数据持久化
主要有两个方面的知识点
第1个: 使用的初始值animeArr来自本地存储 window.localStorage
第2个: 当animeArr任何时候发生改变时, 将改变的值 存储到window.localStorage
这就要用到Vue中的 watch选项
先看第1个, 关于window.localStorage
localStorage 对象
localStorage 对象存储的数据没有时间限制。
第2天、第3周、第4个月或第5年之后,数据依然可用
常用的有如下几个(以localStorage为例):
保存数据:localStorage.setItem(key,value);
读取数据:localStorage.getItem(key);
删除单个数据:localStorage.removeItem(key);
删除所有数据:localStorage.clear();
得到某个索引的key:localStorage.key(index);
提示: 键/值对 必须以字符串存储,
如果直接存对象,则它会bia ji一声,将其toString强转成字符串”[object Object]”,
那么这样一来, 原来的对象就再也找不回来了
如图所示:
下面开始 介绍 Vue里面的 观察者 watch选项
观察者watch选项
类型: {[key: string]: string | Function | Object}
一个对象
对象的键是 需要观察的表达式
值是 对应的回调函数
值也可以是方法名 或 包含选项(deep或immediate)的对象
Vue实例 会在实例化时,调用$watch(), 遍历watch对象的每一个属性
例如:
data: {
girlName: ‘面码’,
girlAge: 15,
animeArr: [{animeName: ‘未闻花名’}]
},
watch: {
girlName: function(value,oldValue){…},
girlAge: ‘girlAgeChangedFunction’,
animeArr: {
deep: true,
immediate: true,
handler: function(value,oldValue){……}
}
}
说明:
1. 观察data选项中的animeArr属性的一举一动,并随之执行相应的业务处理逻辑
2. deep: true ,数组 和 对象 等引用类型,默认只能监视第1层, 为了能够监视其子成员的改变, 必须 深度观察
3. immediate: true ,监听开始时,立即调用(不管值 改没改变)
注意:
不能使用 箭头函数 来定义 watch选项中的函数
原因是: 箭头函数 绑定的是父级作用域的上下文
效果如下:
阶段性总结:
上面的内部包括:
1. sublime-snippet
2. 使用v-text解决闪烁问题
3. 使用Mustache语法 加 v-cloak 加 css 解决闪烁问题
css样式如下:
[v-cloak]: {display: none;}
在Vue管理的根节点上,添加v-cloak属性
<div id=”id_div_container” v-cloak>
</div>
当Vue解析完模板后,会自动将v-cloak属性移除
4. v-if 与 v-show的区别
v-if 是根据条件 决定 是否渲染 (因开销较大,适合于运行时,极少切换的情况)
v-show 是根据条件 决定 是否显示 (适合于 切换较频繁场景)
5. v-pre
告诉Vue不要解析 本节点及其内部子节点
提高解析效率,避免不必要的解析
7. 计算属性 computed 与 getter/setter
解决模板的逻辑过重、冗余、重复调用、难以维护等问题
计算属性虽然本质上是getter/setter方法,但是必须当成属性使用
计算属性会缓存计算的结果
再次强调 计算属性 不能当做方法去调用
事件处理函数 仍然 只能写在 methods选项里
8. 观察者 watch
可以对data选项里的 属性进行监视,一旦属性值改变,立即自动调用相应的业务处理逻辑
如果监视 对象或数组,则需要配置 deep:true
下面开始介绍, 如何通过data对象中的一个属性(中间变量|过滤条件)的值的不同, 过滤出不同的数组显示到界面
又如何通过 window.location.hash变化(onhashchange事件), 改变 data对象中的一个属性(中间变量|过滤条件)的值,
从而过滤出不同的数组显示到界面
最终效果如下:
总的来说,上面的demo概括起来就是:
1. 根据 路由routing 的切换, 导致window.location.hash变化
2. 而监听到地址栏的hash的变化时 , 又人为导致 data中的一个 中间变量(hashString)的变化
3. 而条件(hashString)的变化 , 又改变 计算属性 返回的结果数组 filtedAnimeArr
4. 从而实现了对 列表的过滤与切换
下面开始介绍 如何使用 自定义指令(及5个钩子函数) 来操作DOM (获取焦点,自动聚焦)
在不使用Vue时, H5提供的 autofocus 属性 进行自动获取焦点 是没有任何问题的
但是, 一旦使用Vue, autofocus属性 就根本不好使了
因为:Vue会去解析处理HTML
这儿涉及一个敏感话题, 那就是Vue里的虚拟DOM,这不展开讨论
因此, 在这种情况下, 我们就只能手动去操作DOM了
通过注册自定义指令, 我们可以手动去操作DOM
例如: 输入框 自动获得焦点的问题 (注意: autofocus在iOS的Safari上不工作)
<input type=”text” autofocus />
下面,使用全局自定义组件, 实现此功能 (任何组件中都可以使用)
Vue.directive(“beyond-focus”,{
// 当被绑元素 插入到dom时
inserted: function(inputNode){
// 使用原生JS 操作 DOM 获取焦点
inputNode.focus()
}
})
// 参数1: 指令名v-beyond-focus 所对应的 参数1 在定义时, 有两种不同的写法
// 第1种: 除了v- 的部分, 即beyond-focus
// 第2种: 驼峰命名法,可以写成 beyondFocus
// 两种写法,效果一样,都是同一个指令 v-beyond-focus
// 参数2: 是对象,其属性键值对 是5个钩子函数
// 如果是想注册局部指令, 组件中也有一个directives选项 (只能在该组件内使用)
directives: {
beyond-focus: {
inserted: function(inputNode){
inputNode.focus()
}
}
}
自定义的指令的使用方式如下:
<input type=”text” v-beyond-focus />
注意: 1. 在模板中使用自定义指令,需要加上 v- 前缀
2. 对于用驼峰定义的自定义指令,使用时 用 – 连接
再次强调: 在不可避免 要操作DOM时,就会用到 自定义指令
补充说明: 一个指令定义对象 可以 选择性地实现 以下5个钩子函数
bind: 只调用一次,第一次绑定指令到元素时调用(此时,无父节点, el.parentNode为null)
insert: 只调用一次,被绑定元素 插入到父节点时调用 (仅保证有父节点,但不一定在文档DOM树中)
update: 所在组件的根节点即将更新前 调用 (但其子节点可能尚未开始更新, 需要通过比较 更新前后的 新值 与 旧值,来忽略不必要的模板更新???Excuse Me???) , (此时DOM内容 尚未更新, el.innerHTML为旧的内容)
componentUpdated: 所在组件的根节点及所有子节点 全部更新后 调用( 此时 DOM内容 已经全部更新 ,el.innerHTML为新内容)
unbind: 只调用一次, 指令与元素解绑时调用 (做一些收尾工作,如 window.clearInterval(el.timer) )
钩子函数的四个参数: (除了el,其他3个参数全是只读的)
参数1: el: 指令所绑定的元素,可用于直接操作DOM
参数2: binding: 一个对象,有以下属性
rawName: 指令名, 如v-bind (包含前缀)
name: 指令名,不包括v-前缀,如:v-bind中的 bind
arg: 指令的参数,如v-bind:href中 冒号后面的 href
expression: 字符串形式的指令表达式,如v-bind:href=”6 + 7″中 等号后面的 “6 + 7”
value: 指令的绑定值, 如v-bind:href=”6 + 7″中 等号后面的 13 , 也就是上面属性expression表达式的计算的结果
oldValue: 指令绑定的旧值, 只在 update和componentUpdated钩子中可用
modifiers: 一个包含修饰符的对象,例如指令v-beyond.girl.loli中, 修饰符为: {girl:true,loli:true}
参数3: vnode: Vue编译生成的虚拟节点
参数4: oldNode: Vue编译生成的上一个虚拟节点,只在update和comonentUpdated钩子中可用
再次强调:
示例代码vue_27.html如下:
效果如下:
自定义指令的简写形式 ( bind和update中 代码相同时)
Vue.directive(‘beyond-show’,function(el,binding){
if(binding.value === true){
el.setAttribute(‘sytle’,’color:black’)
}else{
el.setAttribute(‘sytle’,’color:white’)
}
})
对象字面量
如果,指令需要多个值,那么可以传递一个字面量对象
例如:
<div v-beyond-bind:css.a.b.c=”{color: ‘white’, content: ‘snow’}” >
</div>
Vue.directive(‘beyond-bind’,function(el,binding){
NSLog(binding.value.color) // white
NSLog(binding.value.content) // snow
})
指令名name是: beyond-bind
原始指令名raw name是 v-beyond-bind:css.a.b.c
指令参数arg是 css
指令修饰符modifiers是 a b c
效果如下:
关于 指令、参数、表达式、修饰符的说明
<a v-bind:href=”urlExpression”>点我试试</a>
指令是 v-bind
参数是 href
表达式是 urlExpression
<a v-on:click=”xxxFunction”></a>
指令是 v-on
参数是 click
表达式是 xxxFunction
<form v-on:submit.prevent=”xxxFunction”>…</form>
指令是 v-on
参数是 submit
修饰符是 prevent, 即自动调用event.preventDefault()
指令参数
一些指令能够接收一个参数, 在指令名称后 用: 冒号表示
例如
<a v-on:click=”xxxFunction”></a>
指令是 v-on
指令参数是 click
指令修饰符
一些指令还可以添加修饰符, 修饰符Modifiers 是以半角句号 . 指明的特殊后缀
用于指定一个指令该以何种方式进行绑定
例如
<form v-on:submit.prevent=”xxxFunction”>…</form>
指令是 v-on
指令参数是 submit
指令修饰符是 prevent , 意思就是阻止表单提交的默认事件,event.preventDefault()
下面是demo时间,
第2个输入框,初始化时,自动获取焦点,效果如下:
自定义指令, 操作DOM,让第2个inputView 自动获取焦点 的vue_24.html代码如下:
<!DOCTPYE html>
<html lang="zh">
<head>
<link rel="icon" href="/public/img/beyond2.jpg" type="image/x-icon"/>
<meta charset="UTF-8">
<meta name="author" content="beyond">
<meta http-equiv="refresh" content="520">
<meta name="description" content="未闻花名-免费零基础教程-beyond">
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0" />
<meta name="keywords" content="HTML,CSS,JAVASCRIPT,JQUERY,XML,JSON,C,C++,C#,OC,PHP,JAVA,JSP,PYTHON,RUBY,PERL,LUA,SQL,LINUX,SHELL,汇编,日语,英语,泰语,韩语,俄语,粤语,阿语,魔方,乐理,动漫,PR,PS,AI,AE">
<title>beyond心中の动漫神作</title>
<link rel="stylesheet" type="text/css" href="/public/css/beyondbasestylewhite5.css">
<script type="text/javascript" src="/public/js/nslog.js"></script>
<style type="text/css">
body{
font-size: 100%;
margin: 0;
padding: 0;
background-image: url("/public/img/sakura4.png");
background-repeat: no-repeat;
background-position: center center;
}
</style>
<link rel="stylesheet" type="text/css" href="/public/css/beyondbuttongreen.css">
<link rel="stylesheet" type="text/css" href="/public/lib/bootstrap/node_52_v337_bootstrap_backup.css">
<script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.js">
</script>
<head>
<style type="text/css">
#id_div_container {
text-align: center;
margin:0 auto;
}
.class_div_cube {
width: 50px;
height: 50px;
margin:0 auto;
background-image: url("/public/img/beyond.jpg");
background-position: center center;
background-size: cover;
}
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<h1 style="color:white;text-shadow:2px 2px 4px #000;letter-spacing:5px;" class="sgcontentcolor sgcenter">
未闻花名
</h1>
<div v-cloak id="id_div_container" style="color:#666;padding:10px 10px;border:1px solid Cadetblue;margin:4px;border-radius:8px;">
<input type="text" />
<input type="text" v-beyond-focus="isNeedAutoFocus" />
</div>
<script type="text/javascript" src="/node_modules/vue/dist/vue.js"></script>
<script type="text/javascript">
Vue.directive('beyond-focus',{
bind: function (el,binding,vnode,oldNode) {
console.log('bind: el: ', el)
console.log('bind: elのparent: ', el.parentNode)
console.log('bind: binding: ', binding)
console.log('')
},
inserted: function (el,binding,vnode,oldNode) {
if (binding.value === true) {
el.focus()
} else{
el.blur()
}
console.log('inserted: el: ', el)
console.log('inserted: binding: ', binding)
console.log('inserted: elのparent: ', el.parentNode)
console.log('')
},
update: function (el,binding,vnode,oldNode) {
NSLog('update: el: ' + el)
NSLog('update: binding: ' + binding)
},
componentUpdated: function (el,binding,vnode,oldNode) {
NSLog('componentUpdated: el: ' + el)
NSLog('componentUpdated: binding: ' + binding)
},
unbind: function (el,binding,vnode,oldNode) {
NSLog('unbind: el: ' + el)
NSLog('unbind: binding: ' + binding)
}
})
var appVue = new Vue({
data: {
isNeedAutoFocus: true
},
methods: {
}
}).$mount('#id_div_container')
</script>
<p class="sgcenter sgcontentcolor">
<b>注意:
</p>
<footer id="copyright">
<p style="font-size:14px;text-align:center;font-style:italic;">
Copyright © <a id="author">2018</a> Powered by <a id="author">beyond</a>
</p>
</footer>
</body>
</html>
下面是自定义指令v-beyond-show,实现 显示和隐藏
vue_25.html代码如下:
<!DOCTPYE html>
<html lang="zh">
<head>
<link rel="icon" href="/public/img/beyond2.jpg" type="image/x-icon"/>
<meta charset="UTF-8">
<meta name="author" content="beyond">
<meta http-equiv="refresh" content="520">
<meta name="description" content="未闻花名-免费零基础教程-beyond">
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0" />
<meta name="keywords" content="HTML,CSS,JAVASCRIPT,JQUERY,XML,JSON,C,C++,C#,OC,PHP,JAVA,JSP,PYTHON,RUBY,PERL,LUA,SQL,LINUX,SHELL,汇编,日语,英语,泰语,韩语,俄语,粤语,阿语,魔方,乐理,动漫,PR,PS,AI,AE">
<title>beyond心中の动漫神作</title>
<link rel="stylesheet" type="text/css" href="/public/css/beyondbasestylewhite5.css">
<script type="text/javascript" src="/public/js/nslog.js"></script>
<style type="text/css">
body{
font-size: 100%;
margin: 0;
padding: 0;
background-image: url("/public/img/sakura4.png");
background-repeat: no-repeat;
background-position: center center;
}
</style>
<link rel="stylesheet" type="text/css" href="/public/css/beyondbuttongreen.css">
<link rel="stylesheet" type="text/css" href="/public/lib/bootstrap/node_52_v337_bootstrap_backup.css">
<script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.js">
</script>
<head>
<style type="text/css">
#id_div_container {
text-align: center;
margin:0 auto;
}
.class_div_cube {
width: 50px;
height: 50px;
margin:0 auto;
background-image: url("/public/img/beyond.jpg");
background-position: center center;
background-size: cover;
}
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<h1 style="color:white;text-shadow:2px 2px 4px #000;letter-spacing:5px;" class="sgcontentcolor sgcenter">
未闻花名
</h1>
<div v-cloak id="id_div_container" style="color:#666;padding:10px 10px;border:1px solid Cadetblue;margin:4px;border-radius:8px;">
<input type="checkbox" v-model="isVisible"/>
<div class="class_div_cube" v-beyond-show="isVisible"></div>
</div>
<script type="text/javascript" src="/node_modules/vue/dist/vue.js"></script>
<script type="text/javascript">
Vue.directive('beyond-show',function (el,binding) {
var isVisible = binding.value
NSLog('isVisible: ' + isVisible)
var parentNode = el.parentNode
if (!parentNode) {
return
}
var divNode = parentNode.getElementsByClassName('class_div_cube')[0]
if (isVisible === true) {
divNode.setAttribute('style','display:block')
} else{
divNode.setAttribute('style','display:none')
}
})
Vue.directive('beyond-show',{
bind: function (el,binding,vnode,oldNode) {
console.log('bind: el: ', el)
console.log('bind: elのparent: ', el.parentNode)
console.log('bind: binding: ', binding)
console.log('')
},
inserted: function (el,binding,vnode,oldNode) {
console.log('inserted: el: ', el)
console.log('inserted: binding: ', binding)
console.log('inserted: elのparent: ', el.parentNode)
console.log('')
},
update: function (el,binding,vnode,oldNode) {
var isVisible = binding.value
NSLog('isVisible: ' + isVisible)
var parentNode = el.parentNode
var divNode = parentNode.getElementsByClassName('class_div_cube')[0]
if (isVisible === true) {
divNode.setAttribute('style','display:block')
} else{
divNode.setAttribute('style','display:none')
}
console.log('update: el: ' , el)
console.log('update: binding: ' , binding)
console.log('')
},
componentUpdated: function (el,binding,vnode,oldNode) {
console.log('componentUpdated: el: ' , el)
console.log('componentUpdated: binding: ' , binding)
console.log('')
},
unbind: function (el,binding,vnode,oldNode) {
NSLog('unbind: el: ' + el)
NSLog('unbind: binding: ' + binding)
}
})
var appVue = new Vue({
data: {
isVisible: true
},
methods: {
}
}).$mount('#id_div_container')
</script>
<p class="sgcenter sgcontentcolor">
<b>注意: 自定义指令v-beyond-show实现显示和隐藏
</p>
<footer id="copyright">
<p style="font-size:14px;text-align:center;font-style:italic;">
Copyright © <a id="author">2018</a> Powered by <a id="author">beyond</a>
</p>
</footer>
</body>
</html>
效果如下:
核心代码:
// ------------------全局自定义指令(完整形式)------------
// 参数1: 指令名v-beyond-show 所对应的 参数1 在定义时, 有两种不同的写法
// 第1种: 除了v- 的部分, 即beyond-show
// 第2种: 驼峰命名法,可以写成 beyondShow
// 两种写法,效果一样,都是同一个指令 v-beyond-show
// 参数2: 是对象,其属性键值对 是5个钩子函数
Vue.directive('beyond-show',{
// 钩子1: 只调用一次,第一次绑定指令到元素时调用(此时,无父节点), 可做一些初始化设置
// 再次强调,此时无父节点
bind: function (el,binding,vnode,oldNode) {
console.log('bind: el: ', el)
console.log('bind: elのparent: ', el.parentNode)
console.log('bind: binding: ', binding)
console.log('')
},
// 钩子2: 当被绑元素inputNode 插入到dom时,调用insert方法 (只会执行一次)
// 仅保证父节点存在,但不一定已插入到DOM中
// 如果你要操作父节点,至少要写在这个钩子里
inserted: function (el,binding,vnode,oldNode) {
console.log('inserted: el: ', el)
console.log('inserted: binding: ', binding)
console.log('inserted: elのparent: ', el.parentNode)
console.log('')
},
// 钩子3: 此时DOM内部尚未更新
// 如果要获取html更新之前的内容, 代码写这儿
update: function (el,binding,vnode,oldNode) {
// 指令是可以传值的, 根据传递的值(即binding对象中的value)
var isVisible = binding.value
NSLog('isVisible: ' + isVisible)
var parentNode = el.parentNode
var divNode = parentNode.getElementsByClassName('class_div_cube')[0]
if (isVisible === true) {
// 如果为true,则显示
// divNode.setAttribute('style','display:block')
divNode.style.display = 'block'
} else{
// 否则,不显示
// divNode.setAttribute('style','display:none')
divNode.style.display = 'none'
}
console.log('update: el: ' , el)
console.log('update: binding: ' , binding)
console.log('')
},
// 钩子4: 此时 DOM内容 已经全部更新
// 如果要获取html更新之后的内容, 代码写这儿
componentUpdated: function (el,binding,vnode,oldNode) {
console.log('componentUpdated: el: ' , el)
console.log('componentUpdated: binding: ' , binding)
console.log('')
},
// 钩子5:
unbind: function (el,binding,vnode,oldNode) {
NSLog('unbind: el: ' + el)
NSLog('unbind: binding: ' + binding)
}
})
核心代码的简写形式:
自定义指令v-bind效果 (动态绑定属性值)
vue_28.html代码如下:
效果如下:
自定义指令v-beyond-bind 实现添加移除class效果
vue_29.html代码如下
核心代码:
效果如下:
下面我们开始 介绍 todoMVC完整项目
最终我们需要实现的效果如下:
为了完全实现这样的一个demo, 首先打开todoMVC官网: todomvc.com,
可以看到有不同技术实现的todoMVC
这些我们不用理会,只需下载模板即可
第1步,找到我们要下载其template模板(箭头所指的地方就是模板的下载链接): github.com/tastejs/todomvc-app-template
第2步,通过git clone命令,将模板template clone到本地
如果 带有参数 –depth=1 则代表 只下载最后一次的提交,可加快下载速度
git clone https://github.com/tastejs/todomvc-app-template.git
第3步, 安装第3方依赖
npm install
如图所示:
现在打开index.html即可开始编写我们自己的todoMVC项目了
使用上面安装的browser-sync, 通过命令 npm start 打开index.html, 如果没有内容的时候,静态效果如下
有关todoMVC这个案例的需求, 如下图所示: 已经全部写在了 百度脑图
naotu.baidu.com/file/b935b732b2dbf1b2ff12a3291d7f24e5?token=f1973a115e68f4e1
展开百度脑图中的「基础案例: todoMVC」节点, 项目需求,如下图所示:
我的脑图:
完整的todoMVC最终效果如下:
todoMVC完整功能的appVue.js代码如下:
;(function (window) {
'use strict';
// var animeArr = [
// {
// animeID: 1,
// animeName: "未闻花名",
// animeHaveSeen: false
// },
// {
// animeID: 2,
// animeName: "龙与虎",
// animeHaveSeen: true
// },
// {
// animeID: 3,
// animeName: "轻音少女",
// animeHaveSeen: false
// }
// ]
// ------------------全局自定义组件(简写形式)------------
// Vue.directive('beyond-focus',function (el,binding) {
// el.focus()
// })
// ------------------全局自定义指令(完整形式)------------
// 参数1: 指令名v-beyond-show 所对应的 参数1 在定义时, 有两种不同的写法
// 第1种: 除了v- 的部分, 即beyond-show
// 第2种: 驼峰命名法,可以写成 beyondShow
// 两种写法,效果一样,都是同一个指令 v-beyond-show
// 参数2: 是对象,其属性键值对 是5个钩子函数
Vue.directive('beyond-focus',{
// 钩子1: 只调用一次,第一次绑定指令到元素时调用(此时,无父节点), 可做一些初始化设置
// 再次强调,此时无父节点
bind: function (el,binding,vnode,oldNode) {
// 注意:聚焦不能写在bind钩子里
console.log('bind: el: ', el)
console.log('bind: elのparent: ', el.parentNode)
console.log('bind: binding: ', binding)
console.log('')
},
// 钩子2: 当被绑元素inputNode 插入到dom时,调用insert方法 (只会执行一次)
// 仅保证父节点存在,但不一定已插入到DOM中
// 如果你要操作父节点,至少要写在这个钩子里
inserted: function (el,binding,vnode,oldNode) {
console.log('inserted: el: ', el)
console.log('inserted: binding: ', binding)
console.log('inserted: elのparent: ', el.parentNode)
console.log('')
},
// 钩子3: 此时DOM内部尚未更新
// 如果要获取html更新之前的内容, 代码写这儿
update: function (el,binding,vnode,oldNode) {
// 指令是可以传值的, 根据传递的值(即binding对象中的value)
// v-beyond-focus="currentEditAnime === anime"
if(binding.value === true){
el.focus()
}
},
// 钩子4: 此时 DOM内容 已经全部更新
// 如果要获取html更新之后的内容, 代码写这儿
componentUpdated: function (el,binding,vnode,oldNode) {
// console.log('componentUpdated: el: ' , el)
// console.log('componentUpdated: binding: ' , binding)
// console.log('')
},
// 钩子5:
unbind: function (el,binding,vnode,oldNode) {
NSLog('unbind: el: ' + el)
NSLog('unbind: binding: ' + binding)
}
})
// ------------------全局自定义指令(完整形式)------------
// var animeArr = []
// 初始值来自 localStorage (默认初次 没有值, 便以空数组代替)
var animeArrInitString = window.localStorage.getItem('animeArr')
var animeArr = JSON.parse(animeArrInitString || '[]')
// 这儿必须用window 记住, 因为本函数写在闭包里面
window.appVue = new Vue({
// data选项
data: {
animeArr: animeArr,
// 记录 被人双击时的那一个 anime对象
currentEditAnime: null,
// 根据 window.location.hash 进行同步改变; 而hashString又作为 过滤数组的重要的判断条件;
// 从而实现了地址栏 与 列表数组 的联动
hashString: ''
},
// 观察者 选项
// 注意: 不能使用 箭头函数来定义 watch选项中的函数
// 原因是: 箭头函数 绑定的是父级作用域的上下文
watch: {
// 观察data选项中的animeArr属性的一举一动,并随之执行相应的业务处理逻辑
animeArr: {
// 数组 和 对象 等引用类型,默认只能监视第1层,为了能够监视其子成员的改变, 必须 深度观察
deep: true,
// 监听开始时,立即调用(不管改没改变)
immediate: true,
// 发现改变时,随之 调用的业务逻辑 (持久化)
handler: function (newValue,oldValue) {
// NSLog('animeArr发生改变,写入本地存储')
window.localStorage.setItem('animeArr',JSON.stringify(newValue))
}
}
},
// 计算属性 本质是一个getter/setter方法, 但是必须也只能按属性使用
computed: {
// 第1种写法: 直接写, 用 方法 的形式写
// unSeenAnimeCount(){
// var leftCount = this.animeArr.filter(anime =>
// !anime.animeHaveSeen
// ).length
// NSLog(leftCount)
// return leftCount
// }
// 第2种 完整写法 对象形式
unSeenAnimeCount: {
// 默认 只有一个 getter
get: function () {
var leftCount = this.animeArr.filter(anime =>
anime.animeHaveSeen === false
).length
// NSLog(leftCount)
return leftCount
}
},
// 选择全部 / 取消选择
chooseAllOrNot: {
get: function () {
// 当数组中每一个 anime 都 have seen的时候, 自动勾选 checkbox
// 当数组中 只要有一个anime 不是have seen时, 取消勾选 checkbox
// 注意: 计算属性知道自己依赖了 数组, 所以,当 数组 变化时, 计算属性也会重新计算
var b = this.animeArr.every(anime =>
anime.animeHaveSeen === true
)
NSLog('get: ' + b)
return b
},
// 只要用户 勾选或取消 绑定了此计算属性的checkbox, 就会来到setter方法 (因为有 设置值)
// 数组animeArr中的所有animeHaveSeen的状态也就都会跟着联动
set: function () {
// 1. 非常巧妙! 先通过计算属性的getter方法,获取当前的 勾选 状态
var isChecked = this.chooseAllOrNot
// 2. 然后选择相反的状态
var newIsChecked = !isChecked
// NSLog('set: ' + newIsChecked)
// 3. 同步更新所有数组中的成员
this.animeArr.forEach(anime =>
anime.animeHaveSeen = newIsChecked
)
}
},
// 根据条件 过滤出来的 供列表显示的 数组
filtedAnimeArr: {
get: function () {
// 如果 根据路由得出的 中间变量 为 completed 或者 active ,则 过滤相应的数组 展示到界面上
if(this.hashString === 'active'){
return this.animeArr.filter(anime =>
anime.animeHaveSeen === false
)
}else if(this.hashString === 'completed'){
return this.animeArr.filter(anime =>
anime.animeHaveSeen === true
)
}else{
return this.animeArr
}
}
}
},
methods: {
addAnimeFunction(event) {
NSLog('按下回车')
// 获取input节点
var inputNode = event.target
var newAnimeName = inputNode.value.trim()
NSLog(newAnimeName)
// 过滤非空数据
if(!newAnimeName.length){
return
}
var animeArr = this.animeArr
// 索引健壮性
var newAnimeID = -1
if(animeArr.length === 0){
newAnimeID = 1
}else{
newAnimeID = animeArr[animeArr.length -1 ].animeID + 1
}
var newAnime = {
animeID: newAnimeID,
animeHaveSeen: false,
animeName: newAnimeName
}
animeArr.push(newAnime)
// 清空输入框
inputNode.value = ''
},
selectAllOrNotFunction(event){
// 先拿到节点
var checkNode = event.target
// 再拿到其 选中状态 checked
var checkStatus = checkNode.checked
NSLog(checkStatus)
// 如果选中,则将数组内所有的animeHaveSeen状态置为 true
this.animeArr.forEach(item => {
item.animeHaveSeen = checkStatus
})
},
// 当事件处理函数,没有传递参数时,第一个参数就是event
// 当事件处理函数传递了参数时,就没有办法再获取到event对象了
// 因此,我们在传递参数时,就要手动传递事件对象 $event
deleteAnimeBtnClicked(event,index){
// 只有在 调用方法时,手动传递了$event对象,这时,我们才可以在方法里面 拿到事件对象
this.animeArr.splice(index,1)
},
// 双击事件发生时调用
// 参数1: 双击事件
// 参数2: 索引
// 参数3: anime对象
doubleClickFunction(event,index,anime){
// 非常巧妙的1步,使用中间变量 保存被双击的对象(其实用索引也行)
this.currentEditAnime = anime
},
// 按回车 或 失去焦点时, 结束编辑
editCompleteFunction(event,index,anime){
// 1. 退出editing样式(让中间变量currentEditAnime 置null)
this.currentEditAnime = null
// 2. 获取并验证 新输入的 animeName
var editNode = event.target
var newAnimeName = editNode.value
NSLog(newAnimeName)
// 2.1 如果为空,则删除该anime,
if (newAnimeName.length === 0) {
this.animeArr.splice(index,1)
return
}
// 2.2 如果有内容,则更新
anime.animeName = newAnimeName
},
// 按esc时, 结束编辑 不保存
cancleEditFunction() {
// 1. 退出editing样式(让中间变量currentEditAnime 置null)
this.currentEditAnime = null
},
// 点击 清除完成 按钮 事件
clearCompletedAnimeBtnClicked() {
// 注意: 使用forEach 遍历数组时,千万不能 删除元素
// 方式一: 用for语句遍历, 并在删除后,将索引 --
// for (var i = 0; i < animeArr.length; i++) {
// if(this.animeArr[i].animeHaveSeen){
// this.animeArr.splice(i,1)
// // 注意: 千万记得 i--
// i--
// }
// }
// 方式二: 推荐使用filter 过滤出新数组
this.animeArr = this.animeArr.filter( anime =>
!anime.animeHaveSeen
)
},
// 使用方法包装, 获取未观看的动漫数
unSeenAnimeCountFunction() {
return this.animeArr.filter(anime =>
!anime.animeHaveSeen
).length
}
}
}).$mount('#id_section_container')
// -------------------------------------------
function onWindowLocationHashChanged(){
// 拿到 window.location.hash
var tempHashStr = window.location.hash
// 赋值给 data的 中间变量 hashString(即 过滤数组的条件)
// #/active => active #/completed => completed
window.appVue.hashString = tempHashStr.substr(2)
}
// 只有hansh 发生 改变时, 才会调用本方法
window.onhashchange = onWindowLocationHashChanged
// 页面初始化时,手动调用一次,根据当前路由状态进行回显
onWindowLocationHashChanged()
// -------------------------------------------
})(window);
todoMVC完整功能的index.html代码如下:
<!doctype html>
<html lang="en">
<head>
<link rel="icon" href="/public/img/beyond2.jpg" type="image/x-icon"/>
<meta charset="UTF-8">
<meta name="author" content="beyond">
<meta http-equiv="refresh" content="520">
<meta name="description" content="未闻花名-免费零基础教程-beyond">
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0" />
<meta name="keywords" content="HTML,CSS,JAVASCRIPT,JQUERY,XML,JSON,C,C++,C#,OC,PHP,JAVA,JSP,PYTHON,RUBY,PERL,LUA,SQL,LINUX,SHELL,汇编,日语,英语,泰语,韩语,俄语,粤语,阿语,魔方,乐理,动漫,PR,PS,AI,AE">
<title>beyond心中の动漫神作</title>
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
<link rel="stylesheet" href="css/beyondbasestylewhite5.css">
<style type="text/css">
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<section v-cloak id="id_section_container" class="todoapp">
<header class="header">
<h1 style="color:white;text-shadow:2px 2px 4px #e5cdcf;letter-spacing:5px;font-family:inherit;font-weight:380;" class="sgcontentcolor sgcenter">
あの花
</h1>
<input @keydown.enter="addAnimeFunction"
class="new-todo" placeholder="请输入动漫名,按回车键录入" autofocus>
</header>
<template v-if="animeArr.length">
<section class="main">
<input id="toggle-all" class="toggle-all" type="checkbox"
v-model="chooseAllOrNot"
>
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li v-for="(anime,index) in filtedAnimeArr"
v-bind:class="{completed: anime.animeHaveSeen,
editing: currentEditAnime === anime
}"
>
<div class="view">
<input class="toggle" type="checkbox"
v-model="anime.animeHaveSeen"
>
<label v-on:dblclick="doubleClickFunction($event,index,anime)">{{ anime.animeName }}</label>
<button class="destroy"
v-on:click="deleteAnimeBtnClicked($event,index)"
></button>
</div>
<input class="edit" v-bind:value="anime.animeName"
v-on:keydown.enter="editCompleteFunction($event,index,anime)"
v-on:blur="editCompleteFunction($event,index,anime)"
v-on:keydown.esc="cancleEditFunction"
v-beyond-focus="currentEditAnime === anime"
>
</li>
</ul>
</section>
<footer class="footer">
<span class="todo-count">
<strong>
{{ unSeenAnimeCount }}
</strong>
item left</span>
<ul class="filters">
<li>
<a v-bind:class="{selected: hashString === ''}" href="#/">All</a>
</li>
<li>
<a v-bind:class="{selected: hashString === 'active'}" href="#/active">Active</a>
</li>
<li>
<a v-bind:class="{selected: hashString === 'completed'}" href="#/completed">Completed</a>
</li>
</ul>
<button v-if="animeArr.some(anime => anime.animeHaveSeen)" class="clear-completed"
v-on:click="clearCompletedAnimeBtnClicked"
>Clear completed</button>
</footer>
</template>
</section>
<footer id="copyright">
<p style="font-size:14px;text-align:center;font-style:italic;">
Copyright © <a id="author">2018</a> Powered by <a id="author">beyond</a>
</p>
</footer>
<script type="text/javascript" src="js/nslog.js"></script>
<script type="text/javascript" src="node_modules/vue/dist/vue.js"></script>
<script type="text/javascript" src="js/appVue.js"></script>
</body>
</html>
未完待续,下一章节,つづく