Vue3详细篇

大家好我是程序员阿晶,最近在更新Vue3相关的文章,在学习之余又更新了一片关于Vue3的文章,希望能够帮助到大家。

Vue3详细篇

Vue3详细篇

建议大家学习的时候,多多参考文档。

Vue3 One Piece:https://vue3js.cn/

安装开发环境

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

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

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

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

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

npm init vite@latest my-vue-app -- --template vue-ts # 如果项目里使用了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/

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

运行及打包命令

npm run dev # 运行项目
npm run build # 打包项目

运行结果

Vue3详细篇

使用Vue-Cli创建

Vue3官方推荐使用vite来构建项目,但是并不是说使用vue-cli创建项目就不行了。

使用vue-cli创建项目,要确保你的vue-cli的版本要大于4.5.6。

安装vue-cli

建议使用 npm 安装

1、安装命令

npm install -g @vue/cli

2、升级命令

npm update -g @vue/cli

3、查看版本

vue --version
# 或者
vue -V

使用命令行创建项目

vue create vue3-demo-cli

大概需要以下几个步骤:

1、选择安装版本,我们选择第三个自定义安装

Vue3详细篇

2、选择你安装的插件,使用空格选择/取消,选好之后回车

Vue3详细篇

3、选择vue版本,我们选择3.x

Vue3详细篇

4、选择你要的代码风格,路由是否使用历史模式,以及代码检索模式

Vue3详细篇

5、创建成功,会显示两条命令,使用这两条命令,即可运行项目

Vue3详细篇

使用图形界面创建项目

1、打开图形界面

vue ui

2、创建步骤是与使用命令行创建是一样的

Vue3详细篇
Vue3详细篇
Vue3详细篇
Vue3详细篇
Vue3详细篇

创建项目即可。

但是我不太建议使用ui的方式,因为太容易卡住了。

项目配置插件支持

这里使用我之前用Vite创建的项目,作为举例。

由于我之前创建的vue项目并没有集成路由(router)和vuex相关功能。

所以需要为我的项目添加上依赖。

1、安装vue-router

npm install vue-router@4

2、安装vuex

npm install vuex@next --save

3、安装组件库Ant Design Vue

npm i --save ant-design-vue@next

4、安装axios

npm install --save axios vue-axios

main.ts

import axios from 'axios'
import VueAxios from 'vue-axios'
...

app.use(VueAxios, axios);

5、安装qs

npm install qs

6、安装sass

npm install node-sass sass-loader sass -D

7、安装element-plus

npm install element-plus --save

main.ts

import { createApp, Vue } from 'vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import App from './App.vue';

const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')

官方文档:https://element-plus.gitee.io/zh-CN/

目录结构

当前展示的目录结构是基于vite搭建的项目。

Vue3详细篇

1、dist目录

存放打包之后的代码文件

2、node_modules

存放项目依赖的包目录

3、public

存放公共文件目录,放引入别人的文件,基本不会动的文件

如:icon,font、外部第三方文件

4、src

存放代码的主要目录

4.1、assets

放自己写的css、js文件,后期可能会改的文件,以及图片文件等

4.2、components

存放项目使用的公共组件

4.3、router

存放vue-router相关的文件

4.4、views

存放页面文件(.vue)

4.5、App.vue

根组件文件

4.6、main.ts

入口文件ts文件

// 引入vue3中vue框架的createApp这个方法,创建一个实例
import { createApp } from "vue";
import App from "/@/App.vue";
// 创建实例
const app = createApp(App);
// 将示例挂载至节点
app.mount("#app");

4.7、store.ts

vuex全局状态管理

5、.gitignore

Git相关文件,配置不上传至git库的文件

6、global.d.ts

Typescript 全局类型声明文件

7、index.html

项目入口文件

8、package.json

存放依赖包管理及命令管理信息文件

9、package-lock.json

用以记录当前状态下实际安装的各个npm package 的具体来源和版本号

10、README.md

使用markdown编写的文档文件

11、ts.config.json

Typescript的配置文件

12、vite.config.ts

项目配置文件

vue路由vue-router4

具体的用法请参照官方文档:https://router.vuejs.org/zh/index.html

vite配置项目路径别名

webpack中可以通过resolve.alias定义项目路径别名,这样可以在引入文件时,不再需要使用相对路径,统一以根路径(/src/)作为起点。

vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { join } from "path";

// https://vitejs.dev/config/
export default defineConfig({
  resolve: {
    alias: {
      '@': join(__dirname, "src"),
    }
  },
  plugins: [vue()]
})

如果项目是TypeScript编写,还需要修改一下TypeScript的配置:

tsconfig.json

{
   // ...
  "compilerOptions": {
    // ...其他配置
    "baseUrl"".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  // ...
}

如果你是刚创建的TypeScript项目,有可能会遇到找不到模块“path”或其相应的类型声明的错误提示,安装@types/node即可。

npm install @types/node --save-dev

使用案例

比如说某个站点分为三个部分:

1、pc端页面的路由

2、手机端页面的路由

3、后端管理系统的路由

这里放一下文件使用案例,使用的基础语言是typescript:

src/router/index.ts

// 引入vue-router对象
import { createRouter, createWebHistory, createWebHashHistory } from "vue-router";
import axios from 'axios';
/**
 * 定义路由数组
 */

const routes = [
    {// 404路由
        path'/404',
        name'404',
        // 页面是独立的js, 访问当前路由时异步加载
        component() => import('@/views/404.vue')
    },
    {
        // 后端管理系统路由组
        path"/admin",
        redirect"/admin/home",
        name"admin",
        component() => import("@/views/Admin.vue"),
        children: [
            {
                path"home",
                name"home",
                meta: {
                    requireAuthtrue // 添加该字段,表示进入这个路由是需要登录的
                },
                component() => import("@/views/admin/Home.vue")
            },
            {
                path"setting",
                name"setting",
                meta: {
                    requireAuthtrue // 添加该字段,表示进入这个路由是需要登录的
                },
                component() => import("@/views/admin/Setting.vue")
            },
        ]
    },
    {
        // 博客主站pc端页面路由
        path"/pc",
        redirect"/pc/index",
        name"pc",
        component() => import("@/views/Pc.vue"),
        children: [
            {
                path"index",
                name"首页",
                component() => import("@/views/pc/Home.vue"),
            },
        ]
    },
    {
        // 博客主站手机端页面路由
        path"/phone",
        redirect"/phone/index",
        name"phone",
        component() => import("@/views/Phone.vue"),
        children: [
            {
                path"index",
                name"Home",
                component() => import("@/views/phone/Home.vue")
            },
        ]
    },
];

/**
 * 创建路由
 */

const router = createRouter({
    // hash模式:createWebHashHistory, history模式:createWebHistory
    // history: createWebHistory(),
    history: createWebHashHistory(),
    routes,
});

/**
* 路由切换之前执行,路由守卫
*/

router.beforeEach(async (to, from) => {
    // 进行身份验证等操作
    return true
});

/**
 * 输出对象
 */

export default router;

然后在 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')

最终在对应的页面中添加 <router-view/>标签,用于展示内容。

当然,目前这个文件只能满足最基础的跳转。

在页面中使用:

Vue3,是可以和vue2一样将router挂载至vue对象上的。

但是,官方不建议这么做,因此呢,vue-router是每个单页分别引入的。

大概是这个样子:

<template>
  <div>
    {{ data.article_id }} <br>
    {{ data.cate_id_son }} <br>
    <button @click="cateSonShow(1)">点击跳转</button>
    <router-view/>
  </div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from "vue";
import { useRouter, useRoute } from "vue-router";
export default defineComponent({
  name"Article",
  components: {},
  // Vue3 语法 第一个执行的钩子函数
  // setup官方文档
  // https://www.vue3js.cn/docs/zh/guide/composition-api-setup.html#参数
  setup(props: any, content: any) {
    // 实例化路由
    var router = useRouter();
    // 路由参数
    var route = useRoute();

    const data = reactive({
      // 文章id
      article_id: route.query.article_id ? route.query.article_id : 0,
      cate_id_son0,
    });
    const cateSonShow = (cate_id_son: number) => {
      data.cate_id_son = cate_id_son;
      router.push({
        path"/pc/articleList",
        // 路由query传参
        query: {
          cate_id_son: data.cate_id_son,
        },
      });
    };
    return {
      data,
      cateSonShow,
    };
  },
});
</script>
<style>
</style>

更多关于vue-router4的功能,请参考官方文档:https://router.vuejs.org/zh/

项目配置使用404页面

当我们访问的路由没有预先定义的时候,就会跳到一个空白的页面。

这样的体验不太好,那么我们需要配置,访问路由不存在时跳转404页面。

实现这个小功能我用到了vue-router的路由守卫功能。

新建路由守卫方法:

src/router/beforeEach.ts

import { Router } from "vue-router";
export default
    {
        /**
         * 路由守卫检查权限
         * @param guard 
         * @param router 
         */

        checkAuth(guard: any, router: Router) {
            //检查路由是否存在
            if (!router.hasRoute(guard.name)) {
                // 三层不同404路由
                if (guard.fullPath.indexOf("/frame") >= 0) {
                    router.push("/404");
                }
                else if (guard.fullPath.indexOf("/pages") >= 0) {
                    router.push("/404");
                }
                else {
                    router.push("/404");
                }
                return;
            }
        }
    }

在之前的路由配置文件基础上添加功能:

src/router/index.ts

// 引入vue-router对象
import { createRouter, createWebHistory, createWebHashHistory } from "vue-router";
import axios from 'axios';
// 引入路由守卫方法
import beforeEach from "@/router/beforeEach";
/**
 * 定义路由数组
 */

const routes = [
    {// 404路由
        path'/404',
        name'404',
        // 页面是独立的js, 访问当前路由时异步加载
        component() => import('@/views/404.vue')
    },
    {
        // 后端管理系统路由组
        path"/admin",
        redirect"/admin/home",
        name"admin",
        component() => import("@/views/Admin.vue"),
        children: [
            {
                path"home",
                name"home",
                meta: {
                    requireAuthtrue // 添加该字段,表示进入这个路由是需要登录的
                },
                component() => import("@/views/admin/Home.vue")
            },
            {
                path"setting",
                name"setting",
                meta: {
                    requireAuthtrue // 添加该字段,表示进入这个路由是需要登录的
                },
                component() => import("@/views/admin/Setting.vue")
            },
        ]
    },
    {
        // 博客主站pc端页面路由
        path"/pc",
        redirect"/pc/index",
        name"pc",
        component() => import("@/views/Pc.vue"),
        children: [
            {
                path"index",
                name"首页",
                component() => import("@/views/pc/Home.vue"),
            },
        ]
    },
    {
        // 博客主站手机端页面路由
        path"/phone",
        redirect"/phone/index",
        name"phone",
        component() => import("@/views/Phone.vue"),
        children: [
            {
                path"index",
                name"Home",
                component() => import("@/views/phone/Home.vue")
            },
        ]
    },
];

/**
 * 创建路由
 */

const router = createRouter({
    // hash模式:createWebHashHistory, history模式:createWebHistory
    // history: createWebHistory(),
    history: createWebHashHistory(),
    routes,
});

/**
* 路由切换之前执行,路由守卫
*/

/* router.beforeEach(async (to, from) => {
    // 进行身份验证等操作
    return true
}); */


/**
 * 路由守卫
 */

router.beforeEach((guard) => {
    beforeEach.checkAuth(guard, router);
});

router.onError((error) => {
    console.log(error);
});

/**
 * 输出对象
 */

export default router;

至此,访问未定义的路由跳转404的小功能已经完成。

vue-router 在新窗口打开页面

新窗口打开标签页这个功能在html中还是很简单的,添加Target=”__blank”就好。

但是在vue中怎么实现呢?

Vue3

const router = useRouter();
const { href } = router.resolve({
    path"/user/xxx",
    query: {
        
    },
});
window.open(href, "_blank");

路由懒加载

为给客户更好的客户体验,首屏组件加载速度更快一些,解决白屏问题。

懒加载简单来说就是延迟加载或按需加载,即在需要的时候的时候进行加载。

常用的懒加载方式有两种:即使用vue异步组件 和 ES中的import。

vue项目抽离.vue文件中的js、css代码

平常在做开发的时候,一般情况下不会将html,js,css代码写到一个文件中。

基本上都会写在各自对应的文件中,然后再引入即可。

那么在Vue中我们如何抽离vue文件中的js与css代码呢?

方法如下:

// 不加 { } ,表示直接return
const HelloWorld = ()=> import('需要加载的模块地址'

抽离javascript

src/assets/js/admin/Home.ts

import { defineComponent } from "vue";
import HelloWorld from "@/components/HelloWorld.vue";
export default defineComponent({
    name"Home",
    components: {
        HelloWorld,
    },
});

src/views/admin/Home.vue

<template>
  <div>
    <div :style="{ padding: '24px', background: '#fff', minHeight: '360px' }">
      <h1>This is a home page</h1>
      <HelloWorld msg="Hello Vue 3.0 + Vite + TypeScript" />
    </div>
  </div>
</template>
 
<script lang="ts">
import { defineComponent } from "vue";

// 引入js文件
import Home from "@/assets/js/admin/Home";
// 使用js对象
export default defineComponent({
  ...Home
});
</script>

抽离css

src/assets/css/admin/Home.scss

#app {
    font-family"Microsoft YaHei,微软雅黑,Arial,sans-serif,Helvetica Neue,Helvetica,Pingfang SC,Hiragino Sans GB";
    color: red;
    .ant-layout-sider {
        .ant-layout-sider-children .ant-row-flex {
            border-bottom1px solid rgb(240240240);
        }

        .ant-layout-sider-trigger {
            border-top1px solid rgb(240240240);
        }
    }
}

src/views/admin/Home.vue

<template>
  <div>
    <div :style="{ padding: '24px', background: '#fff', minHeight: '360px' }">
      <h1>This is a home page</h1>
      <HelloWorld msg="Hello Vue 3.0 + Vite + TypeScript" />
    </div>
  </div>
</template>
 
<script lang="ts">
import { defineComponent } from "vue";

// 引入js文件
import Home from "@/assets/js/admin/Home";
// 使用js对象
export default defineComponent({
  ...Home
});
</script>

<style lang="sass">
  @import "@/assets/css/admin/Home.scss"
</style>

setup与ref函数

Setup是Vue3.0中为我们新提供的的组件选项。

创建组件实例,然后初始化props,紧接着就调用setup函数,从生命周期钩子的视角来看,他在beforeCreate之前调用。

setup()是函数,具有return,return函数中定义的变量,把其暴露给模板。

setup

1、setup可以替代Vue2中的data和methods函数。

使用Vite创建一个空项目,默认会给我们一个 HelloWorld 组件,我们就使用这个组件去做测试。

这里先展示一个Vue3的新写法:

src/components/HelloWorld.vue

<template>
  <h1>{{ msg }}</h1>
  <button @click="count++">count is: {{ count }}</button>
  <p>
    Edit <code>components/HelloWorld.vue</code> to test hot module replacement.
  </p>
  <button @click="clickMe()">点我弹窗</button>
</template>
 
<script lang='ts'>
import { ref, defineComponent } from "vue";
export default defineComponent({
  name"HelloWorld",
  props: {
    msgString,
  },
  // 使用 setup 代替 data
  // 因为我这里使用的是 typescript,因此需要给参数指定类型
  setup(props: any, context: any) {
    console.log(props, context);
    let count = ref(0);
    const clickMe = () => {
      // 使用ref关键字绑定的变量,赋值的时候必须使用.value
      count.value++;
      alert("hi");
    };
    return {
      count,
      clickMe,
    };
  }, //*/

  // 这是Vue2.0的写法,data与methods
  /*data() {
    return {
      count: 0
    }
  },
  methods:{
    clickMe(){
      this.count++;
      alert('hi');
    }
  }*/

});
</script>

src/App.vue

<template>
  <div>
    <img alt="Vue logo" src="@/assets/logo.png" />
    <HelloWorld msg="Hello Vue 3.0 + Vite + TypeScript" />
  </div>
</template>
<script lang = "ts">
import { defineComponent } from "vue";
import { useRouter, useRoute } from "vue-router";
import HelloWorld from "@/components/HelloWorld.vue";
export default defineComponent({
  name"App",
  components: { HelloWorld },
  setup(props: any, content: any) {
    return {};
  },
});
</script>
<style lang="scss">
#app {
  text-align: center;
}
</style>

2、setup的两个参数(props,context)

关于setup的参数,官方文档有详尽的介绍。

具体,请参照:https://v3.cn.vuejs.org/guide/composition-api-setup.html

(1):props

setup 函数中的 props 是响应式的,当传入新的 props 时,它将被更新。

这个就是父组件传入子组件的参数。具体,请参照上方的代码。

看到这里,其实你可以停下来,自己搭建一个小demo先试一下。

(2):context

这个参数名称不是固定的,叫什么都行。

传递给 setup 函数的第二个参数是 context。

context 是一个普通的 JavaScript 对象,它暴露三个组件的 property:

export default {
  setup(props, context) {
    // Attribute (非响应式对象)
    console.log(context.attrs)
 
    // 插槽 (非响应式对象)
    console.log(context.slots)
 
    // 触发事件 (方法)
    console.log(context.emit)
  }
}

这里我只用到了emit(触发事件),这个可以用作将子组件的值传递给父组件。具体我们之后组件那部分再说。

3、setup中没有办法访问到this

this在setup中不可用,方法和声明周期都可以写在setup中,如果在方法中访问setup中的变量时,直接变量名就可以使用。

方法名和变量名要在setup中return出去才可以正常执行。

4、setup()是函数,具有return,return函数中定义的变量,把其暴露给模板

具体,请参见第一部分的示例代码。

ref关键字

在setup函数中,可以使用ref函数,用于创建一个响应式数据,当数据发生改变时,Vue会自动更新UI。

一定要注意,ref创建的是一个响应式数据。

这个在Vue3.0中非常灵活,几乎是怎么玩都行的。

具体后边还会说到,这里大概说一下基本用法。

1、引入ref

import {
    ref,
from "vue";

2、使用注意点

在 Vue 的模板中使用 ref 的值不需要通过 .value 获取 (Vue 会通过自动给 ref 的值加上 .value)。

在 js 中使用 ref 的值必须使用 .value 获取。

上方代码实现效果:

Vue3详细篇

reactive与toRefs函数

上一部分中我们说到的ref只是作为单个变量的响应。

Vue3.0中还为我们提供了一个对象式响应的reactive函数 。

Reactive函数创建一个响应式对象。

reactive函数

其实,在我看来,reactive函数 就是Vue3.0为我们提供的替代Vue2.0中data的一个函数,因此,在这里,我不建议将方法也写到reactive函数中。

reactive函数中只写数据即可。

1、引入

import {
    ref,
    reactive,
from "vue";

2、实例

我这里使用上一部分中我们测试使用的代码来做示例,刚好对比一下ref与reactive在写法上边的区别。

src/components/HelloWorld.vue

<template>
  <h1>{{ msg }}</h1>
  <!-- <button @click="count++">count is: {{ count }}</button> -->
  <!-- 模板语法需要使用data.属性来调用 -->
  <button @click="data.count++">count is: {{ data.count }}</button>
  <p>
    Edit <code>components/HelloWorld.vue</code> to test hot module replacement.
  </p>
  <button @click="clickMe()">点我弹窗</button>
</template>
 
<script lang='ts'>
import { ref, reactive, defineComponent } from "vue";
export default defineComponent({
  name"HelloWorld",
  props: {
    msgString,
  },
  // 使用setup 代替 data
  // 因为我这里使用的是typescript,因此需要给参数指定类型
  setup(props: any, context: any) {
    // ref 定义响应式数据
    // let count = ref(0);

    // reactive 创建响应式对象
    let data = reactive({
      // 定义响应式数据
      count0,
    });

    const clickMe = () => {
      // 使用ref关键字绑定的变量,赋值 的时候必须使用.value
      // count.value++;
      // 调用 reactive 定义对象的参数的时候需要使用对象.来调用
      data.count++;
      alert("hi");
    };
    return {
      data,
      clickMe,
    };
  },
});
</script>

3、和ref主要区别

  • 在js中调用需要使用data.属性来赋值使用。
  • 在Vue模板语法中,也需要使用data.属性来赋值使用。

toRefs函数

模板语法中需要使用data.属性来调用属性显示,相对来说就比较麻烦。

其实我们可以使用ES6中的扩展运算符来对其进行解构处理,但是,这样吧解构之后,便不再具有响应式的属性,这个不行。

Vue3.0为我们提供了toRefs函数可以解决这个问题。

1、引入

import {
    ref,
    reactive,
    toRefs
from "vue";

2、示例

src/components/HelloWorld.vue

<template>
  <h1>{{ msg }}</h1>
  <!-- <button @click="count++">count is: {{ count }}</button> -->
  <!-- 模板语法需要使用data.属性来调用 -->
  <button @click="count++">count is: {{ count }}</button> <br>
  <button @click="data.count++">count is: {{ data.count }}</button>
  <p>
    Edit <code>components/HelloWorld.vue</code> to test hot module replacement.
  </p>
  <button @click="clickMe()">点我弹窗</button>
</template>
 
<script lang='ts'>
import { ref, reactive, toRefs, defineComponent } from "vue";
export default defineComponent({
  name"HelloWorld",
  props: {
    msgString,
  },
  // 使用setup 代替 data
  // 因为我这里使用的是typescript,因此需要给参数指定类型
  setup(props: any, context: any) {
    // ref 定义响应式数据
    // let count = ref(0);

    // reactive 创建响应式对象
    let data = reactive({
      // 定义响应式数据
      count0,
    });

    const clickMe = () => {
      // 使用ref关键字绑定的变量,赋值 的时候必须使用.value
      // count.value++;
      // 调用 reactive 定义对象的参数的时候需要使用对象.来调用
      data.count++;
      alert("hi");
    };
    // 使用toRefs函数对data对象进行包装,确保使用扩展运算符进行解构之后,仍具有响应式
    let param = toRefs(data);
    return {
      data,
      ...param,
      clickMe,
    };
  },
});
</script>

具体的使用,最好自己试一下,然后根据自己以往做过的项目,想到对应的应用场景。

生命周期及钩子函数

Vue3详细篇

什么是生命周期

Vue实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、卸载等一系列过程,我们称这是Vue的生命周期。

通俗说就是Vue实例从创建到销毁的过程,就是生命周期。

在Vue的整个生命周期中,它提供了一系列的事件,可以让我们在事件触发时注册js方法,可以让我们用自己注册的js方法控制整个大局,在这些事件响应方法中的this直接指向的是vue的实例。

上图,对生命周期图的标注

特别值得注意的是created钩子函数和mounted钩子函数的区别

生命周期钩子函数触发时间

1、beforeCreate

在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。

2、created

实例已经创建完成之后被调用。

在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。

然而,挂载阶段还没开始,$el 属性目前不可见。

3、beforeMount

在挂载开始之前被调用:相关的 render 函数首次被调用。

4、mounted

el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。

5、beforeUpdate

数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。

你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。

6、updated

由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。

该钩子在服务器端渲染期间不被调用。

7、beforeDestroy

实例销毁之前调用。

在这一步,实例仍然完全可用。

8、destroyed

Vue 实例销毁后调用。

调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

该钩子在服务器端渲染期间不被调用。

Vue3.0中的生命周期函数

引入

Vue3.x 生命周期在调用前需要先进行引入,我们先暂时演示前五个生命周期。

import {
    ref,
    reactive,
    toRefs,
    onBeforeMount,// 在组件挂载之前执行的函数
    onMounted,
    onBeforeUpdate,// 在组件修改之前执行的函数
    onUpdated,
    onBeforeUnmount,// 在组件卸载之前执行的函数
    onUnmounted
from "vue";

setup函数的生命周期

setup 这个函数是在 beforeCreatecreated 之前运行的,所以你可以用它来代替这两个钩子函数。

为了看出钩子函数执行的时机,我在setup()函数里,编写了下面的代码:

我这里仍然使用上一部分中的 HelloWorld.vue  文件做修改测试:

src/components/HelloWorld.vue

<template>
  <h1>{{ msg }}</h1>
  <!-- <button @click="count++">count is: {{ count }}</button> -->
  <!-- 模板语法需要使用data.属性来调用 -->
  <button @click="count++">count is: {{ count }}</button> <br />
  <button @click="data.count++">count is: {{ data.count }}</button>
  <p>
    Edit <code>components/HelloWorld.vue</code> to test hot module replacement.
  </p>
  <button @click="clickMe()">点我弹窗</button>
</template>
 
<script lang='ts'>
import {
  ref,
  reactive,
  toRefs,
  onBeforeMount, // 在组件挂载之前执行的函数
  onMounted,
  onBeforeUpdate, // 在组件修改之前执行的函数
  onUpdated,
  onBeforeUnmount, // 在组件卸载之前执行的函数
  onUnmounted,
  defineComponent,
from "vue";
export default defineComponent({
  name"HelloWorld",
  props: {
    msgString,
  },
  // 使用setup 代替 data
  // 因为我这里使用的是typescript,因此需要给参数指定类型
  setup(props: any, context: any) {
    
    console.log("1-开始创建组件-----setup()");

    // ref 定义响应式数据
    // let count = ref(0);

    // reactive 创建响应式对象
    let data = reactive({
      // 定义响应式数据
      count0,
    });

    const clickMe = () => {
      // 使用ref关键字绑定的变量,赋值 的时候必须使用.value
      // count.value++;
      // 调用 reactive 定义对象的参数的时候需要使用对象.来调用
      data.count++;
      alert("hi");
    };

    onBeforeMount(() => {
      console.log("2-组件挂载到页面之前执行-----onBeforeMount()");
    });

    onMounted(() => {
      console.log("3-组件挂载到页面之后执行-----onMounted()");
    });
    onBeforeUpdate(() => {
      console.log("4-组件更新之前-----onBeforeUpdate()");
    });

    onUpdated(() => {
      console.log("5-组件更新之后-----onUpdated()");
    });

    // 使用toRefs函数对data对象进行包装,确保使用扩展运算符进行解构之后,仍具有响应式
    let param = toRefs(data);
    return {
      data,
      ...param,
      clickMe,
    };
  },
});
</script>
Vue3详细篇

当然,Vue3.0中对Vue2.0的写法是完全兼容的,你也可以在setup之外写Vue2.0的生命周期函数。

<template>
  <h1>{{ msg }}</h1>
  <!-- <button @click="count++">count is: {{ count }}</button> -->
  <!-- 模板语法需要使用data.属性来调用 -->
  <button @click="count++">count is: {{ count }}</button> <br />
  <button @click="data.count++">count is: {{ data.count }}</button>
  <p>
    Edit <code>components/HelloWorld.vue</code> to test hot module replacement.
  </p>
  <button @click="clickMe()">点我弹窗</button>
</template>
 
<script lang='ts'>
import {
  ref,
  reactive,
  toRefs,
  onBeforeMount, // 在组件挂载之前执行的函数
  onMounted,
  onBeforeUpdate, // 在组件修改之前执行的函数
  onUpdated,
  onBeforeUnmount, // 在组件卸载之前执行的函数
  onUnmounted,
  defineComponent,
from "vue";
export default defineComponent({
  name"HelloWorld",
  props: {
    msgString,
  },
  // 使用setup 代替 data
  // 因为我这里使用的是typescript,因此需要给参数指定类型
  setup(props: any, context: any) {
    console.log("1-开始创建组件-----setup()");

    // ref 定义响应式数据
    // let count = ref(0);

    // reactive 创建响应式对象
    let data = reactive({
      // 定义响应式数据
      count0,
    });

    const clickMe = () => {
      // 使用ref关键字绑定的变量,赋值 的时候必须使用.value
      // count.value++;
      // 调用 reactive 定义对象的参数的时候需要使用对象.来调用
      data.count++;
      alert("hi");
    };

    onBeforeMount(() => {
      console.log("2-组件挂载到页面之前执行-----onBeforeMount()");
    });

    onMounted(() => {
      console.log("3-组件挂载到页面之后执行-----onMounted()");
    });
    onBeforeUpdate(() => {
      console.log("4-组件更新之前-----onBeforeUpdate()");
    });

    onUpdated(() => {
      console.log("5-组件更新之后-----onUpdated()");
    });

    // 使用toRefs函数对data对象进行包装,确保使用扩展运算符进行解构之后,仍具有响应式
    let param = toRefs(data);
    return {
      data,
      ...param,
      clickMe,
    };
  },
  beforeCreate() {
    console.log("1-组件创建之前-----beforeCreate()");
  },
  beforeMount() {
    console.log("2-组件挂载到页面之前执行-----BeforeMount()");
  },
  mounted() {
    console.log("3-组件挂载到页面之后执行-----Mounted()");
  },
  beforeUpdate() {
    console.log("4-组件更新之前-----BeforeUpdate()");
  },
  updated() {
    console.log("5-组件更新之后-----Updated()");
  },
});
</script>

Vue3与Vue2生命周期的对比

到底使用Vue2.x还是Vue3.x的生命周期钩子函数?

其实这个无所谓,但是切记不要混用,如果你用 setup 这种Vue3的生命周期函数,就不要再使用Vue2的了。

为了你更好的掌握,这里作了一个函数对比:

beforeCreate  -> setup()
created       -> setup()
beforeMount   -> onBeforeMount
mounted       -> onMounted
beforeUpdate  -> onBeforeUpdate
updated       -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed     -> onUnmounted
activated     -> onActivated
deactivated   -> onDeactivated
errorCaptured -> onErrorCaptured

通过这样对比,可以很容易的看出 vue3 的钩子函数基本是再 vue2 的基础上加了一个on,但也有两个钩子函数发生了变化。

BeforeDestroy变成了onBeforeUnmount

destroyed变成了onUnmounted

尤雨溪的介绍是Unmount比Destroy更形象,也和beforeMount相对应。

自定义组件子父传值

在创建项目的时候,官方给出的页面,其实就给出了一个自定义的组件HelloWorld,里边包含了父传子传值。

setup与ref函数 这部分中,setup的第二个参数context对象为我们提供了可触发事件emit,我们可以利用emit将子组件中的值传递给父组件。

多说一句,使用Vite来搭建的项目,就现阶段学习来说,其实挺好用的。

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

子组件:

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

<script lang="ts">
import { ref, defineComponent, toRefs, watch } from "vue";
export default defineComponent({
  name"Emit",
  props: {
    msg: {
      typeString,
      requiredtrue,
    },
  },
  setup(props: any, content: any) {
    const param = toRefs(props);

    const change = () => {
      content.emit("change-msg""123456");
    };

    return {
      ...param,
      change,
    };
  },
});
</script>

父组件:

<template>
  <div>
    <img alt="Vue logo" src="@/assets/logo.png" />
    <!-- <HelloWorld msg="Hello Vue 3.0 + Vite + TypeScript" /> -->
    <Emit :msg="msg" @change-msg="changeMsg" /> <br>
    <h1>这里是父组件的值:{{ msg }}</h1>
  </div>
</template>
<script lang = "ts">
import { ref, defineComponent } from "vue";
import HelloWorld from "@/components/HelloWorld.vue";
import Emit from "@/components/Emit.vue";
export default defineComponent({
  name"App",
  props: {
    msgString,
  },
  components: { HelloWorld, Emit },
  setup(props: any, content: any) {
    const msg = ref("Hello Vue 3");
    const changeMsg = (newMsg: string) => {
      msg.value = newMsg;
      console.log(msg);
    };

    return {
      msg,
      changeMsg,
    };
  },
});
</script>
<style lang="scss">
#app {
  text-align: center;
}
</style>

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

效果:

Vue3详细篇

provide和inject函数

父组件传更深的后代组件,一般往深度层级传递值,有这两种方式:

  • provide / inject
  • vuex

provide / inject

一看到“深”这个字,大家肯定会想到Vue2中的 provide / inject选项。

没错,这套逻辑在Vue3中同样适用,这两个选项变成了 两个方法。

provide 允许我们向当前组件的所有后代组件,传递一份数据,所有后代组件能够通过inject 这个方法来决定是否接受这份数据。

实际应用场景

主要应用的场景有两种:

  • 一种深度传递一个参数或者一个函数的时候。
  • 另一种是给插槽上不确定性的组件传参的时候。

重点说一下给插槽上的组件传参。

先实现一个最外层的ValidateForm组件,它主要负责接受一整个表单数据和整个表单数据的校验规则。

其内部提供了一个插槽,用于放置一些不确定性的组件。

还有一个ValidateFormItem组件可以接受一个字段名,通过这字段名准确知道需要校验哪个字段(tips:功能其实和element-ui类似)。

组件化开发,需要将参数和功能进行解耦,所以我们这样来设计:

  • ValidateForm:model,rules,只管接受整份表单的数据和校验规
  • ValidateFormItem:prop,只管接受字段名,只需知道自己需要验证哪一个字段

src/App.vue

<template>
  <ValidateForm ref="validateFormRef" :model="formData" :rules="rules">
    <ValidateFormItem label="用户名" prop="username">
      <!-- field组件 -->
    </ValidateFormItem>
    <ValidateFormItem label="密码" prop="password">
      <!-- field组件 -->
    </ValidateFormItem>
  </ValidateForm>
</template>
<script lang = "ts">
import { ref, reactive, defineComponent } from "vue";
import ValidateForm from "@/components/ValidateForm.vue";
import ValidateFormItem from "@/components/ValidateFormItem.vue";
export default defineComponent({
  name"App",
  components: {
    ValidateForm,
    ValidateFormItem,
  },
  setup(props: any, content: any) {
    const formData = reactive({
      username"lijing",
      password"123456",
    });
    const rules = reactive({
      username: [
        { requiredtruemessage"请输入用户名" },
        { min3message"用户名长度不能小于3" },
      ],
      password: [
        { requiredtruemessage"请输入密码" },
        { min6message"密码长度不能小于6" },
      ],
    });
    return {
      formData,
      rules,
    };
  },
});
</script>
<style lang="scss">
#app {
  text-align: center;
}
</style>

如果 ValidateFormItem 组件需要通过prop去校验某个字段,那它就需要拿到那份表单的数据,通过formData[prop]去取到那个字段的值,那这份formData从哪里来呢?

首先不可能每写一个ValidateFormItem组件都传递一份。

因为,实际开发中我们并不能确定在ValidateForm下要写多少个ValidateFormItem组件,如果每写一个都手动传递一份表单的数据,这些写起来就会多了很多冗余的代码而且也很麻烦。

所以,就由ValidateForm这个组件独立接受并分发下来。

src/components/ValidateForm.vue

所以我们需要ValidateForm来向下分发数据。

<template>
  <form>
    <slot></slot>
  </form>
</template>


<script lang="ts">
import { defineComponent, provide } from "vue";

// Symbol 类型是 ES6 引入的一种数据类型,表示独一无二的值,是 JavaScript 语言的第七种数据类型。
// 目前我所了解的两个用途:
//  1、作为属性名或变量名,避免重名带来的问题;
//  2、作为内置对象的特定方法的属性名,方便开发者对其改写。 
export const modelKey = Symbol();
export const rulesKey = Symbol();

export default defineComponent({
  name"ValidateForm",
  props: {
    model: {
      typeObject,
    },
    rules: {
      typeObject,
    },
  },
  setup(props) {
    // 向后代发放数据
    provide(modelKey, props.model);
    provide(rulesKey, props.rules);

    return {
        
    };
  },
});
</script>

src/components/ValidateFormItem.vue

ValidateFormItem接受上面传递的数据。

<template>
    <div>
        <slot>这里是插槽,显示默认值</slot>
    </div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, inject, provide } from "vue";
import { modelKey, rulesKey } from "./ValidateForm.vue";

export default defineComponent({
  name"ValidateFormItem",
  props: {
    labelString,
    required: {
      typeBoolean,
      defaultfalse,
    },
    propString,
  },
  setup(props: any, content: any) {
    // 接受ValidateForm传下来的数据
    const model = inject<any>(modelKey, ref({}));
    const rules = inject<any>(rulesKey, ref({}));

    // 根据props.prop在model和rules分别取出需要 校验的数据 和 校验的规则
    console.log(model[props.prop]);
    console.log(rules[props.prop]);
    // 数据校验的逻辑

    return {
      //...
    };
  },
});
</script>

效果:

Vue3详细篇

provide / inject总结

这里提一下它的缺点,就是不能解决兄弟组件之间的通信。

这里的provide和inject我也只是了解,在我的项目中并没有实际应用,我使用的reactive替代vuex的方式来实现更深层次的组件传值。这个在后边会介绍。

补充

父组件可以通过ref创建响应式数据通过provide 共享给子组件

main.ts全局引入axios

vue2中可以将我们需要的插件挂载到vue的主链上(配置成全局属性),然后通过this调用,但是在vue3的ts中使用这样的配置方法的话是没法通过编译的,这个时候我们就需要拓展属性。

下面是在vue3.0定义源文件找到的一段说明注释:

/**
 * Custom properties added to component instances in any way and can be accessed through `this`
 *
 * @example
 * Here is an example of adding a property `$router` to every component instance:
 * ```ts
 * import { createApp } from 'vue'
 * import { Router, createRouter } from 'vue-router'
 *
 * declare module '@vue/runtime-core' {
 *   interface ComponentCustomProperties {
 *     $router: Router
 *   }
 * }
 *
 * // effectively adding the router to every component instance
 * const app = createApp({})
 * const router = createRouter()
 * app.config.globalProperties.$router = router
 *
 * const vm = app.mount('#app')
 * // we can access the router from the instance
 * vm.$router.push('/')
 * ```
 */

改写Vue的原型属性

vue3.x + typescript 配置全局axios属性:

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';
import axios from 'axios'

const app = createApp(App)
app.config.globalProperties.$http = axios
app.use(router)
    .use(store)
    .mount('#app')

然后在组件中引用,注意 Vue3.x中是没有 this 的,使用getCurrentInstance来获取上下文。

const { proxy } = getCurrentInstance() 这里的proxy相当于this

src/App.vue

<template>
  <div></div>
</template>
<script lang="ts">
import { defineComponent, getCurrentInstance } from "vue";
export default defineComponent({
  name"App",
  setup(props: any, content: any) {
    const { proxy }: any = getCurrentInstance();
    proxy.$http.get("api/get").then((response: any) => {
      console.log(response);
    });
  },
});
</script>
<style>
</style>

使用vue-axios插件

安装:

npm install --save axios vue-axios

首先在主入口文件main.ts中引用:

import { createApp } from 'vue'
import App from './App.vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
const app = createApp(App)
app.use(VueAxios,axios);
...

然后在组件中引用,注意vue3.x没有this

axios.get('api/get')
.then((response)=>{
    console.log(response)
})

使用计算属性computed和监听属性watch

computed

首先,尝试一下计算属性computed

第一种写法

src/App.vue

<template>
  <div id="testDiv">
    <p><input type="text" v-model="age" /></p>
    <p><input type="text" v-model="nextAge" /></p>
  </div>
</template>
 
<script lang = "ts">
import { computed, ref, defineComponent } from "vue";
export default defineComponent({
  setup() {
    const age = ref(18);
    const nextAge = computed(() => {
      return age.value * 1 + 1;
    });
    return {
      age,
      nextAge,
    };
  },
});
</script>

<style scoped>
</style>

修改age,nextAge会跟着自动+1

Vue3详细篇

但如果修改nextAge,会有警告:计算属性不能修改

Vue3详细篇

第二种写法

<template>
  <div id="testDiv">
    <p><input type="text" v-model="age" /></p>
    <p><input type="text" v-model="nextAge" /></p>
  </div>
</template>
 
<script lang = "ts">
import { computed, ref, defineComponent } from "vue";
export default defineComponent({
  setup() {
    const age = ref(18);
    const nextAge = computed({
      get() {
        return +age.value + 1;
      },
      set(value: number) {
        console.log(value); // 输出新修改的值
        return (age.value = value - 1);
      },
    });
    return {
      age,
      nextAge,
    };
  },
});
</script>

<style scoped>
</style>

另一种写法:

使用computed和watch,一定记得先引入

computed属性

使用 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象。

<template>
  <div id="testDiv">
    <p>原来值:{{ count }}</p>
    <p>计算属性更改的值:{{ twoNumber }}</p>
  </div>
</template>
 
<script lang = "ts">
import { reactive, computed, toRefs, watch, defineComponent } from "vue";
export default defineComponent({
  setup() {
    const state: any = reactive({
      count1,
      twoNumber: computed(() => state.count * 2),
    });

    //暴露出去给外界使用
    //使用toRefs解构响应式对象数据,实现实时更新
    return {
      ...toRefs(state),
    };
  },
});
</script>

<style scoped>
</style>

watch

watch属性 与 vue2中的 this.$watch (以及相应的 watch 选项) 完全等效。

<template>
  <div id="testDiv">
    <p>
      原来值:
      <input v-model="count" />
    </p>
    <p>计算属性更改的值:{{ twoNumber }}</p>
  </div>
</template>
 
<script lang = "ts">
import { reactive, computed, toRefs, watch, defineComponent, ref } from "vue";
export default defineComponent({
  setup() {
    const state: any = reactive({
      count0,
      twoNumber: computed(() => state.count * 2),
    });

    watch(
      () => state.count,
      (newValue, oldValue) => {
        console.log("改变了");
        console.log("我是新的值", newValue);
        console.log("我是旧的值", oldValue);
      }
    );

    // 暴露出去给外界使用
    // 使用toRefs解构响应式对象数据,实现实时更新
    return {
      ...toRefs(state),
    };
  },
});
</script>

<style scoped>
</style>

watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。

watch在监听 ref 类型时和监听 reactive 类型时watch函数的写发有所不一样。

watch在监听 ref 类型 时:

<script>
import {ref, watch} from 'vue'
export default {
    setup() { 
        const state = ref(0)

        watch(state, (newValue, oldValue) => {
          console.log(`原值为${oldValue}`)
          console.log(`新值为${newValue}`)
          /* 1秒后打印结果:
                  原值为0
                  新值为1
          */

        })

        // 1秒后将state值+1
        setTimeout(() => {
          state.value ++
        }, 1000)
    }
}
</script>

watch在监听 reactive类型时:

<script>
import {reactive, watch} from 'vue'
export default {
    setup() { 
        const state = reactive({count0})

        watch(() => state.count, (newValue, oldValue) => {
          console.log(`原值为${oldValue}`)
          console.log(`新值为${newValue}`)
          /* 1秒后打印结果:
                  原值为0
                  新值为1
          */

        })

        // 1秒后将state.count的值+1
        setTimeout(() => {
          state.count ++
        }, 1000)
    }
}
</script>

watchEffect 它与 watch 的区别主要有以下几点:

  1. 不需要手动传入依赖
  2. 每次初始化时会执行一次回调函数来自动获取依赖
  3. 无法获取到原值,只能得到变化后的值
<script>
import {reactive, watchEffect} from 'vue'
export default {
    setup() { 
          const state = reactive({ count0name'zs' })

          watchEffect(() => {
          console.log(state.count)
          console.log(state.name)
          /*  初始化时打印:
                  0
                  zs

            1秒后打印:
                  1
                  ls
          */

          })

          setTimeout(() => {
            state.count ++
            state.name = 'ls'
          }, 1000)
    }
}
</script>

根据以上特征,我们可以自行选择使用哪一个监听器

另:watch和watchEffect监听器在同一个页面中都可以使用多次,对于分别监听多个变量的时候。

封装axios

关于axios

  • 从浏览器中创建XMLHttpRequest
  • 从node.js发出http请求
  • 支持Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持防止CSRF/XSRF

axios封装

关于Axios的封装这部分涉及到与后端的一些约定。

什么约定呢?就是在我们请求接口的时候,后端会返回给我们一个状态码。

这个不同公司有自己的框架封装,返回的code也不一样。

这样,我们就可以在封装axios的时候提前通过请求返回值code做一些预处理。

这个自行查阅资料:https://www.baidu.com/s?wd=Vue3%E5%B0%81%E8%A3%85axios ,因为老子实在不想封装了。

使用reactive ref 替代vuex状态管理

之前在使用vue2的时候有用到vuex状态管。

用的比较多的地方是子父组件传值,或者爷孙组件传值,就是共享状态。

vuex在这里的作用主要还是为了简化组件中的代码的复杂度。

所用的值使用vuex统一管理,对后期的维护以及扩展性都有很大的帮助。

Vuex为我们提供了大概以下几个功能:

  1. 集中式存储管理应用的「所有组件」的「状态」
  2. 保证状态以「可预测」的方式「发生变化」
  3. 与调试工具集成,提供功能:time-travel、状态快照导入导出

共享状态必须符合两个条件:

  • 响应式:当状态改变时,使用它们的组件也应更新
  • 可用性:可以在任何组件中访问状态

在vue3中,其为我们提供了更加轻量化的ref,reactive响应式方法。

Common.ts

// 公共状态文件:替代vuex
import { reactive } from 'vue';

export const common = reactive({ 
    // 内容
});

只要在你要使用公共状态的地方引入Common.ts,那么你就可以访问到common.ts中我们定义的变量中的值,就是共享状态。

vue3中标还为我们提供了provide、inject依赖注入功能。

参考之前的 provide和inject函数  部分。

这是怎么注入的呢?我们还是看图来说话:

Vue3详细篇

我们都知道 Vue 是一颗「组件树」,我们只要保证是「父节点」 provide,那么它的「子节点」就一定可以通过 inject 获取到。

举例:

A provide,B 可以 inject,C 可以 inject,D 可以 inject

B provide,D 可以 inject

D provide,没有其它节点可以 inject

C provide,没有其它节点可以 inject


Vue3详细篇


原文始发于微信公众号(程序员阿晶):Vue3详细篇

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

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

(0)
小半的头像小半

相关推荐

发表回复

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