Vue3快速入门(一)

在学习本文之前,建议同学们先去学习Vue2的知识点,掌握了Vue2的相关知识以后,再来学习Vue3,感觉会非常轻松。

本文是系列文章,带你从快速入门学习 vue3。

只讲能让你上手做项目的高频知识点,目的让你快速上手vue,之后其他内容还请阅读vue官方文档。

快速认识Vue

认识Vue

vue 是一个 js 框架,他用 js 封装了 DOM操作,所以写 vue 不需要直接操作 DOM,而只要做 2 件事情:

  1. 建立数据变量,把DOM 的”样式/属性/内容“等和”变量“进行绑定
  2. 操作数据变量, 绑定的DOM 样式/属性/内容等会自动根据数据更新

引入vue.js

和其他 js 库一样, 我们可以通过”script”标签引入vue

<script src="https://unpkg.com/vue@next"></script>

初始化vue

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>快速认识Vue</title>
</head>
<body>
    <div id="app"></div>
    <script src="https://unpkg.com/vue@next"></script>
    <script>
         Vue.createApp().mount('#app');
    
</script>
</body>
</html>

Vue.createApp 是vue的初始化语法,通过 "mount" 指定的元素,起内部的元素即可支持vue的模板语法,这里 "#app" 的意思是 id 为app的元素。

下面我们分2步实现让页面显示绿色的  "Hello Vue!"

建立数据变量(data)

Vue.createApp({
    data() {
        return {
            content'Hello Vue!',
            textColor'green'
        };
    }
}).mount('#app');

只有在data字段中定义变量,模板才可以识别并显示。

绑定变量到DOM

"{{}}"放在html标签中间,用来渲染变量

"v-bind:"后面跟着style属性,这样style就可以识别vue的变量

<div id="app">
    <h1 v-bind:style="{color:textColor}">{{content}}</h1>
</div>

页面效果

Vue3快速入门(一)

完整代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>快速认识Vue</title>
</head>

<body>

    <div id="app">
        <h1 v-bind:style="{color:textColor}">{{content}}</h1>
    </div>

    <script src="https://unpkg.com/vue@next"></script>
    <script>
        Vue.createApp({
            data() {
                return {
                    content'Hello Vue!',
                    textColor'green'
                };
            }
        }).mount('#app');
    
</script>
</body>

</html>

操作数据变量

数据操作我们一般都封装成函数放在methods中,修改上面的例子,页面上增加一个按钮用来修改”h1″的颜色和内容,注意:函数中需要通过 “this.” 来访问data中的变量,我们分2步实现:

建立函数

Vue.createApp({
    data() {
        return {
            content'Hello Vue!',
            textColor'green'
        };
    },
    methods: {
        changeH1() {
            this.textColor = "red";
            this.content = "hello world!";
        }
    }
}).mount('#app');

绑定函数到元素

<div id="app">
    <h1 v-bind:style="{color:textColor}">{{content}}</h1>
    <button v-on:click="changeH1">change</button>
</div>

页面效果

Vue3快速入门(一)

当点击了”修改”,那么”h1″就变成了红色字:”hello world!”.

完整代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>快速认识Vue</title>
</head>

<body>

    <div id="app">
        <h1 v-bind:style="{color:textColor}">{{content}}</h1>
        <button v-on:click="changeH1">change</button>
    </div>

    <script src="https://unpkg.com/vue@next"></script>
    <script>
        Vue.createApp({
            data() {
                return {
                    content'Hello Vue!',
                    textColor'green'
                };
            },
            methods: {
                changeH1() {
                    this.textColor = "red";
                    this.content = "hello world!";
                }
            }
        }).mount('#app');
    
</script>
</body>

</html>

模板语法

{{}}

通过标记{{}},我们可以在html中插入变量,即插值语法

变量分隔符,标记变量。

注意这种语法只能使用在html标签中间

<div id="app">
    <h1>{{html}}</h1>
</div>

<script src="https://unpkg.com/vue@next"></script>

<script>
    Vue.createApp({
        data() {
            return {
                html`<small>小孩</small>`
            }
        }
    }).mount('#app');
</script>

而且还可以是js表达式

<div id="app">
    <h1>{{list[1]}}</h1>
    <!-- 生成: <h1>orange</h1> -->

    <h1>{{price*num}}</h1>
    <!-- 生成: <h1>90</h1> -->
</div>

<script src="https://unpkg.com/vue@next"></script>

<script>
    Vue.createApp({
        data() {
            return {
                list: ['apple''orange'],
                price10,
                num9,
            }
        }
    }).mount('#app');
</script>

指令

vue封装了常用dom操作, 通过在元素上标记 v- 开头的属性来使用, 这些属性就叫做指令.

v-bind

绑定vue变量和标签属性。

这里注意下”style”和”class”的特殊用法即可。

<div id="#app">
    <h1 v-bind:align="align">hi vue</h1>
    <!-- 生成: <h1 align="center">hi vue</h1> -->

    <h1 v-bind:style="{color}">hi vue</h1>
    <!-- 生成: <h1 style="color:#f10;">hi vue</h1> -->

    <h1 v-bind:class="className">hi vue</h1>
    <!-- 生成: <h1 class="red big">hi vue</h1> -->

    <h1 v-bind:class="className2">hi vue</h1>
    <!-- 生成: <h1 class="red">hi vue</h1> -->
</div>

<script src="https://unpkg.com/vue@next"></script>
<script>
    Vue.createApp({
        data() {
            return {
                align'center',
                color'#f10',
                className: ['red''big'],
                className2: {
                    redtrue,
                    bigfalse
                },
            }
        }
    }).mount('#app');
</script>

缩写

v-bind:“可以缩写成”:“.

<h1 v-bind:align="align">hi vue</h1>
<!-- 等于 -->
<h1 :align="align">hi vue</h1>

v-on

事件绑定。

语法为: “v-on:事件名“.

<div id="app">
    <button v-on:click="onClick">hi vue</button>
</div>

<script src="https://unpkg.com/vue@next"></script>

<script>
    Vue.createApp({
        methods: {
            onClick() {
                alert("hi vue");
            }
        }
    }).mount('#app');
</script>

缩写

v-on:“可以缩写为”@

<h1 v-on:click="onClick">hi vue</h1>
<!-- 等于 -->
<h1 @click="onClick">hi vue</h1>

v-if

控制是否渲染对应元素

<div id="app">
    <h1 v-if="isShow">hi vue</h1>
</div>

<script src="https://unpkg.com/vue@next"></script>

<script>
    Vue.createApp({
        data() {
            return {
                isShowfalse,
            }
        }
    }).mount('#app');
</script>

配套的还有 “v-else-if” 和 “v-else“:

<h1 v-if="isShow">hi vue</h1>
<h1 v-else>你好</h1>

v-show

通过控制元素的样式 “display:none” 来控制显示隐藏

<div id="app">
    <h1 v-show="isShow">hi vue</h1>
</div>

<script src="https://unpkg.com/vue@next"></script>

<script>
    Vue.createApp({
        data() {
            return {
                isShowfalse,
            }
        }
    }).mount('#app');
</script>

运行结果

<h1 style="display:none;">hi vue</h1>

v-for

循环,注意被循环的元素需要标记key属性,key的值要保证唯一,初期学习可以让key的值等于循环索引(index)

循环数组
<div id="#app">
  <h1 v-for="(item,index) in list" :key="index">{{index}} - {{item}}</h1>
</div>

<script>
Vue.createApp({
    data(){
     return {
        list:['苹果','柿子','香蕉']
      }
    }
}).mount('#app');
</script>

运行结果

  <h1>0 - 苹果</h1>
  <h1>1 - 柿子</h1>
  <h1>2 - 香蕉</h1>
循环对象
<div id="#app">
  <h1 v-for="(value, key) in object" :key="key">{{key}} - {{value}}</h1>k
</div>

<script>
Vue.createApp({
    data(){
     return {
        object:{a:1,b:2,c:3}
      }
    }
}).mount('#app');
</script>

运行结果

  <h1>a - 1</h1>
  <h1>b - 2</h1>
  <h1>c - 3</h1>
循环数字

做测试数据时候很有用

 <h1 v-for="n in 5" :key="n">{{n}}</h1>

运行结果

  <h1>1</h1>
  <h1>2</h1>
  <h1>3</h1>
  <h1>4</h1>
  <h1>5</h1>

注意:第一位数字是1

v-text

填充元素内的文字

<div id="#app">
  <h1 v-text="text"></h1> 
</div>

<script>
Vue.createApp({
    data(){
     return {
        text`大象`
      }
    }
}).mount('#app');
</script>

运行结果

<h1>大象</h1>

v-html

可以填充任意字符串,”v-text”的内容会转义,比如”v-text”的内容不能是html标签,”v-html”可以

<div id="#app">
  <h1 v-html="html"></h1> 
</div>

<script>
Vue.createApp({
    data(){
     return {
        html`<small>小象</small>`
      }
    }
}).mount('#app');
</script>

运行结果

<h1><small>小象</small></h1>

其他指令

下面几个系统指令暂时不展开讲,后面会随着文章的深入针对性的带出

  • v-model:https://v3.cn.vuejs.org/api/directives.html#v-model

  • v-slot:https://v3.cn.vuejs.org/api/directives.html#v-slot

  • v-pre:https://v3.cn.vuejs.org/api/directives.html#v-pre

  • v-cloak:https://v3.cn.vuejs.org/api/directives.html#v-cloak

  • v-once:https://v3.cn.vuejs.org/api/directives.html#v-once

  • v-is:https://v3.cn.vuejs.org/api/directives.html#v-is

template

空标签,并不会渲染到DOM中,一般用来包围元素进行批量操作

<template v-if="isShow">
  <h1>呐喊</h1>
  <p>鲁迅<p>
</template>

<script>
Vue.createApp({
    data(){
     return {
        isShowtrue
      }
    }
}).mount('#app');
</script>

监控数据(watch)

监控定义的变量,发生变化会触发自定义的函数。

使用场景

当我们想监控一个数据的变化,我们可以注册一个监视器,最常见的场景就是”翻页”:

<div id="app">
    <!--控制-->
    <select v-model="page">
        <option v-for="n in 100" :key="n" :value="n">第{{ n }}页</option>
    </select>

    <button @click="page--">上一页</button>
    <button @click="page++">下一页</button>

    <!--显示-->
    <div>
        <p v-for="{ title } in list" :key="title">{{ title }}</p>
    </div>
</div>

<script src="https://unpkg.com/vue@next"></script>

<!-- Axios 是一个基于 promise 的 HTTP 库 -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>


<script>
    Vue.createApp({
        data() {
            return {
                page50,
                list: [],
            };
        },

        watch: {
            page(page, oldPage) {
                console.log(oldPage);
                axios
                    .get(`https://cnodejs.org/api/v1/topics?limit=5`, {
                    params: {
                        page
                    }
                })
                    .then((response) => {
                    const {
                        data
                    } = response.data;
                    this.list = data;
                });
            },
        },
    }).mount('#app');
</script>

在”watch”中注册监听,每个监听对应一个函数,函数有2个参数,第一个代表当前值,第二个代表变化之前的值

axios

一个最常用的ajax请求库,语法很简单,文档看这里:http://www.axios-js.com/

立即执行(immediate)

上面的代码有一个小问题。

那就是打开页面的时候并不会请求数据,虽然这时候我们的”page”是有值的,但是并没有请求数据。

想要打开页面就执行一边watch的代码可以这么修改:

watch: {
    page: {
        immediatetrue,
            handler(page) {
            axios
                .get(`https://cnodejs.org/api/v1/topics?limit=5`, {
                params: {
                    page
                },
            })
                .then((response) => {
                const {
                    data
                } = response.data;
                this.list = data;
            });
        },
    },
},

page的值不再是函数,而是一个对象, 其中”immediate“设置为true,同时把回调函数放在handler字段中。

监听数组和对象(deep)

对于层级深的数组和对象的监听,我们要加上”deep”:

watch: {
  objectOrArray: {
    deeptrue,
    handler(value) {
     // 发生变化
    },
  },
}

计算属性(computed)

计算属性就是把多个vue变量合成一个变量(计算属性)。

使用场景

在模板中放入太多的逻辑会让模板过重且难以维护。

例如计算购物车中商品总价格:

<div id="app">
    <h1>总价格: {{appleNumber*applePrice + bananaNumber*bananaPrice}}</h1>
</div>

<script src="https://unpkg.com/vue@next"></script>

<script>
    Vue.createApp({
        data() {
            return {
                appleNumber71,
                applePrice11,
                bananaNumber99,
                bananaPrice12,
            };
        }
    }).mount('#app');
</script>

假如我们有更多的逻辑要计算,那么模板估计要”爆炸”。

computed

vue提供了一种多数据整合的方案,作者叫他 计算属性 ,他是一个内部返回数据的函数,我们可以把这个函数当做值去使用:

<div id="app">
    <h1>总价格: {{totalPrice}}</h1>
</div>

<script src="https://unpkg.com/vue@next"></script>

<script>
    Vue.createApp({
        data() {
            return {
                appleNumber71,
                applePrice11,
                bananaNumber99,
                bananaPrice12,
            };
        },
        computed: {
            totalPrice() {
                return this.appleNumber * this.applePrice + this.bananaNumber * this.bananaPrice;
            }
        }
    }).mount('#app');
</script>

假如我们修改了其中任意一个值,计算属性的值也会自动变化,同时页面也会变化:

<div id="app">
    <h1>总价格: {{totalPrice}}</h1>
    <button @click="change">让苹果涨价</button>
</div>

<script src="https://unpkg.com/vue@next"></script>

<script>
    Vue.createApp({
        data() {
            return {
                appleNumber71,
                applePrice11,
                bananaNumber99,
                bananaPrice12,
            };
        },
        methods: {
            change() {
                this.applePrice++;
            }
        },
        computed: {
            totalPrice() {
                return this.appleNumber * this.applePrice + this.bananaNumber * this.bananaPrice;
            }
        }
    }).mount('#app');
</script>

表单的双向数据绑定(v-model)

“双向数据绑定” 的意思就是:”数据可以操作DOM,DOM也可以操作数据”。

表单元素比较特别,他们都是支持”输入”的可交互元素,所以表单经过和数据的”绑定”即可实现数据的读写。

v-model

说起来表单和数据绑定也很简单。

input

<div id="app">
    <input v-model="message" placeholder="edit me" />
    <p>Message is: {{ message }}</p>
</div>

<script src="https://unpkg.com/vue@next"></script>

<script type="module">
    Vue.createApp({
        data() {
            return {
                message:''
            };
        }
    }).mount('#app');
</script>
Vue3快速入门(一)

textarea

<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
Vue3快速入门(一)

checkbox

<div id="app">
    <input type="checkbox" id="checkbox" v-model="checked" />
    <label for="checkbox">{{ checked }}</label>
</div>

<script src="https://unpkg.com/vue@next"></script>

<script type="module">
    Vue.createApp({
        data() {
            return {
                checked:false
            };
        }
    }).mount('#app');
</script>
Vue3快速入门(一)

对于”checkbox”,如果我们希望返回的不是”true/false”这么简单, 可以通过”true-value“和”false-value“绑定别的值:

<div id="app">
    <input type="checkbox" id="checkbox" v-model="checked" true-value="yes" false-value="no" />
    <label for="checkbox">{{ checked }}</label>
</div>

<script src="https://unpkg.com/vue@next"></script>

<script type="module">
    Vue.createApp({
        data() {
            return {
                checked:'yes'
            };
        }
    }).mount('#app');
</script>
Vue3快速入门(一)

radio

<div id="app">
    <input type="radio" id="one" value="One" v-model="picked" />
    <label for="one">One</label>
    <br />
    <input type="radio" id="two" value="Two" v-model="picked" />
    <label for="two">Two</label>
    <br />
    <span>Picked: {{ picked }}</span>
</div>

<script src="https://unpkg.com/vue@next"></script>

<script type="module">
    Vue.createApp({
        data() {
            return {
                picked:''
            };
        }
    }).mount('#app');
</script>
Vue3快速入门(一)

select

<div id="app">
    <select v-model="selected">
        <option disabled value="">Please select one</option>
        <option>A</option>
        <option>B</option>
        <option>C</option>
    </select>
    <span>Selected: {{ selected }}</span>
</div>

<script src="https://unpkg.com/vue@next"></script>

<script type="module">
    Vue.createApp({
        data() {
            return {
                selected:''
            };
        }
    }).mount('#app');
</script>
Vue3快速入门(一)

Vue组件

vue的基本单位是组件,每一个vue文件都是组件,本小节我们创建一个vue项目。

工具

首先我们需要准备开发工具。

nodejs

vue的开发/编译工具是基于nodejs平台的,所以第一步先安装nodejs。

下载地址: https://nodejs.org/en/ ,安装LST(长期维护)版本即可。

Vue3快速入门(一)

安装完毕后命令行会增加一个包管理工具:npm包

管理可以理解为通过他可以下载js插件,后续我们开发用到的JS插件都通过他安装

查看下版本:

Vue3快速入门(一)

因为npm下载插件的默认源是国外服务器,为了加速改成国内的,在命令行输入:

npm config set registry https://registry.npm.taobao.org

vscode

编码工具,以下是我目前正在使用的插件,大家可以按需安装

1、Auto Close Tag

2、Auto Complete Tag

3、Auto Rename Tag

4、Beautify

5、Chinese (Simplified) Language Pack for Visual Studio Code

6、Debugger for Java

7、ES7 React/Redux/GraphQL/React-Native snippets

8、ESLint

9、Extension Pack for Java

10、GitHub Theme

11、HTML CSS Support

12、HTML Snippets

13、IntelliJ IDEA Keybindings

14、JavaScript (ES6) code snippets

15、Language Support for Java(TM) by Red Hat

16、Live Server

17、Material Icon Theme

18、Maven for Java

19、Native-ASCII Converter

20、open in browser

21、Project Manager for Java

22、stylus

23、Swig(.tpl)

24、Test Runner for Java

25、Vetur

26、Visual Studio IntelliCode

27、Vue 3 Snippets

28、Xcode Default Theme

29、Vue VSCode Snippets

30、GitLens — Git supercharged

31、GitHub Copilot

vite

vite 是vue官方开发/编译工具,他把vue文件编译成浏览器识别的js,是一个很不错的前端开发与构建工具。

接下来我们初始化一个新vue3项目,比如我学习时的目录为:E:workspace_vscodevue3-study

然后通过命令行切换至该目录,执行如下命令:

npm init vite@latest my-vue-app -- --template vue # 如果项目里使用了ts,这里的vue可以改成vue-ts
cd my-vue-app
npm install
npm run dev

这里推荐大家一个Vue3的开箱模板:https://gitee.com/dishait/tov-template ,一个 vite + vue3 + ts 开箱即用现代开发模板。

创建完毕我们会看到如下项目结构:

Vue3快速入门(一)

先简单介绍下几个主要文件:

main.ts

入口文件,在此处初始化vue实例

import { createApp } from 'vue'

// 根组件
import App from './App.vue'

// 挂载vue实例到id为app的元素
createApp(App).mount('#app')

App.vue

根组件,串联所有其他vue文件,一个页面就是由多个vue文件互相嵌套组成的

Vue3快速入门(一)

components/HelloWorld.vue

子组件, 一般我们把可复用的组件都放在components文件夹,页面级别的组件我们放在新建的views或者pages文件夹中。

注意:组件文件的命名格式使用”开头字母大写的驼峰法”命名

index.html

根HTML模板,在这里可以修改下meta信息相关,这里的信息会被vue的所有页面所继承。

assets/

资源文件夹,图片和字体等资源都放在这里。

运行项目

运行

上述准备工作完毕后,就可以运行项目了:

  1. 用vscode打开项目文件夹
  2. window下按 ctrl+j 打开控制台
  3. 在控制台输入npm run dev
  4. 运行看到提示后,按住ctrl,用鼠标左键点击提示的网址,就可以在默认浏览器中预览项目了,或者复制地址到你正在用的浏览器中打开。
Vue3快速入门(一)
image-20220225174643848

热更新

打开页面后,你随意修改vue代码,可以发现并不需要刷新页面,页面自动就可以根据代码的变化而变化。

组件(components/props)

前面我们说过一个vue文件就是一个组件,那么组件是怎么组成完整页面的呢?

Vue3快速入门(一)

一般情况下,vue项目都是由一个根级组件嵌套多个子组件形成的,当然子组件也可以嵌套子组件,像树一样连接在一起形成页面。

组件的嵌套和复用(components)

写一个简单的组件,文件名为 Child.vue

components/Child.vue

<template>
  <h1 :style="{ color: color }">{{ content }}</h1>
</template>

<script>
import { defineComponent } from "vue";
export default defineComponent({
  data() {
    return {
      content"hi vue",
      color"#3eaf7c"// 绿色
    };
  },
});
</script>

在App.vue中引入Child.vue

<template>
  <div>
    <Child />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Child from "./components/Child.vue";
export default defineComponent({
  // 注册子组件
  components: { Child },
});
</script>

<style>
</style>

运行效果:

Vue3快速入门(一)

代码中做了2件事:

  1. 在js部分导入子组件,然后通过”components”字段注册子组件。
  2. 在模板中以标签形式使用子组件,标签的名称要”components”中组件对应的键值名一致。

复用

实际上我们可以多次引用同一个组件:

<template>
  <div>
    <Child />
    <Child />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Child from "./components/Child.vue";
export default defineComponent({
  // 注册子组件
  components: { Child },
});
</script>

<style>
</style>

预览效果:

Vue3快速入门(一)

这里调用了2次子组件,从这里很明显可以看出组件的优点:

  1. 代码结构清晰
  2. 复用方便

通过props向子组件传数据

上面的例子中2个子组件的文字都是绿色,这显然不是实际开发的常见情景,所以我们需要想办法让组件”可定制”。

同样是调用2次”Child”组件,但是这次在父组件中我们要给子组件传参数:

子组件

删除”data“属性, 使用”props“属性定义子组件的参数

<template>
  <h1 :style="{ color }">{{ content }}</h1>
</template>

<script>
import { defineComponent } from "vue";
export default defineComponent({
  props: {
    content: {
      typeString,
      default"hi vue",
    },

    color: {
      typeString,
      default"#3eaf7c",
    },
  },
});
</script>

父组件

<template>
  <div>
    <Child />
    <Child color="#f44336" content="hi vue3" />
    <!-- 等价于 -->
    <Child :color="'#f44336'" :content="'hi vue3'" />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Child from "./components/Child.vue";
export default defineComponent({
  // 注册子组件
  components: { Child },
});
</script>

<style>
</style>

注意看模板,模板中我写了2种使用参数的方式:

  1. 带”:“表示”=“后面的内容是js代码
  2. 不带”:” 那么”=“后面的内容是只能是字符串,所以如果你传入的参数是”字符串字面量“,那么可以省略:

更多Props的用法:https://v3.cn.vuejs.org/guide/component-props.html#prop

父子组件通信($emit)

上小节最后演示了父组件可以通过子组件的”props”字段传入数据,接下来我们讲一下子组件传数据给父组件的方式。

先说下最重要的结论:在子组件内不能直接修改父组件传进来的数据,需要通过事件告知父组件自己修改。

<template>
  <h1 :style="{ color }">{{ content }}</h1>
</template>

<script>
import { defineComponent } from "vue";
export default defineComponent({
  props: {
    content: {
      typeString,
      default"hi vue",
    },

    color: {
      typeString,
      default"#3eaf7c",
    },
  },
  methods: {
    changeColor() {
      // 错误, 组件内部不能修改props上的字段
      this.color = "red";
    },
  },
});
</script>

子传父

上面说子组件不能直接修改父组件传进来的数据,是因为vue数据通信的设计准则:单向数据流

单向数据流

数据只能从父传到子,子不能在内部修改父的数据,这是为了代码更清晰。

假如别人在阅读父组件代码的时候为了确定一个变量有没有被子组件修改的操作就要阅读所有子组件,这显然减低了代码可读性,所以vue限制数据单向传递,那么子组件要修改父组件的数据怎么办呢?

自定义事件(子传父)

子组件虽然不能直接修改父组件的数据,但是他可以告诉父组件:”我要修改xxx变量”,然后让父组件在父内部进行数据修改。

子和父的通信就要靠”自定义事件”,实现”子传父”需要2步:

  1. 在子组件内触发自定义事件
  2. 在父组件监听子组件的自定义事件,然后根据”指示”修改自己的数据

$emit

通过 $emit(eventName, data) 语法我们可以在子组件内构建并触发一个自定义事件:

子组件

<template>
  <h1 @click="change">{{ msg }}</h1>
</template>

<script lang="ts">
import { ref, defineComponent } from "vue";
export default defineComponent({
  name"Child",
  props: {
    msg: {
      typeString,
      requiredtrue,
    },
  },
  methods: {
    change() {
      // 触发自定义的change事件
      this.$emit("change-msg""子传父成功!");
    },
  },
});
</script>

父组件

<template>
  <div>
    <Child :msg="msg" @change-msg="changeMsg" />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Child from "./components/Child.vue";
export default defineComponent({
  name"Parent",
  data() {
    return { msg"Hello Vue 3" };
  },
  // 注册子组件
  components: { Child },
  methods: {
    changeMsg(newMsg: string) {
      this.msg = newMsg;
    },
  },
});
</script>

<style>
</style>

点击文字后,子组件触发自定义事件”change-msg”,父组件监听到后修改自己的变量”msg”,效果如下:

Vue3快速入门(一)

自定义v-model(双向数据绑定)

实际开发中我们很少会直接使用原生的表单,都是使用第三方的UI库,其中的组件都支持”v-model”

Vue3快速入门(一)
image-20220226082648212

图片来源: Vue Ant UI:https://2x.antdv.com/components/checkbox-cn

Vue3快速入门(一)

接下来我们就讲下如何封装支持”v-model”的组件。

props和$emit

实际上要实现”v-model”并不复杂,只需要2步:

  1. 在”props”中定义个属性叫”modelValue”
  2. 在组件内部通过 “$emit(‘update:modelValue’, 值)” 触发事件

实现一个Dialog组件

实现父子组件,父组件可以控制子组件显示隐藏,子组件可以控制自身的隐藏

创建子组件,components/Dialog.vue

<template>
  <article v-show="modelValue" class="a-dialog">
    你好Vue3
    <a @click="$emit('update:modelValue', false)">关闭</a>
  </article>
</template>

<script>
import { defineComponent } from "vue";
export default defineComponent({
  props: {
    modelValue: {
      typeBoolean,
    },
  },
});
</script>

<style>
.a-dialog {
  display: inline-block;
  padding16px;
  margin16px;
  border-radius4px;
  font-size16px;
  background#3eaf7c;
  color#fff;
  box-shadow1px 2px 3px 1px rgba(0000.2);
}
.a-dialog > a {
  cursor: pointer;
  padding4px;
  border1px solid #fff;
}
</style>

父组件中使用”v-model”:

<template>
  <div>
    <button @click="isShowDialog = !isShowDialog">显示/隐藏</button>
    <br />
      
    <!-- 此处v-model等同于v-model:modelValue -->
    <!-- <Dialog v-model="isShowDialog" /> --> 
      
    <Dialog v-model:modelValue="isShowDialog" />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Dialog from "./components/Dialog.vue";
export default defineComponent({
  name"Parent",
  data() {
    return { isShowDialogfalse };
  },
  // 注册子组件
  components: { Dialog },
});
</script>

<style>
</style>

页面效果:

Vue3快速入门(一)

v-model:isShow

上面的例子中”Dialog”组件在”props”上定义了”modelValue”,这个名字看起来一点都不符合”显示隐藏”的语义,所以我们想改一下名字, 只需要:

  1. 在子组件props上定义属性,比如”isShow”
  2. “$emit”中触发的事件名改为”update:isShow”
  3. 父组件使用”v-model:isShow”

子组件:

<template>
  <!-- <article v-show="modelValue" class="a-dialog">
    你好Vue3
    <a @click="$emit('update:modelValue', false)">关闭</a>
  </article> -->

  <article v-show="isShow" class="a-dialog">
    你好Vue3
    <a @click="$emit('update:isShow', false)">关闭</a>
  </article>
</template>

<script>
import { defineComponent } from "vue";
export default defineComponent({
  props: {
    /* modelValue: {
      type: Boolean,
    }, */

    isShow: {
      typeBoolean,
    },
  },
});
</script>

<style>
.a-dialog {
  display: inline-block;
  padding16px;
  margin16px;
  border-radius4px;
  font-size16px;
  background#3eaf7c;
  color#fff;
  box-shadow1px 2px 3px 1px rgba(0000.2);
}
.a-dialog > a {
  cursor: pointer;
  padding4px;
  border1px solid #fff;
}
</style>

父组件:

<template>
  <div>
    <button @click="isShowDialog = !isShowDialog">显示/隐藏</button>
    <br />
    <Dialog v-model:isShow="isShowDialog" />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Dialog from "./components/Dialog.vue";
export default defineComponent({
  name"Parent",
  data() {
    return { isShowDialogfalse };
  },
  // 注册子组件
  components: { Dialog },
});
</script>

<style>
</style>

其中:

<Dialog v-model:isShow="isShowDialog"/>
<!-- 等价 -->
<Dialog v-model:is-show="isShowDialog"/>

v-model:isShow 也可以写成 v-model:is-show,vue会自动识别。

ES6+基础语法

为了照顾前端基础较弱的同学能顺畅阅读后面的内容,在这里介绍几个后面文章会出现的ES6+语法,如果你已经熟练ES6可以跳过本章节。

参考文档:https://es6.ruanyifeng.com/

什么是ES6+

2015 年 6 月正式发布了ECMAScript 6.0语法正式发布了,简称”ES6″,他是对原有javascript语法的扩充,每年都有新的语法扩充进来,我这里把后续新增的语法统称”ES6+”语法。

let

定义变量,区别于”var”,他所声明的变量只在”let”所在的代码块内有效,总之一句话:”var”就不要用了,都替换成”let”。

{
  let a = 10;
  var b = 1;
}

// ReferenceError: a is not defined.
// 1

const

定义常量,定义后的变量不可修改。

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

数组解构赋值

let [a, b, c] = [123];

// 等价
let a = 1;
let b = 2;
let c = 3;

对象的解构赋值

let { foo, bar } = { foo'aaa'bar'bbb' };
foo // "aaa"
bar // "bbb"

let { x, y, ...z } = { x1y2a3b4 };
// 1
// 2
// { a: 3, b: 4 }

函数参数的解构赋值

function add([x, y]){
  return x + y;
}
add([12]); // 3
function move({x, y} = { x: 0, y: 0 }{
  return [x, y];
}
move({x3y8}); // [3, 8]

模板字符串

字符串中可以优雅的插入变量。

const a = '你好';
const b = `${a} Vue`;
// b == '你好 Vue'

函数参数默认值

function add(a,b = 1){
 return a + b;
}

add(3// 4

箭头函数

function a(){
 return '你好'
}

// 箭头函数
const a = ()=>{
 return '你好';
}

// 还可以更简单
const a = ()=>'你好'

数组的扩展运算符

// 等价于 console.log(1,2,3);
console.log(...[123]);

// 合并数组
const a = [1,2,3];
const b = [...a,4,5]; // [1,2,3,4,5]

对象属性的简洁表示法

const a = 1;

const obj = {a1};
// 简写
const obj = {a};  // {a: 1}

对象方法的简洁表示法

const obj = {
  say:function (){
   return '你好!';
  }
}; 
// 简写,可以省略":function"
const obj = {
  say (){
   return '你好!';
  }
};

对象属性名表达式

对象的属性名可以支持变量。

const a = 'abc';
let obj = {};
obj[`${a}123`] = 1;
console.log(obj) // {abc123:1};

链判断运算符(?)

实现对”?“左边的表达式是否为null或者undefined的判断,如果是立即停止判断,返回undefined或null。

const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName);

// 简写
const firstName = message?.body?.user?.firstName;

Null判断运算符(??)

console.log(0 ?? 1); // 0
console.log(false ?? 1); // false

console.log(undefined ?? 1); // 1
console.log(null ?? 1); // 1

很显然只有”??“前面的值是null或undefined才返回"??“后面的值。

Promise

Promise 是异步编程的一种解决方案,比传统的解决方案”回调函数和事件”更合理。

在这里大概了解下即可,或者参考我之前发过的Promise文章,主要是为了讲解后面的”async/await“,因为在开发中我们使用的第三方插件很多都是封装成Promise格式的,初期需要自己封装的需求很少。

// 封装代码成Promise格式
const promiseA = ()=> new Promise(function(resolve, reject{
  
  // === 你的代码 ===
  setTimeout(()=>{
    if(0.5 < Math.random()){
     resolve('成功');
    } else {
     reject('失败');
    }
 },200);
  // === 你的代码 ===
  
});

// 执行
promiseA().then(value=>{
 // '成功' == value
  console.log(value);
}).catch(error=>{
 // '失败' == error
  console.log(error);
});

async/await

执行Promise函数”更优雅”,用上面封装”promiseA函数”为例:

function funA(){
  promiseA().then(value=>{
    console.log(value);
  }).catch(error=>{
    console.log(error);
  });
}

// 改写, 需要用try/catch来捕获"reject"触发的异常
async function funA(){
  try{
    const value = await promiseA();
    console.log(value);
  } catch(error){
    console.log(error);
  }
}

export

导出模块

// xyz.js
export const a = 1;
export function abc(){
}
export {
  b:'2',
  c:3
};

import

导入模块

import {a,b,c,abc} from './xyz.js';

更多

更多请参考阮一峰老师的教程:https://es6.ruanyifeng.com

生命周期钩子

钩子是指函数,”生命周期钩子”是指vue从初始化运行到销毁过程中,在不同阶段会被自动执行的函数,而我们可以自定义这些钩子函数来让我们自己的代码在特定时期运行。

Vue3快速入门(一)

这是一张官方的图示,很清晰标注了每一个钩子运行的时间点,实际开发中我们最常用的是 mountedbeforeUnmount 2个钩子。

我们只需要先了解这2个钩子函数就可以完成90%以上的业务页面。

mounted

组件元素挂载(插入)到页面后执行的钩子函数,所以这个时候我们是可以拿到组件的根元素的,我们常在这里进行的操作有:

  1. 初始化第三方js插件
  2. 获取接口数据

this.$el

组件的根元素

<template>
  <div>文字</div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  mounted() {
    console.log(this.$el); // <div>文字</div>
  },
});
</script>

<style>
</style>

http请求

在这个阶段,最常见的操作就是获取接口数据

<script>
import { defineComponent } from "vue";
import axios from 'axios'
  
export default defineComponent({
 data(){
   return {list:[]};
  },
  
  async mounted(){
   this.list = await axios.get('/api');
  }
});
</script>

beforeUnmount

组件销毁之前执行的钩子。这是一个生命周期中最后一个可以拿到”this.$el”的钩子。

什么时候会触发beforeUnmount

当组件或者外围组件(或元素)上的”v-if”的值为false的时候

<your-component v-if="isShow"></your-component>
<!--都会在isShow==false的时候, 触发beforeUnmount-->
<div v-if="isShow">
 <your-component v-if="isShow"></your-component>
</div>

还有一种就组件所在的路由页面切换离开的时候。

销毁第三方插件实例

当使用第三方js插件的时候,在这个阶段执行销毁插件实例操作,用echarts举例。

<template>
  <div style="width:300px;height:300px;"></div>
</template>

<script>
import { defineComponent } from "vue";
import * as echarts from 'echarts';
  
export default defineComponent({
  data(){
   return {chart:null};
  },
  
 mounted(){
    // 渲染图表
    const chart = echarts.init(this.$el)
    this.chart = Object.freeze(chart);
   this.chart.setOption(option)
  },
  
  beforeUnmount(){
    // 销毁echart实例
   this.chart.dispose();
  },  
});
</script>

Object.freeze

冻结数据,数据还可以修改,但是vue不能对数据进行响应式配置了,当模板并不需要响应该变量的时候,可以对变量进行冻结。

上面的例子定义”chart“变量仅仅为了可以在”beforeUnmount”钩子中访问他,从而进行”echart”实例的销毁。

更多

更多生命周期钩子参考:https://v3.cn.vuejs.org/api/options-lifecycle-hooks.html

引用DOM(ref)

虽说vue声称不需要操作DOM,但是如果我们使用第三方JS插件的时候,还是需要读取DOM,比如上一节使用echart的例子。

通过在模板中使用”ref”属性,我们可以轻松获取到元素或组件。

对元素使用ref

在js中使用 this.$refs.xx 获取元素

<template>
  <!--标记元素-->
  <input ref="yourInput"/>
</template>

<script lang="ts">
import { ref, defineComponent } from 'vue'
export default defineComponent({
  mounted(){
    // 获取元素
   this.$refs.yourInput.focus();
  }
})
</script>

对组件使用ref

这时 this.$refs.xx 指向组件实例,假如组件methods存在show方法,那么可以直接使用show方法,这里的show方法指的是普遍方法,并不特指。

<template>
  <a-dialog ref="yourDialog"/>
</template>

<script lang="ts">
import { ref, defineComponent } from 'vue'
export default defineComponent({
  mounted(){
   this.$refs.yourDialog.show();
  }
})
</script>

在循环中使用ref

如果想在循环中获取元素我们不可以通过给元素标记名字,而是给ref赋值一个函数,通过函数获取每一次循环的元素。

<template>
  <p :ref="getEl" v-for="n in 5" :key="n">{{ n }}</p>
</template>

<script >
import { defineComponent } from "vue";
export default defineComponent({
  data() {
    return { elList: [] };
  },

  mounted(){
    console.log(this.elList); // 输出5个p元素
  },

  methods: {
    getEl(el) {
      this.elList.push(el);
    },
  },
});
</script>

预览效果

Vue3快速入门(一)

同理循环组件也是这么获取组件的,我想大家应该能够理解。

插槽(slot)

组件的标签内允许插入内容(包括组件),实现内容嵌套,使用效果如下:

<Category title="美食">
    <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="" />
</Category>

创建插槽

新建子组件 Category.vue

<template>
  <div class="category">
    <h3>{{ title }}分类</h3>
    <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
  </div>
</template>

<script lang="ts">
import { ref, defineComponent } from "vue";
export default defineComponent({
  name"Category",
  props: {
    title: {
      typeString,
    },
  },
});
</script>
<style>
.category {
  background-color: skyblue;
  width200px;
  height300px;
}
h3 {
  text-align: center;
  background-color: orange;
}
video {
  width100%;
}
img {
  width100%;
}
</style>

关键点就是这个”<slot>“标签,可以理解为一个占位符,组件渲染的时候会把他替换为你插入的”内容”。

插入组件

在父组件中插入上面的子组件

<template>
  <div class="container">
    <Category title="美食">
      <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="" />
    </Category>

    <Category title="游戏">
      <ul>
        <li v-for="(g, index) in games" :key="index">{{ g }}</li>
      </ul>
    </Category>

    <Category title="电影">
      <video
        controls
        src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"
      >
</video>
    </Category>

    <Category title="其他"> </Category>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Category from "./components/Category.vue";
export default defineComponent({
  components: {
    Category,
  },
  data() {
    return {
      foods: ["火锅""烧烤""小龙虾""牛排"],
      games: ["红色警戒""穿越火线""劲舞团""超级玛丽"],
      films: ["《教父》""《拆弹专家》""《你好,李焕英》"],
    };
  },
});
</script>

<style>
.container {
  display: flex;
  justify-content: space-around;
}
</style>

页面效果

Vue3快速入门(一)

插槽默认值

从上面的例子中可以看出,我在子组件Category.vue中定义了 slot 标签,定义了默认值。

在父组件中使用的时候,如果没在”<Category>“标签内插入内容,那么就渲染”<slot>“中定义的默认内容。

具名插槽

通过给”<slot>“起名,一个组件内可以定义多个插槽:

<template>
  <div class="category">
    <h3>{{title}}分类</h3>
  <slot name="center">我是center默认值,当使用者没有传递具体结构时,我会出现</slot>
  <slot name="footer">我是footer默认值,当使用者没有传递具体结构时,我会出现2</slot>
  </div>
</template>

<script lang="ts">
import { ref, defineComponent } from "vue";
export default defineComponent({
  name"Category",
  props: {
    title: {
      typeString,
    },
  },
});
</script>
<style>
.category {
  background-color: skyblue;
  width200px;
  height300px;
}
h3 {
  text-align: center;
  background-color: orange;
}
video {
  width100%;
}
img {
  width100%;
}
</style>

v-slot指令

通过”v-slot”指令我们可以向指定插槽插入内容。

步骤:

  1. 使用子组件时,添加一个”<template>“,他是个空标签,并不会渲染到实际页面中。
  2. 在”<template>“上添加”v-slot:xxx“属性。
<template>
  <div class="container">
    <Category title="美食">
      <template v-slot:footer>  <br> footer </template>
    </Category>

    <Category title="游戏">
      <template v-slot:center>
        <ul slot="center">
          <li v-for="(g, index) in games" :key="index">{{ g }}</li>
        </ul>
      </template>

      <template v-slot:footer>
        <div class="foot">
          <a href="http://www.xxx.com">单机游戏</a>
          <a href="http://www.xxx.com">网络游戏</a>
        </div>
      </template>
    </Category>

    <Category title="电影">
      <template v-slot:footer>
        <div class="foot">
          <a href="http://www.xxx.com">经典</a>
          <a href="http://www.xxx.com">热门</a>
          <a href="http://www.xxx.com">推荐</a>
        </div>
        <h4>欢迎前来观影</h4>
      </template>
    </Category>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Category from "./components/Category.vue";
export default defineComponent({
  components: {
    Category,
  },
  data() {
    return {
      foods: ["火锅""烧烤""小龙虾""牛排"],
      games: ["红色警戒""穿越火线""劲舞团""超级玛丽"],
      films: ["《教父》""《拆弹专家》""《你好,李焕英》"],
    };
  },
});
</script>

<style>
.container {
  display: flex;
  justify-content: space-around;
}
</style>

结果

Vue3快速入门(一)

可以看出,引用子组件时,没有被template指定的插槽,会以默认值显示。

缩写

类似”v-on:”和”v-bind:”,”v-slot:”可以缩写为”#“。

<template>
  <div class="container">
    <Category title="美食">
      <template #footer>  <br> footer </template>
    </Category>

    <Category title="游戏">
      <template #center>
        <ul slot="center">
          <li v-for="(g, index) in games" :key="index">{{ g }}</li>
        </ul>
      </template>

      <template #footer>
        <div class="foot">
          <a href="http://www.xxx.com">单机游戏</a>
          <a href="http://www.xxx.com">网络游戏</a>
        </div>
      </template>
    </Category>

    <Category title="电影">
      <template #footer>
        <div class="foot">
          <a href="http://www.xxx.com">经典</a>
          <a href="http://www.xxx.com">热门</a>
          <a href="http://www.xxx.com">推荐</a>
        </div>
        <h4>欢迎前来观影</h4>
      </template>
    </Category>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Category from "./components/Category.vue";
export default defineComponent({
  components: {
    Category,
  },
  data() {
    return {
      foods: ["火锅""烧烤""小龙虾""牛排"],
      games: ["红色警戒""穿越火线""劲舞团""超级玛丽"],
      films: ["《教父》""《拆弹专家》""《你好,李焕英》"],
    };
  },
});
</script>

<style>
.container {
  display: flex;
  justify-content: space-around;
}
</style>

作用域插槽

有时插槽内的内容需要访问该组件内的变量,这时我们需要在组件内把变量暴露给插槽

第一步, 绑定

在”<slot>“上使用”v-bind”指令绑定变量到插槽

<template>
  <div>
    <h3>{{ title }}信息</h3>
    <slot v-bind="detail">我是默认值,当使用者没有传递具体结构时,我会出现</slot>
  </div>
</template>

<script lang="ts">
import { ref, defineComponent } from "vue";
export default defineComponent({
  name"Book",
  props: {
    title: {
      typeString,
    },
  },
  data() {
    return {
       detail: { title"呐喊"author"鲁迅" }
    };
  },
});
</script>
<style>
</style>

第二步, 获取

通过”v-slot”获取插槽上绑定的变量

<template>
  <div>
    <Book title="书籍">
      <template v-slot="book">
        <h2>{{ book.title }}</h2>
        <h3>{{ book.author }}</h3>
      </template>
    </Book>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Book from "./components/Book.vue";
export default defineComponent({
  components: {
    Book,
  },
});
</script>

<style>
</style>

效果:

Vue3快速入门(一)

循环插槽

插槽可以放在循环中,这样插入插槽中的内容也会被循环

子组件:

<template>
  <div>
    <p v-for="(color, index) in colors" :key="index">
      <slot v-bind="color"></slot>
    </p>
  </div>
</template>

<script>
import { defineComponent } from "vue";
export default defineComponent({
  data() {
    return {
      colors: [
        { title"red"value"#f00" },
        { title"green"value"#0f0" },
        { title"blue"value"#00f" },
      ],
    };
  },
});
</script>

注意下面的v-slot的用法,其值支持对象解构

// 父组件
<template>
  <Colors v-slot="{ value, title }">
    <span :style="{ color: value }">{{ title }}</span>
  </Colors>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Colors from "./components/Colors.vue";
export default defineComponent({
  components: {
    Colors,
  },
});
</script>

效果:

Vue3快速入门(一)

组件选项的书写顺序

至此节,vue的基础知识点讲的差不多了, 现在我们按照vue官方推荐的书写顺序来复习下选项字段,正好规范代码。

components

注册组件,对象键值是模板中使用的标签名,当然用”-“的表示法模板也是可以解析的。

<template>
  <yourComponent/>
  <!--等价-->
  <your-component/>
</template>

<script>
  import { defineComponent } from "vue";
  import YourComponent from "./YourComponent.vue";

  export default defineComponent({
    components:{
     YourComponent
    }
  });
</script>

props

定义组件属性,可以理解为组件参数,父组件传入数据都通过他。

注意:在子组件内部不可以直接修改props传递的数据。

子组件

<template>
  <h1 :style="{color}">{{content}}</h1>
</template>

<script>
  import { defineComponent } from "vue";
  export default defineComponent({
    props:{
      content:{
        type:String,
        default:'hi vue'
      },
      
      color:{
        type:String,
        default:'#3eaf7c'
      }
    },
  });
</script>

父组件

<template>
  <Child/>
  <Child color="#f44336" content="hi vue3" />
  <!-- 等价于 -->
  <Child :color="'#f44336'" :content="'hi vue3'" />
</template>

<script>
  import { defineComponent } from "vue";
  import Child from './components/Child.vue'
  export default defineComponent({
    components:{Child},
  });
</script>

data

定义组件内部变量,唯一要注意的就是data的值是函数。

<template>
  <h1 :style="{color}">{{content}}</h1>
</template>

<script>
  import { defineComponent } from "vue";
  export default defineComponent({
    data(){
     return {
        content:'hi vue',
       color'#3eaf7c'
      }
    }
  });
</script>

computed

计算属性,当模板中的写的js太长时,你就该考虑改成计算属性。

<template>
  <h1>总价格: {{total}}</h1>
</template>

<script>
  import { defineComponent } from "vue";
  export default defineComponent({
    data(){
      return {
        appleNumber71
        applePrice:11,
        bannerNumber99
        bannerPrice:12,
      };
    },
    
    computed:{
      total(){
        return this.appleNumber * this.applePrice + this.bannerNumber * this.bannerPrice;
      }
    }
  });
</script>

watch

监控数据变化,props/data/computed里的数据都可以被监视。

<script>
import { defineComponent } from "vue";
import axios from "axios";
export default defineComponent({
  data() {
    return {
      page50,
      list: [],
    };
  },

  watch: {
    page(page, oldPage) {
      console.log(oldPage);
      axios
        .get(`https://cnodejs.org/api/v1/topics?limit=5`, { params: { page } })
        .then((response) => {
          const { data } = response.data;
          this.list = data;
        });
    },
  },
});
</script>

mounted

当组件挂载到页面DOM后执行的钩子函数, 一般我们在这里进行:

  1. 请求接口数据
  2. 初始化第三方js插件

beforeUnmount

当组件或者外围组件(或元素)上的”v-if”的值为false的时候。

<your-component v-if="isShow"></your-component>
<!--都会在isShow==false的时候, 触发beforeUnmount-->
<div v-if="isShow">
 <your-component v-if="isShow"></your-component>
</div>

methods

请把函数都放在这里,函数内部操作数据要通过”this”。

<script>
  import { defineComponent } from "vue";
  export default defineComponent({
    data() {
      return {
        color"red",
      };
    },

    methods: {
      changeColor() {
        this.color = "green";
      },
    },
  });
</script>

说明

这些不是全部vue的参数,vue的参数不止这些,但是这些知识点已经覆盖了vue开发公司业务代码的大部分。

这些是基础,熟练了他们再学进阶知识就无往不利。

更多选项介绍看这里:https://v3.cn.vuejs.org/api/options-api.html

动画(transiton)

vue要实现动画很简单,同时他提供了很多种实现方式,为了快速入门,这里只讲一种最简单实用的方法,就是结合 animate.css 这个css动画库。

https://animate.style

准备工作

安装animate.css

npm install animate.css --save

在main.ts中导入animate.css

import 'animate.css/animate.min.css'

transition

vue封装了动画组件,我们可以直接使用,只需要用"<transition>"包裹要添加动画的元素(或组件), 这里我们要注意的就是触发条件:

  1. 被包裹组件(或元素)上的v-if的值发生变化
  2. 被包裹组件(或元素)上的v-show的值发生变化
  3. 动态组件的is值发生变化的时候也会触发动画,动态组件在业务代码中不常见,这里暂时不讲,记住这个名词即可

App.vue

<template>
  <div>
    <button @click="isShow = !isShow">切换</button>

    <transition
      :duration="1000"
      enter-active-class="animate__animated animate__fadeInDown"
      leave-active-class="animate__animated animate__fadeOutUp"
    >

      <p v-if="isShow">hello</p>
    </transition>
  </div>
</template>

<script>
import "animate.css";
import { defineComponent } from "vue";
export default defineComponent({
  data() {
    return {
      isShowtrue,
    };
  },
});
</script>

效果

Vue3快速入门(一)

这里的 animate__animated 代表当前元素使用 animate.css 的动画,这个样式是animate.css自带的,后面的animate__fadeInDown是具体的动画名称。

更多的动画样式名可查看:https://animate.style/

enter-active-class

组件(或元素)的”v-if/v-show”值从”false”变成”true”后,vue会自动给元素增加”enter-active-class”中的样式名,动画执行完毕vue会自动删除元素上添加的”动画样式”。

leave-active-class

与”enter-active-class”,在”v-if/v-show”值从”true”变成”false”后,执行类似操作。

duration

通过duration我们可以指定动画的执行时间。

mode

当”<transition>“下有2个内容的时候,需要控制下动画触发时机,不然可能会出现”重叠”等意外。

<template>
  <transition
    :duration="1000"
    enter-active-class="animate__animated animate__fadeInDown"
    leave-active-class="animate__animated animate__fadeOutUp"
  >

    <p v-if="isShow">hello</p>
    <p v-else>你好</p>
  </transition>
</template>

效果

Vue3快速入门(一)

2段文字再点击切换后同时动画,这肯定不是我们要的,mode可以控制2个元素的动画出现顺序。

mode=out-in 让消失动画先执行,再执行显示动画。

反之还有”in-out”,本例显示是要使用”out-in”:

<template>
  <transition
+   mode="out-in"
    :duration="1000"
    enter-active-class="animate__animated animate__fadeInDown"
    leave-active-class="animate__animated animate__fadeOutUp"
  >

    <p v-if="isShow">hello</p>
    <p v-else>你好</p>
  </transition>
</template>

效果

Vue3快速入门(一)

自定义动画

本文初衷是快速上手企业开发,实际项目开发中很少有需要设计动画的地方,因为使用第三方组件库都自带动画,所以上面的anmiate.css的应用就可以应付企业的需求。

如果你对动画设计特别喜欢还请到vue官方文档深入阅读:https://v3.cn.vuejs.org/guide/transitions-overview.html

路由(vue-router)

vue通过地址栏的hash值的变化来模拟不同的页面。

Vue3快速入门(一)

hash值就是指”#”后面的值,比如上图中的”/ref”,在vue中我们叫”/ref”为路由的路径,后面文章中简称”路径“。

路由就是维护”路径“和vue文件一一映射关系的变量。

安装

npm install vue-router@4

注意:vue3要安装vue-router第4版。

使用

步骤

  1. 新建 ts 文件,编写路由配置,也就是编写路径和vue文件的映射关系
  2. 在模板中添加”<router-view>“标签
  3. 在main.ts中加载路由配置

新建两个页面

src/views/Ref.vue

<template>
  <div>
      This is Ref.vue.
  </div>
</template>

<script>
export default {
  name"Ref",
  components: {},
  props: {},
  data() {
    return {};
  },
};
</script>
<style>
</style>

src/views/Watch.vue

<template>
  <div>
      This is Watch.vue.
  </div>
</template>

<script>
export default {
  name"Watch",
  components: {},
  props: {},
  data() {
    return {};
  },
};
</script>
<style>
</style>

配置路由

新建一个ts文件,src/router/index.ts

// 加载vue-route
import { createRouter, createWebHashHistory } from "vue-router";
import Ref from '../views/Ref.vue';
const routes = [
    // 页面包含在整体js中
    { path'/ref/:id'name'Ref'component: Ref },
    // 页面是独立的js, 访问当前路由时异步加载
    { path'/watch'component() => import('../views/Watch.vue') },
];

// 初始化路由
export default createRouter({
    // 使用"#"路由模式
    history: createWebHashHistory(),
    routes,
})

在”createRouter“中”history:createWebHashHistory()“表示的是路径用”#/”模式,除此之外,还有一种不带”#”的表现方式。

请自行参考文档:https://next.router.vuejs.org/zh/guide/essentials/history-mode.html

“routes”变量就是路由配置,其中”path”是路径,”component”字段指向vue文件,上例中有2条路由,但是component字段的值的数据类型不太一样,接下来讲下区别。

同步加载路由页面

路由配置中,如果 component 字段的值为组件,那么这个路由对应的vue就会被打包。

const routes = [
  // 页面包含在整体js中
  { path'/ref/:id'name:'Ref'component:  Ref}
];

比如我们访问首页时,没有加上面配置的路径,发现 Ref.vue,也被加载了,而Watch.vue没有被加载。

Vue3快速入门(一)
异步加载路由页面

如果路由中”component”的值为函数,比如:”() => import('./views/Watch.vue')“,那么这个Watch页面就会被单独编译成一个独立的js文件,只有访问对应的路由才加载该文件

const routes = [
  // 页面是独立的js, 访问当前路由时异步加载
  { path'/watch'component() => import('./views/Watch.vue') },
];

router-view

在模板中我们要增加一个”<router-view>“用来占位,当路由配置好后,地址栏路由的变化会让”<router-view>“加载不同的vue文件(vue页面)。

大部分场景我们把路由放在根组件中,比如App.vue:

<template>
  <div>
    <router-view></router-view>
  </div>
</template>

<script>
import { defineComponent } from "vue";
export default defineComponent({
});
</script>

在main.ts中加载路由配置文件

使用”app.use”去加载router实例,这样路由的初始化工作就完成了。

import { createApp } from 'vue'
import App from './App.vue'
import 'animate.css/animate.min.css'
import router from './router';

createApp(App)
.use(router)
.mount('#app')

API

上面的配置完成后,现在我们可以在组件中操作路由。

$route(获取地址栏信息)

其上存储了当前路由信息,下面通过访问”http://localhost:3000/#/ref/100?page=1“来说明:

Ref.vue

<template>
  <div>This is Ref.vue.</div>
</template>

<script>
export default {
  name"Ref",
  components: {},
  props: {},
  data() {
    return {};
  },
  mounted() {
    console.log(this.$route);
  },
};
</script>
<style>
</style>

打印结果:

Vue3快速入门(一)

下面解释几个关键的字段。

剩余的字段感兴趣请翻阅文档:https://next.router.vuejs.org/zh/api/#routelocationnormalized

fullPath

当前地址栏上”#”后面的所有文字。

name

对应路由配置中的name字段,他的具体意义一会在”$router“中说。

params

对应路由中配置中”ref/:id“的id,这里”:id”会在解析地址栏的时候捕获ref/后面到”?”之前的一切内容,存储到”params”中,比如我们要访问”ref”后面的”100″:

<template>
  <div>This is Ref.vue.</div>
</template>

<script>
export default {
  name"Ref",
  components: {},
  props: {},
  data() {
    return {};
  },
  mounted() {
    console.log(this.$route);
    console.log(this.$route.params.id); // 100
  },
};
</script>
<style>
</style>
query

解析”?”后面的键值对。

<template>
  <div>This is Ref.vue.</div>
</template>

<script>
export default {
  name"Ref",
  components: {},
  props: {},
  data() {
    return {};
  },
  mounted() {
    console.log(this.$route);
    console.log(this.$route.params.id); // 100
    console.log(this.$route.query); // {page: "1"}
  },
};
</script>
<style>
</style>
path

“fullPath”去掉”query”部分。

$router(地址栏传参)

$route“负责读取路由信息,”$router.push“用来切换路由页面。

App.vue

下面为每隔3秒切换

<template>
  <div>
    <router-view></router-view>
  </div>
</template>

<script>
import { defineComponent } from "vue";
export default defineComponent({
  mounted() {
    // 地址栏切换到"#/ref"
    this.$router.push({ path"/ref" });
    setTimeout(() => {
      // 地址栏切换到"#/ref/99"
      this.$router.push({ name"Ref"params: { id99 } });
      setTimeout(() => {
        // 地址栏切换到"#/ref?page=2"
        this.$router.push({ path"/ref"query: { page2 } });
        setTimeout(() => {
          // 地址栏切换到"#/ref/200?page=3"
          this.$router.push({
            name"Ref",
            params: { id99 },
            query: { page3 },
          });
        }, 3000);
      }, 3000);
    }, 3000);
  },
});
</script>

这里面注意这一行

this.$router.push({name:'Ref'params:{id:99});

注意:如果你要想传递”params”参数,那么就必须给路由配置”name“字段,下面通过”path+param“是无效的:

// 这是错误的, 无效的
this.$router.push({path:'/ref'params:{id:99}});

全局钩子

官方叫”导航守卫”,就是在路由的切换过程中触发的回调函数。

本文只讲”beforeEach“钩子,其他请参照文档:https://router.vuejs.org/zh/guide/advanced/navigation-guards.html

安装axios:

npm i axios -S

src/router/index.ts

// 加载vue-route
import { createRouter, createWebHashHistory } from "vue-router";
import Ref from '../views/Ref.vue';
import axios from 'axios';
const routes = [
    // 页面包含在整体js中
    { path'/ref/:id'name'Ref'component: Ref },
    // 页面是独立的js, 访问当前路由时异步加载
    { path'/watch'component() => import('../views/Watch.vue') },
];

// 初始化路由
const router = createRouter({
    // 使用"#"路由模式
    history: createWebHashHistory(),
    routes,
})


/**
* 路由切换之前执行
*/

router.beforeEach(async (to, from) => {
    if ('/login' !== to.path) {
        const data = await axios.post(`/user/authentication`);
        if (401 == data.status) {
            return { path'/login' };
        } else {
            return true;
        }
    }
    return true
});

export default router;

上面模拟了登陆的场景,在”beforeEach”中,通过返回值决定路由如何切换:

  1. 返回”true”,那么按照用户操作跳转,比如组件内执行了”$router.push({path:'/ref'})“,那么就跳转到”#/ref”。
  2. 返回”false”,那么页面不跳转,地址栏无任何变化。
  3. 返回具体指向,比如返回”{path:'/login'}“就跳转到”#/login”,语法同$router.push的参数。

上面的例子的逻辑是:

  1. 如果是访问”/login”,那么直接跳转到”#/login”。
  2. 否则去接口验证权限,如果接口验证通过,那么按照用户操作跳转。
  3. 如果不通过,跳转到”#/login”。

说明

本文只是讲了路由最常用的也是必须的知识点,内容仅仅是官文档的30%左右,目的仅仅是为了你能快速上手,虽然能满足90%的开发场景,但是建议当熟练了本文内容后,还请阅读 vue-router文档 补充全面的知识。

文档地址:https://router.vuejs.org/zh/index.html

集中状态管理(vuex)

这里的状态是只变量,一个项目中都可以访问的变量。

假设我们有4个文件,他们的父子关系如下:

App.vue => A1.vue => B1.vue => C1.vue

App.vue => A1.vue => B1.vue => C2.vue

App.vue中通过接口获取用户信息,其他的组件也都要使用用户信息,按照我们之前学过的,我们需要通过子组件的props属性一层一层的传递数据。

这显然很麻烦,所以vue给我们设计了一个状态集中管理工具:vuex,让所有组件都可以通过标准的API去操作vuex里的数据。

说在最前面

根据我个人经验,大部分业务逻辑开发不需要使用vuex去进行状态管理, 所以不要轻易把组件中的数据放到vuex中管理

但是毕竟还是有使用场景的,工作中常见的开发场景就是登录后的用户信息可能多个组件需要访问,用户信息需要存储到vuex中。

安装

npm install vuex@next --save

使用

新建一个ts文件,src/store.ts

import { createStore } from 'vuex'

// 创建一个新的 store 实例
export default createStore({
    state() {
        return {
            userName'LiJing'
        }
    }
});

定义了变量”userName”,这个变量在任意组件内都可以访问。

定义变量在”state”字段中, 类似组件的”data”,他也是一个函数

接下来我们关联vuex到vue:

main.ts

import { createApp } from 'vue'
import App from './App.vue'
import 'animate.css/animate.min.css'
import router from './router';
import store from './store';

createApp(App)
    .use(router)
    .use(store)
    .mount('#app')

在组件内访问vuex定义的数据:

xxx.vue

<template>
  <h1>{{ $store.state.userName }}</h1>
</template>

<script>
import { defineComponent } from "vue";
export default defineComponent({
  mounted() {
    console.log(this.$store.state.userName);
  },
});
</script>

我们通过”$store.state“来访问state。

操作state

注意state的数据不能直接修改,需要通过”mutation“,下面我们在”mutations“中定义方法”changeUserName“。

src/store.ts

import { createStore } from 'vuex'

// 创建一个新的 store 实例
export default createStore({
    state() {
        return {
            userName'LiJing'
        }
    },
    mutations: {
        changeUserName(state, name) {
            state.userName = name;
        }
    }
});

在组件中修改state要是用 “$store.commit” 语法:

xxx.vue

<template>
  <h1 @click="changeUserName">{{ $store.state.userName }}</h1>
</template>

<script>
import { defineComponent } from "vue";
export default defineComponent({
  methods: {
    changeUserName() {
      this.$store.commit("changeUserName""晶哥哥");
    },
  },
});
</script>

mutation必须是同步函数

错误:

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

如果需要异步操作state,那么请用action。

action

vuex中唯一允许包含异步操作的函数, 但是他也不可以直接操作state, 他只能操作mutation

const store = createStore({
    state: {
        count0
    },
    mutations: {
        increment(state, count) {
            state.count+= count;
        }
    },
    actions: {
        incrementAsync({ commit },count) {
            // 模拟异步请求
            setTimeout(() => {
                commit('increment',count)
            }, 1000)
        }
    }
})

在组件中触发action

使用 dispatch 方法触发。

this.$store.dispatch('increment',100);

getters

类似组件中的计算属性。

src/store.ts

import { createStore } from 'vuex'

// 创建一个新的 store 实例
export default createStore({
    state: {
        todos: [
            { id1text'...'donetrue },
            { id2text'...'donefalse }
        ]
    },
    getters: {
        doneTodos(state) => {
            return state.todos.filter(todo => todo.done)
        }
    }
});

在组件中使用:

xxx.vue

<template>
  <h1>总数: {{ $store.getters.doneTodos.length }}</h1>
</template>

<script>
import { defineComponent } from "vue";
export default defineComponent({
  mounted() {
    console.log(this.$store.getters.doneTodos);
  },
});
</script>

编译项目(build)

现在教大家如何把vue打包编译,在命令行执行下列命令。

Vue3快速入门(一)

npm run build

编译vue,vue会自动被编译成js文件,生成文件会在项目根目录的”dist“文件夹里。

Vue3快速入门(一)

npm run preview

编译完成我们要预览下效果,”preview“命令会帮我们针对”dist“文件夹的内容启动http服务。

我们可以将其改为 serve,然后执行 npm run serve ,这只是我的使用习惯而已,不改也行。

Vue3快速入门(一)


原文始发于微信公众号(程序员阿晶):Vue3快速入门(一)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/19814.html

(1)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!