每天一点VUE(DAY 7-VUE2篇)

这一篇我们进行一个小的项目实战,做一个todolist。其中有很多细节和知识点。

 我们使用组件化开发,在App.vue的管理下,分为三块。第一块是输入框(ToDoHeader.vue),第二部分是中间的部分(ToDoBody.vue),第三部分是底部的部分(ToDoFooter.vue),中间的部分还包含一个子组件(ToDoItem.vue),即每一条。

注意点1:父向子传参数

        第一步,通过父在子标签上添加属性(注意要在前面加冒号,不然传过去的字符串),然后第二步,在子组件里面通过props接收这个属性,这时就可以在vc实例上获取和使用这个数据或方法了

//App.vue页面,向下传递参数和方法,这里我们举例<ToDoHeader :addFun="addOne"></ToDoHeader>
<template>
  <div id="app">
    <div id="root">
      <div class="todo-container">
        <div class="todo-wrap">
          <ToDoHeader :addFun="addOne"></ToDoHeader>
          <ToDoBody
            :toDoList="ToDoList"
            :changeDone="changeDone"
            :ToDoRemove="ToDoRemove"
          ></ToDoBody>
          <!-- 通过ref的话比较灵活,配合mounted里面的绑定可以使用 -->
          <!-- <ToDoFooter
            :TotleNum="countTotleNum"
            :countDone="countDone"
            :allCheck="allCheck"
            ref="todofooter" 
          ></ToDoFooter> -->
          <!-- 通过指令v-on绑定事件,灵活性差,不用配合ref,
          这里我们如果想给组件绑定原生的事件,不如click事件,需要给他添加一个native,否则会被认为是自定义事件
           -->
          <ToDoFooter
            :TotleNum="countTotleNum"
            :countDone="countDone"
            :allCheck="allCheck"
            @remove="removerAllChecked"
            @click.native="nativeFun"
          ></ToDoFooter>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import ToDoBody from "./components/ToDoBody.vue";
import ToDoHeader from "./components/ToDoHeader.vue";
import ToDoFooter from "./components/ToDoFooter.vue";
import { nanoid } from "nanoid";

export default {
  name: "App",
  components: {
    ToDoBody,
    ToDoHeader,
    ToDoFooter,
  },
  // mounted(){
  //   /*通过ref获取vc的实例对象,并给他绑的一个方法,
  //   另外注意,如果直接把回调函数写在这里的this.removerAllChecked这里,
  //   会出现函数体里面的this是调用remove方法的实例对象,而不是App.vue
  //   */
  //   this.$refs.todofooter.$on('remove',this.removerAllChecked)
  // },
  data() {
    return {
      ToDoList: JSON.parse(localStorage.getItem('todos')),
      
    };
  },
  watch:{
    ToDoList(newValue,oldValue){
      //localStorage是关闭浏览器不会消失,sessionStorage是关闭浏览器就没了
      localStorage.setItem('todos',JSON.stringify(newValue))
      // console.log(JSON.parse(sessionStorage.getItem('todos2')))
      sessionStorage.setItem('todos2',JSON.stringify(oldValue))
      //这个方法是清楚某一项
      // localStorage.removeItem('todos')
      //这个方法是清除所有的
      // localStorage.clear();
    }
  },
  methods: {
    nativeFun(){
      console.log(1)
    },
    //清理所有的完成
    removerAllChecked(...a){
      console.log(a)//这里可以通过...a来接受传过来的参数,并把他们放在一个数组里面
      this.ToDoList=this.ToDoList.filter((item)=>{
        return item.done == false
      })
    },
    //全选与不全选
    allCheck(state){
      this.ToDoList.forEach((item,index)=>{
        item.done = state
      })
    },
    //增加一条
    addOne(item) {
      if (item === "") return;
      const a = { id: nanoid(), name: item, done: false };
      this.ToDoList.unshift(a);
    },
    //修改某一条的状态
    changeDone(i) {
      this.ToDoList.forEach((item) => {
        if (item.id === i) item.done = !item.done;
      });
    },
    //删除某一条
    ToDoRemove(i) {
      this.ToDoList = this.ToDoList.filter((pp) => {
        return pp.id !== i;
      });
    },
    //计算一共有几条
    countTotleNum() {
      return this.ToDoList.length;
    },
    //计算完成了几条
    countDone() {
      //方法1
      // let i = 0;
      // this.ToDoList.forEach((item,index)=>{
      //   if(item.done == true) i++;
      // })
      // return i

      //方法2:reduce专门做条件统计的
      /*
        参数1是一个函数,参数2是初始值,
        数组长是几就掉几次,
        pre是初始值, 或者计算结束后的返回值;currene是当前的这一项;currentIndex是当前的索引;arr是元素所属的数组对象
        第二次调的pre是第一次调用的这个函数的返回值
      */
      // let a =  this.ToDoList.reduce((pre, current,currentIndex,arr) => {
      //   return pre + (current.done == true ? 1 : 0);
      // },0);
      // return a
      return this.ToDoList.reduce((pre, current) => pre +(current.done == true ? 1 : 0),0);
    },
  },
};
</script>

<style>
/*base*/
body {
  background: #fff;
}

.btn {
  display: inline-block;
  padding: 4px 12px;
  margin-bottom: 0;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 1px 2px rgba(0, 0, 0, 0.05);
  border-radius: 4px;
}

.btn-danger {
  color: #fff;
  background-color: #da4f49;
  border: 1px solid #bd362f;
}

.btn-danger:hover {
  color: #fff;
  background-color: #bd362f;
}

.btn:focus {
  outline: none;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>
//这里是是ToDoHeader.vue页面这里用props接受了App.vue传过来的addFun方法
<template>
  <div class="todo-header">
    <input type="text" v-model="Names" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add(Names)"/>
  </div>
</template>

<script>
export default {
  props:['addFun'],
  data(){
    return{
      Names:''
    }
  },
  methods:{
   add(i){
     this.addFun(i);
     this.Names = ''
   }
  }
};
</script>

<style scoped>
/*header*/
.todo-header input {
  width: 560px;
  height: 28px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 7px;
}

.todo-header input:focus {
  outline: none;
  border-color: rgba(82, 168, 236, 0.8);
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
    0 0 8px rgba(82, 168, 236, 0.6);
}
</style>

注意点2:子向父传参数

        第一种办法:通过父给子提前传入一个函数,子组件通过props接受,然后到特定场景,子组件调用这个函数,可以传入参数,然后父组件就可以接收到参数,实现了子向父传。(代码参考父向子传的代码示例)

        第二种办法:通过子组件触法父组件的自定义事件完成,父组件可以通过@即v-bind给子组件绑定一个自定义的事件,回调函数在App.vue,然后子组件通过vc.$emit()方法调用父组件的自定义事件,这里也可以传参数。或者,不用v-bind,可以通过ref属性获取到这个dom,然后在dom导航通过$on给他绑定自定义事件。(用ref比较灵活点,我们可以给他加个定时器等来产生不同的交互效果,如果不用v-bind的话,自定义事件在页面渲染后就会被加载好,在mounted钩子的时候)

//App.vue,这里我们注意ToDoFooter的remove方法,注释掉的是通过ref来绑定自定义事件
<template>
  <div id="app">
    <div id="root">
      <div class="todo-container">
        <div class="todo-wrap">
          <ToDoHeader :addFun="addOne"></ToDoHeader>
          <ToDoBody
            :toDoList="ToDoList"
            :changeDone="changeDone"
            :ToDoRemove="ToDoRemove"
          ></ToDoBody>
          <!-- 通过ref的话比较灵活,配合mounted里面的绑定可以使用 -->
          <!-- <ToDoFooter
            :TotleNum="countTotleNum"
            :countDone="countDone"
            :allCheck="allCheck"
            ref="todofooter" 
          ></ToDoFooter> -->
          <!-- 通过指令v-on绑定事件,灵活性差,不用配合ref,
          这里我们如果想给组件绑定原生的事件,不如click事件,需要给他添加一个native,否则会被认为是自定义事件
           -->
          <ToDoFooter
            :TotleNum="countTotleNum"
            :countDone="countDone"
            :allCheck="allCheck"
            @remove="removerAllChecked"
            @click.native="nativeFun"
          ></ToDoFooter>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import ToDoBody from "./components/ToDoBody.vue";
import ToDoHeader from "./components/ToDoHeader.vue";
import ToDoFooter from "./components/ToDoFooter.vue";
import { nanoid } from "nanoid";

export default {
  name: "App",
  components: {
    ToDoBody,
    ToDoHeader,
    ToDoFooter,
  },
  // mounted(){
  //   /*通过ref获取vc的实例对象,并给他绑的一个方法,
  //   另外注意,如果直接把回调函数写在这里的this.removerAllChecked这里,
  //   会出现函数体里面的this是调用remove方法的实例对象,而不是App.vue
  //   */
  //   this.$refs.todofooter.$on('remove',this.removerAllChecked)
  // },
  data() {
    return {
      ToDoList: JSON.parse(localStorage.getItem('todos')),
      
    };
  },
  watch:{
    ToDoList(newValue,oldValue){
      //localStorage是关闭浏览器不会消失,sessionStorage是关闭浏览器就没了
      localStorage.setItem('todos',JSON.stringify(newValue))
      // console.log(JSON.parse(sessionStorage.getItem('todos2')))
      sessionStorage.setItem('todos2',JSON.stringify(oldValue))
      //这个方法是清楚某一项
      // localStorage.removeItem('todos')
      //这个方法是清除所有的
      // localStorage.clear();
    }
  },
  methods: {
    nativeFun(){
      console.log(1)
    },
    //清理所有的完成
    removerAllChecked(...a){
      console.log(a)//这里可以通过...a来接受传过来的参数,并把他们放在一个数组里面
      this.ToDoList=this.ToDoList.filter((item)=>{
        return item.done == false
      })
    },
    //全选与不全选
    allCheck(state){
      this.ToDoList.forEach((item,index)=>{
        item.done = state
      })
    },
    //增加一条
    addOne(item) {
      if (item === "") return;
      const a = { id: nanoid(), name: item, done: false };
      this.ToDoList.unshift(a);
    },
    //修改某一条的状态
    changeDone(i) {
      this.ToDoList.forEach((item) => {
        if (item.id === i) item.done = !item.done;
      });
    },
    //删除某一条
    ToDoRemove(i) {
      this.ToDoList = this.ToDoList.filter((pp) => {
        return pp.id !== i;
      });
    },
    //计算一共有几条
    countTotleNum() {
      return this.ToDoList.length;
    },
    //计算完成了几条
    countDone() {
      //方法1
      // let i = 0;
      // this.ToDoList.forEach((item,index)=>{
      //   if(item.done == true) i++;
      // })
      // return i

      //方法2:reduce专门做条件统计的
      /*
        参数1是一个函数,参数2是初始值,
        数组长是几就掉几次,
        pre是初始值, 或者计算结束后的返回值;currene是当前的这一项;currentIndex是当前的索引;arr是元素所属的数组对象
        第二次调的pre是第一次调用的这个函数的返回值
      */
      // let a =  this.ToDoList.reduce((pre, current,currentIndex,arr) => {
      //   return pre + (current.done == true ? 1 : 0);
      // },0);
      // return a
      return this.ToDoList.reduce((pre, current) => pre +(current.done == true ? 1 : 0),0);
    },
  },
};
</script>

<style>
/*base*/
body {
  background: #fff;
}

.btn {
  display: inline-block;
  padding: 4px 12px;
  margin-bottom: 0;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 1px 2px rgba(0, 0, 0, 0.05);
  border-radius: 4px;
}

.btn-danger {
  color: #fff;
  background-color: #da4f49;
  border: 1px solid #bd362f;
}

.btn-danger:hover {
  color: #fff;
  background-color: #bd362f;
}

.btn:focus {
  outline: none;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>
//ToDoFooter.vue这里我们在removeAllC方法里面通过this.$emit('name',参数)调用了父组件的方法
<template>
  <div class="todo-footer" v-if="TotleNum()">
    <label>
    <!-- 未使用v-model方法 -->
      <!-- <input type="checkbox" @change="allcheck" :checked="countDone()===TotleNum()&&TotleNum()!=0?true:false"/> -->
      <input type="checkbox" v-model="allChe"/>
    </label>
    <span> <span>已完成{{countDone()}}</span> / 全部 {{TotleNum()}}</span>
    <button class="btn btn-danger" @click="removeAllC()">清除已完成任务</button>
    <button class="btn btn-danger" @click="clearMyEvent()">清除自定义事件</button>
  </div>
</template>

<script>
export default {
  props:['TotleNum','countDone','allCheck'],
  methods:{
    removeAllC(){
      //调用在App.vue里面挂载在ToDoFooter上的事件‘remove’,并且传入1234这4个参数
      console.log('我被调用了,就算实例被销毁或者,事件被解绑,我也可以执行,因为我是原生的事件')
      this.$emit('remove',1,2,3,4)
    },
    clearMyEvent(){
      //解绑remove事件
      this.$off('remove')
    },
    // 未使用v-model方法
    // allcheck(e){
    //   if(e.target.checked){
    //     this.allCheck(true)
    //   }else{
    //     this.allCheck(false)
    //   }
    // }
  },
  computed:{
    allChe:{
      get(){
        return this.countDone()===this.TotleNum()&&this.TotleNum()!=0?true:false
      },
      set(a){
        // console.log(a)
        this.allCheck(a)
      }
    }
  }
};
</script>

<style>
/*footer*/
.todo-footer {
  height: 40px;
  line-height: 40px;
  padding-left: 6px;
  margin-top: 5px;
}

.todo-footer label {
  display: inline-block;
  margin-right: 20px;
  cursor: pointer;
}

.todo-footer label input {
  position: relative;
  top: -1px;
  vertical-align: middle;
  margin-right: 5px;
}

.todo-footer button {
  float: right;
  margin-top: 5px;
}
</style>

注意点3:传参的小技巧

        1、我们在一个函数传了好多参数,但我门只需要第一个时,可以通过点来接受,并放入一个数组内。(传参的时候正常传入)

function a(b,...c){
    console.log(b);
    console.log(c)
}
a(1,2,3,4,5)

        2、我们使用一个函数时可能只需要后面的一个参数,不需要前面的参数,可以用_来做占位符

function d(_,f){
    console.log(f)
}
d(7,8)

注意点4:给组件绑定原生事件

        给组件绑定如onclick,keyup等事件,需要通过一个.native来使这个事件为原生事件,否则会被认为是自定义事件,比如这里绑定的nativeFun事件

        <ToDoFooter
            :TotleNum="countTotleNum"
            :countDone="countDone"
            :allCheck="allCheck"
            @remove="removerAllChecked"
            @click.native="nativeFun"
          ></ToDoFooter>

注意点5:给子组件传或者保存一个对象

        

let a = {
    name:"k",
    age:8}
//转成字符串
console.log(JSON.stringify(a))
//字符串转成对象
console.log(JSON.parse(JSON.stringify(a)))

注意点6:本地缓存storage和session

        storge可以保存数据在浏览器,关闭以后再打开,还存在。session是保存在浏览器,在浏览器关闭后会消失,他们俩的保存,获取,清除的方法都是一样的。

 ToDoList(newValue,oldValue){
       //这是获取缓存的数据
        localStirage.getItem('todos');
      //localStorage是关闭浏览器不会消失,sessionStorage是关闭浏览器就没了
      localStorage.setItem('todos',JSON.stringify(newValue))
      // console.log(JSON.parse(sessionStorage.getItem('todos2')))
      sessionStorage.setItem('todos2',JSON.stringify(oldValue))
      //这个方法是清除某一项
       localStorage.removeItem('todos')
      //这个方法是清除所有的
       localStorage.clear();
    }

注意点7:数组的reduce方法

        我们在需要找出一个数组里面的完成的项的数量,有好几种方法,第一种直接遍历数组,通过if判断出那些可用的,在计算出来,第二种,通过reduce方法,他有两个参数,第一个是一个函数,第二个是初始值,在函数里面有四个参数,pre是初始值, 或者计算结束后的返回值;currene是当前的这一项;currentIndex是当前的索引;arr是元素所属的数组对象

 //计算完成了几条
    countDone() {
      //方法1
      let i = 0;
      this.ToDoList.forEach((item,index)=>{
        if(item.done == true) i++;
      })
      return i

      //方法2:reduce专门做条件统计的
      /*
        参数1是一个函数,参数2是初始值,
        数组长是几就掉几次,
        pre是初始值, 或者计算结束后的返回值;currene是当前的这一项;currentIndex是当前的索引;arr是元素所属的数组对象
        第二次调的pre是第一次调用的这个函数的返回值
      */
      let a =  this.ToDoList.reduce((pre, current,currentIndex,arr) => {
        return pre + (current.done == true ? 1 : 0);
      },0);
      return a
//简写
      return this.ToDoList.reduce((pre, current) => pre +(current.done == true ? 1 : 0),0);
    },

注意点8:生成一个随机数作为id

        通过nanoid这个库来生成。

       

npm i nanoid
import { nanoid } from "nanoid";

//增加一条
    addOne(item) {
      if (item === "") return;
      const a = { id: nanoid(), name: item, done: false };
      this.ToDoList.unshift(a);
    },

注意点9:通过全局事件总线bus来实现任意两个组件的通信

        第一步:安装全局时间总线bus

//main.js

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  beforeCreate(){
    Vue.prototype.$bus = this//安装全局事件总线$bus,this是当前的vm
  }
}).$mount('#app')

        第二步:在收消息的页面App.js绑定事件

//App.vue

 mounted(){
    //绑定全局总线
    this.$bus.$on('changeDone',this.changeDone)
    this.$bus.$on('ToDoRemove',this.ToDoRemove)
  },
  destroyed(){
    //用完了销毁
    this.$bus.$off('changeDone')
    this.$bus.$off('ToDoRemove')
 },

        第三步:在发消息的页面通过emit触发这个全局事件总线上的事件

//ToDoItem.vue
<template>
  <li>
    <label>
      <input type="checkbox" :checked="ToDoItem.done" @change="change(ToDoItem.id)">
      <!-- 这里用v-model也直接可以实现,但是不建议这样写,因为我们不能去改变props里面的值 -->
      <!-- <input type="checkbox" v-model="ToDoItem.done" > -->
      <span>{{ToDoItem.name}}</span>
    </label>
    <button class="btn btn-danger" @click="removethis(ToDoItem.id)">删除</button>
  </li>
</template>

<script>
export default {
  name:'ToDoItem',
  props:['ToDoItem'],
  // methods:{
  //   change(i){
  //     this.changeDone(i)
  //   },
  //   removethis(i){
  //     if(confirm('确定删除')){
  //       this.ToDoRemove(i)
  //     }
  //   }
  // }
  // 通过事件总线来调这两个函数
  methods:{
    change(i){
      this.$bus.$emit('changeDone',i)
    },
    removethis(i){
      if(confirm('确定删除')){
      this.$bus.$emit('ToDoRemove',i)
      }
    }
  }
};
</script>

<style scoped>
li:hover{
  background: #ddd;
}
li:hover button{
  display: block;
}
/*item*/
li {
  list-style: none;
  height: 36px;
  line-height: 36px;
  padding: 0 5px;
  border-bottom: 1px solid #ddd;
}

li label {
  float: left;
  cursor: pointer;
}

li label li input {
  vertical-align: middle;
  margin-right: 6px;
  position: relative;
  top: -1px;
}

li button {
  float: right;
  display: none;
  margin-top: 3px;
}

li:before {
  content: initial;
}

li:last-child {
  border-bottom: none;
}
</style>

注意点10:消息的订阅与发布

      安装

npm i pubsub-js

第一步:消息的订阅,在接收的页面写。

//App.vue页面
import pubsub from "pubsub-js"

 mounted(){
   /*订阅消息,由于要传入id才可以取消订阅,所以把它交给this.id,
    subscribe里面有两个参数,第一个是订阅消息的名字,第二个参数是一个函数,
    函数的第一个值是消息的名字,第二个是返回的参数,不想要名字可以用占位符_
    */
    this.id = pubsub.subscribe('ToDoRemove',(msgName,data)=>{
      console.log(msgName)
      this.ToDoRemove(data)
    })
  },
  beforeDestroy(){
   //取消订阅,注意,取消订阅时,不是传入的订阅消息的名字,而是id
    pubsub.unsubscribe(this.id)
  },
methods:{
    //删除某一条
    ToDoRemove(i) {
      this.ToDoList = this.ToDoList.filter((pp) => {
        return pp.id !== i;
      });
    },
}

第二步:消息的发布

//ToDoItem页面进行消息的发布
import pubsub from "pubsub-js"
  methods:{
    removethis(i){
      if(confirm('确定删除')){
        pubsub.publish('ToDoRemove',i)
      }
    }
  }

注意点11、如果要获取某个dom,可以在传参地方传如$event

 <input @blur="change2(ToDoItem.id,$event)">
methods:{
change2(e,c){
      if(!c.target.value.trim()){
        return
      }else{

      this.$bus.$emit('changetodo2',e,c.target.value)
      }
 },
}

注意点12、判断某个对象上面有没有某个值

可以用hasOwnProperty来判断

let a = {
    name:'kk'
}
a.hasOwnProperty('name')//true

注意点13、判断某个字段是否为空

可以用trim()来判断

 <input @blur="change2(ToDoItem.id,$event)">
methods:{
change2(e,c){
      if(!c.target.value.trim()){
        return
      }else{

      this.$bus.$emit('changetodo2',e,c.target.value)
      }
 },
}

注意点14、$nextTick的使用

        它的意思是在下一轮的时候在执行。

        在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中

/*这里我们要让input框一上来就聚焦,如果直接写是不生效的,因为当执行到那一句的时候
页面还没渲染出来,所以把在页面渲染完成后执行的写在nextTick的回调中,会生效
*/
changetodo(e){
      this.$bus.$emit('changetodo',e)
      this.$nextTick(function(){
      this.$refs.a.focus()
      })
 },

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值