vue3的一些知识点plus--3

二十,兄弟组件传值,Bus

兄弟组件直接的传值,最基础的是通过同一个父级进行数值的传递,使用prop和emit,太过繁琐。

// 父级
  <div>
    <A @on-click="getFlag"></A>
    <B :flag="flag"></B>
  </div>

let flag = ref(false);
let getFlag = (params: boolean) => {
  flag.value = params;
};


// A组件
<div>A</div>
<button @click="emitB">派发一个事件</button>

let emit = defineEmits(["on-click"]);
let flag = false;
let emitB = () => {
  flag = !flag;
  emit("on-click", flag);
};


// B组件
<h1>b组件</h1> {{ flag }}

type Props = {
  flag: boolean;
};
let props = defineProps<Props>();

那我们有什么更好的方法呢,可以直接进行任意组件通讯呢,当然可以,那就是Bus,底层逻辑就是消息订阅形式,订阅的on就可以接收到发送消息的emit的内容。下面的Bus是手动封装的一个简单的版本,利于理解实现原理。

Bus.ts封装,Bus类中包含 on注册方法,emit传递参数,执行方法,通过List进行调度。

// 订阅派发 类
type BusClass = {
  emit:(name:string)=>void
  on:(name:string,callback:Function)=>void
}

type ParamsKey = string | number | symbol

type List = {
  // key值是动态的,对象签名方式
  // value 返回可以是多个的,用 Array ,返回的是callback
  [key:ParamsKey] : Array<Function>
}

// 约束 implements
class Bus implements BusClass {
  // 有调度中心:是一个对象
  list:List

  constructor(){
    this.list = {}
  }

  emit(name:string,...args:Array<any>){
    let eventName:Array<Function> = this.list[name]
    eventName.forEach(fn => {
      // 需要调用fn,this指向,可以调用on(),args传入数组
      fn.apply(this,args)
    })
  }
  // on 可以多次注册,名字一样,支持多个。
  on(name:string,callback:Function){
    let fn:Array<Function> = this.list[name] || []
    fn.push(callback)
    this.list[name] = fn
  }
}

export default new Bus()

那使用方法也很简单,Bus.emit(name,args) 和 Bus.on(name,fn) ,不用涉及到父级是谁。

// A组件
import Bus from "../../Bus";

let emitBBus = ()=>{
  flag = !flag
  Bus.emit('a-click',flag)
}


// B组件
let flag2 = ref(false);
Bus.on("a-click", (flag: boolean) => {
  flag2.value = flag;
});

我们还可以使用第三方的Mitt来实现,封装的更加完善和功能扩充。
先安装包 mitt,然后在main.ts中全局引入,就可以使用了。

// main.ts
import mitt from "mitt";

const Mitt = mitt();

// 全局ts声明
declare module "vue" {
  export interface ComponentCustomProperties {
    $Bus: typeof Mitt;
  }
}
// 挂载全局
app.config.globalProperties.$Bus = Mitt;


// A组件
const instance = getCurrentInstance()
let emitBMit = ()=>{
  instance?.proxy?.$Bus.emit('on-xx','mitt')
}


// B组件
const instance = getCurrentInstance();
let fn = (str: any) => {
  console.log(str, "============str");
};
instance?.proxy?.$Bus.on("on-xx", fn); // 监听
// instance?.proxy?.$Bus.off('on-xx',fn)  // 停止

要注意的是,instance?.proxy? 这里相当于 vue2中的this,这样可以获得实例的绑定属性,得到$Bus。也可以监听所有的事件,清除所有的事件。

// * 代表监听所有事件,回调中多一个参数
instance?.proxy?.$Bus.on('*',(type,str)=>{
  console.log(type,str,'============str');
})
// 删除所有的方法
instance?.proxy?.$Bus.all.clear()

二十一,tsx的使用

如果想使用tsx的语法(类似于react的编写方式),需要先安装 npm i @vitejs/plugin-vue-jsx -D,然后在 vite.config.ts 中进行注册,plugins里,函数形式。

这样我们就可以使用tsx了,新建一个app.tsx的文件,尝试编写。

1,首先是函数渲染形式,没有很复杂的逻辑处理和状态。

export default function(){
    return (<div>小小</div>)
}

2, vue中defineComponent,导出一个对象形式,类似于vue2的写法。

export default defineComponent({
    data() {
        return{
            age:34
        }
    },
    render(){
        // tsx 的变量用单花括号
        return (<div>{this.age}</div>)
    }
})

3,vue3的写法,setup函数使用。在setup中处理逻辑,return返回html内容。使用的是表达式的方式,所以v-if是不支持的。

export default defineComponent({
        // 变量写法: setup函数模式:支持v-show
        setup(){
        // ref 在 template 中会自动解包的(.value)  tsx中不会自动解包,需要手动
        let flag = ref(false)
        // return () => (<div v-show={flag.value}>setup</div>)
        // v-if是不支持的,可以使用js的写法处理(三元表达式)
        // return () => (<div v-if={flag.value}>setup</div>)
        return () => (
            <>
                <div>{flag.value?'v-if的js写法':'false'}</div>
            </>
        )
    }
})

4,setup参数,更加复杂的使用。包括,props,emits,插槽,传值。这里要注意v-slots={slots}的用法,是传入一个对象,有key值(具名插槽,default),value是对应的展示内容。

interface Props{
     name?:string
}
// 插槽 定义一个渲染函数
const A = (_,{ slots })=>(
    <>
        <div>{slots.default ? slots.default():'默认值'}</div>
        <div>{slots.foo?.()}</div>
    </>
)

export default defineComponent({
    props:{
        name:String
    },
    emits:['back-click'],
    // setup参数:第一个:props 第二个对象:emit
    setup(props:Props,{emit}){
        let data = [
            {name:'小小1'},
            {name:'小小2'},
            {name:'小小3'}
        ]
        let fn = (item:any)=>{
            console.log('触发事件',item)
            emit('back-click',item) // 在emits中声明的事件名
        }
        let slots = {
            default:()=>(<div>default slots</div>),
            foo:()=>(<div>foo slots</div>)
        }
        let ipt = ref<string>('')
        return ()=>(
            <>
                <input type="text" v-model={ipt.value}/>
                <div>{ipt.value}</div>
                <hr />
                <A v-slots={slots}></A>
                <hr/>
                <div>props:{props?.name}</div><hr/>
            {/* 数组没有办法使用v-for,用js的循环思想 */}
            {/* 单花括号,属性绑定,代替v-bind  */}
            {/* 事件使用 on+类型 ,用函数形式,避免一上来就被调用 */}
                {data.map(v=>{
                    return <div onClick={()=>fn(v)} name={v.name}>{v.name}</div>
                })}
            </>
        )
    }
})

知识点:setup() 参数,参数一,props传值,可以使用toRef,toRefs进行解构。参数二,context上下文,是个非响应式对象,其中包括attrs,slots,emit,expose.

二十二,尝试手写简单的tsx插件 ☆

知识铺垫:

babel 代码转换,es6->es5 将低版本不识别函数语法进行转换
babel 主要有3个核心功能:
源代码--(编译器parse)-- 抽象语法树AST -- (转换过程transform)-- 修改后的AST -- (生成器generator) -- 转换后代码

实现 tsx 插件的5个依赖包:

  •  @vue/babel-plugin-jsx   编译v-show等指令
  •  @babel/core   babel核心库
  •  @babel/plugin-transform-typescript  编译ts
  •  @babel/plugin-syntax-import-meta  编译import
  •  @types/babel__core   声明文件

现在的plugin的注册形式是函数,所以写一个export default function(){},类型是 Plugin。

import type {Plugin} from 'vite'
import * as babel from '@babel/core'  // 核心库,将源代码转换为目标代码
import jsx from '@vue/babel-plugin-jsx'  // vue给babel写的插件支持tsx  v-model等

export default function():Plugin{
  return {
    // Plugin 的名称是有要求的  vite-plugin 开头
    name:'vite-plugin-vue-tsx',
    config(){
      return {
        // 使用 esbuild 编译ts文件(默认使用的是react.createElement
        esbuild:{
          include:/.ts$/
        }
      }
    },
    async transform(code,id){
      // code 是 转换代码,id是路径。
      if(/.tsx$/.test(id)){
        // console.log(code,id,'>>');
        // ts忽略下一行的检测(没有声明文件)
        // @ts-ignore
        const ts = await import('@babel/plugin-transform-typescript').then(r=>r.default)
        // babel的转换功能 异步的
        const res = await babel.transformAsync(code,{
          ast:true,
          configFile:false,
          babelrc:false,
          plugins:[jsx,[ts,{isTSX:true, allowExtensions:true}]]
        })
        console.log(res?.code);
        return res?.code
      }
      return code
    }
  }
}

二十三,自动引入

vue3使用的是import引入vue里的使用的内容,可以有效的tree-shaking,但是也很繁琐,那么可以使用 unplugin-auto-import 插件来自动引入模块。该插件的作用是识别当前代码中所使用的未导入的模块,并自动根据需要将它们导入到代码中。
unplugin-auto-import 不是一个独立的插件,而是一个适用于不同编辑器和构建工具的插件集合。这里展示的是使用 unplugin-auto-import 在 Vite 构建工具中的配置方式。

在 vite.config.ts 中引入并且注册。也会生成对应的dts文件。

import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      imports:['vue'],
      dts:'src/auto-import.d.ts',  // dts 声明文件
    })
  ]
})

二十四,v-model使用

v-model是实现双向绑定的,对比vue2的可以说是破坏性的更新。

对比vue2 :

  1. prop:value => modelValue
  2. 事件: input => update:modelValue
  3. v-bind的 .sync 修饰符和组件的model移除
  4. 支持多个v-model
  5. 新增自定义,修饰符  Modifiers

通过下面的例子来实现 自定义的v-model

// 父组件
  <div>
    <h1>父组件</h1>
    <div>isShow:{{ isShow }}</div>
    <div>text:{{ text }}</div>
    <button @click="isShow = !isShow">开关</button>
    <hr>
    <vModelVue v-model="isShow" v-model:textVal.isBt="text"></vModelVue>
  </div>


// 子组件
 <div v-if="modelValue" class="model">
    <div class="close"><button @click="close">关闭</button></div>
    <h3>v-model 子组件 dialog</h3>
    <div>内容: <input @input="changetext" type="text" :value="textVal"></div>
 </div>

子组件的接收很重要,modelValue默认是v-model的对应值,那自定义的则需要父级
v-model:xxx='' 的形式定义,自己在props中接收 xxx,作为对应值,那更改xxx时,就需要emit以update:xxx 的形式进行响应
同时还有v-model的自定义修饰符,比如内置的 lazy,number,trim等,例子使用的是 .isBt ,那在接收时用 xxxModifiers 的形式作为 xxx 的修饰符

const props = defineProps<{
  modelValue:boolean,
  textVal:string,
  textValModifiers?:{ // 传入的修饰符
    isBt:boolean
  }
}>()

// 子组件改父组件的内容,固定语法update:modelValue
const emit = defineEmits(['update:modelValue','update:textVal'])
let close = () => {
  emit('update:modelValue',false)
}
let changetext = (e:Event)=>{
  const target = e.target as HTMLInputElement
  emit('update:textVal',props?.textValModifiers?.isBt ? target.value+' 变态 ' : target.value)
}

二十五,自定义指令 directive

directive 在vue3也是破坏性更新。生命周期大调整。

vue2: bind,inserted,update,componentUpdated,unbind
vue3:created,beforeMount,mounted,beforeUpdate,updated,beforeUnmount,unmounted

//vue2的形式
    Vue.directive('focus', {
        //每当指令绑定到元素上时,会立即执行这个bind函数,只执行一次
        bind: function () {

        },
        //inserted表示元素插入到DOM中时,会执行inserted函数,
        //insert方法只触发一次,el表示被绑定的那个标签元素
        inserted: function (el,binding) {
            console.log(binding.name)   // 标签名
            console.log(binding.value)  // 值
            console.log(binding.expression) // 表达式
            el.focus()
        },
         //当VNode更新时会执行updated,可能触发多次
        updated:function(){
        }
    })


// vue3的形式
// 自定义指令命名: 必须以 vNameOfDirective 的形式来命名,可以直接在模板中使用
const vMove: Directive = {
  created() {
    console.log("========created,初始化");
  },
  beforeMount() {
    console.log("========beforeMount,绑定到元素后,只调用一次");
  },
  /*
    参数: 通过  ...args:Array<any>  传入打印
      el:当前绑定这个指令的元素。
      dir: 传过来的值都会放在这里
      vnode: 虚拟dom
      prenode: 上一个虚拟dom
    */
  mounted(el: HTMLElement, dir: DirectiveBinding<Dir>) {
    console.log("========mounted,插入父级dom调用");
    console.log(dir.value.background);
    el.style.background = dir.value.background;
  },
  beforeUpdate() {
    console.log("========beforeUpdate,元素更新之前");
  },
  updated() {
    console.log("========updated,更新后调用");
  },
  beforeUnmount() {
    console.log("========beforeUnmount,在元素移除之前");
  },
  unmounted() {
    console.log("========unmounted,被移除之后,调用一次");
  },
};

v-if 触发的是 beforeUnmount,unmounted;属性改变,触发的是beforeUpdate,updated。还可以自定义参数,修饰符,可以在dir中得到。

    <!-- v-if 触发beforeUnmount,unmounted -->
    <A v-move:aaa.xiaoxiao="{ background: 'red' }" v-if="flag"></A>
    <!-- 元素变了,触发beforeUpdate,updated -->
    <A v-move:aaa.xiaoxiao="{ background: 'green', flag: flag }"></A>

<!-- 也可以自定义参数,修饰符,可以在dir中得到 -->

在具体实战中呢,指令会用在哪里呢,比如权限控制,弹框的拖拽,那以下就是两个例子的实现。

1,权限控制,我们可以在mock中得到权限数组,实战中是接口获得,我这里直接用数组表示;通过指令来判断是否具有这个权限,可以在元素上进行控制。

// html
<button v-has-show="'shop:create'">创建</button>
<button v-has-show="'shop:edit'">编辑</button>
<button v-has-show="'shop:delete'">删除</button>

//js
let permission = [
    'xiaoxiao-id:shop:create',
    'xiaoxiao-id:shop:edit',
    // 'xiaoxiao-id:shop:delete',
]

 let userid = localStorage.getItem('userid') as string
 const vHasShow:Directive<HTMLElement,string> = (el,binding)=>{
    if(! permission.includes( userid + ':' + binding.value ) ){
        el.style.display = 'none'
    }
 }

2,弹框的拖拽 v-move,针对这个指令的元素,都可以进行拖拽移动。

import type { Directive, DirectiveBinding } from "vue";
const vMove: Directive<any, void> = (
  el: HTMLElement,
  binding: DirectiveBinding
) => {
  let moveElement: HTMLDivElement = el.firstElementChild as HTMLDivElement;
  // console.log(moveElement);
  const mousedown = (ev: MouseEvent) => {
    // 初始位置
    let x = ev.clientX - el.offsetLeft;
    let y = ev.clientY - el.offsetTop;
    const move = (e: MouseEvent) => {
      el.style.left = e.clientX - x + "px";
      el.style.top = e.clientY - y + "px";
    };
    // 移动
    document.addEventListener("mousemove", move);
    // 抬起
    document.addEventListener("mouseup", () => {
      document.removeEventListener("mousemove", move);
    });
  };
  // 按下
  moveElement.addEventListener("mousedown", mousedown);
};

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值