Vue - Vue3的特性介绍以及项目的简单创建

前言

近期,我需要支援兄弟组帮忙写页面,而对应使用的前端框架是Vue3,不再是我自己熟悉的React。趁此机会,复习一下Vue(会,但是不熟)以及学习下Vue3相关知识。这里我主要看的是大圣老师的《玩转 Vue 3 全家桶》

一. 前端框架的发展史

这一块看下来,受益匪浅。因此我整理了一下大圣的文章。我们先来看下前端的发展史:

石器时代:

  1. 前端网页基本上都是静态页面。前端工作则一般都是写一下HTML+CSS样式。
  2. 但是随着后端的复杂度不断的增加,出现分层(MVC),需要在前端模板显示需要的后端数据。因此就有了JSP,就有了动态网页。但是它有着一定的缺点:任何数据更新都需要刷新页面。耗时。
  3. 推出了Ajax技术,即允许我们异步获取数据并且刷新页面。因此前端不再受限于后端。Web2.0的时代到来。

铁器时代:

  1. 铁器时代到来的标志就是JQuery的出现。它的使用步骤很简单:靠$来找到某个元素,再对该元素进行DOM操作。JQuery成为了前端初步的主流技术。

工业时代:

  1. 随着前端规模的逐渐升级,前端需要进行规模化的时候。2009年就出现了NodeJs以及AngualarJs(想了想我当时实习的时候,用的就是它,那不知不觉中,我三大框架都算接触过了)
  2. 三大框架的产生:Angular、Vue、React

1.1 三大前端框架

三大框架,他们的主要区别就是针对同一个问题有着不同的解决方案。即:当数据发生变化后,我们如何取通知页面并进行更新渲染?

① Angular

先说下Angular,也是我自己最先接触的框架:

  1. Angular采取的是脏检查每次用户交互时都检查一次数据是否变化,有变化就去更新 DOM

② Vue

其次是Vue

  1. Vue1采取响应式:初始化的时候,每个数据都有自己专属的Watcher监听器。这样数据发生改变的时候,就能精确地知道哪个数据发生改变,即可以针对性的修改对应的DOM。如图:
    在这里插入图片描述

③ React

React对于数据监听的实现:

  1. 初始化的时候,在浏览器DOM之上,创建一个虚拟DOM:用一个JSON对象来描述整个DOM树。通过虚拟DOM的计算来识别出变化的数据,再进行精确的修改。

例如这么一段页面代码:

<div id = "app">
    <p class = "item">Item1</p>
    <div class = "item">Item2</div>
</div>

对应的虚拟DOM对象为:

{
  tag: "div",
  attrs: {
    id: "app"
  },
  children: [
    {
      tag: "p",
      attrs: { className: "item" },
      children: ["Item1"]
    },
    {
      tag: "div",
      attrs: { className: "item" },
      children: ["Item2"]
    }
  ]
}

1.2 Vue 和 React 对比

首先,在数据发生变化之后,通知页面进行更新的方式不同

  • Vue数据改变,框架主动告知你哪些数据被修改。(每个数据都对应着一个监听器)
  • React只能通过新老数据的计算来得知数据的变化。(虚拟DOMDiff算法,这里可以看下我的这篇文章:ReactDOM的Diffing算法

但是从他们的实现角度来说,就有各自的优劣势了。我们来说下性能瓶颈问题

  • Vue:每个响应式数据和一个Watcher进行绑定监听。比较损耗性能。在项目比较大的情况下,影响性能。
  • React:采取虚拟DOMDiff计算,如果虚拟DOM树太大了,计算时间超过了16.6ms。(1秒 / 60帧),那么就容易造成卡顿。

那么为了解决这种性能瓶颈问题,两大框架又引入了不同的解决方案:

  • Vue:为了解决响应式数据过多导致的内存占用问题,推出Vue2,使用虚拟DOM组件之间的变化,通过响应式来更新通知,而组件内部的数据变化则通过虚拟DOM来更新页面。将 Watcher 控制在组件级。 如图:
    在这里插入图片描述

  • React:参考操作系统的时间分片概念,引入Fiber架构。即将虚拟DOM树进行微观化,变成链表在浏览器的空闲时间来计算Diff。一旦有级别更高的任务,可以暂停计算,将主进程的控制权交还给浏览器。等待下次空闲时间。 如图:
    在这里插入图片描述

在书写模板上,两者也有区别:

  • Vue:采用template,例如:v-for,v-if等语法。语法固定。
  • React:采用JSX,拥有JS的全部特性。

备注:

  1. 目前浏览器大多是 60Hz(60帧/s)。
  2. ReactVue都是单向的数据流,和我们通常说的双向绑定不是一个东西,指的是Vue中的v-model这样的语法糖。

1.3 Vue2 简单使用

安装命令:

npm install vue-cli -g

然后快速创建:

>vue init webpack "vue2-test"

如图:
在这里插入图片描述

我们来看下Vue的基础用法。首先我们要npm install下,再npm run dev启动。下文内容只需要修改App.vue文件即可。

1.3.1 数据展示

需求内容:

  1. 在输入框输入内容时候,另外一个区域能够实时的更新显示文本框内容。

编码需要注意的点:

  1. 首先需要在data模块中声明数据。在展示的标签中,例如Input框,通过v-model进行数据的双向绑定。
  2. HTML模板里面,使用{{}}来标记数据即可显示。

案例如下:

<template>
  <div id="app">
    <h1>{{title}}</h1>
    <input type="text" v-model="title" />
  </div>
</template>

<script>
export default {
  name: 'App',
  // 数据声明的地方
  data: function () {
    return {
      title: '',
    }
  }
}
</script>

效果如图:
在这里插入图片描述

1.3.2 集合渲染和用户交互

需求:

  1. 我们能够展示一个清单列表,表示我们即将的事情(集合)。
  2. 在文本框中输入的内容,进行回车后,能够将其添加到清单列表中。同时页面能够更新列表。
  3. 每个清单前面有一个复选框,勾上代表这个清单已经做过。同时计算所有清单中未完成和完成的个数统计。
  4. 有一个按钮可以清除所有的清单,如果清除了,页面显示“暂无要做清单”。

编码需要注意的点:

  1. 数组在vue中的循环采用v-for来进行遍历。
  2. Input文本框需要绑定交互事件。vue中使用@来标记用户的交互操作。@click是点击操作。@keydown是键盘敲击。
  3. vue 中,冒号 : 开头的属性是用来传递数据的。 事件相关的函数一般在method模块中编写。
  4. 还有一个属性为computed,一般用于放计算属性,内置缓存功能。如果依赖数据没变化,多次使用计算属性会直接返回缓存结果。
  5. vue可以使用v-if,v-else结合的方式,来判断页面展示的内容。

案例如下:

<template>
  <div id="app">
    <h1>{{ title }}</h1>
    <input type="text" v-model="title" @keydown.enter="addTodo" /> <button @click="clear">清除清单</button>
    <!-- 如果清单数>0,展示清单内容 -->
    <div v-if="todoList.length > 0">
      <ul>
        <!-- 还要绑定下唯一标识key -->
        <li v-for="(todo, index) in todoList" v-bind:key="index">
          <input type="checkbox" v-model="todo.done" />
          <span :class="{ done: todo.done }"> {{ todo.title }}</span>
        </li>
      </ul>
      <div style="color: red">未完成:&nbsp;&nbsp;{{ activeList }}</div>
      <div style="color: blue">已完成:&nbsp;&nbsp;{{ doneList }}</div>
    </div>
    <!-- 否则,清单数=0 -->
    <div v-else>
      <h1>暂无要做清单</h1>
    </div>
  </div>
</template>

<script>
export default {
  name: 'App',
  // 响应数据声明,记得要把所有变量都返回出去
  data: function () {
    return {
      title: '',
      todoList: [
        // done 代表是否处理过这个清单
        { title: '吃饭', done: false },
        { title: '购物', done: false },
        { title: '学习', done: false },
        { title: '睡觉', done: true }
      ]
    }
  },
  // 函数声明
  methods: {
    // 添加一个清单,清单名称title和Input框进行了v-model绑定,刚加入的清单属于未处理。
    addTodo () {
      this.todoList.push({ title: this.title, done: false })
      // 添加完成之后,清空文本框内容
      this.title = ''
    },
    clear () {
      this.todoList = []
    }
  },
  computed: {
    activeList () {
      return this.todoList.filter((v) => !v.done).length
    },
    doneList () {
      return this.todoList.filter((v) => v.done).length
    }
  }
}
</script>

<style>
#app {
  margin-left: 45%;
}
/* done的样式,就是文字变暗,然后加一条横线 */
.done {
  color: gray;
  text-decoration: line-through;
}
</style>

效果如下:
在这里插入图片描述
其实这些案例看下来,我们可以发现,在使用vue上,我们只需要关注数据本身的状态,无需关注数据如何进行状态更新。不像React。需要使用useState、useEffect等操作来完成响应式和监听。

二. Vue3简介和Vite脚手架的使用

再说Vue3之前,我们先来说下Vue2的一些问题。先从上面的案例出发,我们来看几个问题。

  1. 当代码量增多,尤其是响应式变量增多,而且还和函数进行关联绑定的时候。当代码行很多的时候,在代码编写的时候,无法避免的会在代码中上下滚条滚动,即反复横跳。编写不方便。 即所有的data、methods、computed 都在一个对象里配置,不易维护。
  2. Vue2采用Flow.js进行校验(做静态类型检查的),而Flow.js已经停止维护,目前主流的则使用TS来维护。
  3. Vue2 响应式是基于 Object.defineProperty() 实现的。它是对某个属性进行拦截来完成的。无法监听删除数据的操作,需要外部API辅助监听。

2.1 Vue3 新特性

Vu3在继承了Vue2具有的响应式、虚拟DOM、组件化等优点的情况下,在很多方面还有了改进。我们来探讨下。我们只说几个比较重要的地方。

2.1.1 响应式的实现方式

vue2中,如上文所说,是根据 Object.defineProperty() 实现的。我们来看一下实际的用法。案例如下:

let number = 18
let person = {
  name: '码农',
  sex: '男',
  age: 18
}

Object.defineProperty(person, 'age', {
  // 当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
  get() {
    console.log('有人读取age属性了,值是:', number)
    return number
  },
  // 当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
  set(value) {
    console.log('有人修改了age属性,值是:', value)
    number = value
  }
})
// 读取,调用到get函数
console.log(person.age)
// 赋值修改,调用到set函数
person.age = 20

脚本运行之后,结果如下:
在这里插入图片描述
vue2就是通过这样的拦截方式结合发布订阅来实现响应式功能的。vue3则使用了ES6ProxyAPI进行数据代理。通过 reactive() 函数给每⼀个对象都包⼀层 Proxy,通过 Proxy 监听属性的变化。案例如下:

let number = 18
let person = {
  name: '码农',
  sex: '男',
  age: 18
}

const p = new Proxy(person, {
  //当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
  get() {
    console.log('有人读取age属性了,值是:', number)
    return number
  },
  //当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
  set(value) {
    console.log('有人修改了age属性,值是:', value)
    number = value
  }
})
console.log(p.age)
p.age = 20

执行结果如下:
在这里插入图片描述
我们可以发现,使用Proxy的好处:

  1. Object.defineProperty() 只能监听一个对象的某个属性,而Proxy可以全局监听。
  2. Proxy还可以监听数组。

2.1.2 Vue3 支持碎片(Fragments)、

vue2中只能允许有一个根节点。

<template>
  <div id="app">1</div>
  <div>2</div>
</template>

否则就会报错:
在这里插入图片描述

vue3中则可以有多个根节点

2.1.3 使用 TypeScript 重构

我们知道,JS是弱类型的语言,一般编码的时候无需指定类型。而TS是强类型系统语言。带来了更方便的提示,并且让我们的代码能够更健壮。

2.1.4 Composition API 组合语法

vue2使用的是选项类型API(Options API),而vue3使⽤的是组合式API(Composition API)

首先看下一些属性的定义。vue2中的写法:data、methods、computed分开来写。

<template>
  <div id="app">
    <h1 @click="add">{{count}} * 2 = {{double}}</h1>
  </div>
</template>

<script>
export default {
  name: 'App',
  // 响应数据声明,记得要把所有变量都返回出去
  data: function () {
    return {
      count: 1
    }
  },
  // 函数声明
  methods: {
    add () {
      this.count++
    }
  },
  computed: {
    double () {
      return this.count * 2
    }
  }
}
</script>

vue3中则可以这么写在一起(可以先看2.2节,把项目创建出来即可):

方式一
<template>
  <div>
    <h1 @click="add">{{ state.count }} * 2 = {{ double }}</h1>
  </div>
</template>

<script  lang="ts">
import { reactive, computed, defineComponent } from "vue";
export default defineComponent({
  setup() {
    const state = reactive({ count: 1 });
    function add() {
      state.count++;
    }
    const double = computed(() => state.count * 2);
    return { state, add, double };
  },
});
</script>

方式二,使用<script setup>,无需将属性和函数return出去
<template>
  <div>
    <h1 @click="add">{{ state.count }} * 2 = {{ double }}</h1>
  </div>
</template>

<script setup lang="ts">
import { reactive, computed } from "vue";
const state = reactive({ count: 1 });
function add() {
  state.count++;
}
const double = computed(() => state.count * 2);
</script>

效果如下:
在这里插入图片描述
稍微总结下:

  • 所有API通过import来引入。
  • 可以把同一个功能模块的methods、data放到同一个函数中书写(setup函数),维护起来更轻松。如图:
    在这里插入图片描述
    setup函数可以使vue3中属性和方法的入口,它可以接受两个参数setup(props,context)
  • props :接收来自父组件传来的参数。
  • context :上下文,主要包含3个使用参数:attrs,emits,slots

一般需要将对象或者渲染函数return出去,入上文的案例。当然也有一种替代方式,就是使用 <script setup> 特性来清除这种return写法。同时我们还注意到,代码中已经没有this这样的语句了。

同时,生命周期钩子函数也有一定的不同:
在这里插入图片描述

2.1.5 vite 支持

虽然vite并不在vue3的包内,也并非和其强绑定。但是现在使用vue3的时候,基本上都会和vite一并使用。ViteWebpack是同一层级的框架。他们的作用相同。

Webpack 原理,就是根据你的 import 依赖逻辑,形成一个依赖图,然后调用对应的处理工具,把整个项目打包后,放在内存里再启动调试。因此需要预打包,在项目复杂程度不断增加的时候,启动项目的时间就会越来越长。而 Vite 就是为了解决这个时间资源的消耗问题出现的。打包原理如图:
在这里插入图片描述

在大部分浏览器已经默认支持ES6import语法的背景下,Vite只把首页依赖的文件,依次通过网络请求去按需加载获取。
在这里插入图片描述
这里是我启动vue2项目的时候,控制台的输出:我这里大概8秒钟。

在这里插入图片描述

这是我启动vue3项目的时候, 控制台的输出:大概1秒钟。
在这里插入图片描述

2.2 Vue3 简单使用(Vite 构建)

使用命令:

npm create vite@latest

会依次让你选择:

  1. 项目名称。
  2. 使用哪个框架。Vue、React等。
  3. 使用的语言。

结果如图:
在这里插入图片描述

2.2.1 全局组件的注册

我们可以随便创建一个自定义的组件:
在这里插入图片描述
注册完后,在vue3中,可以再入口处main.ts文件和中进行全局组件的注册:

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import MyComponent from './components/MyComponent.vue'

createApp(App)
	// 注册
    .component('MyComponent', MyComponent)
    .mount('#app')

这样在其他的任何一个组件当中,都可以直接显式地引用名称为MyComponent的组件了,如图:
在这里插入图片描述
效果如下:
在这里插入图片描述

2.2.2 响应式数据声明

先给出案例:

<template>
  <div>
    <h1 @click="add">{{ state.count }} * 2 = {{ double }}</h1>
  </div>
  <div>
    数字:{{num}}<button @click="add2" style="color:red;background:yellow">数字加1</button>
  </div>
  <MyComponent />
</template>

<script setup lang="ts">
import { reactive, computed, ref } from "vue";
let num = ref<number>(1);
const state = reactive({ count: 1 });
const add = () => {
  state.count++;
};
const add2 = () => {
  num.value++;
};
const double = computed(() => state.count * 2);
</script>

效果如下:
在这里插入图片描述

从上面的案例我们还注意到,我们使用了两种方式来声明不同的数据。

  • reactive:用于声明引用数据类型,返回的响应式数据本质是Proxy对象。
  • ref:用于声明基础数据类型或者引用数据类型。值保存在返回的响应式数据的value属性上。

其中衍生的还有这么几个函数,他们不是用来创建响应式的,而是用来延续响应式,即把响应式对象的数据进行拆分和扩散。

  • toRef:接收两个参数,响应式对象以及属性名称。
  • toRefs:接收一个参数,响应式对象,可以直接获得对应的Ref属性。
<template>
  <div>数字:{{ state.foo }}</div>
  <div>
    <button @click="add1">add1</button>
  </div>
  <div>
    <button @click="add2">add2</button>
  </div>
  <div>
    <button @click="add3">add3</button>
  </div>
</template>

<script setup lang="ts">
import { reactive, toRef, toRefs } from "vue";
const state = reactive({
  foo: 1,
  bar: 2,
});
const foorRef = toRef(state, "foo");
const { foo, bar } = toRefs(state);

const add1 = () => {
  foorRef.value++;
};

const add2 = () => {
  state.foo++;
};

const add3 = () => {
  foo.value++;
};
</script>

效果如下:都能对对应属性做出响应式更新。
在这里插入图片描述

2.2.3 项目架构和功能分类

  • Vue:负责核心页面的搭建。
  • Vuex:负责管理数据。
  • Vue-router:负责管理路由。

因此我们在上面的项目基础上,还需要安装Vuex以及Vue-router:

npm install vue-router@next vuex@next

一般我们的项目,有这么几个文件目录组成:
在这里插入图片描述

  • api:数据请求。
  • assets:静态资源。
  • components: 组件。
  • pages:页面。
  • router:路由配置。
  • storevuex数据。
  • utils:工具类。

1.我们现在pages目录下创建两个页面:
在这里插入图片描述

2.创建路由,我们在router文件下下面创建一个index.ts文件:

import { createRouter, createWebHashHistory } from 'vue-router';
import Home from '../pages/Home.vue'
import About from '../pages/About.vue'

const routes = [
    { path: '/', name: 'Home', component: Home },
    { path: '/about', name: 'About', component: About }
]

export default createRouter({
    history: createWebHashHistory(),
    routes
})
  • createRouter :用来新建路由实例。
  • createWebHashHistory :用来配置我们内部使用 hash 模式的路由,即 url 上会通过 # 来区分。

3.在入口处main.ts文件中添加路由配置:

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import MyComponent from './components/MyComponent.vue'
import router from './router/index'

createApp(App)
    .component('MyComponent', MyComponent)
    // 使用我们自定义的路由
    .use(router)
    .mount('#app')

4.最后修改首页:

<template>
  <div>
    <router-link to="/">首页</router-link> |     
    <router-link to="/about">关于</router-link>
  </div>
  <router-view></router-view>
</template>

来说下这几个标签:他们都是由 vue-router 注册的全局组件。我们可以直接拿来用。

  • router-link:负责跳转不同的页面。页面地址由to属性指定。
  • router-view:负责渲染路由匹配到的对应组件内容。

效果如下:
在这里插入图片描述
同时我们的地址就变成了:http://localhost:5173/#/about以及http://localhost:5173/#/

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zong_0915

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值