互动教程(Vue3)
列表渲染
条件语句后是循环操作,也是基本操作
可以使用 v-for 指令来渲染一个基于源数组的列表
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
这里的 todo 是一个局部变量,表示当前正在迭代的数组元素。它只能在 v-for 所绑定的元素上或是其内部访问,就像函数的作用域一样。
注意,我们还给每个 todo 对象设置了唯一的 id,并且将它作为特殊的 key attribute 绑定到每个 <li>。key 使得 Vue 能够精确地移动每个
更新列表
在源数组上调用变更方法
this.todos.push(newTodo)
使用新的数组替代原数组
this.todos = this.todos.filter(/* ... */)
示例代码:
这里有一个简单的 todo 列表——试着实现一下 addTodo() 和 removeTodo() 这两个方法的逻辑,使列表能够正常工作!
<script>
// 给每个 todo 对象一个唯一的 id
let id = 0
export default {
data() {
return {
newTodo: '',
todos: [
{ id: id++, text: 'Learn HTML' },
{ id: id++, text: 'Learn JavaScript' },
{ id: id++, text: 'Learn Vue' }
]
}
},
methods: {
// @submit.prevent属性可以实现当我们点击提交按钮时,表单并不会提交,而是会触发绑定的函数
// 所以addTodo被触发
addTodo() {
// 因为"newTodo" 用v-model进行修饰,所以有双向关联,newTodo会得到用户输入的内容
// 可以添加到todos列表中
this.todos.push({id:id++,text:this.newTodo})
// 常规操作
this.newTodo = ''
},
removeTodo(todo) {
// 遍历this.todos,如果其中的元素item != 当前删除的这个才放到this.todos这个数组
this.todos = this.todos.filter(item => item != todo)
// 参考答案
// 这里用的是 !==
// this.todos = this.todos.filter((t) => t !== todo)
console.log(Object.prototype.toString.call(todo)
}
}
}
</script>
<template>
<form @submit.prevent="addTodo">
<input v-model="newTodo" required placeholder="new todo">
<button>Add Todo</button>
</form>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
</template>
小结:
- 可以用赋值/filter的方法生成数组,遍历操作: (变量) => 变量相关逻辑
form表单可以使用@submit.prevent = "方法名"来阻止默认的提交,并触发指定的方法!==执行严格的不相等比较,不会在检查不相等之前转换操作数的类型。这里是对象比较,比较的是相同
计算属性
这是通过给每一个todo对象添加done属性并将其绑定到复选框上来添加切换功能
如何基于状态渲染不同的列表项,这个要求有点类似上一篇的v-if-else
<li v-for="todo in todos">
<input type="checkbox" v-model="todo.done">
...
</li>
可以使用 computed 选项声明一个响应式的属性,它的值由其他属性计算而来
示例代码:
试着添加 filteredTodos 计算属性并实现计算逻辑
<script>
let id = 0
export default {
data() {
return {
newTodo: '',
hideCompleted: false,
todos: [
{ id: id++, text: 'Learn HTML', done: true },
{ id: id++, text: 'Learn JavaScript', done: true },
{ id: id++, text: 'Learn Vue', done: false }
]
}
},
computed: {
// ② 根据 `this.hideCompleted` 返回过滤后的 todo 项目
filteredTodos() {
return this.hideCompleted ? this.todos.filter((t) => t.done !== true) : this.todos
}
},
methods: {
addTodo() {
this.todos.push({ id: id++, text: this.newTodo, done: false })
this.newTodo = ''
},
removeTodo(todo) {
this.todos = this.todos.filter((t) => t !== todo)
}
}
}
</script>
<template>
<form @submit.prevent="addTodo">
<input v-model="newTodo" required placeholder="new todo">
<button>Add Todo</button>
</form>
<ul>
// ① 关联计算属性
<li v-for="todo in filteredTodos" :key="todo.id">
<!--<li v-for="todo in todos" :key="todo.id"> -->
<input type="checkbox" v-model="todo.done">
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
<button @click="hideCompleted = !hideCompleted">
{{ hideCompleted ? 'Show all' : 'Hide completed' }}
</button>
</template>
<style>
.done {
text-decoration: line-through;
}
</style>
显示所有的todos
隐藏已完成的todos
可以把右侧的show Error关闭,弹出错误提示的时候影响输入
小结
- 计算属性的值依赖其其它的属性而来的,它会自动跟踪其计算中所使用的到的其他响应式状态
- 计算属性可以放在
computed:选项里
生命周期和模板引用
Vue 为我们处理了所有的 DOM 更新,这要归功于响应性和声明式渲染。然而,有时我们也会不可避免地需要手动操作 DOM。 -- 类似C 要有指针去访问地址的概念;有自动挡还是要了解手动档。
这时我们需要使用模板引用——也就是指向模板中一个 DOM 元素的 ref。我们需要通过这个特殊的 ref attribute 来实现模板引用:
<p ref="pElementRef">hello</p>
此元素将作为 this.$refs.pElementRef 暴露在 this.$refs 上。然而,你只能在组件挂载之后访问它。
<script>
export default {
mounted() {
// 重新设置引用的pElementRef元素的textContent
this.$refs.pElementRef.textContent = "World"
}
}
</script>
<template>
<p ref="pElementRef">Hello</p>
</template>
这被称为生命周期钩子——它允许我们注册一个在组件的特定生命周期调用的回调函数。还有一些其他的钩子如 created 和 updated
小结
- 可以通过`ref
标签在Vue中操作DOM元素 Vue提供了若干生命周期的钩子函数,让开发者可以在不同的时间点操作操作元素
侦听器(Watch)
有时我们需要响应性地执行一些“副作用”——例如,当一个数字改变时将其输出到控制台。我们可以通过侦听器来实现它:
export default {
data() {
return {
count: 0
}
},
watch: {
count(newCount) {
// 没错,console.log() 是一个副作用
console.log(`new count is: ${newCount}`)
}
}
}
示例代码
<script>
export default {
data() {
return {
todoId: 1,
todoData: null
}
},
methods: {
async fetchData() {
this.todoData = null
// 获取数据
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${this.todoId}`
)
this.todoData = await res.json()
}
},
mounted() {
this.fetchData()
},
watch: {
// 观察todoId状态,每次点击 Fetch next dodo按钮时
// 因为@click="todoId++",所以todoId的状态值会自增1
// 然后触发watch里的todoId方法,执行fetchData,获取下一条数据
todoId() {
this.fetchData()
}
}
}
</script>
<template>
<p>Todo id: {{ todoId }}</p>
<button @click="todoId++" :disabled="!todoData">Fetch next todo</button>
<p v-if="!todoData">Loading...</p>
<pre v-else>{{ todoData }}</pre>
</template>
小结
- 使用
Watch选项可以设置当某个值变化时触发指定的操作,有点像计算属性的更泛的表达
组件
真正的 Vue 应用往往是由嵌套组件创建的。
父组件可以在模板中渲染另一个组件作为子组件。要使用子组件,我们需要先导入它:
import ChildComp from './ChildComp.vue'
export default {
components: {
ChildComp
}
}
使用模板
<ChildComp />
示例代码
<script>
export default {
// 注册子组件
}
</script>
<template>
<!-- render child component -->
</template>
小结
- 使用
import可以引入子组件 - 使用
components选项可以注册子组件
Props
子组件可以通过 props 从父组件接受动态数据。首先,需要声明它所接受的 props:
// 在子组件中
export default {
props: {
msg: String
}
}
msg prop 就会暴露在 this 上,并可以在子组件的模板中使用
父组件可以像声明 HTML attributes 一样传递 props。若要传递动态值,也可以使用 v-bind 语法:
示例代码
<script>
import ChildComp from './ChildComp.vue'
export default {
components: {
ChildComp
},
data() {
return {
greeting: 'Hello from parent'
}
}
}
</script>
<template>
<!-- 父组件的greeting状态值传递给子组件用prop声明接收的msg -->
<ChildComp :msg="greeting" />
<!-- 使用HTML attributes 传递给子组件 -->
<ChildComp msg="123"> </ChildComp>
</template>
小结
- 子组件可以通过
prop指定接收哪些外部状态 -- 大同小异,差不多就是模型定义的公开属性 - 可以通过
html attributes/v-bind将状态值从父组件传给子组件
Emits
除了接收 props,子组件还可以向父组件触发事件
emit: 发射
export default {
// 声明触发的事件
emits: ['response'],
created() {
// 带参数触发
this.$emit('response', 'hello from child')
}
}
this.$emit() 的第一个参数是事件的名称。其他所有参数都将传递给事件监听器。
示例代码
<script>
import ChildComp from './ChildComp.vue'
export default {
components: {
ChildComp
},
data() {
return {
childMsg: 'No child msg yet'
}
}
}
</script>
<template>
<!-- 父组件可以使用 ``v-on`` 监听子组件触发的事件——这里的处理函数接收了子组件触发事件时的额外参数并将它赋值给了本地状态 -->
<ChildComp @response="(msg) => childMsg = msg" />
<p>{{ childMsg }}</p>
</template>
小结
- 在子组件使用
emits选项可以声明触发某些事件 - 父组件可以使用
v-on在响应子组件的emits的操作 -- 数据如何传递
插槽(slots)
除了通过 props 传递数据外,父组件还可以通过插槽 (slots) 将模板片段传递给子组件:
<ChildComp>
This is some slot content!
</ChildComp>
示例代码
App.vue
<script>
import ChildComp from './ChildComp.vue'
export default {
components: {
ChildComp
},
data() {
return {
msg: 'from parent'
}
}
}
</script>
<template>
<ChildComp>妖怪,还不现出原形</ChildComp>
</template>
ChildComp.vue
<template>
<slot>Fallback content</slot>
</template>
默认显示 Fallback content, 如果父组件中有具体内容,则显示父组件中的内容
小结
- 父组件可以通过插槽
(slots)将模板片段传递给子组件 -- 可以理解为一种自定义的标签
总结
- 如何操作列表
- 通过
computed选项可以关联其它的状态 - 了解了
Vue模板的生命周期 - 通过
watch选项在其它状态变化时触发指定操作 - 如何引入外部组件
- 多个组件间如何进行数据的传递:
props(父组件传子组件),Emits(子组件回传父组件),slot(父组件传子组件)