Vue3
已经出了一段时间了,本人也一直在学习,但楼主是学生,最近一直在做一些微信小程序和可视化的一些东西,所以更新的有点慢。请大家理解一下。
本文主要讲述一下自己在学习Vue3 + Ts + vite过程中,自己的理解即使用方法
个人的Ts
不能说学的很好,只能说会一部分基础。所以在这里就不 误导大家了,本文会在下面Vue3
中进行简单的使用。
如果想深入学习。推荐下面几本书
推荐一本书TypeScript 入门教程,这个是放在Github
(可能需要科学上网)。(推荐优先阅读这本书,个人感觉比较通俗易懂)
第二本书就是 官方手册(也可能需要科学上网),相对于第一本我觉得比较难理解,所以首推第一本
// 安装或者升级
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
推荐使用dart-sass
,(深受node-sass的毒害)。这也是官方所推荐的
有关命令行的步骤。主要是一些配置,大家可以参考一些平常自己的使用。个人推荐Vue ui创建。可能是好看吧
在Vue2
中,每个template
节点只能有一个根节点。
而在Vue3
中,可以有多个根节点
举例说明
//vue2
<template>
<div>
</div>
</template>
// vue3
<template>
<div>
</div>
<router-view/>
</template>
这个就是代替了原来的data函数
,可以接收两个参数,props
,context
(没有用到的时候可以省略不写)。且只执行一次
num:{
{ num }}
这就是一个基本的setup
的过程,可能大家在这里还看不出来,和Vue2
具体有哪些区别,别急。可以试着点击一下button
按钮,发现num
并没有发生改变。看一下控制台(F12),发现输出的是一个number
的值。但是我们点击并没有发生改变。所以接下来,我们将用到第一个 新的API Ref
首先,我们改造一下上面的代码
msg:{
{ msg }}
num:{
{ num }}
首先,先看引用形式,可以看到这里是 按需导入,好处我也不就一一叙述, 我们直接看 控制台
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H6ZDHj1K-1610456412702)(https://upload.cc/i1/2020/10/15/LpIr9k.png)]
一个是 RefImpl
一个是数值 0
。点击按钮发现,msg
可以改变,而num
不行。
RefImpl
是什么呢?我把它理解为 代理对象。就比如我们知道Vue2
中data
的数据是通过Object.defineProperty()来进行拦截。从而达到 数据响应式 的目的。而Vue3
是利用了ES6
中的proxy,相对于Object.defineProperty()
来说,能拦截的方式更多。功能也更加强大。
所以,大家知道为什么msg
能改变,num
不能改变了吗?因为msg
被proxy
进行了代理。而num
并没有。所以才会造成了上面的局面
解释Ref
ref
是一个函数,它接受一个参数,返回的就是一个响应式对象 。
例子中,我们初始化的这个 0
作为参数包裹到这个对象中去,在未来操作这个值的时候,可以检测到改变并作出对应的相应。
如果说我想创建一个对象呢?这时候就不能时使用这个函数了。就要使用接下来这个函数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
中的 解构
所以代码换成了如下
count:{
{ count }}
double:{
{ double }}
这时候大家会发现一个问题。为什么点击按钮不改变状态了呢?
这是因为,解构会破坏代理,把他编程一个普通值。就跟上面的Ref
的例子一样,所以点击按钮并没有发生变化。
这时候,就要请出 另外一个新加的api
了,toRefs
这是引用官网的一句话
toRefs
从组合函数返回反应对象时,此函数很有用,以便使用组件可以对返回的对象进行解构/扩展而不会失去反应性:
使用起来 比较简单,就返回的时候加上就可以
count:{
{ count }}
double:{
{ double }}
beforeMount |
onBeforeMount |
---|---|
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeUnmount |
onBeforeUnmount |
unmounted |
onUnmounted |
errorCaptured |
onErrorCaptured |
renderTracked |
onRenderTracked |
renderTriggered |
onRenderTriggered |
提示(来自官网)
由于
setup
是围绕beforeCreate
和created
生命周期挂钩运行的,因此您无需显式定义它们。换句话说,将在这些挂钩中编写的任何代码都应直接在setup
函数中编写。
// 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是一个对象。要取里面的值
使用方法和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出现可以让我们写的组件移动到指定标签下面。
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>
在我们点击了 按钮之后
这就是基本的用法。
这个新增标签是我觉得最实用的一个标签。解决了我们异步请求中,图片返回过慢导致的空白(虽然可以解决。但是没这个方便)
示例代码
// 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>
效果如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m78Y2Odt-1610456412713)(https://i.loli.net/2020/10/22/f4MhALH36pyIC71.png)]
本部分展示了 关于抽出共用逻辑代码的部分,仅仅是一个例子。 仅仅作为展示如何抽离。
//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
}
}
}
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>
以上是我在学习和使用过程中有的一些实际体会。如有不对,欢迎各位批评指正
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.type
为 module
时,通过 src
及 import
导入的文件会发送 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/')
})
接下来我们启动服务,访问路径。
我们可以看到。我们成功请求到了localhost
index.html
里面存在的
没有成功发送了请求。
// 在原有代码的基础上 添加 记得一定要写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()
})
由此 我们可以看到
main.js
里面的一些比如import { createApp } from 'vue'
这些。我们没有找到。所以接下里的工作就是要让这些可以找的到大家可以看到 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
了
这里需要使用官方提供的两个模块
@vue/compiler-sfc
: 官方的 vue 单文件解析器@vue/compiler-dom
: 经过parser、transform、generate,将虚拟dom渲染成浏览器上的真实domapp.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
已经可以识别并返回了
但是。缺没有出效果。这是为什么呢?
经过一番百度之后。查出原来是缺少一个process对象
。这是在node·
才有的。浏览器端没有。所以。我们接改写一下 第一步
app.use((ctx, next)=>{
const {
request: {
url } } = ctx
if (url === '/') {
let content = fs.readFileSync('./index.html', 'utf-8')
content = content.replace('
)
ctx.type = 'text/html'
ctx.body = content
}
next()
})
这样。我们给了浏览器一个process
属性。就可以识别了
跟解析js
差不多
app.use((ctx, next)=>{
const {
request: {
url } } = ctx
if (url.endsWith('.css')) {
const p = path.resolve(__dirname, url.slice(1))
const file = fs.readFileSync(p, 'utf-8')
const content = `
const css = "${
file.replace(/\n/g, '') }"
const link = document.createElement('style')
link.setAttribute('type', 'text/css')
document.head.appendChild(link)
link.innerHTML = css
export default css
`
ctx.type = 'application/javascript'
ctx.body = content
}
next()
})
然后我们就识别了所有文件
如果想加其他的文件。只需要按照这个方式,做对应的方式加载就行
当我们初步搭建完成后。先解析CSS
。css
是不生效的,但引入了Vue
官方的这两个库,就起了效果。
如果有能帮忙解答疑惑的。不胜感激
本篇主要总结了。