npm install -g @vue/cli
vue create project
# Manually select features
# 选择 3.x
#(*)空格选择
# no history mode -> hash模式
# Standard config
# lint in save
# dedicated config files
# Sava preset(模板)
使用 NPM:
npm init vite@latest
使用 Yarn:
yarn create vite
使用 PNPM:
pnpm create vite
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
# yarn dev
# vite
组合 API (Composition API)
创建一个包含响应式数据的引用(reference)对象
js中操作数据: xxx.value
模板中操作数据: 不需要.value
import { ref } from 'vue'
export default {
setup() {
const count = ref(10)
function updataCount() {
count.value += 6;
}
// 模板中使用
return {
count,
updataCount
}
}
}
<div ref="divRef"></div>
<script>
const divRef = ref<HTMLElement | null>(null)
console.log(divRef.value.property)
</script>
<template>
<div v-for="" :ref="getDom"></div>
</template>
<script>
const lis: HTMLElement[] = new Array()
const getDom = (el: HTMLElement) => lis.push(el)
</script>
const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
响应式转换是“深层的”:会影响对象内部所有
嵌套的属性
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
import { reactive } from 'vue'
export default {
setup() {
const student = reactive({
name: 'li',
hobby: {
kind: 'draw'
}
})
const updataStudent = () => {
student.name += '--'
student.hobby.kind += '--'
}
return {
student
}
}
}
-
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
-
通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等…
通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
文档:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
new Proxy(data, {
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
proxy.name = 'tom'
const fullName = computed(() => {
return user.fistName + '-' + user.lastName
})
const fullName = computed({
get() {
return user.fistName + '-' + user.lastName
},
set(value: string) {
const names = value.split('-')
user.firstName = names[0]
user.lastName = names[1]
}
})
watch(user, () => {
const name = user.firstName + '-' + user.lastName
}, {
immediate: true,
deep: true
})
// 默认立即开始监视
watchEffect(() => {
fullName3.value = user.firstName + '-' + user.lastName
})
watch(()=>person.name,(newValue,oldValue)=>{
console.log(newValue,oldValue);
},{immediate:true,deep:true})
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
console.log(newValue,oldValue);
},{immediate:true,deep:true})
与 2.x 版本生命周期相对应的组合式 API
beforeCreate
-> 使用 setup()
created
-> 使用 setup()
beforeMount
-> onBeforeMount
mounted
-> onMounted
beforeUpdate
-> onBeforeUpdate
updated
-> onUpdated
beforeDestroy
-> onBeforeUnmount
destroyed
-> onUnmounted
errorCaptured
-> onErrorCaptured
新增的钩子函数
组合式 API 还提供了以下调试钩子函数:
import {
ref,
onMounted,
onUpdated,
onUnmounted,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount
} from "vue"
import { ref } from 'vue'
import { axios } from 'axios'
export const getUser<T> = (url: string) => {
const result = ref<T | null>(null)
const loading = ref(true)
const errorMsg = ref(null)
axios.get(url)
.then(response => {
result.value = response.data
loading.value = false
})
,catch(error => {
loading.value = false
errorMsg.value = error.message || "未知错误"
})
return {
loading,
result,
errorMsg
}
}
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
问题: reactive 对象取出的所有属性值都是非响应式的(解构后非响应)
解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性
import { ref, reactive } from 'vue'
setup() {
const state = reactive({
name: 'l',
age: 121
})
const { name, age } = toRefs(state)
}
操作 dom
<template>
<input type="text" ref="inpRef" >
</template>
export default {
setup() {
const inpRef = ref<HTMLElement | null>(null)
onMounted() {
inpRef.value && inpRef.value.focus()
}
return {
inpRef
}
}
}
reactive
或 readonly
方法转换成响应式代理的普通对象。为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值,
更新时二者是同步的(相当于引用)
区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
应用: 当要将 某个prop 的 ref 传递给复合函数时,toRef 很有用
setup() {
const state = reactive({
foo: 'name'
})
const foo = toRef(state, 'foo')
return {
foo
}
}
// 实现延迟更改
<template>
<input v-model="userRef">
</template>
export default {
const userRef = userDevouncedRef('', 200)
return {
userRef
}
}
// 实现函数防抖的自定义 ref
function userDevouncedRef<T>(value: T, dalay = 200) {
let timeout: number
return customRef((track, trigger) => {
return {
get() {
// 追踪数据
return value
},
set(newValue: T) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
// 告诉 Vue 触发界面更新
trigger()
}, delay)
}
}
})
}
<script setup="props, {emit}">
const props = defineProps({
msg: {
type: Stirng,
default: () => "default"
}
})
const msg = ref(props.msg)
// emits
const emits = defineEmits(['method'])
emits('method', params)
</script>
子组件对外暴露之后,父组件才能调用
defineExpose({
list2
})
provide和
inject提供依赖注入,功能类似 2.x 的
provide/inject
// 父组件
/*<template>
<son />
<template/>
*/
import { ref, provide } from 'vue'
setup() {
const color = ref('red')
provide('color', color)
return {
color
}
}
// 子/子孙组件
import { inject } from 'vue'
setup() {
const color = inject('color')
return {
color
}
}
如果是在setup
定义组件内的指令,有一个语法糖可以使用:
任何以v开头的驼峰式命名的变量都可以被用作一个自定义指令,然后在模板中使用。
<script setup>
const vFocus = {
mounted: (el) => el.focus()
}
</script>
如果是使用选项式,则自定义指令需要在directives
选项中注册
<script>
export default {
setup() {},
derectives: {
focus: {
mounted(el) {
el.focus()
}
}
}
}
</script>
// 自定义复制指令
const vCopy = {
mounted: (el: any, { value }: any) => {
el.$value = value
el.handler = () => {
if (!el.$value) {
infoShow('内容为空')
return
}
uni.setClipboardData({
data: el.$value,
success() {
infoShow('复制成功')
},
fail() {
infoShow('复制失败')
},
showToast: false
})
}
//绑定事件
el.addEventListener('click', el.handler)
},
//当传进来的值更新的时候触发
updated(el, { value }) {
el.$value = value
},
//指令与元素解绑的时候
unMounted(el) {
el.removeEventListener('click', el.handler)
}
}
reactive
创建的响应式代理readonly
创建的只读代理reactive
或者 readonly
方法创建的代理const reactiveHandler = {
get (target, key) {
if (key==='_is_reactive') return true
return Reflect.get(target, key)
},
set (target, key, value) {
const result = Reflect.set(target, key, value)
console.log('数据已更新, 去更新界面')
return result
},
deleteProperty (target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('数据已删除, 去更新界面')
return result
},
}
/*
自定义shallowReactive
*/
function shallowReactive(obj) {
return new Proxy(obj, reactiveHandler)
}
/*
自定义reactive
*/
function reactive (target) {
if (target && typeof target==='object') {
if (target instanceof Array) { // 数组
target.forEach((item, index) => {
target[index] = reactive(item)
})
} else { // 对象
Object.keys(target).forEach(key => {
target[key] = reactive(target[key])
})
}
const proxy = new Proxy(target, reactiveHandler)
return proxy
}
return target
}
/*
自定义shallowRef
*/
function shallowRef(target) {
const result = {
_value: target, // 用来保存数据的内部属性
_is_ref: true, // 用来标识是ref对象
get value () {
return this._value
},
set value (val) {
this._value = val
console.log('set value 数据已更新, 去更新界面')
}
}
return result
}
/*
自定义ref
*/
function ref(target) {
if (target && typeof target==='object') {
target = reactive(target)
}
const result = {
_value: target, // 用来保存数据的内部属性
_is_ref: true, // 用来标识是ref对象
get value () {
return this._value
},
set value (val) {
this._value = val
console.log('set value 数据已更新, 去更新界面')
}
}
return result
}
yarn add pinia
# 或者使用 npm
npm install pinia
import { createPinia } from 'pinia'
createApp(App).use(router).use(createPinia()).mount('#app')
第一种
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
/*
* 也可以定义为
* state: () => ({ count: 0 })
*/
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
第二种
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
const increment = () => {
count.value++
}
return { count, name, doubleCount, increment }
})
state的五种方式修改值
// 1.直接修改值
Test.current=2
// 2.通过$patch修改,支持单个或多个属性修改
Test.$patch({current:33})
// 3.$patch工厂函数方式
Test.$patch((state) => {
state.current = 99;
state.name = '范迪塞尔'
})
// 4.$state 缺点是必须修改整个对象
Test.$state = { current: 88, name: '123' }
// 5.action
Test.setCurrent()