# 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()
         }
    }
}

原文出处 (opens new window)

//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>
  `
})

解决函数共享问题 (opens new window)

最后更新: 8/25/2022, 10:41:48 AM