持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情
前言
这一节,我们对主体内容做一下组件封装
主体内容
- 主体内容分为两部分,主体内容容器、todos列表
- 我们先来封装todos列表
todos列表
-
这个封装的就比较麻烦了,逻辑比较复杂。
-
首先我们通过
toRefs
来保持props解构之后的响应式 -
然后通过自定义
v-model
实现completed
和text
响应数据通信 -
接着,将所有的方法通过
emit
发送给父组件,并且传递最新的todo
数据 -
最后,还需要把自定义指令迁移过来。
-
TodoList.vue
<template>
<li :class="{ completed: completed, editing: editingTodo == todo }">
<div class="view">
<input v-model="completed" class="toggle" type="checkbox" />
<label @dblclick="editTodo()">{{ text }}</label>
<button @click="removeTodo" class="destroy"></button>
</div>
<input
v-model.trim="text"
v-edit-focus="editingTodo == todo"
@blur="doneEdit()"
@keyup.enter="doneEdit()"
@keyup.escape="cancelEdit()"
class="edit"
type="text"
/>
</li>
</template>
<script>
import { defineComponent, computed, toRefs } from 'vue';
export default defineComponent({
props: {
todo: Object,
editingTodo: [null, Object]
},
emits: ['update:completed', 'update:text', 'editTodo', 'doneEdit', 'cancelEdit', 'removeTodo'],
setup(props, { emit }) {
const { todo } = toRefs(props);
const completed = computed({
get() {
return todo.value.completed;
},
set(value) {
emit('update:completed', value);
}
});
const text = computed({
get() {
return todo.value.text;
},
set(value) {
emit('update:text', value);
}
});
const editTodo = () => {
emit('editTodo', todo.value);
};
const doneEdit = () => {
emit('doneEdit', todo.value);
};
const cancelEdit = () => {
emit('cancelEdit', todo.value);
};
const removeTodo = () => {
emit('removeTodo', todo.value);
};
return {
completed,
text,
editTodo,
doneEdit,
cancelEdit,
removeTodo
};
},
directives: {
editFocus: (el, { value }) => value && el.focus()
}
});
</script>
复制代码
- 使用
<ul class="todo-list">
<TodoList
v-for="todo in filteredTodos"
:key="todo.id"
:todo="todo"
:editingTodo="editingTodo"
v-model:completed="todo.completed"
v-model:text="todo.text"
@editTodo="editTodo"
@doneEdit="doneEdit"
@cancelEdit="cancelEdit"
@removeTodo="removeTodo"
/>
</ul>
复制代码
- 起初我没有把自定义指令迁移到组件中,报了下面的错误,一直没找到原因。后来仔细看,
withDirectives
说的就是和自定义指令相关的错误,如果没有看过源码的同学,遇到这种错误还是要细细的排查下了。
主体容器
-
封装好了todo列表,我们对整个主体内容做进一步封装。
-
我们把整个todo列表操作逻辑都拆分到这个主容器中,通过更新
todos
列表,完成父子组件数据交互 -
Main.vue
<template>
<section class="main">
<CheckAll v-model:allDone="allDone" />
<ul class="todo-list">
<TodoList
v-for="todo in filteredTodos"
:key="todo.id"
:todo="todo"
:editingTodo="editingTodo"
v-model:completed="todo.completed"
v-model:text="todo.text"
@editTodo="editTodo"
@doneEdit="doneEdit"
@cancelEdit="cancelEdit"
@removeTodo="removeTodo"
/>
</ul>
</section>
</template>
<script>
import { defineComponent, ref, toRefs, computed } from 'vue';
import { filters } from '@/utils/index';
import CheckAll from '@/components/CheckAll.vue';
import TodoList from '@/components/TodoList.vue';
export default defineComponent({
components: {
CheckAll,
TodoList
},
props: {
todos: Array,
visibility: String,
remaining: Number
},
emits: ['update:todos'],
setup(props, { emit }) {
const { todos, remaining, visibility } = toRefs(props);
const filteredTodos = computed(() => filters[visibility.value](todos.value));
const todosValue = computed({
get: () => todos.value,
set: (v) => {
emit('update:todos', v);
}
});
const allDone = computed({
get: () => !remaining,
set: (value) => {
todosValue.value.forEach((todo) => {
todo.completed = value;
});
}
});
const editingTodo = ref(null);
const beforeEditText = ref('');
const editTodo = (todo) => {
editingTodo.value = todo;
beforeEditText.value = todo.text;
};
const doneEdit = (todo) => {
if (!editingTodo.value) return;
todo.text || removeTodo(todo);
editingTodo.value = null;
};
const cancelEdit = (todo) => {
editingTodo.value = null;
todo.text = beforeEditText.value;
};
const removeTodo = (todo) => {
todos.value.splice(todos.value.indexOf(todo), 1);
};
return {
allDone,
editingTodo,
filteredTodos,
editTodo,
doneEdit,
cancelEdit,
removeTodo
};
}
});
</script>
复制代码
- 使用
<Main v-model:todos="todos" :visibility="visibility" :remaining="remaining" />
复制代码
总结
这一节,我们主要做了主体内容的todos列表和主体容器的封装。
项目代码在GitHub,可以查阅