VUE3:shit:
vue3
已经发布了快两年了,并且它的出现极大的改变了vue
传统的写法、解决了2.0
版本的一些疑难杂症.3.0
的出现也预示了vue
未来的发展是会慢慢朝着hook
改变. 所以, 今天就来简单啰嗦几句吧.
VUE3 的优势
- 纯
hook
设计, 紧追潮流, 朝着react
奋起直追. - 从底层重新设计, 使用新的代理方式, 一定程度解决了上一版本的疑难杂症问题
typescript
的支持更加友好(以前版本对typescript
的支持简直就是一坨 :shit:)- 响应式函数的引入, 让
vue3
充满活力, 更加灵活、更加方便的搞事情
安装
因为
typescript
的流行, 所以本文将采用typescript
书写, 脚手架也将采用尤大
亲自操刀设计、开发的vite
.
安装 VITE 项目
使用 yarn
$ yarn create vite
使用 NPM
$ npm create vite@latest
使用 PNPM
$ pnpm create vite
安装 VUE3 项目
# npm 6.x
npm create vite@latest my-vue-app --template vue
# npm 7+
npm create vite@latest my-vue-app -- --template vue
# yarn
yarn create vite my-vue-app --template vue
# pnpm
pnpm create vite my-vue-app --template vue
只需要按照提示一步步来就好, 如果不想这么麻烦, 可以直接拉取
Git
模板 ray-template 已经预设好了一些基础库.
进入正题
vue3.0
虽然保留了options api
的写法, 但是这里不做讲解, 不上新的写法还不如不用. (这里只举例基础的、常用的函数)
书写方式
options api
虽然傻瓜式的书写, 但是有个很大的弊端就是, 很难抽离公共逻辑. 虽然官方提供了mixin
, 但是, 这个蠢东西有问题, 弊端很多. 有兴趣可以去看看这个玩意儿被如何吐槽的
-
Composition API
<template>
<div class="demo">
ref 响应式数据: {{ demoNumberRef }}
demoState 响应式数据: {{ number }}
</div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, toRefs } from 'vue'
export default defineComponent({
name: 'Demo',
setup() {
const demoNumberRef = ref(0)
const demoState = reactive({
number: 0,
})
return {
demoNumberRef,
...toRefs(demoState),
}
},
})
setup
函数参数()
props
和标准的组件一致, 一个setup
函数的props
是响应式的,并且会在传入新的props
时同步更新(不可解构)
context
上下文对象暴露了其他一些在setup
中可能会用到的值(可以安全解构)
attrs: 透传
Attributes
(非响应式的对象,等价于$attrs
)slots: 插槽(非响应式的对象,等价于
$slots
)emit: 触发事件(函数,等价于
$emit
)expose: 暴露公共属性(函数)
与
2.x
版本不同的是,3.0
的核心代码逻辑是写在setup
函数中, 并且声明响应式数据的方法也有所不同, 采用响应式函数来声明(ref
、reactive
)
ref
函数声明数据需要通过xxx.value
获取
reactive
接收一个object
对象, 修改数据时以对象方法获取(不可解构)(demoState.number
)
...toRefs(xxx)
可以将对象转换为响应式数据
-
Setup Sugar
<template>
<div class="demo">
ref 响应式数据: {{ demoNumberRef }} demoState 响应式数据:
{{ demoState.number }}
</div>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
const demoNumberRef = ref(0)
const demoState = reactive({
number: 0,
})
</script>
setup sugar
虽然可以极大的简化书写代码, 但是因为他是由宏编译器实现的自动导出, 所以在使用reactive
声明的变量在template
中引用时需要 (demoState.number
)方式获取
-
Options API
这里不做赘述, 写法与
2.x
大同小异
侦听
watch()
侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数
详细信息
watch()
默认是懒侦听的, 即仅在侦听源发生变化时才执行回调函数
侦听参数来源
- 一个函数, 返回一个值
- 一个 ref
- 一个响应式对象
- 或者是上述情况的组合
参数列表
第一个参数: 第一个参数是侦听器的源
第二个参数: 在发生变化时要调用的回调函数. 这个回调函数接受三个参数: 新值、旧值, 以及一个用于注册副作用清理的回调函数. 该回调函数会在副作用下一次重新执行前调用, 可以用来清除无效的副作用
都三个参数: 对象
immediate
在侦听器创建时立即触发回调. 第一次调用时旧值是undefined
deep
如果源是对象,强制深度遍历,以便在深层级变更时触发回调
flush
调整回调函数的刷新时机
onTrack / onTrigger
调试侦听器的依赖
示例
侦听一个源
const demoNumberRef = ref(0)
watch(
() => demoNumberRef,
(data, preData) => {
/* ... */
},
)
侦听多个源
const fooRef = ref(0)
const demoRef = ref('hello')
watch([fooRef, demoRef], ([foo, demo], [prevFoo, prevDemo]) => {
/* ... */
})
computed()
接受一个
getter
函数,返回一个只读的响应式ref
对象. 该ref
通过.value
暴露getter
函数的返回值. 它也可以接受一个带有get
和set
函数的对象来创建一个可写的ref
对象
示例
创建只读属性 ref
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
创建可写的计算属性 ref
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
},
})
plusOne.value = 1
console.log(count.value) // 0
调试
const plusOne = computed(() => count.value + 1, {
onTrack(e) {
debugger
},
onTrigger(e) {
debugger
},
})
以上都是
vue3.0
最大的改动, 有了这些基础, 我们可以利用它们做一些有意思的东西了
利用响应式函数实现自定义 hook
仅仅是提供一个简单的思路! 写的烂, 别吐槽
实现 useWindowSize
思路, 首先声明
width
、height
两个响应式变量, 然后监听浏览器尺寸的变化, 最后返回一个响应式对象(包含:width
、height
)
实现
- 声明变量
const width = ref(0) // 浏览器宽度
const height = ref(0) // 浏览器高度
- 获取浏览器尺寸
// 判断页面是否含有滚动条
const includeScrollbar = () =>
document.body.scrollHeight >
(window.innerHeight || document.documentElement.clientHeight)
// 获取浏览器尺寸, 然后赋值给: width、height
const update = () => {
if (window) {
if (includeScrollbar()) {
width.value = window.innerWidth
height.value = window.innerHeight
} else {
width.value = window.document.documentElement.clientWidth
height.value = window.document.documentElement.clientHeight
}
}
}
- 监听浏览器尺寸变化
window.addEventListener('resize', update)
- 完整代码
const useWindowSize = () => {
const width = ref(0)
const height = ref(0)
const includeScrollbar = () =>
document.body.scrollHeight >
(window.innerHeight || document.documentElement.clientHeight)
const update = () => {
if (window) {
if (includeScrollbar()) {
width.value = window.innerWidth
height.value = window.innerHeight
} else {
width.value = window.document.documentElement.clientWidth
height.value = window.document.documentElement.clientHeight
}
}
}
window.addEventListener('resize', update)
return {
width,
height,
}
}
- 使用
const { width, height } = useWindowSize()
差不多大概思路就是如此, 利用响应式函数实现一些带有响应式的
hook
题外话
虽然官方建议 (事实上官方主推的
vue
的写法为模板语法) 采用模板语法进行开发, 但是模板语法的局限性还是太大, 尽管官方已经做了很多措施去补全, 但是写过react
的会很不习惯这种方式. 所以, 这边建议采用tsx
形式去开发.j(t)sx
是对于js
的补充拓展, 所以它有很多模板不具有的特点, 其中最主要的就是灵活性, 配合上vue3.0
的hook
能够更加灵活的开发项目
举个栗子 (仅针对 VITE
的使用)
- 安装
# npm
npm install @vue/babel-plugin-jsx -D
# yarn
yarn add @vue/babel-plugin-jsx -D
- 配置
/**
*
* - 打开 vite.config.j(t)s 文件
* - 导入插件
* - 注册插件
* - [babel-plugin-jsx](https://github.com/vuejs/babel-plugin-jsx/tree/dev/packages/babel-plugin-jsx)
*/
import viteVueJSX from '@vitejs/plugin-vue-jsx'
plugins: [viteVueJSX()]
- 使用
# 函数式组件
const App = () => <div></div>
# 实际使用
import { withModifiers, defineComponent } from 'vue'
const App = defineComponent({
setup() {
const count = ref(0)
const inc = () => {
count.value++
}
return {
count,
inc,
}
},
render() {
return <div onClick={withModifiers(this.inc, ['self'])}>{this.count}</div>
},
})
vue-jsx
与react-jsx
的class
命名方式有点区别,vue-jsx
为class
. 具体的使用可以参考官方文档: babel-plugin-jsx