2023/1/9 Vue学习笔记 -TodoList案例 -【props】最初的父传子 子传父值 && localStorage sessionStorage && 组件自定义事件

这里涉及到组件的传值,这是一个很好的例子讲解-最初始的传值方式

1 静态组件 - 准备阶段

App.vue
<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader/>
      <UserList/>
      <UserFooter/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {}
  },
  methods: {},
  components: {UserHeader, UserList, UserFooter}
}
</script>

<style>
/* base */
body {
  background-color: #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>

UserHeader.vue
<template>
  <div class="todo-header">
    <input type="text"
           placeholder="请输入你的任务名称,按回车键确认">
  </div>
</template>

<script>
export default {
  name: `UserList`
}
</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>

UserList.vue
<template>
  <ul class="todo-main">
    <UserItem/>
    <UserItem/>
    <UserItem/>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem";

export default {
  name: `UserList`,
  components: {UserItem}
}
</script>


<style scoped>
/* main */
.todo-main {
  margin-left: 0px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding: 0px;
}

.todo-empty {
  height: 40px;
  line-height: 40px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding-left: 5px;
  margin-top: 10px;
}
</style>

UserItem.vue
<template>
  <li>
    <label>
      <input type="checkbox"/>
      <span>xxxxx</span>
    </label>
    <button class="btn btn-danger" style="display: none">删除</button>
  </li>

</template>

<script>
export default {
  name: `UserItem`
}
</script>

<style scoped>
/* 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;
}

li:hover {
  background-color: #ddd;
}

li:hover button {
  display: block;
}
</style>

UserFooter.vue
<template>
  <div class="todo-footer">
    <label>
      <input type="checkbox"/>
    </label>
    <span>
      <span>已完成0</span> / 全部2
    </span>
    <button class="btn btn-danger">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: `UserFooter`
}
</script>

<style scoped>
/* 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>

在这里插入图片描述

2 TodoList案例_初始化列表- 这里涉及到父组件给子组件传值

UserList.vue
<template>
  <ul class="todo-main">
    <UserItem v-for="todo in todoList" :key="todoList.id" :todo="todo"></UserItem>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem";

export default {
  name: `UserList`,
  components: {UserItem},
  data() {
    return {
      todoList: [
        {id: '0001', title: '抽烟', done: true},
        {id: '0002', title: '喝酒', done: false},
        {id: '0003', title: '烫头', done: true},
      ]
    }
  }
}
</script>
UserItem.vue

其中,对于 :checked=“true” 的理解:如果为true 则拥有checked属性,为false则,则不存checked属性

<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: `UserItem`,
  // 声明接受todo
  props: ['todo']
}
</script>

在这里插入图片描述

3 TodoList案例_添加 - 这里涉及到子组件给父组件传值

在这里插入图片描述

1)uuid标准:用于生成全球唯一的字符串编码。(因为这个包太大了,所以本节中用的是uuid的精简版,nanoid)
2)nanoid安装指令:npm i nanoid
	nanoid的库用了分别暴露,所以需要这样引用:
	import {nanoid} from 'nanoid'
3)子组件传值父组件简单原理:
	父亲先传给儿子一个函数
	儿子在合适的时候调用,调用的时候父亲就能收到参数。
App.vue
<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader :addTodo="addTodo"/>
      <UserList :todoList="todoList"/>
      <UserFooter/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: [
        {id: '0001', title: '抽烟', done: true},
        {id: '0002', title: '喝酒', done: false},
        {id: '0003', title: '烫头', done: true},
      ]
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    }
  },
  components: {UserHeader, UserList, UserFooter}
}
</script>
UserHeader.vue

对输入值得获取:方式一:
在这里插入图片描述
方式二:v-model = “”
在这里插入图片描述

<template>
  <div class="todo-header">
    <input type="text"
           placeholder="请输入你的任务名称,按回车键确认"
           @keyup.enter="add"
           v-model="inputText">
  </div>
</template>

<script>
import {nanoid} from "nanoid"

export default {
  name: `UserList`,
  data() {
    return {
      inputText: ''
    }
  },
  methods: {
    add() {
      // 将用户的输入包装成一个对象
      if (this.inputText.trim()) {
        const todo = {id: nanoid(), title: this.inputText, done: false}
        this.addTodo(todo)
        this.inputText = ''
      }
    }
  },
  props: ['addTodo']
}
</script>
UserList.vue
<template>
  <ul class="todo-main">
    <UserItem v-for="todo in todoList" :key="todo.id" :todo="todo"></UserItem>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem";

export default {
  name: `UserList`,
  components: {UserItem},
  data() {
    return {}
  },
  props: ['todoList']
}
</script>

在这里插入图片描述

4 TodoList案例_勾选-这里涉及到一个爷爷给孙子传值的问题

4.1 这里涉及到一个爷爷给孙子传值的问题

在这里插入图片描述

App.vue
<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader :addTodo="addTodo"/>
      <UserList :todoList="todoList" :checkTodo="checkTodo"/>
      <UserFooter/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: [
        {id: '0001', title: '抽烟', done: true},
        {id: '0002', title: '喝酒', done: false},
        {id: '0003', title: '烫头', done: true},
      ]
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    },
    checkTodo(id) {
      this.todoList.forEach(todo => {
        if (todo.id === id) {
          todo.done = !todo.done
        }
      })
    }
  },
  components: {UserHeader, UserList, UserFooter}
}
</script>
UserList.vue
<template>
  <ul class="todo-main">
    <UserItem
        v-for="todo in todoList"
        :key="todo.id" :todo="todo"
        :checkTodo="checkTodo"></UserItem>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem";

export default {
  name: `UserList`,
  components: {UserItem},
  data() {
    return {}
  },
  props: ['todoList', 'checkTodo']
}
</script>
UserItem.vue
<template>
  <li>
    <label>
      <!--      <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>-->
      <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
      <span>{{ todo.title }}</span>
    </label>
    <button class="btn btn-danger" style="display: none">删除</button>
  </li>
</template>

<script>

export default {
  name: `UserItem`,
  // 声明接受todo
  props: ['todo', 'checkTodo'],
  methods: {
    handleCheck(id) {
      // 通知App组件将对应的todo 的 done 取反
      this.checkTodo(id)
    }
  }
}
</script>

在这里插入图片描述
提出一个新的问题:v-model 的双向绑定的问题

 <input type="checkbox" v-model="todo.done"/>
UserItem.vue
<template>
  <li>
    <label>
      <!--      <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>-->
      <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: `UserItem`,
  // 声明接受todo pros是只读的、不可以修改
  props: ['todo'],
  methods: {}
}
</script>

在这里插入图片描述
1)数据在哪个页面,那么对数据的操作就应该在哪个页面。
2)本节中的第二个做法:

<input type="checkbox" v-model="todo.done"/>

因为v-model是双向绑定数据,所以修改有效。但是,props被修改了?

本节中的举例:
 Vue不能监测到的修改:let obj = {a:1,b:2},修改成 obj.a = 666
 Vue能监测到的修改:let obj = {a:1,b:2},修改成 obj = {x:100,y:200}
也就是说Vue监视的是浅层次的修改。不建议这样做,因为这样会违反原则修改props,虽然并没有被Vue监测到。

5 TodoList案例_删除

在这里插入图片描述

App.vue
<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader :addTodo="addTodo"/>
      <UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
      <UserFooter/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: [
        {id: '0001', title: '抽烟', done: true},
        {id: '0002', title: '喝酒', done: false},
        {id: '0003', title: '烫头', done: true},
      ]
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    },
    checkTodo(id) {
      this.todoList.forEach(todo => {
        if (todo.id === id) {
          todo.done = !todo.done
        }
      })
    },
    deleteTodo(id) {
      this.todoList = this.todoList.filter(todo => {
        return todo.id !== id
      })
    }
  },
  components: {UserHeader, UserList, UserFooter}
}
</script>
UserList.vue
<template>
  <ul class="todo-main">
    <UserItem
        v-for="todo in todoList"
        :key="todo.id" :todo="todo"
        :checkTodo="checkTodo"
        :deleteTodo="deleteTodo"></UserItem>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem";

export default {
  name: `UserList`,
  components: {UserItem},
  data() {
    return {}
  },
  props: ['todoList', 'checkTodo', 'deleteTodo']
}
</script>
UserItem.vue
<template>
  <li>
    <label>
      <!--      <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>-->
      <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
      <span>{{ todo.title }}</span>
    </label>
    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
  </li>
</template>

<script>

export default {
  name: `UserItem`,
  // 声明接受todo
  props: ['todo', 'checkTodo', 'deleteTodo'],
  methods: {
    handleCheck(id) {
      // 通知App组件将对应的todo 的 done 取反
      this.checkTodo(id)
    },
    handleDelete(id) {
      if (confirm('确定删除吗?')) {
        this.deleteTodo(id)
      }
    }
  }
}
</script>

<style scoped>
/* 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;
}

li:hover {
  background-color: #ddd;
}

li:hover button {
  display: block;
}
</style>

在这里插入图片描述

6 TodoList案例_底部统计 - 利用了计算属性

在这里插入图片描述

App.vue
<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader :addTodo="addTodo"/>
      <UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
      <UserFooter :todoList="todoList"/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: [
        {id: '0001', title: '抽烟', done: true},
        {id: '0002', title: '喝酒', done: false},
        {id: '0003', title: '烫头', done: true},
      ]
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    },
    checkTodo(id) {
      this.todoList.forEach(todo => {
        if (todo.id === id) {
          todo.done = !todo.done
        }
      })
    },
    deleteTodo(id) {
      this.todoList = this.todoList.filter(todo => {
        return todo.id !== id
      })
    }
  },
  components: {UserHeader, UserList, UserFooter}
}
</script>
UserFooter.vue
<template>
  <div class="todo-footer">
    <label>
      <input type="checkbox"/>
    </label>
    <span>
      <span>已完成{{ doneTotal }}</span> / 全部{{ todoList.length }}
    </span>
    <button class="btn btn-danger">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: `UserFooter`,
  props: ['todoList'],
  computed: {
    doneTotal: function () {
      // return this.todoList.filter(todo => todo.done).length
      return this.todoList.reduce((pre, current) => pre + (current.done ? 1 : 0), 0)
    }
  }
}
</script>

在这里插入图片描述

7 TodoList案例_底部交互

方式一:
在这里插入图片描述

App.vue
<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader :addObjToTodoList="addObjToTodoList"/>
      <UserList :todoList="todoList"
                :checkTodo="checkTodo"
                :deleteTodo="deleteTodo"/>
      <UserFooter :todoList="todoList"
                  :clearAllTodo="clearAllTodo"
                  :checkAllTodo="checkAllTodo"/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: [
        {id: '0001', title: '抽烟', done: false},
        {id: '0002', title: '喝酒', done: false},
        {id: '0003', title: '烫头', done: false},
      ]
    }
  },
  methods: {
    addObjToTodoList(obj) {
      this.todoList.unshift(obj)
    },
    checkTodo(id) {
      this.todoList
          .filter(item => item.id === id)
          .map(item => item.done = !item.done);
    },
    deleteTodo(id) {
      this.todoList = this.todoList
          .filter(item => item.id !== id)
    },
    checkAllTodo(checked) {
      console.log(checked)
      if (checked) {
        this.todoList.map(item => {
          item.done = checked
        })
      } else {
        this.todoList.map(item => {
          item.done = false
        })
      }
    },
    clearAllTodo() {
      if (confirm('确认清空列表吗?')) {
        this.todoList.map(item => {
          if (item.done === true) {
            item.done = false
          }
        })
      }
    },
  },
  components: {UserHeader, UserList, UserFooter}
}
</script>
UserFooter.vue
<template>
  <div class="todo-footer" v-show="total">
    <label>
      <input type="checkbox" v-model="isAll"/>
    </label>
    <span>
      <span>已完成{{ doneTotal }}</span> / 全部{{ total }}
    </span>
    <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: `UserFooter`,
  props: ['todoList', 'clearAllTodo', 'checkAllTodo'],
  computed: {
    doneTotal() {
      return this.todoList.filter(item => item.done).length
    },
    total() {
      return this.todoList.length
    },
    isAll: {
      get() {
        return this.doneTotal === this.total && this.total > 0
      },
      set(value) {
        this.checkAllTodo(value)
      }
    },
  },
  methods: {
    clearAll() {
      this.clearAllTodo()
    },
  }
}
</script>

在这里插入图片描述
方式二实现:
在这里插入图片描述

8 TodoList案例_总结

1.组件化编码流程:

1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
	一个组件在用:放在组件自身即可。
	一些组件在用:放在他们共同的父组件上(状态提升)
3)实现交互:从绑定事件开始。

2.props适用于:

1)父组件 ==> 子组件 通信
2)子组件 ==> 父组件 通信 (要求父先给子一个函数)

3.使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
4.props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

9 浏览器本地存储的讲解 localStorage sessionStorage

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="localStorage">
    <button onclick="save()">点击保存一个数据</button>
    <button onclick="read()">点击读取一个数据</button>
    <button onclick="deleteOne()">点击删除一个数据</button>
    <button onclick="clearAll()">点击清空所有数据</button>
</div>
<script type="text/javascript">
    let person = {name: 'zhaoshuai-lc', age: 27}
    let name = 'zhaoshuai-lc'
    let age = '27'
    function save() {
        localStorage.setItem('name', name)
        localStorage.setItem('age', 'age')
        localStorage.setItem('person', JSON.stringify(person))
    }

    function read() {
        console.log(localStorage.getItem('person'));
        console.log(localStorage.getItem('name'));
        console.log(localStorage.getItem('age'));
    }

    function deleteOne() {
        localStorage.removeItem('name')
    }

    function clearAll() {
        localStorage.clear()
    }
</script>
</body>
</html>

在这里插入图片描述
清空之后 再读取数据:
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="sessionStorage">
    <button onclick="save()">点击保存一个数据</button>
    <button onclick="read()">点击读取一个数据</button>
    <button onclick="deleteOne()">点击删除一个数据</button>
    <button onclick="clearAll()">点击清空所有数据</button>
</div>
<script type="text/javascript">
    let person = {name: 'zhaoshuai-lc', age: 27}
    let name = 'zhaoshuai-lc'
    let age = '27'

    function save() {
        sessionStorage.setItem('name', name)
        sessionStorage.setItem('age', 'age')
        sessionStorage.setItem('person', JSON.stringify(person))
    }

    function read() {
        console.log(sessionStorage.getItem('person'));
        console.log(sessionStorage.getItem('name'));
        console.log(sessionStorage.getItem('age'));
    }

    function deleteOne() {
        sessionStorage.removeItem('name')
    }

    function clearAll() {
        sessionStorage.clear()
    }
</script>
</body>
</html>

1.存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
2.浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
3.相关API:

1)xxxxxStorage.setItem( 'key', 'value' );
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
2)xxxxxStorage.getItem( 'person' );
该方法接受一个键名作为参数,返回键名对应的值。
3)xxxxxStorage.removeItem( 'key' );
 该方法接受一个键名作为参数,并把该键名从存储中删除。
4)xxxxxStorage.clear()
 该方法会清空存储中的所有数据。

4.备注:

1)SessionStorage存储的内容会随着浏览器窗口关闭而消失。
2)LocalStorage存储的内容,需要手动清楚才会消失。
3)xxxxxStorage.getItem( xxx ) 如果xxx对应的value获取不到,那么getItem的返回值是null4JSON.parse(null)的结果依然是null

10 将本地存储应用到todoList案例中

在这里插入图片描述

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader :addTodo="addTodo"/>
      <UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
      <UserFooter :todoList="todoList" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: JSON.parse(localStorage.getItem('todoList')) || []
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    },
    checkTodo(id) {
      this.todoList.forEach(todo => {
        if (todo.id === id) {
          todo.done = !todo.done
        }
      })
    },
    deleteTodo(id) {
      this.todoList = this.todoList.filter(todo => {
        return todo.id !== id
      })
    },
    checkAllTodo(done) {
      this.todoList.forEach(todo => {
        todo.done = done
      })
    },
    clearAllTodo() {
      this.todoList = this.todoList.filter(todo => !todo.done)
    }
  },

  watch: {
    // 默认监视的是数组层次-浅监视
    /*    todoList(value) {
          localStorage.setItem('todoList', JSON.stringify(value))
        },*/
    todoList: {
      // 深度监视
      deep: true,
      handler(newValue, oldValue) {
        localStorage.setItem('todoList', JSON.stringify(newValue))
      }
    },

  },
  components: {UserHeader, UserList, UserFooter}
}
</script>

在这里插入图片描述

11 组件自定义事件_绑定

11.1 【props】子组件给父组件传值:

School.vue

<template>
  <!--  组件的结构-->
  <div class="sc">
    <h2>学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
    <button @click="sendSchoolName">传值给App-SchoolName</button>
  </div>
</template>

<script>

export default {
  name: `School`, // 跟文件名保持一致
  data() {
    return {
      name: '北京大学',
      address: '北京'
    }
  },
  methods: {
    sendSchoolName() {
      return this.getSchoolName(this.name)
    }
  },
  props: ['getSchoolName']
}
</script>

<style>
/*组件的样式*/
.sc {
  background-color: aquamarine;
  padding: 5px;
}
</style>

App.vue

<template>
  <div class="app">
    <h1>{{ msg }}</h1>
    <School :getSchoolName="getSchoolName"/>
    <Student/>
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-zhaoshuai-lc',
    }
  },
  methods: {
    getSchoolName(name) {
      console.log(name)
    }
  },
  mounted() {
  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>

在这里插入图片描述

11.2 自定义事件-1:通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) this.$emit(‘inspur’, this.name)[自定义事件的触发]

App.vue

<template>
  <div class="app">
    <h1>{{ msg }}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>
    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
    <Student v-on:inspur="getStudentName"/>
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-zhaoshuai-lc',
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('getSchoolName', name)
    },
    getStudentName(name) {
      console.log('getStudentName', name)
    }
  },
  mounted() {
  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>

Student.vue

<template>
  <!--  组件的结构-->
  <div class="student">
    <h2>学生名字:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <button @click="sendStudentName">传值给App-StudentName</button>
  </div>
</template>

<script>

export default {
  name: `Student`,
  data() {
    return {
      name: 'zhaoshuai-lc',
      sex: 'male'
    }
  },
  methods: {
    sendStudentName() {
      // 触发Student组件身上的inspur事件
      this.$emit('inspur', this.name)
    }
  }
}
</script>

<style>
/*组件的样式*/
.student {
  background-color: pink;
  padding: 5px;
}
</style>

在这里插入图片描述

11.3 自定义事件-2 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref)

App.vue

<template>
  <div class="app">
    <h1>{{ msg }}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
    <!--    <Student v-on:inspur.once="getStudentName"/>-->

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
    <Student ref="student"/>
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-zhaoshuai-lc',
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('getSchoolName', name)
    },
    getStudentName(name, ...params) {
      console.log('getStudentName', name, params)
    }
  },
  mounted() {
    setTimeout(() => {
      // this.$refs.student.$on('inspur', this.getStudentName)
      // 触发一次
      this.$refs.student.$once('inspur', this.getStudentName)
    }, 0)

  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>

Student.vue

<template>
  <!--  组件的结构-->
  <div class="student">
    <h2>学生名字:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <button @click="sendStudentName">传值给App-StudentName</button>
  </div>
</template>

<script>

export default {
  name: `Student`,
  data() {
    return {
      name: 'zhaoshuai-lc',
      sex: 'male'
    }
  },
  methods: {
    sendStudentName() {
      // 触发Student组件身上的inspur事件
      this.$emit('inspur', this.name, this.sex)
    }
  }
}
</script>

<style>
/*组件的样式*/
.student {
  background-color: pink;
  padding: 5px;
}
</style>

在这里插入图片描述

12 组件自定义事件_解绑

销毁了当前student组件的实例vc,销毁后所有student实例的自定义事件全都不奏效。

App.vue
<template>
  <div class="app">
    <h1>{{ msg }}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
    <Student @inspur.once="getStudentName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
    <!--    <Student ref="student"/>-->
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-zhaoshuai-lc',
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('getSchoolName', name)
    },
    getStudentName(name, ...params) {
      console.log('getStudentName', name, params)
    }
  },
  mounted() {
    setTimeout(() => {
      // this.$refs.student.$on('inspur', this.getStudentName)
      // 触发一次
      this.$refs.student.$once('inspur', this.getStudentName)
    }, 0)

  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>

Student.vue
<template>
  <!--  组件的结构-->
  <div class="student">
    <h2>学生名字:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <button @click="sendStudentName">传值给App-StudentName</button>
    <button @click="unbind">解绑inspur事件</button>
  </div>
</template>

<script>

export default {
  name: `Student`,
  data() {
    return {
      name: 'zhaoshuai-lc',
      sex: 'male'
    }
  },
  methods: {
    sendStudentName() {
      // 触发Student组件身上的inspur事件
      this.$emit('inspur', this.name, this.sex)
    },
    unbind() {
      // 解绑一个自定义事件
      // this.$off('inspur')

      // 解绑多个自定义事件 用一个数组传值
      // this.$off(['inspur'])

      // 所有的自定义事件全都解绑
      this.$off([])
    }
  }
}
</script>

<style>
/*组件的样式*/
.student {
  background-color: pink;
  padding: 5px;
}
</style>

13 组件自定义事件_总结

App.vue

<template>
  <div class="app">
    <h1>{{ msg }} {{ studentName }}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
    <Student @inspur.once="getStudentName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
        <Student ref="student"/>
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-',
      studentName: ''
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('getSchoolName', name)
    },
    getStudentName(name, ...params) {
      this.studentName = name
      console.log('getStudentName', name, params)
    }
  },
  mounted() {
    setTimeout(() => {
      // this.$refs.student.$on('inspur', this.getStudentName)
      // 触发一次
      this.$refs.student.$once('inspur', this.getStudentName)
    }, 0)

  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>
引出问题1:this.$refs.student.$once('inspur', this.getStudentName) —> this.$refs.student.$once('inspur', function() {this.studentName = name })

在这里插入图片描述
如上代码调整为如下代码:这是一个最大的坑:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
正确方式1 – 推荐使用这种方式:

this.$refs.student.$on('inspur', this.getStudentName)

正确方式2:箭头函数 this->App.vue
在这里插入图片描述

引出问题2: 自定义组件绑定原生事件 (@click=“” 默认原生事件为自定义事件) - 解决方案 <Student ref="student" @click.native="showAlert"/>
<template>
  <div class="app">
    <h1>{{ msg }} {{ studentName }}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
    <!--    <Student @inspur.once="getStudentName"/>-->

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
    <Student ref="student" @click.native="showAlert"/>
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-',
      studentName: ''
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('getSchoolName', name)
    },
    getStudentName(name, ...params) {
      this.studentName = name
      console.log('getStudentName', name, params)
    },
    showAlert() {
      alert('@click.native="showAlert"')
    }
  },
  mounted() {
    setTimeout(() => {
      // this.$refs.student.$on('inspur', this.getStudentName)
      // 改写方式一:
      this.$refs.student.$on('inspur', (name) => {
        console.log(this)
        this.studentName = name
      })

    }, 0)

  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>

在这里插入图片描述
组件的自定义事件

  • 1.一种组件间通信的方式,适用于:子组件 ==> 父组件。
  • 2.使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
  • 3.绑定自定义事件:
    (1)第一种方式,在父组件中:
<Demo @inpsur="test"/><Demo v-on:inspur="test"/>

(2)第二种方式,在父组件中:

<Demo ref="demo">
......
mounted(){
    this.$refs.xxx.$on('inspur',this.test)
}

(3)若想让自定义事件只能触发一次,可以使用 once 修饰符或 $once 方法。

4.触发自定义事件:this.$emit('inspur',数据)
5.解绑自定义事件:this.$off('inspur')
6.组件上也可以绑定原生DOM事件,需要使用 native 修饰符。
7.注意:通过 this.$refs.xxx.$on('inspur',回调) 绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

14 TodoList案例_自定义事件

App.vue
<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader @addTodo="addTodo"/>
      <UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
      <UserFooter :todoList="todoList" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: JSON.parse(localStorage.getItem('todoList')) || []
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    },
    checkTodo(id) {
      this.todoList.forEach(todo => {
        if (todo.id === id) {
          todo.done = !todo.done
        }
      })
    },
    deleteTodo(id) {
      this.todoList = this.todoList.filter(todo => {
        return todo.id !== id
      })
    },
    checkAllTodo(done) {
      this.todoList.forEach(todo => {
        todo.done = done
      })
    },
    clearAllTodo() {
      this.todoList = this.todoList.filter(todo => !todo.done)
    }
  },

  watch: {
    todoList: {
      // 深度监视
      deep: true,
      handler(newValue, oldValue) {
        localStorage.setItem('todoList', JSON.stringify(newValue))
      }
    },

  },
  components: {UserHeader, UserList, UserFooter}
}
</script>

<style>
/* base */
body {
  background-color: #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>

UserHeader.vue
<template>
  <div class="todo-header">
    <input type="text"
           placeholder="请输入你的任务名称,按回车键确认"
           @keyup.enter="add"
           v-model="inputText">
  </div>
</template>

<script>
import {nanoid} from "nanoid"

export default {
  name: `UserHeader`,
  data() {
    return {
      inputText: ''
    }
  },
  methods: {
    add() {
      // 将用户的输入包装成一个对象
      if (this.inputText.trim()) {
        const todo = {id: nanoid(), title: this.inputText, done: false}
        this.$emit('addTodo', todo)
        this.inputText = ''
      }
    }
  },
}
</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>

UserFooter.vue
<template>
  <div class="todo-footer" v-show="total">
    <label>
      <input type="checkbox" :checked="isAll" @change="checkAll"/>
      <!--      <input type="checkbox" v-model="isAll"/>-->
    </label>
    <span>
      <span>已完成{{ doneTotal }}</span> / 全部{{ total }}
    </span>
    <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: `UserFooter`,
  props: ['todoList'],
  computed: {
    doneTotal() {
      // return this.todoList.filter(todo => todo.done).length
      return this.todoList.reduce((pre, current) => pre + (current.done ? 1 : 0), 0)
    },
    total() {
      return this.todoList.length
    },
    // 只被读取、不被修改的方式-函数式
    /*isAll() {
      return this.doneTotal === this.total && this.total > 0
    },*/
    isAll: {
      get() {
        return this.doneTotal === this.total && this.total > 0
      },
      set(value) {
        // this.checkAllTodo(value)
        this.$emit('checkAllTodo', value)
      }
    },


  },
  methods: {
    checkAll(event) {
      // this.checkAllTodo(event.target.checked)
      this.$emit('checkAllTodo', event.target.checked)
    },
    clearAll(event) {
      // this.clearAllTodo()
      this.$emit('clearAllTodo')
    }
  }
}
</script>

<style scoped>
/* 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>

UserList.vue
<template>
  <ul class="todo-main">
    <UserItem
        v-for="todo in todoList"
        :key="todo.id" :todo="todo"
        :checkTodo="checkTodo"
        :deleteTodo="deleteTodo"></UserItem>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem";

export default {
  name: `UserList`,
  components: {UserItem},
  data() {
    return {}
  },
  props: ['todoList', 'checkTodo', 'deleteTodo']
}
</script>


<style scoped>
/* main */
.todo-main {
  margin-left: 0px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding: 0px;
}

.todo-empty {
  height: 40px;
  line-height: 40px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding-left: 5px;
  margin-top: 10px;
}
</style>

UserItem.vue
<template>
  <li>
    <label>
      <!--      <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>-->
      <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
      <span>{{ todo.title }}</span>
    </label>
    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
  </li>
</template>

<script>

export default {
  name: `UserItem`,
  // 声明接受todo
  props: ['todo', 'checkTodo', 'deleteTodo'],
  methods: {
    handleCheck(id) {
      // 通知App组件将对应的todo 的 done 取反
      this.checkTodo(id)
    },
    handleDelete(id) {
      if (confirm('确定删除吗?')) {
        this.deleteTodo(id)
      }
    }
  }
}
</script>

<style scoped>
/* 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;
}

li:hover {
  background-color: #ddd;
}

li:hover button {
  display: block;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值