Vue3.X--教程笔记

240 阅读11分钟

一、安装

安装Vue官方脚手架以及创建项目

bbs.itying.com/topic/5fbb1…

Vue 官网地址:cn.vuejs.org/

Vue3.x Githtub地址: github.com/vuejs/vue-n…

Vue3.x 文档地址: v3.vuejs.org/

Vue3.x 中文文档地址:v3.cn.vuejs.org/

注意: 安装脚手架创建项目之前之前,我们的电脑上必须得安装Nodejs,推荐安装nodejs稳定版本

  • 安装Vue-cli,在同一个电脑上面只需要安装一次
npm uninstall -g vue-cli // 卸载2.0  // 更新npm版本
npm install -g npm
npm install -g @vue/cli // 更新,下载安装
E:\node_global>vue -V  // 查看 版本
  • 通过Vue-cli创建项目 首先cd到项目要存放的文件夹目录下
vue create hello-vue3
  • 项目启动 npm run serve
  • cd 到 hello-vue3 运行 npm i 安装依赖
  • vue-cli3运行npm run serve修改为npm run dev
  • 找到package.json文件,打开文件找到"scripts",将"serve"改为"dev"
  • "serve": "vue-cli-service serve"  这一行,把前面的 serve 修改 dev 后保存文件 image.png

image.png

二、绑定数据

v-bind动态参数

<a v-bind:[attributeName]="url"> ... </a>

这里attributeName将被动态地评估为JavaScript表达式,并且其评估值将用作参数的最终值。例如,如果您的组件实例具有一个数据属性attributeName,其值为"href",则此绑定将等效于v-bind:href

export default {
    name: "App",
    data() {
        return {
           attributeName: "href",
           linkUrl: "http://www.itying.com",
        };
    },
};
<a v-bind:[attributeName]="linkUrl"> 这是一个地址 </a>
或者
<a :[attributeName]="linkUrl"> 这是一个地址 </a>

v-for循环对象

(value, name, index) in myObject

export default {
    name: "App",
    data() {
        return {           
            myObject: {
                title: 'How to do lists in Vue',
                author: 'Jane Doe',
                publishedAt: '2020-03-22'
            }
        };
    },
};
<ul id="v-for-object" class="demo">

  <li v-for="(value, name, index) in myObject" :key="index">
  	 {{ name }}: {{ value }}--{{index}}
  </li>
</ul>

三、类和样式绑定

v-bind:class绑定对象

export default {
    name: "App",
    data() {
        return {
            isActive: true,
            hasError: false
        };
    },
};
<div class="static" :class="{ 'active': isActive, 'error': hasError }">
    v-bind:class演示
</div>

v-bind:style 绑定内联样式

第一种绑定方式

data() {
  return {
    activeColor: 'red',
    fontSize: 30
  }
}
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

第二种绑定方式

data() {
  return {
      baseStyles: {
        color: 'orange',
        fontSize: '13px'
      },
      overridingStyles: {
        width: "100px",
        height: "100px",
        background: "blue"
     }
  }
}
<div :style="[baseStyles, overridingStyles]"></div>

自动前缀

当您使用需要一个CSS属性供应商前缀:style,例如transform,Vue公司会自动检测并添加适当的前缀到应用的样式。

多个值

您可以为样式属性提供多个(前缀)值的数组,例如:

<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

这只会呈现浏览器支持的数组中的最后一个值。在此示例中,它将display: flex为支持非前缀版本的flexbox的浏览器呈现。

案例:循环数据 第一个数据高亮显示

<ul>
    <li v-for="(item,index) in list" :key="index" :class="{'red':index==0,'blue':index==1}">{{item}}</li>
</ul>

四、事件

vue中监听事件可以使用v-on:click或者 @click ,@clickv-on:click的简写。

$event

有时我们还需要在内联语句处理程序中访问原始DOM事件。您可以使用特殊$event变量将其传递给方法。

<button data-aid='123' @click="eventFn($event)">事件对象</button>

eventFn(e){
    console.log(e);
    // e.srcElement  dom节点
    e.srcElement.style.background='red';
    console.log(e.srcElement.dataset.aid);  /*获取自定义属性的值*/
}

多个参数时,$event放在最后

<button @click="warn('Form cannot be submitted yet.', $event)">Submit</button>

多事件处理程序

可以在事件处理程序中使用逗号分隔多个事件处理程序

methods: {
  one(event) {
    // first handler logic...
  },
  two(event) {
    // second handler logic...
  }
}

<button @click="one($event), two($event)">Submit</button>

事件修饰符

vue中阻止冒泡 阻止默认行为,可以通过事件对象event.preventDefault()event.stopPropagation()实现,还可以通过事件修饰符实现。

// vue中给我们提供了很多的修饰符:

.stop
.prevent
.capture
.self
.once
.passiv

// 例:stopPropagation
<a @click.stop="doThis"></a>

// preventDefault
<a @click.prevent="doThat"></a>

// stopPropagation And preventDefault
<a @click.stop.prevent="doThat"></a>

按键修饰符

监听键盘事件时,我们通常需要检查特定的键。Vue允许在监听关键事件时v-on@在监听关键事件时添加按键修饰符:

<input @keyup.enter="submit" />

Vue为最常用的键提供别名:

  • .enter
  • .tab
  • .delete (同时捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

五、 input、checkbox、radio、select、 textarea中的双向数据绑定

<template>
<h2>人员登记系统</h2>

<div class="people_list">
    <ul>
        <li>姓 名: <input type="text" v-model="peopleInfo.username" /></li>
        <li>年 龄: <input type="text" v-model="peopleInfo.age" /></li>
        <li>性 别:</li>
        <input type="radio" value="1" id="sex1" v-model="peopleInfo.sex" />
        <label for="sex1"></label>
        <input type="radio" value="2" id="sex2" v-model="peopleInfo.sex" />
        <label for="sex2"></label>
        <li>
            城 市:
            <select name="city" id="city" v-model="peopleInfo.city">
                <option v-for="(item, index) in peopleInfo.cityList" :key="index" :value="item">
                    {{ item }}
                </option>
            </select>
        </li>

        <li>
            爱 好:

            <span v-for="(item, index) in peopleInfo.hobby" :key="index">
                <input type="checkbox" :id="'check' + index" v-model="item.checked" />
                <label :for="'check' + key"> {{ item.title }}</label>
                &nbsp;&nbsp;
            </span>
        </li>

        <li>
            备 注:

            <textarea name="mark" id="mark" cols="30" rows="4" v-model="peopleInfo.mark"></textarea>
        </li>

    </ul>

    <button @click="doSubmit()" class="submit">获取表单的内容</button>
</div>
</template>

业务逻辑:

export default {
    data() {
        return {
            peopleInfo: {
                username: "",
                age: "",
                sex: "2",
                cityList: ["北京", "上海", "深圳"],
                city: "上海",
                hobby: [{
                        title: "吃饭",
                        checked: false,
                    },
                    {
                        title: "睡觉",
                        checked: false,
                    },
                    {
                        title: "敲代码",
                        checked: true,
                    },
                ],
                mark: "",
            },
        };
    },
    methods: {
        doSubmit() {
            console.log(this.peopleInfo);
        },
    },
};

六、JavaScript表达式 、条件判断、 计算属性和watch侦听

注意:双向数据绑定主要用于表单中

在 <template> 元素上使用 v-if 条件渲染分组

因为 v-if 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 <template> 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 <template> 元素。

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

v-if 和 v-show的区别

v-if是dom操作,v-show只是css的显示隐藏,一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好

计算属性

1、在模板中表达式非常便利,但是它们实际上只用于简单的操作。

2、模板是为了描述视图的结构。在模板中放入太多的逻辑会让模板过重且难以维护。这就是为什么 Vue.js 将绑定表达式限制为一个表达式。如果需要多于一个表达式的逻辑,应当使用计算属性。

computed: {
    reverseMsg() {
        return this.message.split("").reverse().join("")
    },
    ```
    searchList() {
        var tempArr = [];
        this.listData.forEach((value) => {
            if (value.indexOf(this.keyword) != -1 && this.keyword != ""){
                tempArr.push(value)
            }
        })
        return tempArr;
    }
<template>
    {{reverseMsg}}
<ul>
    <li v-for="(item,index) in searchList" :key="index">{{item}}</li>
</ul>
</template>

watch监听数据变化

Vue.js 提供了一个方法 watch ,它用于观察 Vue 实例上的数据变动。当一些数据需要根据其它数据变化时,watch 很诱人 —— 特别是如果你来自 AngularJS 。不过,通常更好的办法是使用计算属性而不是一个命令式的 watch 回调

export default {
    data() {
        return {           
            firstName: "",
            lastName: "",
            fullName: ""
        };
    },   
    watch: {
        firstName: function (val) {
            this.fullName = val + this.lastName;
        },
        lastName: function (val) {
            this.fullName = this.firstName + val;
        }
    }
};
<input type="text" v-model="firstName">
<input type="text" v-model="lastName">

{{ fullName }}

七、Vue3.x中集成Sass/scsss

安装sass-loader node-sass

npm install -D sass-loader node-sass

style中配置sass/scss

lang可以配置scss ,scoped表示这里写的css只有当前组件有效

<style lang = "scss" scoped>
    h2 {
        text-align: center;
    }
</style>

八、Vue3.x中的模块化以及封装Storage

Vue3.x 实现一个完整的toDoList(待办事项) 以及类似京东App搜索缓存数据功能

image.png

 localStorage里面的方法

localStorage.setItem(key,value)
localStorage.getItem(key)
localStorage.removeItem(key);
localStorage.clear();

封装localStorage

1、新建models/storage.js

var storage = {
    set(key,value){
        localStorage.setItem(key,JSON.stringify(value));
    },
    get(key){
        return JSON.parse(localStorage.getItem(key));
    },
    remove(key){
        localStorage.removeItem(key);
    }
}
export default storage;

2、引入

import storage from './model/storage.js';

九、组件

Props验证

条件不满足时,只会在浏览器控制台中提示警告。可在开发公共组件时使用。

父子 prop 之间单向下行绑定:父级 prop 的更新会向下流动到子组件中,反之不行。父级组件数据更新,子组件中所有的 prop 随之更新。防止子组件意外变更父级组件的状态,从而导致数据流向难以理解。

 props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function() {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function(value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    },
    // 具有默认值的函数
    propG: {
      type: Function,
      // 与对象或数组默认值不同,这不是一个工厂函数 —— 这是一个用作默认值的函数
      default: function() {
        return 'Default function'
      }
    }

父组件主动获取子组件的数据和执行子组件方法

  1. 调用子组件的时候定义一个ref
   <v-header ref="xxx"></v-header>
  1. 父组件主动获取子组件数据
this.$refs.xxx.属性
  1. 父组件主动执行子组件方法
this.$refs.xxx.方法

子组件主动获取父组件的数据和执行父组件方法

  1. 子组件主动获取父组件的数据
 this.$parent.数据
  1. 子组件主动获取父组件的方法
this.$parent.方法

子组件给父组件传值

(注意: Vue官方推荐始终使用 kebab-case 的事件名)

  • 子组件DatePicker.vue
<template>
<button @click="run">通过广播方式实现子组件给父组件传值</button>
</template>

<script>
export default {
    // 建议定义所有发出的事件,以便更好地记录组件应该如何工作。
    emits: ["run-parent"],
    data() {
        return {}
    },
    methods: {
        run() {
            this.$emit("run-parent", "这是子组件传过来的值")
        }
    }
}
</script>
  • 父组件Home.vue
<template>
<div>
    <date-picker @run-parent="getChild">
    </date-picker>
</div>
</template>

<script>
import DatePicker from "./DatePicker"
export default {
    data() {
        return {
            title: "你好vue"
        }
    },
    components: {
        DatePicker
    },
    methods: {
        getChild(data) {
            alert(data)
        }
    }
}
</script>

<style lang="scss">

</style>

自定义事件验证

只能控制台打印错误提示

// 子组件
export default {
    // 建议定义所有发出的事件,以便更好地记录组件应该如何工作。
    emits: {
        submit: ({
            username,
            password
        }) => {
            if (username && password) {
                return true
            } else {
                console.warn('传入的参数不能为空!')
                return false
            }
        }
    },
    data() {
        return {
            username: "张三",
            password: ""
        }
    },
    methods: {
        run() {
            this.$emit("submit", {
                username: this.username,
                password: this.password
            })
        }
    }
}

第三方插件mitt 实现非父子组件传值

github.com/developit/m…

Vue3.x以后从实例中移除了 $on$off$once 方法,$emit 仍然是现有 API 的一部分,只能实现子组件触发父组件的方法。

  • 使用mitt之前先安装mitt模块
 npm install --save mitt
  • 新建model/event.js 引入
import mitt from 'mitt'  // 引入
const VueEvent = mitt(); // 实例化

export default VueEvent; // 暴漏实例

引入后可通过 emit 广播数据,通过 on 监听数据。

import VueEvent from '../model/event'
export default {
    methods: {
        doLogin() {
            VueEvent.emit("login");
        }
    }
}
import VueEvent from '../model/event'
export default {  
    mounted() {
        VueEvent.on("login", () => {
            alert("doLogin")
        })
    }
}

组件使用v-model实现双向数据绑定

1、单个v-mode数据绑定

默认情况下,组件上的 v-model 使用 modelValue 作为 prop 和 update:modelValue 作为事件。我们可以通过向 v-model 传递参数来修改这些名称:

<my-component v-model:foo="bar"></my-component>

在本例中,子组件将需要一个 foo prop 并发出 update:foo 要同步的事件:

const app = Vue.createApp({})

app.component('my-component', {
  props: {
    foo: String
  },
  template: `
    <input 
      type="text"
      :value="foo"
      @input="$emit('update:foo', $event.target.value)">
  `
})

2、多个 v-model 绑定

通过利用以特定 prop 和事件为目标的能力,正如我们之前在v-model 参数中所学的那样,我们现在可以在单个组件实例上创建多个 v-model 绑定。

每个 v-model 将同步到不同的 prop,而不需要在组件中添加额外的选项。

<user-name
  v-model:first-name="firstName"
  v-model:last-name="lastName"
></user-name>

username.vue

 <input 
      type="text"
      :value="firstName"
      @input="$emit('update:firstName',$event.target.value)">
<input
      type="text"
      :value="lastName"
      @input="$emit('update:lastName',$event.target.value)">
  props: {
    firstName: String,
    lastName: String
  }

非 Prop 的Attribute 继承

  • 一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应props定义的 attribute。常见的示例包括 classstyleid 属性。
  • 当组件返回单个根节点时,非 prop attribute 将自动添加到根节点的 attribute 中。
  • 同样的规则适用于事件监听器
// 父组件:
<date-picker @change="submitChange"></date-picker>

// 子组件:
mounted() {
  console.log(this.$attrs) // { onChange: () => {}  }
}

示例:

// 子组件DatePicker.vue
<template>
     <select>
        <option value="1">Yesterday</option>
        <option value="2">Today</option>
        <option value="3">Tomorrow</option>
    </select>
</template>

// 父组件
 <date-picker @change="showChange"></date-picker>

methods: {
    showChange(event) {
      console.log(event.target.value) // 获取子组件选择的值
    }
 }
  • 自定义 Attribute 继承 如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false。例如:

禁用 attribute 继承的常见情况是需要将 attribute 应用于根节点之外的其他元素。

通过将 inheritAttrs 选项设置为 false,你可以访问组件的 $attrs property,该 property 包括组件 propsemits property 中未包含的所有属性 (例如,classstylev-on 监听器等)。

// 子组件:
<div class="date-picker">
    <input type="date" v-bind="$attrs" />
</div>

export default {
    inheritAttrs: false,
    data() {
        return {}
    }
}
// 父组件:
<template>
    <date-picker data-status="activated"></date-picker>
</template>

// 渲染完成的效果:
<div class="date-picker">
  <input type="datetime" data-status="activated" />
</div>

十、生命周期函数

生命周期

image.png

export default {
    data () {
        return {}
    },
    beforeCreate () {
        console.log('实例刚刚被创建1')
    },
    create () {
        console.log('实例已经创建完成2')
    },
    beforeMount () {
        console.log('模板编译之前3')
    },
    mounted () {
        /* 请求数据,操作dom,放在这个里面 mounted */
        console.log('模板编译完成/数据渲染完成4')
    },
    beforeUpdate () {
        console.log('数据更新之前')
    },
    updated () {
        console.log('数据更新完成')
    },
    activated () {
        console.log('keep-alive 缓存的组件激活时调用')
    },
    deactivated () {
        console.log('keep-alive 缓存的组件停用时调用')
    },
    beforeUnmount () { // vue2.x中的beforeDestroy
        /* 页面销毁的时候要保存一些数据,就可以监听这个销毁的生命周期函数 */
        console.log('实例销毁之前')
    },
    unmounted () { // vue2.x 中的 destroyed
        console.log('实例销毁完成')
    }
}
  • beforeCreate: 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
  • created: 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el property 目前尚不可用。
  • beforeMount: 在挂载开始之前被调用:相关的 render 函数首次被调用。
  • mounted: 实例被挂载后调用,这时 Vue.createApp({}).mount() 被新创建的 vm.$el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。

注意 mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm。$nextTick

mounted() {
  this.$nextTick(function () {
    // 仅在渲染整个视图之后运行的代码
  })
}
  • beforeUpdate: 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
  • updated: 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性侦听器取而代之。

注意,updated 不会保证所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以在 updated 里使用 vm.$nextTick

updated() {
  this.$nextTick(function () {
    // 仅在渲染整个视图之后运行的代码
  })
}
  • activated: 被 keep-alive 缓存的组件激活时调用。
  • deactivated: 被 keep-alive 缓存的组件停用时调用。
  • beforeUnmount: 在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。
  • unmounted: 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。

动态组件 keep-alive

当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题,这个时候就用用keep-alive

在不同路由切换的时候想保持组件的状态也可以使用keep-alive

    <keep-alive>
        <life-cycle v-if="isShow"></life-cycle>
    </keep-alive>

this.$nextTick

Vue中可以把获取Dom节点的代码放在mounted里面,但是如果要在渲染完成数据后获取DOM节点就需要用到this.$nextTick

 mounted() {
      this.$nextTick(function () {
        // 仅在渲染整个视图之后运行的代码
      })
 },
mounted() {
       
        var oDiv1 = document.querySelector("#msg");
        console.log("1-" + oDiv1.innerHTML);
        this.msg = "$nextTick演示";
        var oDiv2 = document.querySelector("#msg");
        console.log("2-" + oDiv2.innerHTML);
        this.$nextTick(() => {
            // 仅在渲染整个视图之后运行的代码
            var oDiv3 = document.querySelector("#msg");
            console.log("3-" + oDiv3.innerHTML);
        })
    },

十一、接口

Vue3.x全局绑定Axios

安装

npm install axios --save  

全局绑定

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import Axios from 'axios'
// 原来的代码
// createApp(App).mount('#app')
// 修改后的代码
const app = createApp(App);
app.config.globalProperties.Axios = Axios; // 使用this.Axios
app.mount('#app');

storage.js绑定全局

//  main.js
import Storage from './models/storage'
app.config.globalProperties.Storage = Storage; // 使用this.Storage

使用fetch-jsonp请求jsonp接口

axios不支持jsonp请求,可以使用fetch-jsonp模块

// 安装
npm install fetch-jsonp --save 
(--save会把依赖写入dependencies,工具类--save -dev 是写入devDependencies)

全局配置绑定

// main.js
import fetchJsonp from 'fetch-jsonp'
app.config.globalProperties.fetchJsonp = fetchJsonp;

请求接口时需要配置cb

getData() {
    let api = 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=php'
    this.fetchJsonp(api, {
        jsonpCallback: 'cb',
    })
    .then(function (response) {
         return response.json()
    }).then((data) => {
         console.log(data)
         this.list = data.s // 用到this一定要注意this指向
    }).catch(function (error) {
         console.log(error)
    })
}

使用函数防抖实现搜索

getData() {
    if (this.keyword != "") {
        clearTimeout(this.timer);
        this.timer = setTimeout(() => {
            let api = "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=" + this.keyword
            this.fetchJsonp(api, {
                jsonpCallback: 'cb',
            })
            .then(function (response) {
                 return response.json()
            }).then((json) => {
                 this.list = json.s
            })
       }, 200)
    }
   }

十二、Mixin

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

Mixin

1、新建mixin/base.js

const baseMixin = {
    data() {
       return{
            apiDomain: ""
       }
       
    },
    methods: {
        success() {
            console.log('succss')
        }
    }
}

export default baseMixin;

2、使用mixin

<script>
import BaseMixin from '../mixin/base'
export default {
    mixins: [BaseMixin],
    data() {return {}}
}
</script>

全局配置Mixin

import { createApp } from 'vue'
import App from './App.vue'
import BaseMixin from './mixin/base'

const app=createApp(App);
app.mixin(BaseMixin)
app.mount('#app');

十三、模态对话框

Teleport

Vue3.x中的组件模板属于该组件,有时候我们想把模板的内容移动到当前组件之外的DOM 中,这个时候就可以使用 Teleport。

表示teleport内包含的内容显示到body中

<teleport to="body">
内容
</teleport>
<teleport to="#app">
内容
</teleport>

十四、Composition API

共享和重用代码

compositon-api提供了以下几个函数:

  • setup
  • ref
  • reactive
  • watchEffect
  • watch
  • computed
  • toRefs
  • 生命周期的hooks

setup 组件选项

定义数据、方法

ref、reactive 定义数据

  • ref用来定义响应式的 字符串、 数值、Bool类型、数组
  • reactive 用来定义响应式的对象
import { ref, reactive } from "vue"
export default {
    name: "Home组件",
    setup(){
        // red 定义响应式数据 定义字符串、num、bool、数组
        // reactive 定义响应式数据 定义对象
        let title = ref("我是一个标题")
        let userinfo = reactive({
            username: "张三",
            age: 20
        })
        // 获取 ref 里面定义的数据
        var GetTitle = () => {
            console.log(title.value)
        }
        // 获取 reactive 里面定义的数据
        var GetUserName = () => {
            console.log(userinfo.username)
        }
        // 修改 ref 里面定义的数据
        var SetTitle = () => {
            title.value = "修改后的ref"
        }
        // 修改 reactive 里面定义的数据
        var SetUserName = () => {
            userinfo.username = "李四"
        }
        return {
            title,
            userinfo,
            GetTitle,
            GetUserName,
            SetTitle,
            SetUserName
        }
    }

}

使用 this

setup() 内部,this 不会是该活跃实例的引用,因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这在和其它选项式 API 一起使用 setup() 时可能会导致混淆。

 toRefs - 解构响应式对象数据

<template>
<div>
    <h1>解构响应式对象数据</h1>
    <p>Username: {{username}}</p>
    <p>Age: {{age}}</p>
</div>
</template>

<script>
import {
    reactive,
    toRefs
} from "vue";

export default {
    name: "解构响应式对象数据",
    setup() {
        const user = reactive({
            username: "张三",
            age: 10000,
        });

        return {
            ...toRefs(user)
        };
    },
};
</script>

computed - 计算属性

import {
    computed
} from "vue";

export default {
    name: "解构响应式对象数据",
    setup() {
        const fullName = computed(() => {
            return user.firstName + " " + user.lastName
        })
        return {
            fullName
        };
    },
};

readonly “深层”的只读代理

传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理

<script>
import { reactive, readonly } from "vue";

export default {
  name: "Readonly",
  setup() {
    const original = reactive({ count: 0 });
    original = readonly(original);
    const user = { // 这种也是只读,非响应式
        firstName: "",
        lastName: "",
     }

    return { original, user };
  },
};
</script>

监听数据变化

watchEffect

import { reactive, toRefs, watchEffect } from 'vue'
export default {
    setup(){
        let data = reactive({
            num: 1
        })
        watchEffect(() => { // 无论数据改不改变,都会最少执行一次;
            // 可以详细监听对象内的属性
            console.log(`num = ${data.num}`)
        })
        setInterval(() => {
            data.num++
        }, 1000)
        return {
            ...toRefs(data)
        }
    }
}

watch

import { reactive, toRefs, watch } from 'vue'
export default {
    setup(){
        let data = reactive({
            num: 1
        })
        watch(data, () => {
            console.log(`num = ${data.num}`)
        })
        setInterval(() => {
            data.num++
        }, 1000)
        return {
            ...toRefs(data)
        }
    }
}

watch 与watchEffect区别

  • watchEffect 刚开始会执行一次;
  • watch 懒加载,只有变化时才会触发,也就是说仅在侦听的源变更时才执行回调;
  • watch 访问侦听状态变化前后的值
 // 侦听单个数据源
watch(keywords, (newValue, oldValue) => {
    console.log(newValue, oldValue)
});
  • watch 更明确哪些状态的改变会触发侦听器重新运行;
<template>
<div>
    <h1>watch - 侦听器</h1>
    <p>count1: {{data.count1}}</p>
    <p>count2: {{data.count2}}</p>
    <button @click="stopAll">Stop All</button>
</div>
</template>

<script>
import {
    reactive,
    watch
} from "vue";
export default {
    name: "Watch",
    setup() {
        const data = reactive({
            count1: 0,
            count2: 0
        });
        // 侦听单个数据源
        const stop1 = watch(data, () =>
            console.log("watch1", data.count1, data.count2)
        );
        // 侦听多个数据源
        const stop2 = watch([data], () => {
            console.log("watch2", data.count1, data.count2);
        });
        setInterval(() => {
            data.count1++;
        }, 1000);
        return {
            data,
            stopAll: () => {
                stop1();
                stop2();
            },
        };
    },
};
</script>

组合式api生命周期钩子

setup 在 beforeCreate、created 之前执行

选项式 APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered

因为 setup 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

export default {
  setup() {
    // mounted
    onMounted(() => {
      console.log('Component is mounted!')
    })
  }
}

Props

export default {
  //接收父组件数据
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }

注意:

因为 props 是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。

如果需要解构 prop,可以通过使用 setup 函数中的 toRefs 来安全地完成此操作。

// MyBook.vue

import { toRefs } from 'vue'

setup(props) {
	const { title } = toRefs(props)
	console.log(title.value)
}

Provider Inject 父子组件传值

通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。

对于这种情况,我们可以使用一对 provide 和 inject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。

image.png

非组合式api中的写法

父组件数据改变,子组件不会随之改变,不会响应式改变数据

// 父组件
export default {
  components: {
    MyMarker
  },
  data () {
      return {
          title: '标题'
      }
  },
  provide: {
    location: 'North Pole',
    geolocation: {
      longitude: 90,
      latitude: 135
    }
  },
  // 或者另一种写法
  provide () {
      return {
          title: this.title
      }
  }
}
// 子组件
export default {
  inject: ['location', 'geolocation']
}

组合式api中的写法

provider inject 实现父子组件传值时,子组件改变数据也会影响父组件

// 父组件
import Home from './compontents/Home'
import { ref, reactive, toRefs, provide } from 'vue'
export default {
    name: 'app',
    data () {},
    components: { Home },
    setup() {
        let title = ref('app父组件里面的title')
        let userinfo = reactive({
            username: '张三',
            age: 20
        })
        let setTitle = () => {
            title.value = "父组件改变的标题"
        }
        // provide 是 key value的写法
        provide('title', title)
        provide('userinfo', userinfo)
        return {
            title,
            setTitle,
            ...toRefs(userinfo)
        }
    }

}


// 子组件
import { inject} from 'vue'
export default{
    setup() {
        let title = inject('title')
        let userinfo = inject('userinfo')
        return {
            title,
            userinfo
        }
    }
}

十五、Typescript

Ts基础教程:www.itying.com/goods-905.h…

# 1. Install Vue CLI, if it's not already installed
npm install --global @vue/cli

# 2. Create a new project, then choose the "Manually select features" option
vue create my-project-name

# If you already have a Vue CLI project without TypeScript, please add a proper Vue CLI plugin:
vue add typescript

选择vue3 -> 打开项目所在文件夹 -> vue add typescript -> Still proceed?输入 Yes -> Use class-style component syntax?输入 No (这样便和官方文档一致)

定义组件

  • vue3.x中集成ts后请确保组件的 script 部分已将语言设置为 TypeScript
  • 要让 TypeScript 正确推断 Vue 组件选项中的类型,需要使用 defineComponent 全局方法定义组件
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
    
})
</script>

可以对数据进行类型校验

<script lang="ts">
import { defineComponent } from 'vue'
let title:string = "我是一个home组件"
export default({
    data () {
        return {
            title
        }
    },
    methods: {
        setTitle():void{ // void 无返回值
            this.title=123 // 报错
        }
    }
})
</script>

约束组件中的数据类型

<script lang="ts">
import { defineComponent } from 'vue'
// data数据的接口
interface News{
    title: string,
    descripton:string,
    count:number|string, // 定义多种类型
    content?:string
}
let newsData:News={
    title: '标题',
    descripton: '描述',
    count:12
} 
// 或者
let newsData={
    title: '标题',
    descripton: '描述',
    count:12
} as News
export default({
    data () {
        return newsData
    },
    methods: {
        // 无传参
        setTitle():void{ // void 无返回值
            this.title=123 // 报错,只能字符串
            this.count=123
        },
        // 有传参
        setDescripton(descripton:string):void{
            this.descripton=descripton
        }
        
    },
    computed:{ // 计算属性
        reverseTitle():string{ // 要求返回string类型
            // return 123 报错
            return this.title.split("").reverse().join("")
        }
    }
})
</script>

与组合式 API 一起使用

  • 实现接口的第一种写法
let user:User=reactive({})
  • 实现接口的第二种写法
let user=reactive<User>({})
  • 实现接口的第三种写法
let user=reactive({}) as User
  • 使用ref指定传入类型
let count=ref<number|string>("20")
  • 计算属性限制返回类型
let reverseUsername=computed(():string => {
    return user.username.split("").reverse().join("")
})
<script lang="ts">
import { defineComponent, reactive, toRefs,ref } from 'vue'
interface User{
    username:string,
    age:number,
    setUsername(username:string):void, // 参数为string类型,无返回值
    getUsername():string // 返回值为string类型
}
export default defineComponent({
    setup(){
        // 1.实现接口的第一种写法
        let user:User=reactive({
            username:"张三",
            age:29,
            serUsername(username:string){
                this.username = username
            },
            getUsername(){
                return this.username
            }
        })
        // 2.实现接口的第二种写法
        let user=reactive<User>({
        })
        // 3.实现接口的第三种写法
        let user=reactive({
        }) as User
        // ref限制类型
        let count=ref<number|string>("20")
        // 计算属性限制返回类型
        let reverseUsername=computed(():string => {
            return user.username.split("").reverse().join("")
        })
        return{
            ...toRefs(user),
            count,
            reverseUsername
        }
    }
})
</script>

十六、Router 路由

路由可以让应用程序根据用户输入的不同地址动态挂载不同的组件。 next.router.vuejs.org/

npm install vue-router@next --save

配置路由

新建src/routes.ts 配置路由

import {createRouter,createWebHashHistory} from 'vue-router'

import Home from "./components/Home.vue"
import News from "./components/News.vue"

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    { path: '/', component: Home },
    { path: '/news', component: News }  
  ],
})

export default router

routes中配置路由,且必须是数组

挂载路由

在main.ts中挂载路由

import { createApp } from 'vue'
import App from './App.vue'
import router from './routes'

// createApp(App).mount('#app')

const app = createApp(App)
//挂载路由
app.use(router)

app.mount('#app')

渲染组件

App.vue中通过router-view渲染组件

<template>
<ul>
    <li>
        <router-link to="/">首页</router-link>
    </li>
    <li>
        <router-link to="/news">新闻</router-link>
    </li>
</ul>
<router-view></router-view>
</template>

<script lang="ts">
import {
    defineComponent
} from 'vue';
export default defineComponent({
    name: 'App',
});
</script>

动态路由

1. 配置动态路由传值

  routes: [
    { path: '/', component: Home },
    { path: '/newsContent/:id', component: NewsContent },    
  ],

路由跳转

<li v-for="(item,index) in list" :key="index">
    <router-link :to="`/newsContent/${index}`">{{item}}</router-link>
</li>

获取路由

this.$route.params

2. Get传值

  routes: [
    { path: '/', component: Home },
    { path: '/newsContent', component: NewsContent },    
  ],
<router-link to="`/newsContent?id=${id}`">Get传值</router-link>
this.$route.query // 获取get传值

路由编程式导航(Js跳转路由)

this.$router.push({ path: 'news' })
this.$router.push({ path: '/newscontent/123'})
this.$router.push({ path: '/newscontent',  query:{id:14} }

HTML5 History 模式和 hash 模式

  • hash 模式: 路由前面会有 #
  • 如果开启h5History 模式,需要后台配置伪静态
history: createWebHashHistory(), // hash 模式
history: createWebHistory(), // Html5 History模式

注意:开启Html5 History模式后,发布到服务器需要配置伪静态:

router.vuejs.org/zh/guide/es…

命名路由

  routes: [
    {
      path: '/user/:userId',
      name: 'user',
      component: User
    }
  ]
  • 要链接到一个命名路由,可以给 router-linkto 属性传一个对象:
{ path: '/user/:id', name: 'user',component: user },    
// 动态路由用params
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

这跟代码调用 router.push() 是一回事:

// 动态路由用params
this.$router.push({ name: 'user', params: { userId: 123 }})

这两种方式都会把路由导航到 /user/123 路径。

this.$router.push({name:'content',query:{id:222}})

路由重定向

重定向也在routes配置中完成。要从重定向/a/b

const routes = [{ path: '/home', redirect: '/' }]

重定向也可以针对命名路由:

const routes = [{ path: '/home', redirect: { name: 'homepage' } }]

甚至使用函数进行动态重定向:

const routes = [
  {
    // /search/screens -> /search?q=screens
    path: '/search/:searchText',
    redirect: to => {
      return { path: '/search', query: { q: to.params.searchText } }
    },
  },
  {
    path: '/search',
    // ...
  },
]

相对重定向

也可以重定向到相对位置:

const routes = [
  {
    path: '/users/:id/posts',
    redirect: to => {
      // the function receives the target route as the argument
      // return redirect path/location here.
    },
  },
]

路由别名

重定向是指用户访问时/home,URL将被替换/,然后与匹配/。但是什么是别名?

别名/as/home表示用户访问时/home,URL保持不变/home,但将被匹配,就像用户正在访问时一样/

以上内容可以在路由配置中表示为:

const routes = [{ path: '/', component: Homepage, alias: '/home' }]

嵌套路由

routes: [
        { path: '/', component: Home, alias: '/home' },
        {
            path: '/news', component: News,
            children: [  //子路由
                { path: '', redirect:"/news/add"},
                { path: 'add', component: NewsAdd },
                { path: 'edit', component: NewsEdit },
            ]
        },
        { path: '/user', component: User },
    ],

十七、Vuex 状态管理模式

官网:next.vuex.vuejs.org/

  • vuex可以实现vue不同组件之间的状态共享 (解决了不同组件之间的数据共享)
  • 可以实现组件里面数据的持久化 Vuex的几个核心概念
  • State 定义数据
  • Getters 相当于计算属性
  • Mutations 相当于方法
  • Actions 提交,触发方法,异步操作
  • Modules 模块

基本使用

安装依赖

npm install vuex@next --save

src目录下面新建一个vuex的文件夹,vuex 文件夹里面新建一个store.js

import { createStore } from 'vuex'
const store = createStore({
    state () {
        return {
            count:1
        }
    },
    mutations: { // 方法
        incCount(state){
            state.count++
        },
        setCount(state,num){ // 传值
            state.count = num
        }
         
    }
})
export default store

main.ts中挂载Vuex

import { createApp } from 'vue'
import App from './App.vue'
import route from './routes'
import store from './vuex/store'

let app=createApp(App);
//挂载路由
app.use(route)
//挂载vuex
app.use(store)
app.mount('#app'

State、Mutations

  • 获取state的数据
  • 由于全局配置了Vuex app.use(store),所以直接可以通过下面方法获取store里面的值。
computed:{
    count() {
        this.$store.state.count
    }
}
// this.$store.state.count
  • 调用方法更改数据,触发mutations
this.$store.commit('incCount')
this.$store.commit('setCount',15) // 传值
  • 通过mapState助手获取State
import { defineComponent } from 'vue'
import { mapState } from 'vuex'
export default defineComponent({
    computed: {
        ...mapState(['count', 'list'])  // 第一种写法,变量名称一致
        ...mapState({                   //第二种写法,起别名
            myCount: (state) => state.count,
            myList: (state) => state.list
        })
    }
})

Getters

Getter有点类似计算属性,可以认为是 store 的计算属性。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

  • 定义Getters
const store = createStore({
  state: {
      count:1,
      msg: '你好'
  },
  getters: {
    reverseMsg: state => {
        return state.msg.split("").reverse().join("")
    },
    num(state){
        return state.count+10
    }
  }
})
  • 获取Getters里面的数据
computed:{
    num(){
        return this.$store.state.count
    },
    revMsg(){
        return this.$store.getters.reverseMsg // 第一种获取方法
    }
}
  • 通过mapGetters 辅助函数
import { defineComponent } from 'vue'
import { mapState, mapGetters } from 'vuex'
export default defineComponent({
    computed: {
        ...mapState(['count', 'list'])
        ...mapGetters(['num', 'reverseMsg']) // 第一种写法
        ...mapGetters({revmsg: 'reverseMsg'}) // 第二种写法,起别名
    }
})

Actions

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
mutations:{ // 方法
    incCount(state){
        state.count++
    },
    setMsg(state, msg){
        state.msg = msg
    }
},
getters:{ // 计算属性
},
actions:{ // 执行mutations里面的方法,异步操作放在actions
    incCount(context){
        context.commit("incCount") // 执行mutations incCOUNT
    },
    incSetMsg(context,msg){
        setTimeout(()=>{
            context.commit("setMsg",msg) // 执行mutations setMsg
        },1000)
    },
    // 另一种写法,解构
    incSetMsg({commit},msg){
        setTimeout(()=>{
            commit("setMsg",msg) // 执行mutations setMsg
        },1000)
    }
}

分发 Action(触发Action中的方法)dispatch

this.$store.dispatch('increment')

Modules

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

  • moduleA.js
let moduleA = {
  state(){
      return{
        count: 0
      }
  },
  mutations: {
    increment (state) {
      // `state` is the local module state
      state.count++
    }
  },
  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}
export default moduleA
  • store.js
import { createStore } from 'vuex'
import moduleA from './moduleA'
const store = createStore({
    modules:{
        'moduleA': moduleA
    }
})
export default store
  • 获取state数据
this.$store.state.moduleA.count
  • 调用方法
this.$store.commit('increment')

十八、Vuex 结合组合式合成API

组合式api中没有this.$store,可以使用useStore来替代