Vue.js的todolist案例(之二)用自定义事件替代props从子组件向父组件传递数据&本地存储

饮水思源:https://www.bilibili.com/video/BV1Zy4y1K7SH?p=75

①底部统计

MyFooter.vue:

<template>
  <div>
    <label>
      <input type="checkbox" />
    </label>
    <span>
      <span>已完成{{doneTotal}}</span> / 全部{{todos.length}}
    </span>
    <button>清除已完成项目</button>
  </div>
</template>

<script>
export default {
  name: 'MyFooter',
  props: ['todos'],
  computed: {
    doneTotal() {
      return this.todos.reduce((pre, current) => {
        if (current.done) return pre + 1;
        return pre;
      }, 0)
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>

 

②底部交互

MyFooter.vue:

<template>
  <div v-show="total">
    <label>
      <input type="checkbox" :checked="allDone" @click="CheckAllTodo"/>
    </label>
    <span>
      <span>已完成{{doneTotal}}</span> / 全部{{total}}
    </span>
    <button>清除已完成项目</button>
  </div>
</template>

<script>
export default {
  name: 'MyFooter',
  props: ['todos', 'appCheckAllTodo'],
  computed: {
    total() {
      return this.todos.length
    },
    doneTotal() {
      return this.todos.reduce((pre, current) => {
        if (current.done) return pre + 1;
        return pre;
      }, 0)
    },
    allDone() {
      return this.total > 0 && this.doneTotal === this.total
    },
  },
  methods: {
    CheckAllTodo(e) {
      this.appCheckAllTodo(e.target.checked)
    }
  },
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>

v-model配合计算属性set实现:

<template>
  <div v-show="total">
    <label>
      <input type="checkbox" v-model="allDone" />
    </label>
    <span>
      <span>已完成{{doneTotal}}</span> / 全部{{total}}
    </span>
    <button>清除已完成项目</button>
  </div>
</template>

<script>
export default {
  name: 'MyFooter',
  props: ['todos', 'appCheckAllTodo'],
  computed: {
    total() {
      return this.todos.length
    },
    doneTotal() {
      return this.todos.reduce((pre, current) => {
        if (current.done) return pre + 1;
        return pre;
      }, 0)
    },
    allDone: {
      get() {
        return this.total > 0 && this.doneTotal === this.total
      },
      set(val) {
        this.appCheckAllTodo(val)
      }
    },
  },
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>
MyFooter.vue

 

③总结TodoList案例

饮水思源:https://www.bilibili.com/video/BV1Zy4y1K7SH?p=77&spm_id_from=pageDriver

 

④本地存储

“虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

深度检测:为了发现对象内部值的变化,可以在选项参数中指定 deep: true。

查看本地存储:

思路就是,检测todos,每当todos改变时,将todos保存到localStorage,每当启动app,就从localStorage读出数据进行初始化,几个注意点:

  1. 要把list转化为字符串存储,取出时进行字符串解析
  2. 在list中没数据时,或者第一次启动app时,注意localStorage.getItem返回null的问题
  3. 处理无法保存勾选状态的现象——启用深度检测

App.vue:

<template>
  <div>  
    <MyHeader :appAddItem="appAddItem" />
    <MyList :todos="todos" :appRemoveTodo="appRemoveTodo" />
    <MyFooter :todos="todos" :appCheckAllTodo="appCheckAllTodo" />
  </div>
</template>

<script>
import MyHeader from './components/MyHeader.vue'
import MyList from './components/MyList.vue'
import MyFooter from './components/MyFooter.vue'

export default {
  name: 'App',
  data() {
    return {
      todos: JSON.parse(localStorage.getItem('todos')) || [] // 初始化时调用一次
    }
  },
  watch: {
    todos: {
      deep: true,
      handler(newVal) {
        localStorage.setItem('todos', JSON.stringify(newVal))     
      },  
    },
  },
  methods: {
    appAddItem(x) {
      this.todos.unshift(x);
    },
    appRemoveTodo(todoId) {
      this.todos = this.todos.filter(todo => todo.id !== todoId);
    },
    appCheckAllTodo(done) {
      this.todos.forEach(todo => {
        todo.done = done
      })
    },
  },
  components: {
    MyHeader,
    MyList,
    MyFooter,
  }
}
</script>

<style>

</style>

 

④用自定义事件替代props从子组件向父组件传递数据

MyHeader向App传数据。

App向MyHeader绑定事件并传入回调函数(除了写在标签里外,还有更灵活的方式,详见视频):

<template>
  <div>  
    <MyHeader @appAddItem="appAddItem" />
    <MyList :todos="todos" :appRemoveTodo="appRemoveTodo" />
    <MyFooter :todos="todos" :appCheckAllTodo="appCheckAllTodo" />
  </div>
</template>

<script>
import MyHeader from './components/MyHeader.vue'
import MyList from './components/MyList.vue'
import MyFooter from './components/MyFooter.vue'

export default {
  name: 'App',
  data() {
    return {
      todos: JSON.parse(localStorage.getItem('todos')) || [] // 初始化时调用一次
    }
  },
  watch: {
    todos: {
      deep: true,
      handler(newVal) {
        localStorage.setItem('todos', JSON.stringify(newVal))     
      },  
    },
  },
  methods: {
    appAddItem(x) {
      this.todos.unshift(x);
    },
    appRemoveTodo(todoId) {
      this.todos = this.todos.filter(todo => todo.id !== todoId);
    },
    appCheckAllTodo(done) {
      this.todos.forEach(todo => {
        todo.done = done
      })
    },
  },
  components: {
    MyHeader,
    MyList,
    MyFooter,
  }
}
</script>

<style>

</style>

MyHeader中触发事件:

<template>
  <input type="text" placeholder="输入任务,按回车确认" @keyup.enter="addItem"/>
</template>

<script>
import {nanoid} from 'nanoid'

export default {
  name: 'MyHeader',
  methods: {
    addItem(e) {
      const todoObj = {
        id: nanoid(),
        title: e.target.value,
        done: false,
      }
      // 触发绑定在自己身上的appAddItem事件,会导致调用父组件传入的回调函数
      this.$emit('appAddItem', todoObj)
    },
  },
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>

通过开发者工具查看已触发的自定义事件:

 

posted @ 2022-02-09 17:21  xkfx  阅读(153)  评论(0)    收藏  举报