# vue3
# 模拟vue
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="./my-vue.js"></script>
</head>
<body>
<div id="app"></div>
<button id="btn">点我</button>
<script>
const root = document.querySelector('#app')
const btn = document.querySelector('#btn')
const obj = reactive({
name: "vue",
age: 6
})
let double= computed(()=>obj.age*2)
effect(() => {
root.innerHTML = `<h1>${obj.name}版本${obj.age} double:${double.value}</h1>`
})
btn.addEventListener('click', () => {
obj.age += 1
}, false)
</script>
</body>
</html>
# my-vue.js
let toProxy = new WeakMap() //存储 原始--响应
let toRaw = new WeakMap() //存储 响应--原始
const baseHander = {
get(target, key) {
const res = Reflect.get(target, key)
//收集依赖
track(target, key)
// 递归查找
return typeof res === 'object' ? reactive(res) : res
},
set(target, key, val) {
const info = {
oldValue: target[key],
newValue: val
}
// if(target.hasOwnProperty(key)){ // 即如果触发的是私有属性,可以直接触发视图更新,length会屏蔽掉
// trigger();
// }
const res = Reflect.set(target, key, val)
// 触发更新
trigger(target, key, info)
return res
}
}
function reactive(target) {
// 查询缓存
let observed = toProxy.get(target)
if (observed) {
return observed;
}
if (toRaw.get(target)) {
return target
}
observed = new Proxy(target, baseHander)
//设置缓存
toProxy.set(target, observed)
toRaw.set(observed, target)
return observed
}
let effectStack = []
let tagetMap = new WeakMap() //存储effect
function trigger(target, key, info) {
const depsMap = tagetMap.get(target)
if (depsMap === undefined) {
return
}
const effects = new Set()
const computedRuners = new Set()
if (key) {
let deps = depsMap.get(key)
deps.forEach((effect) => {
if(effect.computed){
computedRuners.add(effect)
}else{
effects.add(effect)
}
})
}
// const run = effect =>effect()
effects.forEach(effect => effect())
computedRuners.forEach(effect => effect())
}
function track(target,key) {
let effect = effectStack[effectStack.length - 1]
if (effect) {
let depsMap = tagetMap.get(target)
if (depsMap === undefined) {
depsMap = new Map()
tagetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (dep === undefined) {
dep = new Set()
depsMap.set(key, dep)
}
if (!dep.has(effect)) {
dep.add(effect)
effect.deps.push(dep)
}
}
}
function effect(fn, options={}) {
let e = createReactiveEffect(fn, options)
if(!options.lazy){
e()
}
return e
}
function createReactiveEffect(fn, options) {
const effect = function effect(...args) {
return run(effect, fn, args)
}
effect.deps = []
effect.computed = options.computed
effect.lazy = options.lazy
return effect
}
function run(effect, fn, args) {
if (effectStack.indexOf(effect) === -1) {
try {
effectStack.push(effect)
return fn(...args)
} finally {
effectStack.pop()
}
}
}
function computed(fn) {
const runner = effect(fn,{computed:true,lazy:true})
return {
effect:runner,
get value(){
return runner()
}
}
}
//mainjs
import { createApp } from 'vue';
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
//vue2mianjs
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App)
}).$mount("#app");
# toRefs toRef
可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
当你要将 prop 的 ref 传递给复合函数时,toRef 很有用:
export default {
setup(props) {
useSomeFeature(toRef(props, 'foo'))
}
}
即使源 property 不存在,toRef 也会返回一个可用的 ref。这使得它在使用可选 prop 时特别有用,可选 prop 并不会被 toRefs 处理。
# vue3vuex
//vuex-vue2
import vue from "vue"
import vuex from "vuex"
vue.use(vuex)
export default new vuex.Store({
})
//vue3-vuex
import Vuex from 'vuex'
export default Vuex.createStore({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
console.log('当前count:',state.count);
}
},
actions: {},
modules: {}
});
<template>
<div>
<button @click="increment">increment</button>
</div>
</template>
<script>
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const count = store.state.count;
console.log("vuex >>", count);
const increment = () => {
// mutations
store.commit("increment");
};
return {
increment,
};
}
};
</script>
vue3中响应式的store数据,需要借助computed实现功能
<template>
{{count}}
</template>
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup () {
const store = useStore()
return {
// 在 computed 函数中访问 state
count: computed(() => store.state.count),
// 在 computed 函数中访问 getter
double: computed(() => store.getters.double)
}
}
}
或者借助vue2中插件的写法 $store.state.xxx
<div class='count'>
count1:{{$store.state.count1}}
</div>
# vue3vuerouter
//vue2router
import Vue from 'vue'
import Router from 'vue-router'
import Index from '../components/Index'
import Linkman from '../components/Linkman'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/index',
name: 'Index',
component: Index,
children:[
{
path: 'linkman',
name: 'Linkman',
component:Linkman
}
]
},
]
})
//vue3router
import { createRouter, createWebHashHistory } from 'vue-router';
import Home from '../views/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
# getRoutes() 和 options.routes
const router = useRouter()
router.getRoutes()//获取路由表 获取的是扁平化的路由表
menuList.value =router.options.routes //=>获取的是配置的路由表的完整结构
<template>
<div>
<button @click="increment">increment</button>
<button @click="gotoAbout">about</button>
</div>
</template>
<script>
import { useRouter } from "vue-router";
export default {
setup() {
// 使用vue-router
const router = useRouter();
const gotoAbout = () => {
router.push("/");
};
return {
increment,
gotoAbout
};
}
};
</script>
<template>
<div class="home">
home
<div>{{count}}</div>
<button @click="add">+</button>
<div>money:{{state.yuan}}</div>+<div>yuan1:{{yuan1}}</div>
<button @click="double">double</button>
<Plus @click="plusclick" @k="aaa"></Plus>
<button @click="stopwatch">stop</button>
<div ref="refdemo">domref</div>
<input type="range" v-model="size" />
<button @click="increment">increment</button>
<div>{{`${x}-${y}`}}</div>
</div>
</template>
<script>
import {ref,reactive,toRefs,computed,watchEffect,watch, onMounted, onUnmounted} from "vue"
import { useStore } from "vuex";
import Plus from "./Plus.vue"
export default {
name: 'Home',
components:{
Plus
},
setup(props,context){
let count =ref(7)
const stop= watchEffect(() => {
console.log(`effect 触发了!${count.value}`);
});
const store = useStore();
const count1 = store.state.count1;
console.log("vuex >>", count1);
const increment = () => {
// mutations
store.commit("increment");
};
const refdemo = ref(null);
console.log(refdemo)
const size = ref(24);
// 监听拖动
watch(size, (val, oldVal) => {
refdemo.value.style.fontSize = size.value + "px";
});
function add(){
console.log(context.count)//undefined
console.log(this.count)
console.log(count)
console.log(count.value)
console.log(this.count===count)//false
console.log(typeof(this.count),typeof(count))//number object
count.value++;
this.count++;
}
let state=reactive({
yuan:1,
yuan1:computed(()=>count.value*3)
})
function double(){
console.log(this.state.yuan)
console.log(state===this.state)//true
console.log(this.yuan===this.state.yuan)
//toRefs可以支持类似vue2的this直接取data里的值而不需要加this.data.xxx
state.yuan *=2;
}
function plusclick(){
alert("1111")
}
function aaa(){
alert("laji1")
}
watch(count,(count,pre)=>{
console.log(count,pre)
})
const useMouse = () => {
const state = reactive({
x: 0,
y: 0
});
const update = e => {
state.x = e.pageX;
state.y = e.pageY;
};
onMounted(() => {
window.addEventListener("mousemove", update);
});
onUnmounted(() => {
window.removeEventListener("mousemove", update);
});
return toRefs(state);
};
// 停止监听
const stopwatch = () => stop();
//需要使用的方法和数据需要return出去
return{
count,
// plus,
add,
double,
state,
...toRefs(state),
plusclick,
aaa,
stopwatch,
refdemo,
size,
increment,
...useMouse()
}
}
}
</script>
//子组件
<template>
<div class="plus">
<button @click='plus11'>---</button>
</div>
</template>
<script>
export default {
name: 'plus',
setup(props,context){
console.log(props,"---",context)
function plus11(){
context.emit("k")
}
return{
plus11,
}
}
}
</script>
# 抽离useMouse
- 组件
<template>
<div @click="fun">{{ a }}</div>
<div>state:{{ x }} - {{ y }}</div>
<div @click="increment">{{ arr }}</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from "vue";
import { useMouse } from "@/hooks/useFun";
export default defineComponent({
setup() {
const a = ref<number>(0);
function fun() {
a.value++;
}
onMounted(() => {
console.log("test");
});
return { a, fun, ...useMouse() };
},
});
</script>
- hooks event事件标注类型MouseEvent
import { reactive, onMounted, onUnmounted, toRefs } from "vue";
import { useStore } from "vuex";
// 返回鼠标位置
const useMouse = () => {
const state = reactive({
x: 0,
y: 0,
});
const update = (e: MouseEvent) => {
console.log(e);
state.x = e.pageX;
state.y = e.pageY;
};
const store = useStore();
const arr = store.state.f2arr;
console.log("vuex >>", arr);
const increment = () => {
// mutations
store.commit("addarr");
};
onMounted(() => {
window.addEventListener("mousemove", update);
});
onUnmounted(() => {
window.removeEventListener("mousemove", update);
});
return { ...toRefs(state), update, increment, arr };
};
export { useMouse };
- vuex
import { createStore } from "vuex";
interface IPoint {
f1: number;
f2arr: Array<number>;
}
export default createStore({
state: (): IPoint => ({
f1: 1,
f2arr: [15],
}),
mutations: {
addarr(state, arg: string | number = 30) {
console.log(arg, typeof arg);
if (typeof arg === "string") {
arg = parseFloat(arg);
}
state.f2arr.push(arg);
},
},
actions: {},
modules: {},
});
# vue防抖和节流
Vue 没有内置支持防抖和节流,但可以使用 Lodash 等库来实现。
如果某个组件仅使用一次,可以在 methods 中直接应用防抖:
<script src="https://unpkg.com/lodash@4.17.20/lodash.min.js"></script>
<script>
Vue.createApp({
methods: {
// 用 Lodash 的防抖函数
click: _.debounce(function() {
// ... 响应点击 ...
}, 500)
}
}).mount('#app')
</script>
但是,这种方法对于可复用组件有潜在的问题,因为它们都共享相同的防抖函数(引用了同一个地址的函数)。为了使组件实例彼此独立,可以在生命周期钩子的 created 里添加该防抖函数:
app.component('save-button', {
created() {
// 使用 Lodash 实现防抖
this.debouncedClick = _.debounce(this.click, 500)
},
unmounted() {
// 移除组件时,取消定时器
this.debouncedClick.cancel()
},
methods: {
click() {
// ... 响应点击 ...
}
},
template: `
<button @click="debouncedClick">
Save
</button>
`
})