Vue3+TS体验并开发+Vite浅析

前言

Vue3已经出了一段时间了,本人也一直在学习,但楼主是学生,最近一直在做一些微信小程序和可视化的一些东西,所以更新的有点慢。请大家理解一下。

本文主要讲述一下自己在学习Vue3 + Ts + vite过程中,自己的理解即使用方法

Ts

个人的Ts不能说学的很好,只能说会一部分基础。所以在这里就不 误导大家了,本文会在下面Vue3中进行简单的使用。

如果想深入学习。推荐下面几本书

推荐一本书TypeScript 入门教程,这个是放在Github(可能需要科学上网)。(推荐优先阅读这本书,个人感觉比较通俗易懂)

第二本书就是 官方手册(也可能需要科学上网),相对于第一本我觉得比较难理解,所以首推第一本

Vue3基本环境及语法

配置 vue3 开发环境

// 安装或者升级
npm install -g @vue/cli
yarn global add @vue/cli


// 全局升级vueCli
npm update -g @vue/cli
// 或者
yarn global upgrade --latest @vue/cli



// 保证 vue cli 版本在 4.5.0 以上
vue --version

// 创建项目
vue create my-project

// 或者使用图形化界面创建
vue ui

CSS预处理器推荐

推荐使用dart-sass,(深受node-sass的毒害)。这也是官方所推荐的

有关命令行的步骤。主要是一些配置,大家可以参考一些平常自己的使用。个人推荐Vue ui创建。可能是好看吧

template的变化

Vue2中,每个template节点只能有一个根节点。

而在Vue3中,可以有多个根节点

举例说明

//vue2
<template>
    <div>
        
    </div>
</template>


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

新增语法

setUp

这个就是代替了原来的data函数,可以接收两个参数,props,context(没有用到的时候可以省略不写)。且只执行一次

示例代码




这就是一个基本的setup的过程,可能大家在这里还看不出来,和Vue2具体有哪些区别,别急。可以试着点击一下button按钮,发现num并没有发生改变。看一下控制台(F12),发现输出的是一个number的值。但是我们点击并没有发生改变。所以接下来,我们将用到第一个 新的API Ref

Ref

首先,我们改造一下上面的代码




首先,先看引用形式,可以看到这里是 按需导入,好处我也不就一一叙述, 我们直接看 控制台

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H6ZDHj1K-1610456412702)(https://upload.cc/i1/2020/10/15/LpIr9k.png)]

一个是 RefImpl 一个是数值 0。点击按钮发现,msg可以改变,而num不行。

RefImpl是什么呢?我把它理解为 代理对象。就比如我们知道Vue2data的数据是通过Object.defineProperty()来进行拦截。从而达到 数据响应式 的目的。而Vue3是利用了ES6中的proxy,相对于Object.defineProperty()来说,能拦截的方式更多。功能也更加强大。

所以,大家知道为什么msg能改变,num不能改变了吗?因为msgproxy 进行了代理。而num并没有。所以才会造成了上面的局面

解释Ref

ref是一个函数,它接受一个参数,返回的就是一个响应式对象

例子中,我们初始化的这个 0 作为参数包裹到这个对象中去,在未来操作这个值的时候,可以检测到改变并作出对应的相应。

如果说我想创建一个对象呢?这时候就不能时使用这个函数了。就要使用接下来这个函数Reactive

Reactive

创建一个 JavaScript对象 反应式状态,就要使用reactive方法:

示例代码

<template>
  <div>
    {
     {
      data }}
    <h1>
      count:{
     {
      data.count }}
    </h1>
    <h1>
      double:{
     {
      data.double }}
    </h1>
  </div>
  <button @click="data.increase">1</button>
</template>

<script lang="ts">
import {
      reactive, computed } from 'vue'

export default {
     
  name: 'App',
  setup () {
     
    const data = reactive({
     
      count: 0,
      increase: () => {
     
        data.count++
      },
      double: computed(() => data.count * 2)
    })
    return {
     
      data
    }
  }
}
</script>

这时候呢,我们点击按钮就可以改变状态了。

computed计算属性,给Vue2差不多。学过Vue2的同学可以理解。如果不理解,请看后续有专门介绍

这时候可能回有人嫌比较麻烦,为什么不直接{ {count}}这样呢。就想到了Es6中的 解构

所以代码换成了如下




这时候大家会发现一个问题。为什么点击按钮不改变状态了呢?

这是因为,解构会破坏代理,把他编程一个普通值。就跟上面的Ref的例子一样,所以点击按钮并没有发生变化

这时候,就要请出 另外一个新加的api了,toRefs

toRefs

这是引用官网的一句话

toRefs 从组合函数返回反应对象时,此函数很有用,以便使用组件可以对返回的对象进行解构/扩展而不会失去反应性

使用起来 比较简单,就返回的时候加上就可以




生命周期改变

beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered

提示(来自官网)

由于setup是围绕beforeCreatecreated生命周期挂钩运行的,因此您无需显式定义它们。换句话说,将在这些挂钩中编写的任何代码都应直接在setup函数中编写。

原来语法

watch

// watch 基本使用   记得引入
watch(data, () => {
     
  document.title = '更新过后 ' + data.count
})
// watch 的两个参数,代表新的值和旧的值
watch(data, (newValue, oldValue) => {
     
  console.log('old', oldValue)
  console.log('new', newValue)
  document.title = '更新过后 ' + data.count
})

// watch 多个值,返回的也是多个值的数组
watch([greetings, data], (newValue, oldValue) => {
     
  console.log('old', oldValue)
  console.log('new', newValue)
  document.title = '更新过后' + greetings.value + data.count
})

// 使用函数 getter写法
watch([greetings, () => data.count], (newValue, oldValue) => {
     
  console.log('old', oldValue)
  console.log('new', newValue)
  document.title = '更新过后' + greetings.value + data.count
})

特别注意,data是一个对象。要取里面的值

computed

使用方法和vue2一样。可以写在reactive内部,也可写在外部

setup () {
     
    const data = reactive({
     
      count: 0,
      increase: () => {
     
        data.count++
      },
      double: computed(() => data.count * 2)
    })
    const conComputed = computed(() => data.count * 2)
    const number = ref(0)
    watch(data, () => {
     
      console.log(data)
      document.title = 'updated ' + data.count
    })
    watch(number, () => {
     
      console.log(number)
    })
    return {
     
      number,
      conComputed,
      ...toRefs(data)
    }
  }

新增标签

Teleport瞬间移动

Teleport 英文文档地址

平时我们的遮罩层都存在于 某个多级标签下面,这样其实是不合理的。

Teleport出现可以让我们写的组件移动到指定标签下面。

to是要移动到哪个 标签下。支持选择器

示例代码

// model
<template>
  <teleport to="#modal">
    <div id="center">
      <h1>this is a modal</h1>
    </div>
  </teleport>
</template>

<script>
export default {
     
  name: 'model'
}
</script>

<style scoped lang="scss">
#center {
     
  width: 200px;
  height: 200px;
  background: red;
  position: fixed;
  left: 50%;
  top: 50%;
  margin-left: -100px;
  margin-top: -100px;
}
</style>


// App.vue
<template>
  <model v-if="show"></model>
  <button @click="show = !show">show</button>
</template>

<script lang="ts">
import {
      ref } from 'vue'
import model from './components/model.vue'
export default {
     
  name: 'App',
  components: {
     
    model
  },
  setup () {
     
    const show = ref(false)
    return {
     
      show,
    }
  }
}
</script>

在我们点击了 按钮之后

这就是基本的用法。

Suspense异步请求

这个新增标签是我觉得最实用的一个标签。解决了我们异步请求中,图片返回过慢导致的空白(虽然可以解决。但是没这个方便)

示例代码

// showPic
<template>
  <img :src="result && result.url" alt="">
</template>

<script lang="ts">
import axios from 'axios'
import {
      defineComponent } from 'vue'
export default defineComponent({
     
  async setup () {
     
    const rawData = await axios.get('https://picsum.photos/id/786/200/200')
    console.log(rawData)
    return {
     
      result: rawData.config
    }
  }
})
</script>
// App
<template>
  <Suspense>
    <template #default>
      <pic-show />
    </template>
    <template #fallback>
      <h1>Loading !...</h1>
    </template>
  </Suspense>
</template>

<script lang="ts">
import ShowPic from './components/ShowPic.vue'
export default {
     
  name: 'App',
  components: {
     
    ShowPic
  }
}
</script>

效果如下

Vue3+TS体验并开发+Vite浅析_第1张图片[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m78Y2Odt-1610456412713)(https://i.loli.net/2020/10/22/f4MhALH36pyIC71.png)]

自定义HOOKS

本部分展示了 关于抽出共用逻辑代码的部分,仅仅是一个例子。 仅仅作为展示如何抽离。

//useMousePosition.ts
import {
     onMounted, onUnmounted, ref} from "vue";

function useMousePosition() {
     
    const x = ref(0)
    const y = ref(0)
    const updateMouse = (e: MouseEvent) => {
     
        x.value = e.pageX
        y.value = e.pageY
    }

    onMounted(() => {
     
        document.addEventListener('mousemove', updateMouse)
    })

    onUnmounted(() => {
     
        document.removeEventListener('mousemove', updateMouse)
    })
    return {
     x, y }
}

export default useMousePosition

// app
import useMousePosition from "@/hooks/useMousePosition";
export default {
     
    setup(){
     
        const {
     x, y} = useMousePosition()
        return {
     
            x,y
        }
    }
}

结合Ts开发组件

新增语法

defineComponent。创建组件需要用这个包裹。

PropType 类型断言 判断是哪一个类型(Vue2中已经存在)

/**
	定义
*/
// 主要写一下TS  template 和 style 就先不写了  这里展示一个基本的下来框
<script lang="ts">
import {
     
  defineComponent,
  PropType,
  computed
} from 'vue'

// 把接口类型导出。在使用的过程中导入接口,对接口进行定义
export interface ColumnProps {
     
  id: number;
  title: string;
  avatar?: string;
  des: string;
}

export default defineComponent({
     
  name: 'ColumnList',
  props: {
     
    list: {
     
      type: Array as PropType<ColumnProps[]>,
      required: true
    }
  },
  setup(props) {
      //  这里使用到了props
    const ColumnList = computed(() => {
     
      return props.list.map((item) => {
     
        if (!item.avatar) {
     
          item.avatar = require('@/assets/logo.png')
        }
        return item
      })
    })
    return {
     
      ColumnList
    }
  }
})
</script>


// 使用
<template>
  <div id="container">
    <ColumnList :list="list"></ColumnList>
  </div>
</template>
<script lang="ts">
import {
      defineComponent } from 'vue'
import ColumnList, {
      ColumnProps } from '@/components/ColumnList.vue'

const testDate: ColumnProps[] = [
  {
     
    id: 1,
    title: 'test1',
    des: 'test1',
    avatar: 'https://picsum.photos/id/239/200/200'
  }, {
     
    id: 2,
    title: 'test1',
    des: 'test1',
    avatar: 'https://picsum.photos/id/239/200/200'
  }, {
     
    id: 3,
    title: 'test1',
    des: 'test1',
    avatar: 'https://picsum.photos/id/239/200/200'
  }, {
     
    id: 4,
    title: 'test1',
    des: 'test1',
    avatar: 'https://picsum.photos/id/239/200/200'
  }
]
export default defineComponent({
     
  name: 'App',
  components: {
     
    ColumnList,
  },
  setup() {
     
    return {
     
      list: testDate,
    }
  }
})
</script>

以上是我在学习和使用过程中有的一些实际体会。如有不对,欢迎各位批评指正

Vite

什么是Vite

github:https://github.com/vitejs/vite

Vite 是一个由原生 ESM 驱动的 Web 开发构建工具。在开发环境下基于浏览器原生 ES imports 开发,在生产环境下基于 Rollup打包。

作者原话: Vite,一个基于浏览器原生 ES Modules 的开发服务器。利用浏览器去解析模块,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。同时不仅有 Vue 文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。

它主要具有以下特点:

  • 快速的冷启动
  • 即时的模块热更新
  • 真正的按需编译

简单安装和使用

// npm i vite

npm init vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev

安装过后 的文件目录

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nyViCXNc-1610456412715)(https://i.loli.net/2020/10/24/PA95akNcu4y2xtT.png)]

打开页面为

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fj6BCYJq-1610456412716)(https://i.loli.net/2020/10/24/hsb2NxYjt7qVB6v.png)]

至于集成vue-router等。大家请自行摸索。这里只做一个简单介绍

原理

Vite的实现是个基于浏览器原生支持的 模块功能

script.typemodule 时,通过 srcimport 导入的文件会发送 http 请求。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-52VOWvaW-1610456412716)(https://i.loli.net/2020/10/24/Mp3IwVvuleNmCrJ.png)]

由图可知,通过发送请求,来获取文件,当需要用到的使用,才会过去请求。真正的 按需加载

简单实现

第一步,初始搭建

首先 我先搭建一个基本的框架,在 根目录下创建一个viteText.js文件。 基于Koa进行的实现。如果有不了解的。请访问Koa官网

const fs = require('fs')
const path = require('path')
const Koa = require('koa')


const app = new Koa()

// 这个主要是对请求进行重定向使用的   可以先不管
function rewriteImport(content) {
     
// 目的是改造.js文件内容, 不是/ ./ ../开头的import,替换成/@modules/开头的
  return content.replace(/ from ['|"]([^'"]+)['|"]/g,function(s0,s1){
     
    // console.log(s0,s1)
    if(s1[0]!=='.'&&s1[1]!=='/'){
     
      return ` from '/@modules/${
       s1}'`
    }else{
     
      return s0
    }
  })
}

//  这里图了方便 直接使用了中间件。
app.use((ctx, next)=>{
     
  const {
      request: {
      url } } = ctx
  if (url === '/') {
      // 发现当请求的是根目录是否 返回index 文件
    let content = fs.readFileSync('./index.html', 'utf-8')
    ctx.type = 'text/html'
    ctx.body = content
  }
  next()
})


app.listen(8888, ()=>{
     
  console.log('http://localhost:8888/')
})

接下来我们启动服务,访问路径。

Vue3+TS体验并开发+Vite浅析_第2张图片

  1. 我们可以看到。我们成功请求到了localhost

  2. index.html 里面存在的

    没有成功发送了请求。

第二部 集成js

// 在原有代码的基础上 添加    记得一定要写next()方法 否则 不会执行下一个 中间件
app.use((ctx, next)=>{
     
  const {
      request: {
      url } } = ctx
  if (url.endsWith('.js')) {
     
    console.log(url.slice(1)) // 打印出请求路径
    const p = path.resolve(__dirname, url.slice(1)) // 找到对应文件
    const content = fs.readFileSync(p, 'utf-8') // 读取文件
    ctx.type = 'application/javascript'
    ctx.body = rewriteImport(content) // 吧一些以  路径进行更换
  }
  next()
})

由此 我们可以看到

Vue3+TS体验并开发+Vite浅析_第3张图片

  1. 但是还有一些红色的。是没有成功得到响应的。也就是main.js里面的一些比如import { createApp } from 'vue'这些。我们没有找到。所以接下里的工作就是要让这些可以找的到

第三步 更改modules路径

大家可以看到 Vue的请求路径是http://localhost:8888/@modules/vue。但是我们文件夹里没有这个 **@**路径。所以我们需要对这个进行一个特殊处理。

app.use((ctx, next)=>{
     
  const {
      request: {
      url } } = ctx
  if (url.startsWith('/@modules/')) {
     
    const prefix = path.resolve(__dirname, 'node_modules', url.replace('/@modules/', ''))
    console.log(prefix)  // 打印出来的  是拼接后的路径
    const module = require(prefix + '/package.json').module
    console.log(module) // 获取 vue/module的路径
    const p = path.resolve(prefix, module)
    console.log(p) // 再次拼接读取
    const ret = fs.readFileSync(p, 'utf-8')
    ctx.type = 'application/javascript'
    ctx.body = rewriteImport(ret)
  }
  next()
})

主要注意一点。就是对文件路径的读取加拼接。可能会有点迷。大打印几次。了解每个地方就可以了

这时候。我们就可以请求到 Vue

Vue3+TS体验并开发+Vite浅析_第4张图片

第四步 解析Vue文件并返回

  • 这里需要使用官方提供的两个模块

    1. @vue/compiler-sfc: 官方的 vue 单文件解析器
    2. @vue/compiler-dom: 经过parser、transform、generate,将虚拟dom渲染成浏览器上的真实dom
    app.use((ctx, next)=>{
           
      const {
            request: {
            url,query } } = ctx
      if (url.indexOf('.vue') > -1) {
           
        // import xx from 'xx.vue'
        // 1. 单文件组件解析
        console.log(123456)
        const p = path.resolve(__dirname, url.split('?')[0].slice(1))
        // 解析单文佳年组建,需要官方的库
        const {
            descriptor } = compilerSfc.parse(fs.readFileSync(p, 'utf-8'))
        if (!query.type) {
           
          // js内容
          ctx.type = 'application/javascript'
          ctx.body = `
    ${
              rewriteImport(
              descriptor.script.content.replace('export default ',
                  'const __script = ')) }
    import {render as __render} from "${
              url }?type=template"
    __script.render = __render
    export default __script
          `
        } else if (query.type == 'template') {
           
          // 解析我们的template 编程render函数
          const template = descriptor.template
          const render = compilerDom.compile(template.content, {
            mode: 'module' }).code
          ctx.type = 'application/javascript'
          ctx.body = rewriteImport(render)
        }
      }
      next()
    })
    

这时候大家就会发现。APP.vue已经可以识别并返回了

Vue3+TS体验并开发+Vite浅析_第5张图片

但是。缺没有出效果。这是为什么呢?

经过一番百度之后。查出原来是缺少一个process对象。这是在node·才有的。浏览器端没有。所以。我们接改写一下 第一步

app.use((ctx, next)=>{
     
  const {
      request: {
      url } } = ctx
  if (url === '/') {
     
    let content = fs.readFileSync('./index.html', 'utf-8')
    content = content.replace(', `
      
      
                    
                    

你可能感兴趣的:(vue,前端,vue,javascript)