vue3的一些知识点plus--3

2023-11-12

二十,兄弟组件传值,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);
};

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

vue3的一些知识点plus--3 的相关文章

随机推荐

  • sqli labs less 25

    按照常规输入id 1 提示报错信息如下 一个很常见的报错 可以看出是单引号闭合的语句 输入 id 1 or 1 报出的错误信息不明显 加入一个括号进行试探能不能报出更多的语句错误 输入 id 1 or 1 从上图看出来or 被屏蔽了 经过更
  • ubuntu搭建android编译环境

    本文来自linux与嵌入式技术Q群52240781 ubuntu12 04 14 04安装后搭建android编译环境 注安装的是64位ubuntu系统12 04或14 04 请安装ubuntu的LTS版即12 04或14 04 每2年发布
  • 87_BigDecimal的doubleValue()、toString()、toPlainString()与科学计数法

    主题 BigDecimal toPlainString 可以避免出现科学计数法格式的数据 项目上面有个小伙伴在用Bigdecimal进行数值计算时 用return num doubleValue 的方式将结果送到前台 测试数值较小时无问题
  • 无法将“node”项识别为 cmdlet、函数、脚本文件或可运行程序的名称

    报错如下 检查npm目录下是否还有node exe文件如果没有复制过来 我复制过来问题解决 首先我是node和npm版本和项目版本不适应 需要更换版本 所以重新安装node js在重新安装的过程中将安装的位置改变了 所以造成了这个可能
  • DSS简介

    原文链接 去中心化软件服务 DSS 是一个基于CCR的轻量级 net运行时环境 DSS提供了一个轻量级的 状态导向的服务模型 它将REST概念和构造高性能高扩展性应用的系统级方法结合在一起 在DSS中服务被暴露为一种可以被程序和UI操作界面
  • 将 Tocmat5.0 注册为 Windows 的服务程序

    将 Tocmat5 0 注册为 Windows 的服务程序 步骤 1 下载 Tomcat 5 0 x 不要下载安装版本 2 解压到 TOMCAT HOME 3 安装或者从别处拷贝JRE 推荐拷贝 可以删除不需要的文件 如文档等 4 在 TO
  • Qt编译MySQL数据库驱动

    文章目录 一 我的编译环境 二 需要 三 Qt的下载 四 编译驱动 主题 4 1 第一步打开msql pro 4 2 第二步 4 3 第三步 4 4 第四步 4 5 第五步 编译 最后一步 一 我的编译环境 Qt 5 14 2 mingw7
  • 大模型时代,企业如何重构 AI 应用落地范式?

    近一年来 生成式人工智能 AIGC 技术的快速发展和各种大模型的涌现 引发了全球范围内对于通用人工智能 AGI 时代是否即将到来的讨论 在 AIGC 大模型公共服务逐渐被大众辩证地接受后 如何用 AIGC 技术重塑企业智能服务成为一个深水区
  • 使用Jmeter进行http接口做功能、性能测试

    使用Jmeter进行http接口做功能 性能测试 1 使用Jmeter进行http接口做功能 性能测试 在测试移动APP时 会有很多接口需要做测试 我在这里介绍一下对HTTP接口做功能 性能的测试 首先我们会从开发人员拿到接口数据 1 1
  • 苹果IOS开发者账号总结

    详细地址 https developer apple com programs which program 个人账号 Individual 费用99美金一年 该账号在App Store销售者只能显示个人的ID 比如zhitian zhang
  • Linux 查看进程消耗内存情况总结

    在Linux中 有很多命令或工具查看内存使用情况 今天我们来看看如何查看进程消耗 占用的内存情况 Linux的内存管理和相关概念要比Windows复杂一些 在此之前 我们需要了解一下Linux系统下面有关内存的专用名词和专业术语概念 物理内
  • LeetCode之最长公共子序列问题LCS解决方法

    Leetcode官网解答 使用动态规划原理 请参考原文地址 https leetcode cn com problems longest common subsequence solution zui chang gong gong zi
  • Python机器学习--算法实现--常用算法在Sklearn中的回归算法关键参数详解

    常用算法在Sklearn中的关键参数详解 回归算法 线性回归算法 from sklearn linear model import LinearRegression LinearRegression fit intercept True n
  • VS2008调试dump文件

    让程序在崩溃时体面的退出之Dump文件 在我的那篇 让程序在崩溃时体面的退出之CallStack 中提供了一个在程序崩溃时得到CallStack的方法 可是要想得到CallStack 必须有pdb文件的支持 但是一般情况下 发布出去的程序都
  • 网址服务器不稳定,关于网站被360搜索提示服务器不稳定可能无法正常访问的解决方法...

    相信很多做网站的站长们都有过类似的经历吧 有时候因为服务器节点问题或者其他问题导致服务器或vps暂时性无法运行导致网站暂时无法打开 这些都是属于正常的现象 但是运气不好的时候辛辛苦苦优化的网站可能就会被搜素引擎标记为 该页面因服务器不稳定可
  • 锦囊妙计--组建新团队整体规划

    详细请见幕布链接 https mubu com doc 2MicLCcsoC 幕布内的具体内容还在持续完善中 敬请期待
  • 【洛谷 P1115】最大子段和 题解(贪心算法)

    最大子段和 题目描述 给出一个长度为 n n n 的序列 a a a 选出其中连续且非空的一段使得这段和最大 输入格式 第一行是一个整数 表示序列的长度 n
  • 报错unable to access android sdk add-on list

    前言 初次安装Android Studio AS启动后 会在默认路径下检测是否有Android SDK 如果没有的话 就会报错如下 解决办法 我这里安装了sdk 然后点击cancel 下一步会自动读取我们的sdk 然后点击next就行了 如
  • OPENWRT中SSH免密钥登陆(详细步骤)

    通过使用ssh keygen生成公钥 在两台机器之间互相建立新人通道极客 假设本地机器是client 远程机器为server 1 使用ssh keygen生成rsa keygen 在这里会覆盖以前生成的 ssh id rsa文件 请提前做好
  • vue3的一些知识点plus--3

    二十 兄弟组件传值 Bus 兄弟组件直接的传值 最基础的是通过同一个父级进行数值的传递 使用prop和emit 太过繁琐 父级 div a a b b div let flag ref false let getFlag params bo