# Vue3 笔记

# composition-api

为什么会有 composition-api,想一下有一个这样的场景,一个页面的功能有很多个,这些功能都是由一个又一个的组件拼凑起来的,这个时候代码的复用性非常重要,如果按照常规写法一定是一个功能都维护一份自己独有的 data,method,computed,watch 但是这样写下去的后果就是会将整个逻辑部分撑得很大,导致组件难以阅读和理解,尤其是那些没有编写过这些组件的人。或许你会想到使用 vue 的 mixin ,没错它一定意义地解决了复用抽离的问题,但是通常情况下我们混入整个 mixin,不管是变量还是 method ,都无法直接读出整个变量或者函数的真正作用和意义,很难读懂源码。composition-api 就是用来解决这种问题的。

image-20210719144849043

img

# setup

setup 是 Vue3.x 新增的一个选项, 他是组件内使用 Composition API的入口。

setup 执行时机beforeCreate 之前

setup 参数

  1. props: 传入的属性
  2. context:context中就提供了this中最常用的三个属性:attrsslotemit,分别对应 Vue2.x 中的 $attr属性、slot插槽 和$emit发射事件。并且这几个属性都是自动同步最新的值,所以我们每次使用拿到的都是最新值。
export default defineComponent({
  name: 'App',
  props: {
    name: String
  },
  // 接受两个参数 props 和 context
  setup(props,context) {
    // props 属性
    console.log(props.name)
    // Attribute (非响应式对象)
    console.log(context.attrs)

    // 插槽 (非响应式对象)
    console.log(context.slots)

    // 触发事件 (方法)
    console.log(context.emit)
  }
})

# reactive、ref 与 toRefs

看以下代码就可以知晓这三个 API 的作用

<template>
  <div class="homePage">
    <p>第 {{ year }} 年</p>
    <p>姓名: {{ nickname }}</p>
    <p>年龄: {{ age }}</p>
  </div>
</template>

<script>
import { defineComponent, reactive, ref, toRefs } from "vue";
export default defineComponent({
  setup() {
    // 声明响应式变量
    const year = ref(0);
    // 声明响应式对象
    const user = reactive({ nickname: "xiaofan", age: 26, gender: "女" });
    setInterval(() => {
      // ref声明的变量,在非template处需要用.value取值
      year.value++;
      user.age++;
    }, 1000);
    return {
      year,
      // 使用reRefs可以将普通对象转为ref对象,因为直接用解构的话会消除响应式
      ...toRefs(user),
    };
  },
});
</script>

# 生命周期

img

img

新增的钩子,监听响应式数据

// 新增的钩子用来监听响应式数据变化
setup(props) {
 // debug监听响应式对象更新的console
    onRenderTriggered((event) => {
      console.log(event)
    })
}

# v-model

image-20210913145157150

# watch

**监听 ref **数据

import { ref, defineComponent, reactive, toRefs, watch } from "vue";
export default defineComponent({
  name: "HelloWorld",
  props: {
    msg: {
      type: String,
      required: true,
    },
  },
  setup: () => {
    const state = reactive({
      count: 0,
      nickName: "tom",
      age: 12,
    });
    const num = ref(0)
    setTimeout(() => {
      state.count++;
    }, 100);
    
    watch(()=>num,(newVal,oldVal) => {
      console.log(newVal,oldVal)
    })
    
    return {
      ...toRefs(state),
    };
  },
});

监听 reactive 定义的数据

import { ref, defineComponent, reactive, toRefs, watch } from "vue";
export default defineComponent({
  name: "HelloWorld",
  props: {
    msg: {
      type: String,
      required: true,
    },
  },
  setup: () => {
    const state = reactive({
      count: 0,
      nickName: "tom",
      age: 12,
    });
    const num = ref(0)
    setTimeout(() => {
      state.count++;
    }, 100);
    
    watch(
      () => state.count,
      (curCount,preCount) => {
        console.log(curCount,preCount)
      },
      {
        // options
        deep: true,
        immediate: true
      }
    )
    
    return {
      ...toRefs(state),
    };
  },
});

监听多个数据

import { ref, defineComponent, reactive, toRefs, watch } from "vue";
export default defineComponent({
  name: "HelloWorld",
  props: {
    msg: {
      type: String,
      required: true,
    },
  },
  setup: () => {
    const state = reactive({
      count: 0,
      nickName: "tom",
      age: 12,
    });
    const num = ref(0)
    setTimeout(() => {
      state.count++;
    }, 100);
    
    watch(
      [() => state.count,num],
      ([curCount,newNum],[preCount,oldNum]) => {
        console.log(curCount,preCount)
        console.log(newNum,oldNum)
      }
    )
    
    return {
      ...toRefs(state),
    };
  },
});

//watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {

# 获取 ref

<template>
  <div ref="domRef"></div>
 </template>

export default defineComponent ({
 setup() {
// 1. 还是常规写法
  const domRef = ref(null)
  onMounted(() => {
    console.log(domRef.value)
  })
// 
} 
})

# computed

import { computed } from 'vue';
const user = reactive({
  firstName: '韩',
  lastName: '志伟',
});
const fullName1 = computed(() => {
  return user.firstName + user.lastName;
});
return {
  user,
  fullName1,
};

另外支持传入一个对象,可以进行读取和修改操作

const fullName2 = computed({
  get() {
    return user.firstName + '_' + user.lastName;
  },
  set(val: string) {
    const names = val.split('_');
    user.firstName = names[0];
    user.lastName = names[1];
  },
});
return {
  user,
  fullName2,
};

# 自定义Hooks

vue3 中我们想去定义一个可以服用的逻辑代码时通常采用 hooks 的方式,然后再引入。这种方式可以将某个需求功能拆分出来,看起来更舒服,并且可以扩展更多功能。

hooks 下新建 useMousePosition.ts

import { ref, onMounted, onUnmounted } 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('click',updateMouse)
  })
  onUnmounted(() => {
    document.removeEventListener('click',updateMouse)
  })
  return { x,y }
}

export default useMousePosition

接着在需要使用的组件上引用即可

// 自定义hooks
const { x, y } = useMousePosition()
return {
  x,
  y
}

# 新增组件 Suspence和teleport

Suspence:异步组件

定义一个需要进行异步加载的组件 DogShow.vue

<template>
<img :src = "result && result.message">
</template>
<script lang="ts">
import 'axios' from 'axios'
import {defineComponent} from 'vue'
export default {
  name: 'DogShow',
  async setup(props) {
    const data = axios.get(url)
    return {
      result: data.data
    }
  }
}
</script>

然后在需要引入的页面中引入

<template>
<Suspence>
  <!--异步加载完成展示-->
  <template #default>
    <async-show />
    <dog-show />
  </template>
  <template #fallback>
    <h1>Loading......</h1>
  </template>
</Suspence>
</template>

teleport:瞬移组件

<template>
<!-- teleport标签作用:将包裹组件隔离传送至指定选择器 -->
<teleport to='#extra'>
  <img alt="Vue logo" src="./assets/logo.png" />
</teleport>
  <p>{{ count }}</p>
  <p>{{ double }}</p>
  <p>x: {{ x }}</p>
  <p>y: {{ y }}</p>
  <h1 v-if="loading">Loading .....</h1>
  <h1 v-if="loaded">
    <div>
      <img :src="result.message" />
    </div>
  </h1>
  <button @click="increase">+1</button>
</template>

效果图

img (opens new window)

# 插槽

# slot 具名插槽语法

在 Vue2.x 中, 具名插槽的写法:

<!--  子组件中:-->
<slot name="title"></slot>

在父组件中使用:

<template slot="title">
    <h1>歌曲:成都</h1>
<template>

如果我们要在 slot 上面绑定数据,可以使用作用域插槽,实现如下:

// 子组件
<slot name="content" :data="data"></slot>
export default {
    data(){
        return{
            data:["走过来人来人往","不喜欢也得欣赏","陪伴是最长情的告白"]
        }
    }
}

<!-- 父组件中使用 -->
<template slot="content" slot-scope="scoped">
    <div v-for="item in scoped.data">{{item}}</div>
<template>

在 Vue2.x 中具名插槽和作用域插槽分别使用slotslot-scope来实现, 在 Vue3.0 中将slotslot-scope进行了合并同意使用。

Vue3.0 中v-slot

<!-- 父组件中使用 -->
 <template v-slot:content="scoped">
   <div v-for="item in scoped.data">{{item}}</div>
</template>

<!-- 也可以简写成: -->
<template #content="{data}">
    <div v-for="item in data">{{item}}</div>
</template>

# 自定义指令

Vue2.x 写法实例

Vue2.x 自定义指令钩子

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。

  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

  • unbind:只调用一次,指令与元素解绑时调用。

Vue使用自定义指令实现按钮级别的权限控制,需要设置两个值,一个是该按钮需要的权限,一个是当前用户用户角色

  1. 首先定义Vue组件,这里可以使用Vuex来保存角色信息
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    userInfo: {
      name: 'Suk',
      roles: ['add', 'delete'] // 管理员有哪些权限
    }
  },
  mutations: {},
  actions: {},
  getters: {},
})
export default store
  1. 编写 directives 实现自定义指令
directives: {
  // 指令名
  'permission':{
    //dom被插入元素时执行的钩子,el获取dom,binding.value拿到指令绑定的值,vnode.context可以拿到实例
    inserted:(el,binding,vnode)=>{
      // 获取保定的值
      const userRoles = bing.value
      // 获取按钮需要的权限
      const btnRole = el.getAttribute('data-rule')
      // 判断是否该角色是否有权限,无权限移除元素
      if(!userRoles.includes(btnRole)){
        el.parentNode.removeChild(el)
      }
    }
  }
}
  1. 应用指令
<template>
  <div class="test">
    {{ userInfo.name }}拥有的按钮权限:
    <el-button data-rule="add" v-permission="userInfo.roles">新增</el-button>
    <el-button data-rule="delete" v-permission="userInfo.roles">删除</el-button>
    <el-button data-rule="update" v-permission="userInfo.roles">修改</el-button>
  </div>
</template>

Vue3.x 写法

钩子变更

img

const { createApp } from "vue"

const app = createApp({})
app.directive('focus', {
    mounted(el) {
        el.focus()
    }
})

# 父组件保留子组件能力和方法

  <el-cascader
    v-model="defaultData"
    :options="calcData"
    clearable
    v-bind="$attrs"
    v-on="$listeners"
  ></el-cascader>

# Vue3-json-schema-form 项目笔记

# 前置目标

  • [ ] 开源项目的创建发布流程
  • [ ] 如何更合理的设计广泛设计型 API
  • [ ] 如何保证代码质量
  • [ ] Vue3 实现原理

# 项目初始化,集成 TSX

# Vue3-Compoonents

第一步、考虑需要传什么参数

# ColumnList

# Alert

# Validate-Input

# Validate-form

# Loading

# Upload

Last update: 3/6/2022, 9:03:36 AM