Vue安装
安装脚手架,使用全局安装就可以
npm install -g @vue/cli
安装完使用这个命令查看vue cli的版本
vue -V
初始化一个Vue项目
vue create lk-demo
然后会进入一大堆的选项
1. Manually select features
2. 空格选择,A全选,手动配置
运行项目
npm run serve
将项目打包到dist目录
npm run build
如果只有配置文件,那么需要手动安装依赖才能运行
npm install
Vue指令
双括号表达式{{intro}}
- 可以直接展示出data中的数据
- 还可以对数据进行一些字符串的操作:
{{intro.toUpperCase()}}
- 注意双括号表达式是写在两个标签中间的,这意味着你可以将它与其它字符进行拼接
v-text
- v-text是一个属性,如果倪同时还在两个标签中间写上其它字符,其它字符并不会显示出来
- 仅仅是纯文本,data中的数据会被完完整整地展示出来
v-html
- data中的数据会以html的形式展示出来
v-bind
这些指令都不需要在双引号内部写双括号表达式
v-bind:href
- 作用就是给HTML标签绑定某个属性,该属性在data里面:
v-bind:href="site"
- 可以简写为:
:href="site"
v-bind:class
样式类可以是字符串
样式类可以是对象
样式类可以是数组
样式类可以是数组
- 如果是字符串,那么引号里的是data里的属性
- 如果是对象,对象里的就是样式表里的选择器
- 如果是数组,数组里的字符串也是样式表里的选择器
- 同样是数组,但是数组里不是字符串,那就可以写data里的数据
v-bind:style
样式类可以是字符串
- v-bind:style的方式同样可以给元素绑定样式,只不过是内联样式。
- 注意属性名采用驼峰命名法,属性实在data中定义的。
v-on:click
- 就是给该元素绑定某个methods中的事件
- 可以不传递参数:
v-on:click="study"
,也可以传递参数@click="study('小撩')"
- 当然,你应该也看出来了,它可以简写:
@click="study"
- 传递参数之后,用什么来接收参数呢?首先方法里面的形参一定是有的,方法里面,可以用
${name}
,来接收参数。 -
,@click不仅可以用来绑定函数,还可以直接把方法写在里面。
study(name){
alert(`${name},祝你学有所成!`);
}
v-model
- 该指令的作用就是将它所绑定的数据和data中的数据实现一个双向绑定,实时互通。
- 用法就是:
v-model="msg"
v-if
今晚要上课!
今晚不上课!
- v-if还有与之配套的v-else
- v-if并不是通过设置css属性display完成隐藏的
v-show
今晚讲Vue!
今晚不讲Vue!
- 是通过设置
display: none;
来隐藏元素的。因此如果需要频繁进行显示、隐藏操作的话,它是比v-if更好的选择。
v-for
遍历数组
-
ID: {{personsKeys[index]}} ---- {{index}} ) 姓名:{{person.name}}, 年龄:{{person.age}}, 性别:{{person.sex}}
- v-for应该用在li标签而不是ul标签上
- 注意括号里的第一个参数就相当于数组里的每一项,使用它就可以用对象的付出调用其各个数据
注意:这里还涉及到shortid
的使用
安装
shortid
:npm i shortid --save
在该组件中导入:
import shortId from 'shortid'
在data中要有
personsKeys
这个数据,它是一个空的数组注意要添加一个mounted挂载点,调用shortid自动产生一个随机数作为id
mounted() {
this.personsKeys = this.persons.map(v=>shortId.generate())
}
- 然后就可以如上面代码部分那样使用了
- v-for可能需要使用
:key
这个属性,不然的话可能在控制台会报警告
遍历对象
-
{{key}} --- {{item}}
- key就是对象的key
- item相当于对象的value
其它指令
v-pre
{{intro}}
- 貌似加了这个属性之后,双括号表达式就不会解析了,而会被当作纯文本展示在页面上。
- 它并不需要参数
v-cloak
{{message}}
- 当某些情况下,Vue加载有点慢,这个时候可能就会以源码的形式展示出来,比如这里的双括号表达式,这个时候使用v-cloak就可以避免这个闪烁问题。
v-once
{{name}}
- 它会使得双括号表达式仅解析一次,之后即便通过v-model改变了data中的数据也不会更新
ref的使用
我是一只鱼
- 这东西并不是一个指令,好像是引用什么的吧。官方的解释是:ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件。
- 其实可以把它看做id属性,通过它获取到该元素然后就可以进行某些操作。
- 如果想要输出它里面的内容,可以采用这种方式。注意这里的innerHTML只会输出
标签中间部分的html内容
console.log(this.$refs.fish.innerHTML);
自定义全局和局部指令
自定义全局指令
Vue.directive('upper-word', (el, binding)=>{
console.log(el, binding);
el.textContent = binding.value.toUpperCase();
});
- 全局指令需要写在main.js中
- Vue的指令都需要在前面加一个
v-
自定义局部指令
directives: {
'lower-word'(el, binding){
console.log(el, binding);
el.textContent = binding.value.toLowerCase();
}
}
- 局部指令的定义使用的是
directives
这个钩子选项 - 注意它和全局指令的定义有一些细微的不同。
计算属性
- 计算属性和data中的数据一样,都可以使用v-model来进行绑定
- 计算属性都有
set()
和get()
方法,get()
用于对data中的数据进行计算,set()
用于将从它获取到的数据赋给data中的数据
fullNameTwo: {
get(){
// console.log(`调用了fullNameTwo的getter方法`);
return this.firstName + '·' + this.lastName;
},
set(value){
// console.log(`调用了fullNameTwo的setter方法,值:${value}`);
// 1.更新firstName和lastName
let names = value.split('·');
console.log(names);
this.firstName = names[0];
this.lastName = names[1];
}
}
数据监听watch
// 配置watch
watch: {
// 监听firstName
firstName(value){
console.log(`watch监视到firstName发生改变:${value}`);
// 更新fullNameThree
this.fullNameThree = value + '·' + this.lastName;
},
// 监听lastName
lastName(value){
console.log(`watch监视到lastName发生改变:${value}`);
// 更新fullNameThree
this.fullNameThree = this.firstName + '·' + value;
}
}
- watch和data、computed是同一级属性,可以用来对data中的数据进行监听。
- watch里面每个函数的名字与data里的属性相同,当该属性被改变,就会自动调用该方法。
- 该方法可以内部同样可以中data中的数据进行运算。
事件处理
事件对象
- 可以在触发事件的同时将参数和事件对象都传递过去,该事件对象里面有很多很多与时间相关的属性
事件修饰符@click.prevent
撩课
- 它的存在是可以阻止该元素默认的行为的。
- 比如上面这个是个超链接,但是现在点击之后并不会跳转,而是执行aClick()方法
- 如果是form,也会阻止掉自动提交的方法。
事件修饰符@click.stop
- 该修饰符是用在子元素标签上的,可以用来阻止事件冒泡。也就是说,使用之后,点击子元素的标签并不会触发父元素上的事件。
按键修饰符
dealKey(event){
console.log(event);
console.log(event['keyCode']);
}
- 使用@keyup可以在后面跟上很多种按键,也就是当按下然后松开之后就会执行该事件。
- 另外在触发事件的对象里可以通过
event['keyCode']
获取按键的ASCII码
过滤器
全局过滤器
Vue.filter('wholeMoneyFormat', (value)=>{
return '¥' + Number(value).toFixed(4);
});
{{money | wholeMoneyFormat}}
- 全局过滤器需要写在main.js中,如上面的代码所示
- 上面的过滤器的意思是:将传过来的参数转成数字,并且保留四位小数,然后在前面加上一个人民币的符号并返回。
- 使用的时候需要使用双括号表达式,第一个是需要过滤的参数,第二过滤器的名字,中间用竖线隔开。
局部过滤器
filters: {
moneyFormat(value){
return '¥' + Number(value).toFixed(2);
},
timeFormat(value, format='YYYY-MM-DD HH:mm:ss'){
return moment(value).format(format);
}
}
- 局部过滤器的定义如上面的代码所示。使用方法和全局过滤器是完全一样的
- 第一个金钱过滤器,是将传递过来的参数转成保留两位小数的数字,并且在前面加上一个人民币符号。
- 第二个时间过滤器需要使用moment插件,将value中的事件转成format中的时间格式。当然也可以在调用的时候手动修改过滤的时间格式。
{{time | timeFormat('YYYY-MM-DD')}}
Vue过渡和动画
只使用类
撩课学院
- 在Vue中使用动画,需要使用transition标签,该标签必须具备一个name属性
- 我们可以通过设置一个v-if来切换该元素出现和消失时的动画
- 然后在样式表中写好终点的样式以及进行中的样式。选择器的名字需要使用到上面提到的name属性,比如:
.fade-enter
,.fade-leave-to
,.fade-enter-active
,.fade-leave-active
- 下面这个就是一个CSS的例子
.fade-enter, .fade-leave-to{
opacity: 0;
transform: translateX(200px) scale(3);
}
.fade-enter-active, .fade-leave-active{
transition: all 2s ease-in-out;
}
使用@keyframes
- 注意:如果图片采用:src这种方式导入的话,需要先导入这样图片
import pic from '@/assets/img_02.jpg'
,然后将其作为data中的一个数据 - 两个CSS类,第一个表示进入时候的动画以及持续时间。第二个表示消失时候的动画以及持续时间,并且表明和第一个动画是相反的。
- 详细代码如下
.bounce-enter-active {
animation: bounce 1s;
}
.bounce-leave-active {
animation: bounce 1s reverse;
}
@keyframes bounce {
0% {
transform: scale(0);
}
25% {
transform: scale(0.2);
}
50% {
transform: scale(0.4);
}
75% {
transform: scale(0.6);
}
100% {
transform: scale(1);
}
}
导入外部的动画CSS库
- 该动画库的使用情况如上面的代码
- 安装animate.css:
npm i animate.css --save
- 然后导入
animate.css
:import animate from 'animate.css'
- transition中的三个属性分别表示进入时候的动画、消失时候的动画,以及进入和消失的持续时间。
生命周期
- Vue的生命周期按顺序有:
beforeCreate
、created
、beforeMount
、mounted
、beforeUpdate
、updated
、beforeDestroy
、destroyed
- 一般来说只要该组件启用,前面四个生命周期都会很快依次调用。
- 然后,每次修改data中的数据,都会调用
beforeUpdate
、updated
两个方法 - 最后,如果想要进入销毁的生命周期需要主动触发下面代码的这个方法
- 另外,定时器如果在生命周期里,注意它是异步的,即便已经销毁可能定时器仍然在工作。这个时候最好在销毁的生命周期里设置一个清除定时器的方法。
destory(){
this.$destroy();
}
组件通信
子组件通知父组件,我触发了某个方法
父组件
- App.vue在组件上面需要绑定事件
- 在methods中写事件
deleteP(args){
console.log(args);
this.$refs.word.remove();
}
子组件
- 子组件同样需要给某个元素绑定事件
- 但不是自己处理事件,而是告诉父元素,自己触发了哪个事件,参数是什么
btnClick(){
// 告诉父组件,我点击了按钮
this.$emit('btnClick', {name: '哈哈哈', sex:'男'});
// TODO
}
父组件向子组件传递数据和方法:props
props: {
name: String,
age: Number,
person: Object,
logPerson: Function
}
*/
props: {
name: {type: String, required: true, default: '撩课'},
age: {type: Number, required: true, default: 20},
person: Object,
logPerson: Function
}
- 父组件通过在组件上写
:age
的形式,向子组件传递数据和方法。这些数据和方法都是在父组件中已经定义好的 - 子组件需要通过props这个钩子选项接收父组件传递过来的数据和方法,有上面两种形式
- 然后子组件就可以像使用自己的数据和方法一样使用父组件的数据和方法
自定义事件
- 由子组件发送给父组件,函数名称,函数参数
this.$emit('addTodo', todo);
- 父组件需要监听这个组件:
- 然后给父组件绑定自定义事件的监听:
this.$refs.header.$on('addTodo', this.addTodo);
注意这是写在mounted
这个钩子选项里的;然后不用写()来接参数,这种写法就会自动把todo参数传递过来
发布订阅模式
- 安装pubsub插件:
npm install --save pubsub-js
- 在App.vue和item中引入pubsub-js:
import PubSub from 'pubsub-js'
,反正哪里需要发布、订阅就需要引入 - 子组件发布消息:
PubSub.publish('delTodo', this.index)
- 父组件接收消息。代码如下。注意,同样写在mounted这个钩子选项下,token就是子组件传递过来的参数
PubSub.subscribe('delTodo', (msg, token)=>{
// console.log(msg, token);
this.delTodo(token);
});
插槽
- footer中不用自己写代码了,直接把插槽留出来就行。使用的是
标签,注意必须有name属性。 - 父组件里面直接往子组件里面插入
- 这样就可以直接调用父组件里面的方法和计算属性了
Vuex
- 安装:
vue add vuex
Vue router
Vue-router的基本使用
- 安装:npm install vue-router --save
- 新建router.js文件
- 在main.js中引入,并且放入Vue的实例中
- 新建view页面,并且在App.vue中配置路由导航和路由出口
router.js
import Vue from 'vue'
import Router from 'vue-router'
// 引入页面
import Home from './views/Home'
import About from './views/About'
Vue.use(Router);
export default new Router({
// history模式就是没有#
// mode: 'history',
routes: [
{ path: '/', redirect: '/home' },
// 下面这个就是重定向到一个命名路由,name就是路由的名字
// { path: '/', redirect: {name: 'about'} },
// 下面这个是使用一个方法重定向到目标路由
// { path: '/', redirect: to => { return '/home'}},
// 这里name属性仅仅只是这个路由的名字,暂时没有用处
{path: '/home', name: 'home', component: Home},
{path: '/about', name: 'about', component: About},
]
})
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App),
}).$mount('#app');
在App.vue中配置路由导航和路由出口
路由history和hash模式
获取路由参数
import Vue from 'vue'
import Router from 'vue-router'
// 引入页面
import Home from './views/Home'
import About from './views/About'
import Mine from './views/Mine'
Vue.use(Router);
/*
let func = ({params, query})=>{
return {
name: params.name,
sex: params.sex,
height: query.height,
dog: query.dog,
}
};
*/
let func = (route)=>{
return {
name: route.params.name,
sex: route.params.sex,
height: route.query.height,
dog: route.query.dog,
}
};
export default new Router({
routes: [
{ path: '/', redirect: '/home' },
{path: '/home', name: 'home', component: Home},
{path: '/about', name: 'about', component: About},
// {path: '/mine/:name/:sex', name: 'mine', component: Mine}
// {path: '/mine', name: 'mine', component: Mine, props: {name: '小撩'}}
// {path: '/mine/:name/:sex', name: 'mine', component: Mine, props: true}
{path: '/mine/:name/:sex', name: 'mine', component: Mine, props: func}
]
})
Mine.vue
个人中心
------------------------------------------
根据路由对象获取的路径参数
姓名:{{$route.params.name}}
性别:{{$route.params.sex}}
身高:{{$route.query.height}}
小狗:{{$route.query.dog}}
根据属性对象获取的路径参数
姓名:{{name}}
性别:{{sex}}
身高:{{height}}
小狗:{{dog}}
- 关于获取路由参数的代码在上边
- 所谓的获取路由参数其实就是指,数据是在链接上的,我们需要通过某种方式获取到这些数据
- 通过路由传递参数主要有几种方式:
-
{path: '/mine/:name/:sex', name: 'mine', component: Mine}
,通过在路由上写冒号 -
{path: '/mine', name: 'mine', component: Mine, props: {name: '小撩'}}
,通过props获取数据,注意需要在该组件内部写出props这个钩子选项,并且内容为这些数据的变量名 -
{path: '/mine/:name/:sex', name: 'mine', component: Mine, props: true}
,通过以上两种方式获取数据 -
{path: '/mine/:name/:sex', name: 'mine', component: Mine, props: func}
,通过地址和方法传递数据
嵌套路由
import Vue from 'vue'
import Router from 'vue-router'
// 一级界面
import Home from './views/Home'
import About from './views/About'
import Mine from './views/Mine'
// 二级界面
import News from './views/News'
import Shop from './views/Shop'
Vue.use(Router);
export default new Router({
routes: [
{ path: '/', redirect: '/home' },
{
path: '/home',
name: 'home',
component: Home,
children: [
{ path: '/home', redirect: '/home/news' },
{path: 'news', name: 'news', component: News},
{path: 'shop', name: 'shop', component: Shop},
]
},
{path: '/about', name: 'about', component: About},
{path: '/mine', name: 'mine', component: Mine}
]
})
- 所谓的嵌套路由就是斜杠后面还有斜杠
- 嵌套路由的配置其它文件并没有什么不同,主要还是在router.js上的不同
- 嵌套路由的配置主要小心:子路由不需要在前面写/
全局路由前置和后置守卫
import Vue from 'vue'
import Router from 'vue-router'
// 一级界面
import Login from './views/Login'
import DashBoard from './views/DashBoard'
// 二级界面
import Home from './views/Home'
// import About from './views/About'
import Mine from './views/Mine'
const About = ()=> import('./views/About');
Vue.use(Router);
const router = new Router({
routes: [
{ path: '/', redirect: '/dashboard' },
{
path: '/dashboard',
name: 'dashboard',
component: DashBoard,
children: [
{ path: '/dashboard', redirect: '/dashboard/home' },
{path: 'home', name: 'home', component: Home,},
{path: 'about', name: 'about', component: About},
{path: 'mine', name: 'mine', component: Mine}
],
},
{path: '/login', name: 'login', component: Login}
]
});
// 全局路由前置守卫
router.beforeEach((to, from, next)=>{
// console.log(to, from);
if(to.path !== '/login'){ // 验证是否登录
if(window.isLogin){ // 已经登录
next();
}else { // 没有登录
// 将你要去的地址也传到登录页去,这样当你登录成功之后可以立即重定向到该页
// next('/login?redirect='+ to.path);
// next('/login?redirect=/dashboard/mine');
next('/login');
}
}else { // 不需要验证
next();
}
// 放行
next();
});
// 全局路由后置守卫
router.afterEach((to, from) => {
// console.log('来了!');
});
export default router;
- 主要通过前置守卫来控制什么情况下用户可以进入页面
- 如果用户进的是登录页,无需检查直接放行
- 如果用户进的是其它页,检查是否登录,如果没有登录前往登录页,如果已经登录放行
- 其它情况一律放行
mine.vue
export default {
name: "Mine",
beforeRouteEnter(to, from, next){
console.log('进入之前调用');
next();
},
beforeRouteUpdate(to, from, next){
console.log('路由的参数变了');
next();
},
beforeRouteLeave(to, from, next){
console.log('路由离开前调用');
next();
}
}
这里主要设置了几个生命周期的钩子选项,在进入这个组件之前、路由参数改变、以及路由离开的时候都会分别调用这里的方法
login.vue
登录界面
- 登录的这个页面主要负责处理登录相关的事务
- 当用户点击登录按钮之后,首先将全局的isLogin设置为true
- 获取用户的回调地址,如果有回调地址则直接前往;如果没有则前往主页
数据本地化
- 手写一个工具类
- 在app.vue中导入该工具类
- data中的数据需要从localStorage中读取,当todos数据发生任何改变就要把该数据存储到localStorage
工具类
const LK_TODO = 'lk_todo';
export default {
readTodos(){
return JSON.parse(localStorage.getItem(LK_TODO) || '[]');
},
saveTodos(todos){
console.log(todos);
localStorage.setItem(LK_TODO, JSON.stringify(todos));
}
}
- 使用localStorage无论是读还是取,都需要传递一个key,注意这里仅仅一个数组,所以使用了一个字符串常量作为key
- 第一个读取数据的方法内部有
|| '[]'
是为了在任何情况下都能够读到数据,哪怕是一个空的数组
读数据
todos: localStorageUtil.readTodos()
取数据
watch: {
// 深度监视
todos: {
handler: localStorageUtil.saveTodos,
deep: true, // 深度监视
// immediate: true
}
}
- 取数据需要深度监视,因为数组里面还有对象,只有深度监视能够监听数组里面对象属性的改变。
- immediate如果为true,就是当初始化的时候就立即执行handler方法,否则就是当todos发生改变才会执行handler方法。
UI框架
elementUI
- element文档:https://element.eleme.cn/#/zh-CN/component/layout
- 安装:
npm i element-ui -S
- 给Vue添加UI库:
vue add element
- 选项:Fully import、N、CN
-
vue ui
:使用可视化面板管理项目(一般不会用的)
做完以上几步就会发现,自动集成了plugins/element.js
,并且自动在main.js
中导入了。然后就可以直接在app.vue中使用了。
- 复制HTML代码到模板里面
- 复制script代码data部分到app.vue里,注意这里和vue的代码结构是完全一样的。
VantUI
- 文档:https://youzan.github.io/vant/#/zh-CN/
- 安装:
npm i vant -S
- 配置按需加载,引入babel:
npm i babel-plugin-import -D
- 配置babel.config.js,可以从文档里复制粘贴
- 在main.js从引入需要的组件,或者单独做一个js文件,然后通过import来引入
配置babel.config.js
module.exports = {
presets: [
'@vue/app'
],
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
};
在main.js中引入需要的组件
import { Button } from 'vant';
Vue.use(Button);
import { Cell, CellGroup } from 'vant';
Vue.use(Cell).use(CellGroup);
import { DatetimePicker } from 'vant';
Vue.use(DatetimePicker);
插件的安装和使用
shortid
-
ID: {{personsKeys[index]}} ---- {{index}} ) 姓名:{{person.name}}, 年龄:{{person.age}}, 性别:{{person.sex}}
- 安装
shortid
:npm i shortid --save
- 在该组件中导入:
import shortId from 'shortid'
- 在data中要有
personsKeys
这个数据,它是一个空的数组 - 注意要添加一个mounted挂载点,调用shortid自动产生一个随机数作为id
mounted() {
this.personsKeys = this.persons.map(v=>shortId.generate())
}
- 然后就可以如上面代码部分那样使用了
- v-for可能需要使用
:key
这个属性,不然的话可能在控制台会报警告
moment
- 安装:
npm i moment --save
- 使用:注意任何插件的使用,都需要在该组件内部首先导入
import moment from 'moment'
。然后就可以像调用函数一样使用该插件了
案例汇总
v-for排序小案例
- 数据来源于data中已经定义好的
- v-for的使用和之前遍历数组的时候是一样的,同样用到了shortid,但是并没有把shortid展示在页面上,而是用索引加1的方式
- v-for里的in,是已经排序好的数组,该数组是使用计算属性而得到的
- 排序分为默认,按年龄升序、降序,是通过向计算属性里传递不同参数而区别开的。
- 排序的代码如下。注意需要先过滤以下数组,然后根据条件进行排序
computed: {
filterPersons() {
// 1. 获取数据
let {searchName, persons, orderType} = this;
// 2. 取出数组中的数据
let arr = [...persons];
// 3. 过滤数组
if (searchName.trim()) {
arr = persons.filter(p => p.name.indexOf(searchName) !== -1);
}
// 4. 排序
if (orderType) {
arr.sort((p1, p2) => {
if (orderType === 1) { // 降序
return p2.age - p1.age
} else { // 升序
return p1.age - p2.age
}
});
}
return arr;
}
},
表单添加删除小案例
- data里有有已经存在的数据和一个新的空对象对象,这个空对象可以设定一些默认值
- 每个input标签都会设置v-model,分别和空对象里的各个属性绑定起来
- 添加数据的时候:首先使用解构赋值从该对象中获取输入框的数据;然后对数据进行验证,比如不能为空;插入数据直接使用数组的unshift方法就可以;最后记得要清空这个空对象,让其恢复默认值
- 点击删除按钮的时候:直接调用splice方法就可以,第一个参数是索引,第二个参数是从该索引的位置删除几个元素
todoList
todoList第一版
Header
- Header的主要任务是为todoList添加新任务,主要方法就是添加todo
- 方法的实现是在App.vue,然后使用
:addTodo="addTodo"
的方式向子组件传递自己的方法。当然子组件需要用props来接收。 - 子组件只负责一个数据title,因为创建的时候任务不可能完成,所以finished也就是那个复选框默认设置为false就行了。
- 子组件仍需要一些其它的操作:①:判断用户输入的是否是空字符串;②:根据用户的输入拼接一个todo,它主要就是包含title、finished两个数据;③:调用父组件的添加方法;④:清空title数据
List
- List的主要任务就是:①:当鼠标移动到某一条todo上时,该todo的背景颜色变为灰色,并且显示出来删除按钮
- App.vue需要向List传递todos数据和删除某一条todo的方法
- List使用props接收todos和删除方法。然后使用v-for对todos进行遍历,当然遍历的每一个item就是它的子组件,List需要向item传递它的数据todo、该todo在todos中的索引,以及删除todo的方法
- 子子组件item首先需要接收上面传递过来的两个数据一个方法。
- item实现鼠标移入就出现删除按钮以及背景颜色的改变是通过
@mouseenter
、@mouseleave
两个触发事件的方式,分别向同一个事件传递true
、false
两个不同的参数。 - 背景颜色是通过设置
:style
使用的是data里面的某个值,这样就可以使用方法改变data的这个属性来调整背景颜色 - 按钮的显示和隐藏使用的是v-show,同样是data里的一个数据控制true还是false,这样我们在方法里就可以设置该属性为true,来让按钮显示出来
- 删除该todo直接使用父组件里的方法就可以了
Footer
- footer的作用主要是:①:当上面的每一条todo都选中的时候,它的复选框也要选中;②:全选;③:点击删除所有finished的按钮。
- 选中所有todo和删除已经完成的todo都是在app.vue中定义的。然后将这两个方法以及todos的数据全部传递给footer组件
- footer里面很重要的一个功能就是要知道目前已经完成的todo数量,这是通过计算属性实现的。代码见下方。使用的是数组的reduce这个方法,其实就是一个遍历+1计数的操作。
- footer里面很重要的一个功能就是全选,也就是当上面全部选中它也要选中,它如果选中,上面也要全部选中。这是通过给该复选框设置一个v-model,并且绑定一个计算属性实现的。
- 其中get()是处理第一个的,判断条件就是目前已经完成的todo数量和todos的长度相等,并且长度不为0.
- 其中set()是处理第二个的,它会将目前该计算属性的值true、false作为参数调用父组件的全选方法。父组件的全选方法就会遍历整个todos,将其中的finished属性全部设定为参数。需要注意的是,这里有一个小窍门,就是footer的全选按钮和v-model绑定,它选中那么set()方法的参数就是true。
- 最后删除所有已经完成的todo则很简单了,不需要传递任何参数,直接调用父组件的方法即可。
finishedCount(){
return this.todos.reduce((total, todo)=> total + (todo.finished ? 1 : 0), 0);
},