Skip to content

about

https://cn.vuejs.org/

vue3是渐进式 JavaScript 框架,也是目前默认的vue版本,是现在和未来的主流。

安装

前提:已安装 16.0 或更高版本的 Node.js,安装教程:<cnblogs.com/Neeo/articles/11637320.html>

C:\Users\zhangkai>node -v
v16.14.2

C:\Users\zhangkai>npm -v
8.7.0

你可以直接通过cdn的形式在html中引入:

html
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

hello world

如下示例主要通过一个简单示例,来快速上手一下vue3:

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="app">
        <p>{{ msg }}</p>
    </div>
</body>
<script src="./vue.global.js"></script>
<script>
    // 1. 创建配置对象
    const configObj = {  // 配置对象
        // 写法1
        data(){
            // 必须return一个对象
            return {
                msg: "hello world1",

            }
        },
        // 写法2
        // data: function () {
        //     return {
        //         msg: "hello world2",
        //     }
        // }
    }
    
    // 2. 根据配置对象创建app应用
    // createApp,创建出来一个应用,然后通过mount挂在到容器中,进行关联
    Vue.createApp(configObj).mount('#app');  // 可以这么写
    // 也可以通过变量接收,干点其它的,比如use其它组件
    let app = Vue.createApp(configObj);
    app.mount('#app')
</script>
</html>

vite

vite中文文档:https://cn.vitejs.dev/

关于node版本

Vite 需要 Node.js 版本 14.18+,16+。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。

下载安装

不需要下载安装!!!

使用vite+vue创建vue项目

这家伙依赖npm版本,不同版本命令不太一样:

# npm 6.x
npm create vite@latest my-vue-app --template vue

# npm 7+, 需要额外的加两个短横线:
npm create vite@latest my-vue-app -- --template vue

# yarn
yarn create vite my-vue-app --template vue

# pnpm
pnpm create vite my-vue-app --template vue

来个示例:

# 首先确认下npm的版本
D:\tmp\studyvue3>npm -v
8.7.0

# 1. 创建vue项目
# 我的npm版本是7+,所以,我可以在命令行执行下面的命令,创建一个名为 v3d1 的vue项目
# 如果遇到有个 ok 什么什么process(y)的提示,输入y即可,没有这个提示的话,就算了
D:\tmp\studyvue3>npm create vite@latest v3d1 -- --template vue

Scaffolding project in D:\tmp\studyvue3\v3d1...

Done. Now run:

  cd v3d1
  npm install
  npm run dev


# 2. 按照要求cd到项目根目录去,然后安装该项目的依赖
D:\tmp\studyvue3>cd v3d1

D:\tmp\studyvue3\v3d1>npm install

added 32 packages in 2s


# 3. 启动vue项目,默认监听的是本机5173端口
D:\tmp\studyvue3\v3d1>npm run dev

> v3d1@0.0.0 dev
> vite


  VITE v3.1.3  ready in 309 ms

  ➜  Local:   http://127.0.0.1:5173/
  ➜  Network: use --host to expose

1832671032507367424.png

额外的,如果你想指定端口启动,可以修改vue项目根目录下的vite.config.js

javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server: {
    host: "0.0.0.0",
    port: 8822,
    open: true, // 启动后是否自动在浏览器中打开
  }
})

然后执行npm run dev启动就行了,如果是在项目运行时修改的,会自动重启,超级快!

1832671033157484544.png

后续的代码,我基本上都会在这个项目中进行演示,毕竟不用来回手动刷新页面了。

自定义IP和端口

vite+vue项目中,可以有几种自定义端口的方式,我们一起来看看。

方式1: 通过命令行指定ip和端口

另外的指定端口的方式就是命令行了:

D:\tmp\studyvue3\v3d1>npm run dev -- --port 8882 --host 0.0.0.0

> v3d1@0.0.0 dev
> vite "--port" "8882" "--host" "0.0.0.0"


  VITE v3.1.3  ready in 364 ms

  ➜  Local:   http://localhost:8882/
  ➜  Network: http://192.168.221.1:8882/
  ➜  Network: http://192.168.233.1:8882/
  ➜  Network: http://192.168.8.113:8882/

方式2:修改vue项目根目录下的vite.config.js文件

这个vite.config.js文件位于项目根目录下,如果不存在就创建。

javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server: {
    host: "0.0.0.0",
    port: 8822,
    open: true, // 启动后是否自动在浏览器中打开
  }
})

然后执行npm run dev启动就行了。

模板语法

v-bind

v-bind指令动态的绑定标签属性的内容,包括标签自带的属性和自定义属性,样式这些都可以。

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
<!--    没有通过v-bind绑定的属性,就是普通属性,你写啥就是啥-->
    <p><a href="url">百度</a></p>
<!--    通过v-bind绑定后,就可以和数据进行绑定了,那么对应的属性也是动态的了-->
    <p><a v-bind:href="url">百度</a></p>

</div>
</body>
<script src="./vue.global.js"></script>
<script>
    const configObj = {  // 配置对象
        // 写法1
        data(){
            // 必须return一个对象
            return {
                url:"https://www.baidu.com"
            }
        }
    }
    let app = Vue.createApp(configObj);
    app.mount('#app')
</script>
</html>

v-html

模板语法会将数据解释为普通文本,而非HTML代码,有些情况下,需要输出真正的html,我们可以通过v-html指令来做。

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <div>{{ h2 }}</div>
    <div v-html="h2">{{ h2 }}</div>
</div>
</body>
<script src="./vue.global.js"></script>
<script>
    const configObj = {  // 配置对象
        data(){
            return {
                "h2": "<h2>二级标题</h2>"
            }
        }
    }
    let app = Vue.createApp(configObj);
    app.mount('#app')
</script>
</html>

在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞。请仅在内容安全可信时再使用 v-html,并且永远不要使用用户提供的 HTML 内容。

使用JavaScript表达式

模板语法中支持JavaScript表达式:

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <h3>示例1</h3>
    <p>age: {{ age }}</p>
    <button @click="changeAge">使用方法</button>
<!--    模板语法中支持JavaScript表达式的写法-->
    <button @click="age-=1">使用JavaScript表达式</button>
    <h3>示例2</h3>
    <p>{{ name }}</p>
    <p>{{ name.split("").reverse().join('') }}</p>
</div>
</body>
<script src="./vue.global.js"></script>
<script>
    const configObj = {  // 配置对象
        data(){
            // 必须return一个对象
            return {
                age:1,
                name: "张开"
            }
        },
        methods: {
            changeAge(){
                this.age += 1
            }
        }
    }
    let app = Vue.createApp(configObj);
    app.mount('#app')
</script>
</html>

v-once

v-once将插值动作只执行一次,即数据再次发生改变时,该v-once绑定的插值出的内容不再发生变化。

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <p>{{ msg }}</p>
    <p v-once>{{ age }}</p>
    <p>{{ age }}</p>
    <button @click="changeAge">点我</button>
</div>
</body>
<script src="./vue.global.js"></script>
<script>
    const configObj = {  // 配置对象
        data(){
            return {
                msg: "hello world1",
                age: 1,
            }
        },
        methods: {
            changeAge(){
                this.age += 1
            }
        }
    }
    let app = Vue.createApp(configObj);
    app.mount('#app')
</script>
</html>

配置对象

data

methods

注意:在methods中,定义方法,请避免使用箭头函数形式,因为这会组织Vue绑定恰当的this指向。

计算属性:computed

vue提供计算属性(computed),用来创建一个变量保存data中的数据计算结果。

src/App.vue

html
<script>
    export  default {
        data(){
            return {
                num1: 0,
                num2: 0,
            }
        },
        computed:{
            // 虽然计算属性的定义看着是函数形式,但本质上还是一个属性,所以调用时不要加括号
            // 这里先学简单的简写形式,对计算属性有个了解
            // 常用写法1,这种方式用的还比较多
            total(){
                console.log('computed被调用了')
                return parseFloat(this.num1) + parseFloat(this.num2)
            }
            // 常用写法2
            // total: function () {
            //     return parseFloat(this.num1) + parseFloat(this.num2)
            // }
        },
        methods:{
            getTotal(){
                console.log('getTotal方法被调用了')
                return parseFloat(this.num1) + parseFloat(this.num2)
            }
        }
    }
</script>
<template>
    <div>
        <h1>计算属性</h1>
        <input type="text" v-model="num1">
        <input type="text" v-model="num2">
        <p>第一种方法:使用js计算,不推荐<span>{{ parseFloat(this.num1) + parseFloat(this.num2) }}</span></p>
        <h3>注意通过F12观察函数和计算属性被调用几次,就知道为啥推荐使用计算属性不推荐使用函数了</h3>
        <h4>函数</h4>
        <p>第二种方法:使用函数,不推荐<span>{{ getTotal() }}</span></p>
        <p>第二种方法:使用函数,不推荐<span>{{ getTotal() }}</span></p>
        <p>第二种方法:使用函数,不推荐<span>{{ getTotal() }}</span></p>
        <h4>计算属性</h4>
        <p>第三种方法:使用计算属性,推荐<span>{{ total }}</span></p>
        <p>第三种方法:使用计算属性,推荐<span>{{ total }}</span></p>
        <p>第三种方法:使用计算属性,推荐<span>{{ total }}</span></p>
    </div>
</template>
<style>
</style>

通过示例,我们也能看出使用计算属性和使用方法二者的区别了:

  • 对于方法调用,你使用了几次方法调用,这个方法就被调用了几次。
  • 对于计算属性,无论使用了几次计算属性调用,计算属性的get方法只调用了一次,其原因就是计算属性内部是走了缓存,所以效率高。
  • 另外,对于计算属性中用到的数据发生变化,那么计算属性也会同步发生更新。

计算属性的getter和setter

默认的,每个计算属性都有两个函数:

  • getter函数,即在模板语法中使用该计算属性时,真正执行的函数。
  • setter函数,当设置或者修改计算属性时,执行的函数,但是希望你永远不要用到这个函数,因为推荐你将计算函数当做只读属性来处理。所以,在上面的示例中,我们都是直接写的简写形式。

虽然不推荐使用setter函数,这里也来看看完整的计算属性怎么写:

html
<script>
    export  default {
        data(){
            return {
                num1: 0,
                num2: 0,
            }
        },
        computed:{
            // 计算属性的完整写法,每个计算属性都包含setter和getter函数
            total: {
                get(){  // getter函数的固定写法,不需要接收参数
                    console.log('计算属性total的getter函数被调用了')
                    return parseFloat(this.num1) + parseFloat(this.num2);
                },
                set(newValue){  // setter函数的固定写法,接收一个参数
                    console.log('计算属性total的setter函数被调用了', newValue, typeof newValue);
                    this.num1 += parseFloat(newValue);
                    this.num2 += parseFloat(newValue);
                }
            }
            // 上面演示了计算属性的完整写法,但通常用得最多的还是下面的简写形式
            // total(){
            //     console.log('computed被调用了')
            //     return parseFloat(this.num1) + parseFloat(this.num2)
            // }
            // 常用写法2
            // total: function () {
            //     return parseFloat(this.num1) + parseFloat(this.num2)
            // }
        },
        methods:{
            getTotal(){
                console.log('getTotal方法被调用了')
                return parseFloat(this.num1) + parseFloat(this.num2)
            }
        }
    }
</script>
<template>
    <div>
        <h1>计算属性</h1>
        <input type="text" v-model="num1">
        <input type="text" v-model="num2">
        <p>第一种方法:使用js计算,不推荐<span>{{ parseFloat(this.num1) + parseFloat(this.num2) }}</span></p>
        <h3>注意通过F12观察函数和计算属性被调用几次,就知道为啥推荐使用计算属性不推荐使用函数了</h3>
        <h4>函数</h4>
        <p>第二种方法:使用函数,不推荐<span>{{ getTotal() }}</span></p>
        <p>第二种方法:使用函数,不推荐<span>{{ getTotal() }}</span></p>
        <p>第二种方法:使用函数,不推荐<span>{{ getTotal() }}</span></p>
        <h4>计算属性</h4>
        <p>第三种方法:使用计算属性,推荐<span>{{ total }}</span></p>
        <p>第三种方法:使用计算属性,推荐<span>{{ total }}</span></p>
        <p>第三种方法:使用计算属性,推荐<span>{{ total }}</span></p>
        <br>
        <button @click="this.total='1'">点我让每个num值加1</button>
    </div>
</template>
<style>
</style>

让我们牢记官网的建议:

  • 不要在计算函数中做异步请求或者更改dom。
  • 一个计算属性的声明中描述的是如何根据其他值派生一个值。因此计算函数的职责应该仅为计算和返回该值。从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。更改快照是没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改——应该更新它所依赖的源状态以触发新的计算。

侦听器:watch

侦听器也叫监听器,或者叫做监听属性或者侦听属性,主要用来监听data中指定属性,当属性的值发生变化就调用相应的方法。

监听属性是一个对象,它的键是要监听的对象或者变量,值一般是函数,当侦听的data数据发生变化时,会自定执行的对应函数,这个函数在被调用时,vue会传入两个形参,第一个是变化前的数据值,第二个是变化后的数据值。

html
<script>
    export  default {
        data(){
            return {
                num1: 0,
                num2: 0,
                total2: "", // 被监听的属性一定要存在,不存在监听谁啊,但属性值是什么不确定,后面根据监视给它赋值
            }
        },
        computed:{
            total(){
                console.log('computed被调用了')
                return parseFloat(this.num1) + parseFloat(this.num2)
            }
        },
        watch: {
            // 写法1,监听属性的简写形式,用的不多
            // num1(newValue, oldValue){
            //     console.log('监听属性', newValue, oldValue)
            //     this.total2 = parseFloat(newValue) + parseFloat(this.num2)
            // }
            // 写法2,写起来相对复杂,但支持的姿势多,所以用的最多
            num1: { // 这是个配置对象
                // 配置对象的属性必须叫做handler,因为后面的函数名可以变
                // 注意是handler,不是handle,小心写错了
                // handler:function () {}  // 通常不这么写
                // 当数据发生改变的时候会自动调用handler
                handler(newValue, oldValue){
                    console.log('监听属性', newValue, oldValue)
                    this.total2 = parseFloat(newValue) + parseFloat(this.num2)
                },
                //重要的:配置这个配置项的作用是无论监视到属性发生不发生变化,都要强制的执行一次回调
                // 这里它解决了初次访问页面时,不显示值的问题
                immediate: true
            }
            // 能同时监听多个属性,这里不再多表
        }
    }
</script>
<template>
    <div>
        <h1>监听属性</h1>
        <input type="text" v-model="num1">
        <input type="text" v-model="num2">
        <p>计算属性computed</p>
        {{ total }}
        <p>监听属性watch</p>
        {{ total2 }}
        <br>
        <button @click="this.num1+=1">点我让num1的值加1</button>
    </div>
</template>
<style>
</style>

上例同时用了watch和computed两个,实际上,解决示例的需求,只需要computed就行了,使用watch是表示我(watch)也能行。

那么watch和computed的区别是啥呢?

  • computed是计算属性,一般适用于没有这个值,但想要使用这个值,且这个值是根据已有的数据计算出来的,这样的场景。
  • watch是监视属性,它要监视的属性必须存在,以及后期要修改的属性也必须存在。
  • 通常能用computed的场景都可以使用watch,但是,能使用watch解决的问题,你用computed不一定能解决。

其次,我们比较computed和watch的时候,其实比较的是计算属性的get方法,而计算属性的set,仅仅是对计算的属性添加了监视,即当计算属性的值被修改时,会调用set,可以参考计算属性部分setter函数部分的示例。

最后,也是最重要的区别,computed中只能使用同步,而watch当中可以使用同步,也可以是异步,来看个简单示例:

html
<script>
    export  default {
        data(){
            return {
                n2: "before"
            }
        },
        computed:{
            n1(){
                let a = "before"
                setTimeout(()=>{
                    a = "after"
                }, 2000)
                return a
            }
        },
        watch: {
            n2:{
                // 注意是handler,不是handle,小心写错了
                handler(newValue, oldValue){
                    setTimeout(()=>{
                        this.n2 = "after"
                    }, 2000)
                },
                // 必须加上immediate配置项
                immediate: true
            }
        }
    }
</script>
<template>
    <div>
        <h3>在computed中使用异步,不行</h3>
        <p>{{ n1 }}</p>
        <h3>在watch中使用异步,可以</h3>
        <p>{{ n2 }}</p>
    </div>
</template>
<style>
</style>

补充:在watch、computed、metheds中的this都指向vue的实例化对象。

扩展:

  • vue控制的所有回调函数的this都是vue的实例化对象。
  • 所有data/computed/method中的属性/方法都会成为vue的实例化对象的属性/方法。
  • 模板中表达式读取数据,是读取的vue的实例化对象的属性,模板中调用的方法,调用的是vue的实例化对象的方法。

深度监听:deep

vue面试中也有一个常问的点,那就是深度监视/深度监听,什么意思呢?上面关于监听的例子都是监听data中第一季的某个属性值,那么如果这个属性是个对象,而我们想要监听这个对象中某个属性怎么办?按照上面的写法是做不到的,所以我们需要使用deep来声明下,深度监听。

下面的示例展示了监听、深度监听的几种常见写法,以及区别:

html
<script>
    export  default {
        data(){
            return {
                obj1: "张开1",
                obj2: { name: '张开2', age: 18, info:{addr:"北京"} },
                obj3: { name: '张开3', age: 18, info:{addr:"北京"}  },
                obj4: { name: '张开4', age: 18, info:{addr:"北京"} },
            }
        },
        watch: {
            obj1: {
                handler(newValue){
                    console.log('obj1', newValue);
                    this.obj1 = 'after 张开1'
                },
                // 也可以根据需要决定是否需要使用immediate
                // immediate: true
            },
            // 默认的,监听器不会监听对象内部是否有变动
            obj2:{
                handler(newValue){
                    console.log('obj2', newValue);
                    this.obj1.info.addr = 'after 北京啦啦啦'
                },
                // 也可以根据需要决定是否需要使用immediate
                // immediate: true
            },
            // 通过设置deep为true来开启深度监听,即监听器会递归的为对象中的每个属性,给其加上监听器
            obj3:{
                handler(newValue){
                    // 传过来的newValue是个完整更新后的对象
                    console.log('obj3', newValue);
                    // this.obj3.name = newValue.name
                    // this.obj3.info.addr = "after 上海"
                },
                // 也可以根据需要决定是否需要使用immediate
                // immediate: true
                // deep的值是布尔值,表示是否开启深度监听
                deep: true
            },
            // 通过设置deep为true来开启深度监听,但如果我们只需要对对象中某一个属性进行监听,那么上面obj3那个监听器就有点浪费了
            // 所以,可以通过下面这种字符串的形式对对象中指定属性进行监听,性能较好
            "obj4.info.addr":{
                handler(newValue){
                    // 传过来的newValue是个完整更新后的对象
                    console.log('obj4', newValue);
                    // this.obj3.info.addr = "after 上海"
                },
                // 也可以根据需要决定是否需要使用immediate
                // immediate: true
                // 无论是监听对象中的指定属性还是递归监听所有属性,都需要声明deep
                deep: true
            }
        }
    }
</script>
<template>
    <div>
        <p>默认的监听器监听data中顶层的属性</p>
        <button @click="obj1='after zhangkai1'">name</button> {{ obj1 }}
        <br>
        <p>默认的监听器监听data中顶层的属性,对于data中属性是对象的,对于对象内部的属性变动不会监听,下面的示例虽然结果有变动,但是监听器的obj2部分代码没有执行</p>
        <button @click="obj2.info.addr='after 北京'">addr</button> {{ obj2.info.addr }}
        <br>
        <p>通过设置deep为true来开启深度监听,即监听器会递归的为对象中的每个属性,给其加上监听器,慎用这种方式,比较消耗资源,因为它为对象中的每个属性都加上了监听器</p>
        <button @click="obj3.name='after 张开3'">name</button> {{ obj3.name }}
        <button @click="obj3.info.addr='after 北京'">addr</button> {{ obj3.info.addr }}
        <br>
        <p>通过字符串的形式,为对象中指定属性添加监听器,消耗较小,下面的示例虽然name值也变动了,但监听器代码没执行,而只有点击addr那个按钮时监听器才执行</p>
        <button @click="obj4.name='after 张开4'">name</button> {{ obj4.name }}
        <button @click="obj4.info.addr='after 北京'">addr</button> {{ obj4.info.addr }}
        <br>
    </div>
</template>
<style>
</style>

ref

vue中的ref属性通常有两个作用:

  1. ref应用于普通标签,则可以通过this.$refs.ref值来获取标签的原始dom对象。
  2. ref应用于组件,那么就可以通过ref来进行组件间通信。

ref应用于普通标签

html
<script>
    export  default {
        data(){
            return {}
        },
        methods: {
            handleClick(){
                // 注意,在标签定义ref时,属性名是ref,在通过this对象操作时,就要使用$refs了
                console.log(this.$refs)  // 获取所有具有ref属性的标签对象
                console.log(this.$refs.input1)  // 根据标签中的ref属性对应的值获取到该标签的原生dom对象
                console.log(this.$refs.input1.value)  // 然后就可以执行dom操作
                this.$refs.input1.value = 123;  // 能获取值,也就是能赋值
            }
        }
    }
</script>
<template>
    <div>
        <h1>ref应用于普通标签</h1>
        <!-- 直接在标签上填写ref和对应的值,这个值的名字任意起 -->
        <input type="text" ref="input1">
        <input type="text" ref="input2">
        <button @click="handleClick">点我</button>
    </div>
</template>
<style>
</style>

用法比较简单。

ref应用于组件间通信