目录
TodoList案例有点类似于我们平时使用的“备忘录”的功能。在这次案例中主要是为了体验“组件化编程的流程”,组件化编程的流程有以下三个:
拆分静态组件;组件要按照功能点拆分,命名不要与html元素冲突
实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用;
实现交互:从绑定事件开始
其中实现组件化又分为了两种:
一个组件在用:放在组件自身即可.
一些组件在用:放在他们共同的父组件上(状态提升)
项目拆分组件及功能如图所示:
下面是项目实现的完整代码,可以参考学习
Mylist.vue是列表显示组件,它有一个子组件item,也是每个“要做的事儿~”
- <template>
- <ul class="todo-main">
- <Item v-for="todoobj in todos"
- :key="todoobj.id"
- :todo="todoobj"
- :checkTodo='checkTodo'
- :deleteTodo='deleteTodo'/>
- </ul>
- </template>
-
- <script>
- import Item from "./Myitem.vue";
-
- export default {
- name:"List",
- components:{Item},
- props:[
- 'todos',
- 'checkTodo',
- 'deleteTodo'
- ]
- }
- </script>
-
- <style scoped>
- .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>

Myitem组件是每一个具体要做的事情`
- <template>
- <li>
- <label>
- <!-- checked = true表示勾选 :checked="todo.done动态绑定是否勾选-->
- <!-- 可以綁定@change事件或者是@click事件 -->
- <input type="checkbox" :checked="todo.done" @change="handlecheck(todo.id)" />
- <!-- 以下代碼也可以完成功能,但是不建議使用,只是转了v-model空子 -->
- <!-- <input type="checkbox" v-model="todo.done"/> -->
-
- <!-- 父传子,拿到title数据 -->
- <span>{{ todo.title }}</span>
- </label>
- <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
- </li>
- </template>
-
- <script>
- export default {
- name: "Item",
- data() {
- return {
-
- }
- },
- // 声明接收todo对象(父传子)
- props: ["todo", "checkTodo","deleteTodo"],
- methods: {
- handlecheck(id) {
- console.log(id)
- this.checkTodo(id);
- },
- handleDelete(id) {
- if (confirm("确定删除吗?")) {
- this.deleteTodo(id);
- }
- }
-
- },
- }
- </script>
-
- <style scoped>
- 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>

MyHeader组件是头部添加部分
- <template>
- <div class="todo-header">
- <input type="text" v-model="title" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add"/>
- </div>
- </template>
-
- <script>
- import {nanoid} from "nanoid"
- export default {
- name:"MyHeader",
- data() {
- return {
- title:'',
- }
- },methods: {
- add(){
- // 校验输入的数据不能为空
- if(!this.title.trim()) return alert("输入不能为空")
- // event.target.value获取发生事件的元素的值,这里使用的是双向数据绑定,两种都可以
- // 将用户的输出包装成todo对象 uuid用于生成全球唯一字符编码id nanoid是在uuid的基础上做的精简
- const todoobj ={id:nanoid(),title:this.title,done:false}
- this.addTodo(todoobj)
- // 清空
- this.title=''
-
- }
- },props:[
- 'addTodo'
- ]
- }
- </script>
-
- <style scoped>
- .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>

MyFooter是底部“完成件儿数和事件的总数以及清空已完成部分”这一部分的内容
- <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">清除已完成任务</button>
- </div>
- </template>
-
- <script>
- export default {
- name: "Footer",
- data() {
- return {
-
- }
- },
- props: [
- 'todos','checkAlltodo'
- ],
- methods: {
- checkAll(e) {
- this.checkAlltodo(e.target.checked)
- }
- },
- computed: {
- total() {
- return this.todos.length
- },
- doneTotal() {
- // reduce做条件查询的 第一个参数是函数(调用的次数是数组的长度),第二个参数是初始符合条件的个数
- // pre第一次调用是0,第二次pre是第一次pre的返回值
- // current是当前todo对象
- return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)
- },
- isAll:{
- get(){
- return this.doneTotal === this.total && this.total > 0
- },
- set(value){
- this.checkAlltodo(value)
- }
- }
- }
- }
- </script>
-
- <style scoped>
- .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>

App组件是前面所有组件的父组件以及“爷爷”组件。
- <template>
- <div id="app">
- <div class="todo-container">
- <div class="todo-wrap">
- <Header :addTodo="addTodo" />
- <List :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo" />
- <Footer :todos="todos" :checkAlltodo="checkAlltodo"/>
- </div>
- </div>
- </div>
- </template>
-
- <script>
- import Header from './components/MyHeader.vue';
- import List from "./components/Mylist.vue";
- import Footer from "./components/MyFooter.vue";
-
- export default {
- name: 'App',
- components: {
- Header,
- List,
- Footer
- },
- data() {
- return {
- todos: [
- // id:唯一标识
- // title:需要做的事
- //done:是否完成 true完成 FALSE失败
- {
- id: '001',
- title: '吃饭',
- done: true
- }, {
- id: '002',
- title: '喝酒',
- done: false
- }, {
- id: '003',
- title: '吃饭',
- done: true
- }
- ]
- }
- },
- methods: {
- // 添加
- addTodo(x) {
- console.log("我收到了数据", x);
- // 对数组进行修改
- this.todos.unshift(x)
- },
- // 勾選or或取消勾選
- checkTodo(id) {
- this.todos.forEach((todo) => {
- if (todo.id == id) todo.done = !todo.done;
- console.log(todo.done)
- })
- },
- // 删除
- deleteTodo(id) {
- this.todos= this.todos.filter((todo) => {
- return todo.id !== id
- })
- },
- // 全选、全不选
- checkAlltodo(done){
- this.todos.forEach((todo)=>{
- todo.done=done
- })
- }
- },
- }
- </script>
-
- <style>
- 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>

在剖析todolist案例前呢,我们一定要先熟练地使用props(父传子传值),因为在这次案例中使用较多。
功能:让组件接收外部传过来的数据
(1)、父组件传递数据:
<Student name="李四" :age="11"></Student>
(2)、子组件使用props接收数据,接收的同时对数据类型进行限制+默认值的制定+必要性限制
- props:
- {
- name: {
- type: String,
- required: true
- },
- age: {
- type: Number,
- default:99
- }
- }
备注:props是只读的。vue底层会检监测你对props的修改,如果修改了,就会发出警告,
若业务需求需要修改,那么复制props的内容到data中一份,然后去修改data中的数据
由于todos是有很多个组件进行操作,所以在定义的时候为了方便操作,我们会把它方法App.vue(公用父组件)中,由于列表的显示是在item组件中,在我们没有学会消息订阅与发布以及全局事件总线前,我们只能先将todos通过props先传递给mylist组件、然后使用v-for进行数据的遍历这样就可以得到list列表显示的效果了
- <ul class="todo-main">
- <Item v-for="todoobj in todos"
- :key="todoobj.id"
- />
- </ul>
- </template>
-
- <script>
- import Item from "./Myitem.vue";
-
- export default {
- name:"List",
- components:{Item},
- props:[
- 'todos',
-
- ]
- }
- </script>

由于添加功能的时候,所处的组件关系不是父子关系,所以添加功能相对于其他的功能会复杂一些,在我们没有学过全局事件总线以及消息的订阅与发布之前,我们可以使用组件自定义事件进行myheader组件对vue.app组件 的传值(子传父),而再将拿到的数据通过父传子传给mylist子组件即可。子组件向父组件传值怎么传递呢?方法如下:
子传父的方式:
方式一:通过父组件给子组件擦魂帝函数类型的props实现;子给父传递函数
方式二:通过父组件给子组件绑定一个自定义事件实现:子传父传递数据
方式三:通过ref ,灵活性更强(方式三是方式二的另一种实现方式)
假定有两个组件,app.vue为父组件,school,student为子组件
(1)、通过父组件给子组件擦魂帝函数类型的props实现;子给父传递函数
- app组件: <School :getSchoolName="getSchoolName"></School>
-
- getSchoolName(name) {
- this.msg = name
- }
- School组件:<button @click="sendSchoolName">点我将学校名传给APP组件</button>
-
- methods: {
- sendSchoolName(){
- this.getSchoolName(this.name)
- }
- },
- props:[
- 'getSchoolName'
- ]
(2)、通过父组件给子组件绑定一个自定义事件实现:子传父传递数据
- app组件 : <Student v-on:atguigu="getStudentName"></Student>
-
- getStudentName(name) {
- console.log("收到了"+name)
- this.msg = name
- }
- Student组件:
- <button @click="sendStudentName">点我将学生名传给APP</button>
- methods: {
- sendStudentName(){
- // 触发Student组件实例身上的atguigu事件
- this.$emit('atguigu',this.name)
- }
- },
(3)、通过ref ,灵活性更强(方式三是方式二的另一种实现方式)
- APP组件:
- <Student ref="student"></Student>
-
- getStudentName(name) {
- console.log("收到了"+name)
- this.msg = name
- },
-
- mounted() {
- setTimeout(() => {
- // 绑定自定义事件
- // this.$refs.student.$on('atguigu', this.getStudentName)
- // 绑定自定义事件,只执行一次
- this.$refs.student.$once('atguigu', this.getStudentName)
- }, 5000)
-
- },

- Student组件:
- methods: {
- sendStudentName(){
- // 触发Student组件实例身上的atguigu事件
- this.$emit('atguigu',this.name)
- }
- },
按照这三种方式,也可以完成子组件向父组件间的传值功能,可以体会一下~后期学了“消息的订阅与发布”和“全局事件总线”后,会有更加便捷的方法~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。