TodoList
静态组件
OptionItem为OptionList的子组件。
FooterItem、HeaderItem、OptionList为App子组件?
初始化列表
目前把数据放进OptionList里,子组件item通过props接收数据。
List:
<template>
<ul class="todo-main">
<!--eslint-disable-next-line vue/valid-v-for-->
<option-item v-for="todoobj in todos" :key="todoobj.id" :todo="todoobj"></option-item>
</ul>
</template>
<script>
import OptionItem from "./OptionItem.vue";
export default {
name: "OptionList",
data() {
return {
todos:[
{id:"001",title:'吃饭',done:true},
{id:"002",title:'睡觉',done:true},
{id:"003",title:'喝水',done:false},
],
};
},
components: {
// eslint-disable-next-line vue/no-unused-components
OptionItem,
},
};
</script>
Item”:
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done"/>
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" style="display: none">删除</button>
</li>
</template>
<script>
export default {
name: "OptionItem",
data() {
return {
};
},
//接收数据todo对象
props:["todo"],
};
</script>
添加
目前需要Header和List之间传输数据,目前没有方法做到。
需要借用App组件。
?子传父,父配置方法methods,并在模板中传入,子组件接收并调用方法。(引用传递)?
父传子,父配置数据,并在模板中传入,子组件接收(propps)接收数据,再传递给子子组件。
子组件里面的数据想要传给父组件需要前提父组件先定义一个函数方法并且将这个函数方法传给子组件,然后子组件的数据代入为函数形参,这样父组件就可以有子组件的数据了
*Vue props传入function时的this指向问题
- props传入function时,函数中this自动绑定Vue实例;
- 在H5的Vue中项目中,console将输出 “this is parent.”;
但在uni-app小程序中使用Vue时,console将输出“this is child”;
Vue中不推荐向子组件传递Function的方式,因为Vue有更好的事件父子组件通信机制;
在Header里添加一个数据时, 调用addTodo,App里的数据变化,模板重新解析,然后把新的todos交给list,list收到数据,重新解析模板,生成4个item,diff算法新旧dom对比,传出todoobj,item获取todoobj,解析数据。
App.vue
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<header-item :addTodo='addTodo'></header-item>
<option-list :todos='todos'></option-list>
<footer-item></footer-item>
</div>
</div>
</div>
</template>
<script>
import HeaderItem from "./components/HeaderItem.vue";
import FooterItem from "./components/FooterItem.vue";
import OptionList from "./components/OptionList.vue";
export default {
name: "App",
data() {
return {
todos:[
{id:"001",title:'吃饭',done:true},
{id:"002",title:'睡觉',done:true},
{id:"003",title:'喝水',done:false},
],
};
},
components: {
// eslint-disable-next-line vue/no-unused-components
HeaderItem,
// eslint-disable-next-line vue/no-unused-components
FooterItem,
// eslint-disable-next-line vue/no-unused-components
OptionList,
},
methods: {
addTodo(obj){
this.todos.unshift(obj);
}
},
};
</script>
header.vue
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add" />
</div>
</template>
<script>
import {nanoid} from 'nanoid';
export default {
name: "HeaderItem",
data() {
return {
title:''
};
},
methods:{
add(){
//校验数据
if(!this.title.trim()) return alert('输入不能为空');
//将用户的输入包装为对象,
const todoobj = {id:nanoid(),title:this.title,done:false};
this.addTodo(todoobj);
//清空输入
this.title = '';
}
},
//接收app传过来的方法
//receive方法出现在vc上
//函数也是对象 引用传递(传递地址)
props:['addTodo']
};
</script>
?list.vue
<template>
<ul class="todo-main">
<!--eslint-disable-next-line vue/valid-v-for-->
<option-item v-for="todoobj in todos" :key="todoobj.id" :todo="todoobj"></option-item>
</ul>
</template>
<script>
import OptionItem from "./OptionItem.vue";
export default {
name: "OptionList",
data() {
return {
};
},
components: {
// eslint-disable-next-line vue/no-unused-components
OptionItem,
},
//接收
//出现在vc上
props:['todos']
};
</script>
勾选
给App添加一个checkTodo方法,传给list再传给item,item传入id参数,通知app组件将id对应的todo对象done取反。
不推荐下面这样做:
不建议v-model绑定props传入的数据。
<input type="checkbox" v-model="todo.done">input框为CheckBox时,并且v-model绑定了一个boolean时,就会决定其是否勾选。
v-model:初始化时就维护好勾不勾,同时,勾或者不勾时,todo.done也会变化,todo是一个对象。
todo是props传进来的,props是只读的,不允许修改。当一个数据是引用对象时,vue监测的是地址值。当一个基础类型的数据被props传入时,在子组件中不允许修改该基础类型的数据。
App.vue
methods: {
addTodo(obj){
this.todos.unshift(obj);
},
//修改勾选状态todo
checkTodo(id){
this.todos.forEach((todo)=>{
if(todo.id === id){
todo.done = !todo.done;
}
});
}
},
item.vue
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change='handleCheck(todo.id)'/>
<!--eslint-disable-next-line vue/no-mutating-props-->
<input type="checkbox" v-model="todo.done">
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" style="display: none">删除</button>
</li>
</template>
<script>
export default {
name: "OptionItem",
data() {
return {
};
},
//接收数据todo对象
props:["todo",'checkTodo'],
methods: {
handleCheck(id){
//通知app组件将id对应的todo对象done取反
this.checkTodo(id);
}
},
};
</script>
删除
给App添加一个deleteTodo方法,传给list再传给item,item传入id参数,通知app组件将id对应的todo对象删除。
App.vue
methods: {
//添加对象
addTodo(obj){
this.todos.unshift(obj);
},
//修改勾选状态todo
checkTodo(id){
this.todos.forEach((todo)=>{
if(todo.id === id){
todo.done = !todo.done;
}
});
},
deleteTodo(id){
this.todos = this.todos.filter( todo => todo.id !== id);
}
},
item.vue
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change='handleCheck(todo.id)'/>
<!--eslint-disable-next-line vue/no-mutating-props-->
<input type="checkbox" v-model="todo.done">
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</template>
<script>
export default {
name: "OptionItem",
data() {
return {
};
},
//接收数据todo对象
props:["todo",'checkTodo','deleteTodo'],
methods: {
handleCheck(id){
//通知app组件将id对应的todo对象done取反
this.checkTodo(id);
},
handleDelete(id){
if(confirm('确定要删除吗?')){
//通知app组件将id对应的todo对象删除
this.deleteTodo(id);
}
},
},
};
</script>
底部统计
采用es6的Array.prototype.reduce()方法统计。
<template>
<div class="todo-footer" v-show='total'>
<label>
<input type="checkbox" :checked='isAll' @click="checkAll"/>
</label>
<span> <span>已完成{{totalDone}}</span> / 全部{{total}} </span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: "FooterItem",
data() {
return {
total:this.todos.length,
};
},
props:['todos','checkAllTodo'],
computed:{
totalDone(){
//reduce统计功能
//调用3次
//第二次pre的值是第一次调用的返回值,不写就是undefined
//current就是每一个todo项
return this.todos.reduce((pre,current)=>{
//最后一次调用该函数的返回值作为reduce的返回值
return pre + (current.done ? 1:0);
},0);
},
isAll(){
return this.totalDone === this.total && this.total>0;
}
},
methods: {
checkAll(e){
this.checkAllTodo(e.target.checked)
}
},
};
</script>
如果把total写在data里:
发现修改todos,total不会发生变化。
?
?如果把total写在computed里:
发现修改todos,total会发生变化。
?data 和 computed 最核心的区别在于 data 中的属性并不会随赋值变量的改动而改动,而computed 会。?
底部交互
App向foot传递方法,底部选择框传入选择状态,App根据选择状态将所有todo设置为该状态。
当采用这种方式时:勾或者取消勾时,isAll都会变化。
?isAll是计算出来的,并且只被读取,不被修改。
?此时需要get和set
<template>
<div class="todo-footer" v-show='total'>
<label>
<!--<input type="checkbox" :checked='isAll' @click="checkAll"/>-->
<input type="checkbox" v-model="isAll">
</label>
<span> <span>已完成{{totalDone}}</span> / 全部{{total}} </span>
<button class="btn btn-danger" @click="clearDone">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: "FooterItem",
data() {
return {
//total:this.todos.length
};
},
props:['todos','checkAllTodo','clearAllTodo'],
computed:{
total(){
return this.todos.length
},
totalDone(){
//reduce统计功能
//调用3次
//第二次pre的值是第一次调用的返回值,不写就是undefined
//current就是每一个todo项
return this.todos.reduce((pre,current)=>{
//最后一次调用该函数的返回值作为reduce的返回值
return pre + (current.done ? 1:0);
},0);
},
/*
isAll(){
return this.totalDone === this.total && this.total>0;
}*/
isAll:{
get(){
return this.totalDone === this.total && this.total>0;
},
//修改的值
set(value){
this.checkAllTodo(value)
}
}
},
methods: {
/*checkAll(e){
//传入选择框的选中状态
this.checkAllTodo(e.target.checked)
}*/
clearDone(){
this.clearAllTodo();
}
},
};
</script>
总结
浏览器本地储存
localStorage浏览器本地缓存,不登录也能缓存。(没有存在服务器数据库里)
关闭浏览器,数据也不会消失。
?此时存入的是p.toString()?
?sessionStorage,浏览器一关闭,会话消失
<body>
<h2>localStorage</h2>
<button onclick="saveData()">点击保存本地储存</button><br>
<button onclick="readData()">点击读取本地储存</button><br>
<button onclick="deleteData()">点击删除本地储存</button><br>
<button onclick="deleteAllData()">点击删除本地储存</button><br>
<script type="text/javascript">
const p = {name:'jack',age:18}
console.log(p.toString())
function saveData(){
window.localStorage.setItem('msg','hi');
//localStorage.setItem('person',p);
localStorage.setItem('person',JSON.stringify(p));
}
function readData(){
console.log(localStorage.getItem('msg'));
console.log(JSON.parse(localStorage.getItem('person')));
console.log(localStorage.getItem('msg2'));//null
console.log(JSON.parse(localStorage.getItem('person2')));//null
}
function deleteData(){
localStorage.removeItem('msg');
}
function deleteAllData(){
localStorage.clear();
}
</script>
</body>
<body>
<h2>sessionStorage</h2>
<button onclick="saveData()">点击保存session储存</button><br>
<button onclick="readData()">点击读取session储存</button><br>
<button onclick="deleteData()">点击删除session储存</button><br>
<button onclick="deleteAllData()">点击删除session储存</button><br>
<script type="text/javascript">
const p = {name:'jack',age:18}
console.log(p.toString())
function saveData(){
window.sessionStorage.setItem('msg','hi');
//sessionStorage.setItem('person',p);
sessionStorage.setItem('person',JSON.stringify(p));
}
function readData(){
console.log(sessionStorage.getItem('msg'));
console.log(JSON.parse(sessionStorage.getItem('person')));
console.log(sessionStorage.getItem('msg2'));//null
console.log(JSON.parse(sessionStorage.getItem('person2')));//null
}
function deleteData(){
sessionStorage.removeItem('msg');
}
function deleteAllData(){
sessionStorage.clear();
}
</script>
TodoList本地存储
数组里面的数据(第0项,第1项 ....)并没有get和set。
vue对数组的监测并不是通过get和set,而是通过包装数组身上常见的方法(能够影响原数组的方法)。
需要开启深度监视,才能保存todos数组里的对象的状态。
?组件的自定义事件
? ? 给student的组件实例对象vc绑定了一个事件叫atguigu,子组件触发事件会触发getStudentName函数。getStudentName作为事件的回调函数,App没有向子组件传递数据。
?通过ref属性绑定事件:
App.vue
<template>
<div class="app1">
<h1>{{msg}}</h1>
<hr>
<!--eslint-disable-next-line vue/no-parsing-error-->
<!--给student的组件实例对象vc绑定了一个事件叫atguigu,触发事件触发getStudentName函数 -->
<!--
<student-info
v-on:atguigu.once='getStudentName'>
</student-info>-->
<!--给student的组件实例对象vc绑定了一个事件叫atguigu,触发事件触发getStudentName函数 第二种写法 -->
<student-info
ref='student'>
</student-info>
<hr>
<!--eslint-disable-next-line vue/no-parsing-error-->
<school-info
:getSchoolName='getSchoolName'>
</school-info>
</div>
</template>
<script>
import StudentInfo from './components/StudentInfo.vue';
import SchoolInfo from './components/SchoolInfo.vue'
export default{
name:'App',
data(){
return {
msg:'App hello!'
}
},
components:{
// eslint-disable-next-line vue/no-unused-components
StudentInfo,
// eslint-disable-next-line vue/no-unused-components
SchoolInfo,
},
methods: {
getSchoolName(name){
console.log('App收到了学校名',name)
},
getStudentName(name,...arg){
console.log('App收到了学生名',name,arg)
}
},
//App挂载完毕
//此种方法灵活性更强
mounted(){
//student的组件实例对象,给它绑定事件
/*setTimeout(() => {
this.$refs.student.$on('atguigu',this.getStudentName);
}, 3000);*/
this.$refs.student.$once('atguigu',this.getStudentName);
},
}
</script>
<style>
.app1{
background-color: blue;
padding: 5px;
}
</style>
?student.vue
<template>
<div class="student">
<h1>{{ msg }}</h1>
<h2>stuname:{{ name }}</h2>
<h2>stuage:{{ stuage }}</h2>
<button @click="sendStudentName">把学生名字给app</button>
</div>
</template>
<script>
export default {
name: "StudentInfo",
data() {
return {
msg: "尚硅谷666",
name: "jack",
stuage: 18,
};
},
methods:{
sendStudentName(){
//触发给student的组件实例对象vc绑定的事件atguigu
this.$emit('atguigu',this.name,132,"a",1234)
}
}
};
</script>
<style>
.student{
background-color: red;
padding: 5px;
margin: 5px;
}
</style>
解绑
unbind(){
//解绑一个自定义事件
this.$off('atguigu')
//解绑多个自定义事件
this.$off(['atguigu','demo']);
//解绑所有自定义事件
this.$off();
}
*Destroyed 移除事件(自定义事件)监听,不是指原始dom监听。
此时销毁组件,点击+1按钮,add方法仍然调用,但数据不更新。
?
一个组件被销毁了,它的自定义事件也消失了。getStudentName事件不触发。student的原生dom事件click还在。
?.3秒后销毁vm:子组件以及子组件自定义事件都销毁。
?路由跳转时,当前组件也会销毁。身上的自定义事件也就都失效了。
总结组件自定义事件
此时这样写,app不能获取到studentname
?此时的this是组件Student的实例vc
?vue底层是这么设计的,谁触发的当前事件,回调函数当中的this就是谁。
此时getStudentName写在了methods里面,函数内的this就是组件实例对象。
?此时可以获取到studentname,写成了箭头函数,往外找到mounted,this是app的组件实例对象。
?给组件绑定原生事件.native,把事件交给了student最外层的div
?总结
?TodoList自定义事件
修改数据传递方式
|