Vue.js 四天课程学习笔记_第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的全局配置
可以在启动应用之前 设置以下属性:再插一句:
提到了项目管理软件 worktile trello Teambition
下面开始 安装 浏览器自动同步刷新神器 browser-sync
第1步,下载browser-sync,官网:browsersync.io
npm install browser-sync --save-dev
或者使用简写(注意大写D)
npm i browser-sync -D
注意: --save-dev 参数表示 这个browser-sync的依赖 并不是必须的! (可有可无)
有了这个-dev的参数, 最后这个browser-sync依赖 会被写到package.json文件的 devDependencies节点
(插入一句说明: npm install 命令 会同时安装 dependencies节点和devDependencies节点)
(插入一句说明: 如果不想安装package.json文件中的devDependencies节点,而只想安装dependencies节点的话,可以使用命令npm install --production)
第2步,打开package.json进行配置// 注意,package.json中不能写注释
"scripts": {
// dev代表的就是后面的长长的命令, 后面代表的是要监视的文件路径和类型,public目录下的所有html是我手动加的
"dev": "browser-sync start --server --files \"*.html, css/*.css, public/*.html, js/*.js\"",
// 使用npm start命令时,就会执行后面的 npm run dev命令
// 一般都要写npm run start,但只有这个start比较特殊,可以省掉中间的run,从而简写写npm start
"start": "npm run dev"
},
如图所示:
配置scripts节点就是为了方便他人运行该项目(而无需输入具体的.js名字)
第3步: 启动
npm start效果如下: 以后每次改动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 与 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??? 为啥没效果
如图所示: (为啥会没有效果)
列表渲染之条件渲染
v-for可以把一个(过滤后的)数组 对应生成 一组元素
有时候,我们想要显示一个数组的排序副本或者过滤之后的数组,
而不实际改变或重置原始的数组
在这种情况下, 我们就可以创建一个 能够返回 过滤或排序后的数组 的计算属性
例如: 过滤出 loli
v-show: 显示不显示 (无论如何都会进行渲染)
v-if指令可以决定哪个DOM元素,是否需要进行渲染
假如有许多个DOM元素都需同时进行判断是否需要进行渲染,而且判断的条件还是同一个的话女主: 面码
年代: 2011年
}
vue_18.html代码如下:
动漫: 未闻花名
女主: 面码
年代: 2011年
效果如下:
右键,审查元素,我们可以发现, 虚拟元素奇迹般地完成其历史使命后消失了,仿佛从来没有出现过一样(好神奇)
再啰嗦一下:
在虚拟元素上使用v-if进行条件渲染
因为我们知道,v-if指令必须加到一个HTML元素上,才能生效
但是,如果要同时根据同一条件去判断是否渲染多个HTML元素呢?
此时最好的办法就是: 把一个虚拟元素作为他们的容器(根节点)
这时候,只要在虚拟元素上使用v-if指令即可
最终,渲染出来的页面上,不会存在虚拟元素,但其子元素都可以正常展示
下面简单演示一下v-if与v-else-if与v-else指令的使用
vue_19.html代码如下:
{{ girlAge }} is baby
{{ girlAge }} is loli
{{ girlAge }} is girl
注意: v-if、v-else-if、v-elseの演示
效果如下:
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;
}
使用:
v-once 指令 (不需要表达式)
用法: 只渲染元素一次. 随后的渲染,都将视该元素/组件及其所有子节点 为 静态内容
作用: 优化性能
{{ girlName }}
v-pre (不需要表达式)
用法:
比v-once更生猛,该指令直接跳过所在元素及其子元素的编译过程
因此,可以用来显示 大量 原始的 Mustache标签,
自动跳过大量没有 指令的 元素(如文章详情), 加快编译
{{ 这个是文章详情, 大量图文, 没有指令, 无需参与编译 }}
效果如下:
下面介绍, 使用v-text指令, 解决 Mustache语法的 闪烁小bug
当我们在使用Mustache语法时, 其实是有一个小bug的
那就是在频繁刷新渲染界面时,会在页面上看到渲染数据之前的占位符{{ girlName }}
因为 浏览器在渲染的时候, 不认Mustache语法, 例如: {{ girlName }}
所以,就会直接将 {{ girlName }}显示到界面上,
等Vue将这个Mustache语法中的girlName 替换之后, 会重新渲染绑定 的值
因此, 就会出现界面的闪烁~
为此,我们可以v-text指令避免这个问题
解决办法之一是: 使用v-text指令 修复刷新时的小bug
{{ girlName }}
// 两者最终的渲染是完全一样的, (唯一不同的是: 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元素如下:
这样一来, 一开始 DOM在渲染时,由于 div 拥有 v-cloak属性, 所以是display: none; 是隐藏的 (自然也就看不到{{ girlName }})
当等到Vue完成数组绑定时,重新渲染DOM时, 会奇迹般自动把div上的v-cloak属性移除,
没有了v-cloak属性了, 自然样式display:none;也就不生效了 (赞~~)
下面开始详细介绍 事件处理 之 捕获按键
按键修饰符.once 修饰符, 让点击事件只触发一次
上面代码的意思是,当用户按上并弹起回车键时, 执行处理函数xxxFunction
vue_20.html代码如下:
{{ result1 }}
{{ result2 }}
{{ result3 }}
{{ result4 }}
{{ result5 }}
注意: Vue按键修饰符
效果如下:
下面开始 对 指令部分,作一个 全面细致的总结:
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运算异常复杂 且 大量重复的时候,
模板会变得繁重 和 冗余 且 难以维护
例如: 下面的就是模板的 逻辑过重
{{ loliCount }}
计算属性的setter
默认的计算属性只有getter, 但是也可以自己手动实现一个setter运行时, 只要勾选或取消了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)的对象
3. immediate: true ,监听开始时,立即调用(不管值 改没改变)
注意:
不能使用 箭头函数 来定义 watch选项中的函数
原因是: 箭头函数 绑定的是父级作用域的上下文
效果如下:
阶段性总结:
上面的内部包括:
1. sublime-snippet
2. 使用v-text解决闪烁问题
3. 使用Mustache语法 加 v-cloak 加 css 解决闪烁问题
css样式如下:
[v-cloak]: {display: none;}
在Vue管理的根节点上,添加v-cloak属性
下面开始介绍, 如何通过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了
下面,使用全局自定义组件, 实现此功能 (任何组件中都可以使用)
Vue.directive("beyond-focus",{
// 当被绑元素 插入到dom时
inserted: function(inputNode){
// 使用原生JS 操作 DOM 获取焦点
inputNode.focus()
}
})
// 如果是想注册局部指令, 组件中也有一个directives选项 (只能在该组件内使用)
directives: {
beyond-focus: {
inserted: function(inputNode){
inputNode.focus()
}
}
}
自定义的指令的使用方式如下:
注意: 1. 在模板中使用自定义指令,需要加上 v- 前缀
2. 对于用驼峰定义的自定义指令,使用时 用 - 连接
componentUpdated: 所在组件的根节点及所有子节点 全部更新后 调用( 此时 DOM内容 已经全部更新 ,el.innerHTML为新内容)
unbind: 只调用一次, 指令与元素解绑时调用 (做一些收尾工作,如 window.clearInterval(el.timer) )
参数4: oldNode: Vue编译生成的上一个虚拟节点,只在update和comonentUpdated钩子中可用
再次强调:
示例代码vue_27.html如下:
{{ isVisible }}
注意: update钩子中, el.innerHTML是更新前的内容
componentUpdated钩子中, el.innerHTML是更新后的内容
效果如下:
自定义指令的简写形式 ( bind和update中 代码相同时)
Vue.directive('beyond-show',function(el,binding){
if(binding.value === true){
el.setAttribute('sytle','color:black')
}else{
el.setAttribute('sytle','color:white')
}
})
对象字面量
如果,指令需要多个值,那么可以传递一个字面量对象
例如:
})
指令名name是: beyond-bind
原始指令名raw name是 v-beyond-bind:css.a.b.c
指令参数arg是 css
指令修饰符modifiers是 a b c
效果如下:
关于 指令、参数、表达式、修饰符的说明
点我试试
指令是 v-bind
参数是 href
表达式是 urlExpression
指令是 v-on
参数是 click
表达式是 xxxFunction
修饰符是 prevent, 即自动调用event.preventDefault()
指令参数
一些指令能够接收一个参数, 在指令名称后 用: 冒号表示
例如
指令是 v-on
指令参数是 click
指令修饰符
一些指令还可以添加修饰符, 修饰符Modifiers 是以半角句号 . 指明的特殊后缀
用于指定一个指令该以何种方式进行绑定
例如
指令修饰符是 prevent , 意思就是阻止表单提交的默认事件,event.preventDefault()
下面是demo时间,
第2个输入框,初始化时,自动获取焦点,效果如下:
自定义指令, 操作DOM,让第2个inputView 自动获取焦点 的vue_24.html代码如下:
beyond心中の动漫神作
未闻花名
注意:
下面是自定义指令v-beyond-show,实现 显示和隐藏
vue_25.html代码如下:
beyond心中の动漫神作
未闻花名
注意: 自定义指令v-beyond-show实现显示和隐藏
效果如下:
核心代码:
// ------------------全局自定义指令(完整形式)------------
// 参数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)
}
})
核心代码的简写形式:
// ------------------全局自定义指令(简写形式)------------
Vue.directive('beyond-show',function (el,binding) {
// 指令是可以传值的, 根据传递的值(即binding对象中的value)
var isVisible = binding.value
NSLog('isVisible: ' + isVisible)
var parentNode = el.parentNode
// 由于是简写,bind钩子 和 update钩子 写在一起
// 而bind钩子执行的时候,还取不到 父节点
if (!parentNode) {
return
}
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'
}
})
自定义指令v-bind效果 (动态绑定属性值)
vue_28.html代码如下:
{{ girlName }}
注意: 自定义指令v-beyond-bind演示
效果如下:
自定义指令v-beyond-bind 实现添加移除class效果
vue_29.html代码如下
注意: 自定义指令v-beyond-bind演示
核心代码:
// 对象
for(var className in binding.value){
if(binding.value[className]){
el.classList.add(className)
}else{
el.classList.remove(className)
}
}
效果如下:
下面我们开始 介绍 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代码如下:
beyond心中の动漫神作
あの花
-
未完待续,下一章节,つづく