由于工作接手的项目需要,本文主要针对 Vue 的选项式 API 编写。

本文的例子主要来自 Vue 的官方互动教程:https://cn.vuejs.org/tutorial/#step-1;官方更详细的指南文档:https://cn.vuejs.org/guide/introduction.html

组件

Vue 组件有三个主要部分:script、template 和 style;script 中写 JavaScript、template 中写 HTML、style 中写 CSS。

<script>
    export default {
        data() {
            return {
                message: 'Hello World!',
            }
        }
    }
</script>

<template>
    <p>{{ message }}</p>
</template>

<style>
    p {
        color: red;
    }
</style>

script 中:

  • export default 用于导出组件定义(打包),以便在其他 Vue 组件中使用;
  • data 选项定义了组件数据,类似面向对象中的字段;其他选项还有例如 methods、computed 等;
  • return 就相当于各种语言函数中那一句 return。

template 中用到了 Mustache 插值,将在下一节讲解。

style 中是简单的 CSS。

绑定

本节主要涉及内容如下:

  • Mustache 映射文本;
  • computed 根据依赖项更新映射;
  • v-bind 映射标签属性;
  • v-on 监听事件并调函数;
  • v-model 实现两处数据同步更新;
  • watch 可在 v-model 基础上实现调函数。

Mustache 插值

将任何有效的 JavaScript 表达式放在 HTML 中,并用双大括号包围,表达式将被解析为文本。下面依次举例解析字符串、数字、布尔值、数组、对象、js 表达式、Vue 实例的数据属性和计算属性:

<p>{{ 'Hello, Vue!' }}</p>
<p>{{ 42 }}</p>
<p>{{ true }}</p>
<p>{{ [1, 2, 3] }}</p>
<p>{{ { name: 'John', age: 25 } }}</p>
<p>{{ message.split('').reverse().join('') }}</p>
<p>{{ message }}</p>
<p>Count is: {{ counter.count }}</p>

computed 计算属性

computed 可根据其依赖的数据计算出一个新的值,依赖变化时则重新计算;可用 Mustache 直接引用 computed 方法。

<script>
    export default {
        data() {
            return {
                text: 'Hello, World!'
            };
        },
        computed: {
            textLength() {
                return this.text.length;
            }
        }
    };
</script>

<template>
    <div>
        <p>Text: {{ text }}</p>
        <p>Length: {{ textLength }}</p>
    </div>
</template>

v-bind 属性绑定

v-bind 用于将 Vue 实例(常常在 script 中定义)中一个数据属性绑定到 HTML 中一个标签属性。

以下例子将 Vue 实例中的 titleClass 数据属性绑定到了 p 标签的 class 属性上:

<script>
    export default {
        data() {
            return {
                titleClass: 'title'
            }
        }
    }
</script>

<template>
    <p :class="titleClass">Red</p>
</template>

<style>
    .title {
        color: red;
    }
</style>

v-on 事件绑定

v-on 指令用于监听 DOM 事件,@ 后跟动作和与动作绑定的方法,例如 @click="increment" 就表示 click 时调用 increment 方法。

<script>
    export default {
        data() {
            return {
                count: 0
            }
        },
        methods: {
            increment() {
                this.count++
            }
        }
    }
</script>

<template>
    <button @click="increment">count is: {{ count }}</button>
</template>

v-model 双向绑定

可以同时使用 v-bind 和 v-on 来在表单的输入元素上创建双向绑定,Vue 中提供了 v-model 来简化表达;v-model 也支持多选框、单选框、下拉框等。

<script>
    export default {
        data() {
            return {
                text: ''
            }
        }
    }
</script>

<template>
    <input v-model="text" placeholder="Type here">
    <p>{{ text }}</p>
</template>

watch 侦听器

单纯 v-model 只能实现对数据的双向绑定,而不能调用函数或方法,后者可以通过侦听器实现。

<template>
    <div>
        <input v-model="inputText" type="text" placeholder="输入文字">
        <p>输入的文字:{{ inputText }}</p>
        <p>文字长度:{{ textLength }}</p>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                inputText: '',
                textLength: 0,
            };
        },
        watch: {
            inputText(newValue, oldValue) {
                // 当inputText属性发生变化时触发的回调函数
                this.textLength = newValue.length;
            },
        },
    };
</script>

流程控制

v-if 条件渲染

使用 v-if 指令来有条件地渲染元素。v-if 后面的表达式可以是任何能被解析为布尔值的 JavaScript 表达式;它还可以可与 v-else、v-else-if 组合使用。

<script>
    export default {
        data() {
            return {
                awesome: true
            }
        },
        methods: {
            toggle() {
                this.awesome = !this.awesome
            }
        }
    }
</script>

<template>
    <button @click="toggle">toggle</button>
    <h1 v-if="awesome">Vue is awesome!</h1>
    <h1 v-else>Oh no</h1>
</template>

v-for 列表渲染

v-for 用于简洁地渲染列表。

<script>
    // 给每个 todo 对象一个唯一的 id
    let id = 0

    export default {
        data() {
            return {
                todos: [
                    { id: id++, text: 'Learn HTML' },
                    { id: id++, text: 'Learn JavaScript' },
                    { id: id++, text: 'Learn Vue' }
                ]
            }
        }
    }
</script>

<template>
    <ul>
        <li v-for="todo in todos" :key="todo.id">
            {{ todo.text }}
        </li>
        <!-- todo 是一个局部变量,表示当前正在迭代的数组元素 -->
    </ul>
</template>

生命周期和模板引用

生命周期钩子函数允许我们在组件不同的阶段执行自定义的逻辑,例如数据的初始化、异步请求、DOM 操作、清理等操作。

模板引用使用 this.$refs.自定义属性名称 来在 script 中访问 template 元素;被引元素处需用 ref 属性指定。模板引用可以对被引元素直接执行一些 DOM 操作,例如修改 textContent:this.$refs.pElementRef.textContent = 'Mounted!':

<script>
    export default {
        mounted() {
            this.$refs.pElementRef.textContent = 'Mounted!'
        }
    }
</script>

<template>
    <p ref="pElementRef">Hello!</p>
</template>

常用的生命周期钩子函数有:

  • beforeCreate():在实例被创建之前调用,此时数据观测和事件配置尚未完成。
  • created():在实例创建完成后调用,此时实例已经完成数据观测,可以访问到数据、计算属性和方法,但尚未挂载到 DOM 上。
  • beforeMount():在实例挂载到 DOM 元素之前调用,此时模板编译已完成,但尚未将模板渲染成最终的 DOM。
  • mounted():在实例挂载到 DOM 元素后调用,此时实例已经完成 DOM 渲染,可以访问到挂载后的 DOM 元素。常用于执行一些需要 DOM 操作的逻辑。
  • beforeUpdate():在数据更新之前调用,发生在虚拟 DOM 重新渲染和打补丁之前。可以用于在更新之前进行一些额外的操作。
  • updated(): 在数据更新之后调用,发生在虚拟 DOM 重新渲染和打补丁之后。可以用于执行一些更新后的操作。
  • beforeUnmount():在实例销毁之前调用,此时实例仍然完全可用。
  • unmounted():在实例销毁后调用,此时实例的所有指令和事件监听器都已经被移除,可以进行一些清理操作。

多组件

引入子组件

引入子组件实现嵌套。方法如下:

  • import 指定名称,名称可以自定义,但建议与要引入的文件名相同;
  • 在 script 中的 components 列表里注册子组件;
  • 在 template 中用组件名标签引入,例如 <ChildComp />

举例如下:

父组件 App.vue 内容如下:

<script>
    import ChildComp from './ChildComp.vue'

    export default {
        components: {
            ChildComp
        }
    }
</script>

<template>
    <ChildComp />
</template>

子组件 ChildComp.vue 内容如下:

<template>
    <h2>A Child Component!</h2>
</template>

props

相当于跨组件的 v-bind,子组件可以通过 props 从父组件接受数据。props 的使用方法如下:

  • 在子组件中定义 props,类似声明变量;
  • 在父组件中引入并注册子组件;
  • 在父组件中 v-bind 引用 props,类似给变量赋值。

举例如下:

父组件 App.vue:

<script>
    import ChildComp from './ChildComp.vue'

    export default {
        components: {
            ChildComp
        },
        data() {
            return {
                greeting: 'Hello from parent'
            }
        }
    }
</script>

<template>
    <ChildComp :msg="greeting" />
</template>

子组件 ChildComp.vue:

<script>
    export default {
        props: {
            msg: String
        }
    }
</script>

<template>
    <h2>{{ msg || 'No props passed yet' }}</h2>
</template>

emits

相当于跨组件的 v-on,子组件可以通过 emits 向父组件触发事件。emits 的使用方法如下:

  • 在子组件中定义 emits,类似声明函数;
  • 在父组件中引入并注册子组件;
  • 在父组件中 v-on 引用 emits,类似调用父组件中定义的函数;

举例如下:

父组件 App.vue:

<script>
    import ChildComp from './ChildComp.vue'

    export default {
        components: {
            ChildComp
        },
        data() {
            return {
                childMsg: 'No child msg yet'
            }
        }
    }
</script>

<template>
    <ChildComp @response="(msg) => childMsg = msg" />
    <p>{{ childMsg }}</p>
</template>

子组件 ChildComp.vue:

<script>
    export default {
        emits: ['response'],
        created() {
            this.$emit('response', 'hello from child')
        }
    }
</script>

<template>
    <h2>Child component</h2>
</template>

slots 插槽

父组件可以通过 slots 将模板片段传递给子组件。子组件 slot 标签内的内容将被父组件 ChildComp 标签内的内容替换。举例如下:

父组件 App.vue:

<script>
    import ChildComp from './ChildComp.vue'

    export default {
        components: {
            ChildComp
        }
    }
</script>

<template>
    <ChildComp>Message from parent</ChildComp>
</template>

子组件 ChildComp.vue:

<template>
    <slot>Fallback content</slot>
    <p>Message from child</p>
</template>