Shero.

生活百般滋味,你要笑着面对😊


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

web安全

发表于 2022-08-03

web安全

网站或移动端

  • 硬件
    • 服务器—》运维的 硬件 软件
  • 软件或者程序
    • 后台 java php python …编程都涉及到程序安全
    • 前端 与后台交互 容易出现安全隐患
      • 加密 校验 程序的抗压力

xss攻击(跨站脚本攻击)

  • web页面
    • 包含html js文件 嵌入可执行代码(js)
    • 网络传输的时候,js文件可下载的或通过ajax提交
  • xss危害
    • 用户提交的数据,未经处理之前 ===》 js obj.value
    • 强行改变程序的执行方式
  • xss解决
    • 运维=>设置只读
    • 通过后台程序,写一套监测程序,监测文件的大小改变,报警更换文件
    • 校验用户输入信息 正则验证合法性
    • 对特殊符号 !、?、@… 进行过滤

跨站请求伪造(csrf攻击)

  • 用户和后台交互数据的时候,交互的页面是伪造页面,所有的用户信息将被泄露
  • csrf解决
    • token 添加二次校验
    • 验证码 手机号 微信验证(保证验证信息 是自己项目发的)

sql注入

  • 一般发生在注册、评论、添加商品…凡是有输入的地方,就有可能发生sql注入
  • 原理
    • or 1 == 1 or 2 > 3
    • and select * from user where uName = $uName and uPwd = $uPwd
      * uName = '' or 1 == 1 and uPwd = '' or 1 == 1
      
  • 危害:任意账户 任意登录 任意操作
  • 避免:前端
    — 添加正则校验(特殊符号)
    — 屏蔽敏感词汇(过滤程序)
    — 缩减用户权限(取最小)
    — sql语句(有工具可以检测漏洞)

接口加密

  • url?keyword=va11 & id=3 & name=xx & pwd=1333 — get传输、post传输有可能 — 数据有可能会被劫持—》抓包
  • js原生

    1
    2
    3
    4
    5
    let url = "https://www.baidu.com"
    let params = "?keyword="+val
    params+="&id=3"
    params+="&name"+"xxx"
    params+="&pwd"+"123"
  • md5() — 加密方式 — “?keyword=”+md5(val) — 值加密

  • 路径加密 url = md5(url+params)
  • 前端后端都需要加密 —- 加密是双向的
  • vue 加密
    • 加密软件对象 npm install bcryptjs — 一般这种加密包,加密方式不止一种
    • 对象.标准.加密方法(数据, 密匙, 参数)
      1
      2
      3
      4
      npm install bcryptjs
      import bcrypt from 'bcryptjs'
      var salt = bcrypt.genSaltSync(12); //定义密码加密的计算强度,默认10
      var hash = bcrypt.hashSync(明文密码, salt); //变量hash就是加密后的密码

持续集成与持续部署

发表于 2022-07-29

持续集成与持续部署

传统开发过程中的坑

  • BUG总是在最后才发现
  • 越到项目后期,加班越严重
  • 交付无法保障
  • 变更频繁,导致效率低下
  • 无效的等待多,用户满足度低

  • 理想状态

  • 不用为开发测试环境不一致而苦恼
  • 不用麻烦运维人员帮忙调试环境
  • 不用手动进行测试,模拟环境中进行自测
  • 不用手动发布、部署,自动化实现发布部署
  • 不用管开发、测试环境,只用专注代码的开发

持续集成CI解决问题?

  • 提高软件质量
  • 效率迭代
  • 便捷部署
  • 快速交付、便于管理

持续集成CI(continuous integration) 持续交付CD(continuous delivery) 持续部署CD(continuous deployment)

  • CI 不断提交代码测试
  • CD 在CI的基础上,对代码进行构建,打包进行储存,发布交由人工完成
  • CD 在CD的基础上,对代码进行自动化发布部署,快速验证

持续集成组成要素

  • 版本管理工具 github gitlab
  • 构建脚本工具
  • CI服务器

应用场景

  • 打包平台
    • 常见的大包,java应用(Gradle/Maven)、Nodejs前端应用(npm/yarn)
    • 移动端大包(Android/IOS)
  • 测试平台
    • 接口测试
    • 自动化测试Robotium/TestLink
    • 单元测试junit
    • 性能测试 JmeTest
  • 自动部署
    • FTP
    • Shell
    • Tomcat/Docker
  • 持续集成
    • Git: gitlab/github/gitee等
    • Jenkins/TravisCi/CircleCi
    • Docker

Image text

SEO

发表于 2022-07-20

SEO 搜索引擎优化

最基本条件

  • 多页面 —- (蜘蛛)抓取
  • 页面含有(蜘蛛)抓取的内容
  • title、描述、关键词

vue解决SEO方案

  • 方式1: 预渲染

    • vue插件: prerender-spa-plugin
    • vue.config.js 配置 Image text (注:有多少个路由、需要配置多少个)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      const path = require('path');
      const PrerenderSPAPlugin = require('prerender-spa-plugin');
      module.exports = {
      publicPath: './',
      configureWebpack: {
      plugins: [
      new PrerenderSPAPlugin({
      staticDir: path.join(__dirname, 'dist'),
      routes: [
      '/',
      '/about',
      '/contact'
      ]
      })
      ]
      }
      };
    • 解决title、描述、关键词插件:vue-meta-info

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // main.js
      import MetaInfo from 'vue-meta-info'
      Vue.use(MetaInfo)
      // 页面使用
      metaInfo: {
      title: '',
      meta: [{
      name: '',
      content: ''
      }]
      }
  • 不足:如果有很多详情页需要seo,预渲染就不合适,动态去改变title、描述、关键字是无效的

  • 适合:项目某几个页面做seo

  • 方式2: 服务端渲染

    • Nuxt.js 处理流程
      • npm run build
      • 将打包完成的文件单独拷贝出来
        • nuxt.config
        • .nuxt
        • package.json
        • static
      • 将4个文件拷贝到服务器,服务器安装node环境
        • npm install
      • 运行 npm run start
      • nginx设置代理

vue项目打包上线

  • 打包路径 vue.config.js publicPath: ‘./‘
  • 路由模式:history
    • 前端如果自己测试项目,hash
    • 项目上线要求是 history模式,重定向

vite(2.9.9)+vue3+antdv(3.2.3)+ts+pinia项目总结

发表于 2022-07-19 | 分类于 前端框架

总结

vite项目require语法兼容问题

  • 使用插件,vite.config.ts配置
    • yarn add -D vite-plugin-require-transform
    • npm i vite-plugin-require-transform –save-dev
  • 遇到问题 build不成功,与ant-vue组件冲突
  • 解决 vite静态资源处理

使用vite-plugin-html插件,vite.config.ts配置;拦截接口,重定向其它内网地址获取登录信息,无效

  • yarn add vite-plugin-html -D
  • npm i vite-plugin-html -D

父子组件的传参无效

  • 父组件定义参数 let info = reactive({}),子组件接收不到
  • 解决 该用ref定义,reactive定义需要解构,一个个传递

子组件更新父组件传递的状态

1
2
const emit = defineEmits(["update:show"]);
emit("update:show", false);

校验,滚动到第一个校验失败位置

1
2
3
4
5
6
7
8
9
10
11
// 由于校验失败ant会自动给失败表单项添加类名,直接获取滚动到对应位置
setTimeout(() => {
const errorList = (document as any).querySelectorAll(".ant-form-item-has-error");
if (errorList.length) {
errorList[0].scrollIntoView({
block: "center",
behavior: "smooth",
});
return;
}
}, 50);

校验,手动触发校验及清除校验

1
2
3
4
5
6
7
8
9
10
11
// ref="formRef"
const formRef = ref<FormInstance>();
(formRef.value as any)
.validateFields()
.then(() => {
// 校验成功,逻辑
})
.catch((errorInfo: any) => {});

// rpName 清除校验字段
formRef.value as any).clearValidate(["rpName"]);

lodash使用

1
2
3
4
5
6
7
8
9
10
11
// import { throttle } from "lodash-es";
const throttleFun = throttle(
() => {
initClassTree();
},
500,
{
leading: true,
trailing: true,
}
);

父组件使用v-model传值

  • 注 update:modelValue 冒号两边无空格
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <Editor v-model="state"></Editor>
    // 子组件
    props: {
    modelValue: {
    type: Object,
    }
    },
    emits: ['update:modelValue'], // 触发事件
    setup (props, { emit }) {
    const data = computed({
    get() {
    return props.modelValue
    },
    set(newValue) {
    emit('update:modelValue', deepcopy(newValue));
    }
    });
    }

TS

发表于 2022-03-30 | 分类于 JS

typeScript

  • Typescript
  • 使用 nvm 来管理 node 版本

    为什么要学习ts

  • 程序更容易理解
    • 函数或者方法输入输出的参数类型、外部条件
    • 动态语言的约束:需要手动调试等过程
    • ts可以解决以上问题
  • 效率更高
    • 在不同的代码块和定义中进行跳转
    • 代码自动补全
    • 丰富的接口提示
  • 更少的错误
    • 编译期间能够发现大部分错误
    • 杜绝一些比较常见的错误
  • 非常好的包容性
    • 完全兼容javascript
    • 第三方库可以单独编写类型文件
    • 大多数项目都支持ts
  • 缺点
    • 增加一些学习成本
    • 短期内增加了一些开发成本
  • 安装 Typescript:

    1
    2
    npm install -g typescript
    yarn global add typescript
  • 使用 tsc 全局命令:

    1
    2
    3
    4
    5
    6
    // 查看 tsc 版本
    tsc -v
    // 编译 ts 文件
    tsc fileName.ts
    // 运行
    ts-node fileName.ts
  • 数组和元组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //最简单的方法是使用「类型 + 方括号」来表示数组:
    let arrOfNumbers: number[] = [1, 2, 3, 4]
    //数组的项中不允许出现其他的类型:
    //数组的一些方法的参数也会根据数组在定义时约定的类型进行限制:
    arrOfNumbers.push(3)
    arrOfNumbers.push('abc')

    // 元祖的表示和数组非常类似,只不过它将类型写在了里面 这就对每一项起到了限定的作用
    let user: [string, number] = ['viking', 20]
    //但是当我们写少一项 就会报错 同样写多一项也会有问题
    user = ['molly', 20, true]
  • interface 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // 我们定义了一个接口 Person
    interface Person {
    name: string;
    age: number;
    }
    // 接着定义了一个变量 viking,它的类型是 Person。这样,我们就约束了 viking 的形状必须和接口 Person 一致。
    let viking: Person ={
    name: 'viking',
    age: 20
    }

    //有时我们希望不要完全匹配一个形状,那么可以用可选属性:
    interface Person {
    name: string;
    age?: number;
    }
    let viking: Person = {
    name: 'Viking'
    }

    //接下来还有只读属性,有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性

    interface Person {
    readonly id: number;
    name: string;
    age?: number;
    }
    viking.id = 9527
  • 函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 来到我们的第一个例子,约定输入,约定输出
    function add(x: number, y: number): number {
    return x + y
    }
    // 可选参数
    function add(x: number, y: number, z?: number): number {
    if (typeof z === 'number') {
    return x + y + z
    } else {
    return x + y
    }
    }

    // 函数本身的类型
    const add2: (x: number, y: number, z?:number) => number = add

    // interface 描述函数类型
    const sum = (x: number, y: number) => {
    return x + y
    }
    interface ISum {
    (x: number, y: number): number
    }
    const sum2: ISum = sum
  • 类型推论,联合类型 和 类型断言

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 联合类型 - union types
    // 我们只需要用中竖线来分割两个
    let numberOrString: number | string
    // 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
    numberOrString.length
    numberOrString.toString()
    // 类型断言 - type assertions
    // 这里我们可以用 as 关键字,告诉typescript 编译器,你没法判断我的代码,但是我本人很清楚,这里我就把它看作是一个 string,你可以给他用 string 的方法。
    function getLength(input: string | number): number {
    const str = input as string
    if (str.length) {
    return str.length
    } else {
    const number = input as number
    return number.toString().length
    }
    }
    // 类型守卫 - type guard
    // typescript 在不同的条件分支里面,智能的缩小了范围,这样我们代码出错的几率就大大的降低了。
    function getLength2(input: string | number): number {
    if (typeof input === 'string') {
    return input.length
    } else {
    return input.toString().length
    }
    }
  • 枚举 Enums

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 数字枚举,一个数字枚举可以用 enum 这个关键词来定义,我们定义一系列的方向,然后这里面的值,枚举成员会被赋值为从 0 开始递增的数字,
    enum Direction {
    Up,
    Down,
    Left,
    Right,
    }
    console.log(Direction.Up)

    // 还有一个神奇的点是这个枚举还做了反向映射
    console.log(Direction[0])

    // 字符串枚举
    enum Direction {
    Up = 'UP',
    Down = 'DOWN',
    Left = 'LEFT',
    Right = 'RIGHT',
    }
    const value = 'UP'
    if (value === Direction.Up) {
    console.log('go up!')
    }
  • 泛型 Generics

    • 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
    • 相当于一个占位符
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      function echo(arg) {
      return arg
      }
      const result = echo(123)
      // 这时候我们发现了一个问题,我们传入了数字,但是返回了 any

      function echo<T>(arg: T): T {
      return arg
      }
      const result = echo(123)

      // 泛型也可以传入多个值
      function swap<T, U>(tuple: [T, U]): [U, T] {
      return [tuple[1], tuple[0]]
      }

      const result = swap(['string', 123])
  • 泛型约束

    • 在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      function echoWithArr<T>(arg: T): T {
      console.log(arg.length)
      return arg
      }

      // 缺陷 只能指定特定类型
      // function echoWithArr<T>(arg: T[]): T[] {
      // console.log(arg.length)
      // return arg
      // }
      // 上例中,泛型 T 不一定包含属性 length,我们可以给他传入任意类型,当然有些不包括 length 属性,那样就会报错

      interface IWithLength {
      length: number;
      }
      function echoWithLength<T extends IWithLength>(arg: T): T {
      console.log(arg.length)
      return arg
      }

      echoWithLength('str')
      const result3 = echoWithLength({length: 10})
      const result4 = echoWithLength([1, 2, 3])
  • 泛型与类和接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    class Queue {
    private data = [];
    push(item) {
    return this.data.push(item)
    }
    pop() {
    return this.data.shift()
    }
    }

    const queue = new Queue()
    queue.push(1)
    queue.push('str')
    console.log(queue.pop().toFixed())
    console.log(queue.pop().toFixed())

    //在上述代码中存在一个问题,它允许你向队列中添加任何类型的数据,当然,当数据被弹出队列时,也可以是任意类型。在上面的示例中,看起来人们可以向队列中添加string 类型的数据,但是那么在使用的过程中,就会出现我们无法捕捉到的错误,

    class Queue<T> {
    private data = [];
    push(item: T) {
    return this.data.push(item)
    }
    pop(): T {
    return this.data.shift()
    }
    }
    const queue = new Queue<number>()

    //泛型和 interface
    interface KeyPair<T, U> {
    key: T;
    value: U;
    }

    let kp1: KeyPair<number, string> = { key: 1, value: "str"}
    let kp2: KeyPair<string, number> = { key: "str", value: 123}
  • 类型别名(Type Aliases) 和 交叉类型( Intersection Types)

    • 类型别名,就是给类型起一个别名,让它可以更方便的被重用
1
2
3
4
5
6
7
8
9
10
11
12
13
let sum: (x: number, y: number) => number
const result = sum(1,2)
type PlusType = (x: number, y: number) => number
let sum2: PlusType

// 支持联合类型
type StrOrNumber = string | number
let result2: StrOrNumber = '123'
result2 = 123

// 字符串字面量
type Directions = 'Up' | 'Down' | 'Left' | 'Right'
let toWhere: Directions = 'Up'
1
2
3
4
5
interface IName  {
name: string
}
type IPerson = IName & { age: number }
let person: IPerson = { name: 'hello', age: 12}
  • more…

移动端H5

发表于 2022-03-28 | 分类于 前端框架

移动端H5

HTML

  • 调用系统功能
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- 拨打电话 -->
    <a href="tel:10086">拨打电话给10086小姐姐</a>

    <!-- 发送短信 -->
    <a href="sms:10086">发送短信给10086小姐姐</a>

    <!-- 发送邮件 -->
    <a href="mailto:young.joway@aliyun.com">发送邮件给JowayYoung</a>

    <!-- 选择照片或拍摄照片 -->
    <input type="file" accept="image/*">

    <!-- 选择视频或拍摄视频 -->
    <input type="file" accept="video/*">

    <!-- 多选文件 -->
    <input type="file" multiple>

CSS

  • 自动适应布局

    • rem布局比例设置成1rem=100px,即在设计图上100px长度在CSS代码上使用1rem表示

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      function AutoResponse(width = 750) {
      const target = document.documentElement;
      if (target.clientWidth >= 600) {
      target.style.fontSize = "80px";
      } else {
      target.style.fontSize = target.clientWidth / width * 100 + "px";
      }
      }
      AutoResponse();
      window.addEventListener("resize", () => AutoResponse());
    • calc()动态声明的font-size

      1
      2
      3
      html {
      font-size: calc(100vw / 7.5);
      }
    • iPad Pro分辨率1024px为移动端和桌面端的断点,还可结合媒体查询做断点处理。1024px以下使用rem布局,否则不使用rem布局

      1
      2
      3
      4
      5
      @media screen and (max-width: 1024px) {
      html {
      font-size: calc(100vw / 7.5);
      }
      }
  • 监听屏幕旋转

    1
    2
    3
    4
    5
    6
    7
    8
    /* 竖屏 */
    @media all and (orientation: portrait) {
    /* 自定义样式 */
    }
    /* 横屏 */
    @media all and (orientation: landscape) {
    /* 自定义样式 */
    }
  • 美化滚动条

    • ::-webkit-scrollbar:滚动条整体部分
    • ::-webkit-scrollbar-track:滚动条轨道部分
    • ::-webkit-scrollbar-thumb:滚动条滑块部分
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      ::-webkit-scrollbar {
      width: 6px;
      height: 6px;
      background-color: transparent;
      }
      ::-webkit-scrollbar-track {
      background-color: transparent;
      }
      ::-webkit-scrollbar-thumb {
      border-radius: 3px;
      background-image: linear-gradient(135deg, #09f, #3c9);
      }

JS

  • 简化回到顶部
    • 该函数就是scrollIntoView,它会滚动目标元素的父容器使之对用户可见,简单概括就是相对视窗让容器滚动到目标元素位置。它有三个可选参数能让scrollIntoView滚动起来更优雅
    • behavior:动画过渡效果,默认auto无,可选smooth平滑
    • inline:水平方向对齐方式,默认nearest就近对齐,可选start顶部对齐、center中间对齐和end底部对齐
    • block:垂直方向对齐方式,默认start顶部对齐,可选center中间对齐、end底部对齐和nearest就近对齐
    • TIPS 当然还可滚动到目标元素位置,只需将document.body修改成目标元素的DOM对象
      1
      2
      const goTopBtn = document.getElementById("go-top-btn");
      goTopBtn.addEventListener("click", () => document.body.scrollIntoView({ behavior: "smooth" }));

了解更多

postcss-px-to-viewport

  • postcss-px-to-viewport 对内联css样式,外联css样式,内嵌css样式有效,对js动态css无效。所以要动态改变css展示效果的话,要使用静态的class定义变化样式,通过js改变dom元素的class实现样式变化
  • Vant组件的设计稿尺寸是375px,可用通过覆盖:root下的Vant的css变量中px单位的方式,对Vant组件做适配
  • vue模板中的px单位不会被转换

Router

发表于 2022-03-23 | 分类于 tool

vue-router

router 路由

  • 应为vue是单页应用不会有那么多html 让我们跳转 所有要使用路由做页面的跳转
  • Vue 路由允许我们通过不同的 URL 访问不同的内容。通过 Vue 可以实现多视图的单页Web应用
  • 构建前端项目

    1
    2
    3
    npm init vue@latest
    //或者
    npm init vite@latest
  • 使用Vue3 安装对应的router4版本

    1
    npm install vue-router@4
  • 使用Vue2安装对应的router3版本

  • 在src目录下面新建router 文件 然后在router 文件夹下面新建 index.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //引入路由对象
    import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router'
    //vue2 mode history vue3 createWebHistory
    //vue2 mode hash vue3 createWebHashHistory
    //vue2 mode abstact vue3 createMemoryHistory

    //路由数组的类型 RouteRecordRaw
    // 定义一些路由
    // 每个路由都需要映射到一个组件。
    const routes: Array<RouteRecordRaw> = [{
    path: '/',
    component: () => import('../components/a.vue')
    },{
    path: '/register',
    component: () => import('../components/b.vue')
    }]

    const router = createRouter({
    history: createWebHistory(),
    routes
    })
    //导出router
    export default router
  • router-link #

    • 没有使用常规的 a 标签,而是使用一个自定义组件 router-link 来创建链接。这使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码
  • router-view #
    • router-view 将显示与 url 对应的组件。你可以把它放在任何地方,以适应你的布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<h1>router</h1>
<div>
<!--使用 router-link 组件进行导航 -->
<!--通过传递 `to` 来指定链接 -->
<!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
<router-link tag="div" to="/">跳转a</router-link>
<router-link tag="div" style="margin-left:200px" to="/register">跳转b</router-link>
</div>
<hr />
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</template>
  • 最后在main.ts 挂载
    1
    2
    3
    4
    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router'
    createApp(App).use(router).mount('#app')

命名路由-编程式导航

  • 命名路由

    • 除了 path 之外,你还可以为任何路由提供 name。这有以下优点:
      • 没有硬编码的 URL
      • params 的自动编码/解码
      • 防止你在 url 中出现打字错误
      • 绕过路径排序(如显示一个)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        const routes:Array<RouteRecordRaw> = [
        {
        path:"/",
        name:"Login",
        component:()=> import('../components/login.vue')
        },
        {
        path:"/reg",
        name:"Reg",
        component:()=> import('../components/reg.vue')
        }
        ]
  • router-link跳转方式需要改变 变为对象并且有对应name

    1
    2
    3
    4
    5
    6
    <h1>router</h1>
    <div>
    <router-link :to="{name:'Login'}">Login</router-link>
    <router-link style="margin-left:10px" :to="{name:'Reg'}">Reg</router-link>
    </div>
    <hr />
  • 编程式导航

    • 除了使用 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现
    • 字符串模式

      1
      2
      3
      4
      5
      import { useRouter } from 'vue-router'
      const router = useRouter()
      const toPage = () => {
      router.push('/reg')
      }
    • 对象模式

      1
      2
      3
      4
      5
      6
      7
      8
      import { useRouter } from 'vue-router'
      const router = useRouter()

      const toPage = () => {
      router.push({
      path: '/reg'
      })
      }
    • 命名式路由模式

      1
      2
      3
      4
      5
      6
      7
      8
      import { useRouter } from 'vue-router'
      const router = useRouter()

      const toPage = () => {
      router.push({
      name: 'Reg'
      })
      }
    • a标签跳转

      • 直接通过a href也可以跳转但是会刷新页面
        1
        <a href="/reg">rrr</a>

历史记录

  • replace的使用
    • 采用replace进行页面的跳转会同样也会创建渲染新的Vue组件,但是在history中其不会重复保存记录,而是替换原有的vue组件
  • router-link 使用方法

    1
    2
    <router-link replace to="/">Login</router-link>
    <router-link replace style="margin-left:10px" to="/reg">Reg</router-link>
  • 编程式导航

    1
    2
    3
    4
    5
    6
    7
    8
    <button @click="toPage('/')">Login</button>
    <button @click="toPage('/reg')">Reg</button>
    // js
    import { useRouter } from 'vue-router'
    const router = useRouter()
    const toPage = (url: string) => {
    router.replace(url)
    }
  • 横跨历史

    • 该方法采用一个整数作为参数,表示在历史堆栈中前进或后退多少步
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <button @click="next">前进</button>
      <button @click="prev">后退</button>
      const next = () => {
      //前进 数量不限于1
      router.go(1)
      }
      const prev = () => {
      //后退
      router.back()
      }

路由传参

  • Query路由传参

    • 编程式导航 使用router push 或者 replace 的时候 改为对象形式新增query 必须传入一个对象
      1
      2
      3
      4
      5
      6
      const toDetail = (item: Item) => {
      router.push({
      path: '/reg',
      query: item
      })
      }
  • 接受参数

    • 使用 useRoute 的 query
      1
      2
      3
      import { useRoute } from 'vue-router';
      const route = useRoute()
      <div>ID:{{ route.query?.id }}</div>
  • Params路由传参

    • 编程式导航 使用router push 或者 replace 的时候 改为对象形式并且只能使用name,path无效,然后传入params
      1
      2
      3
      4
      5
      6
      const toDetail = (item: Item) => {
      router.push({
      name: 'Reg',
      params: item
      })
      }
  • 接受参数

    • 使用 useRoute 的 params
      1
      2
      3
      4
      5
      import { useRoute } from 'vue-router';
      const route = useRoute()
      <div>品牌:{{ route.params?.name }}</div>
      <div>价格:{{ route.params?.price }}</div>
      <div>ID:{{ route.params?.id }}</div>
  • 动态路由传参

    • 很多时候,我们需要将给定匹配模式的路由映射到同一个组件。例如,我们可能有一个 User 组件,它应该对所有用户进行渲染,但用户 ID 不同。在 Vue Router 中,我们可以在路径中使用一个动态字段来实现,我们称之为 路径参数
    • 路径参数 用冒号 : 表示。当一个路由被匹配时,它的 params 的值将在每个组件
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      const routes:Array<RouteRecordRaw> = [
      {
      path:"/",
      name:"Login",
      component:()=> import('../components/login.vue')
      },
      {
      //动态路由参数
      path:"/reg/:id",
      name:"Reg",
      component:()=> import('../components/reg.vue')
      }
      ]
      const toDetail = (item: Item) => {
      router.push({
      name: 'Reg',
      params: {
      id: item.id
      }
      })
      }
      import { useRoute } from 'vue-router';
      import { data } from './list.json'
      const route = useRoute()
      const item = data.find(v => v.id === Number(route.params.id))
  • 二者的区别

    • query 传参配置的是 path,而 params 传参配置的是name,在 params中配置 path 无效
    • query 在路由配置不需要设置参数,而 params 必须设置
    • query 传递的参数会显示在地址栏中
    • params传参刷新会无效,但是 query 会保存传递过来的值,刷新不变
    • 路由配置

嵌套路由

  • 一些应用程序的 UI 由多层嵌套的组件组成。在这种情况下,URL 的片段通常对应于特定的嵌套组件结构,例如:
  • 如你所见,children 配置只是另一个路由数组,就像 routes 本身一样。因此,你可以根据自己的需要,不断地嵌套视图
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const routes: Array<RouteRecordRaw> = [
    {
    path: "/user",
    component: () => import('../components/footer.vue'),
    children: [
    {
    path: "",
    name: "Login",
    component: () => import('../components/login.vue')
    },
    {
    path: "reg",
    name: "Reg",
    component: () => import('../components/reg.vue')
    }
    ]
    },
    ]
  • TIPS:不要忘记写router-view
    1
    2
    3
    4
    5
    6
    7
    <div>
    <router-view></router-view>
    <div>
    <router-link to="/">login</router-link>
    <router-link style="margin-left:10px;" to="/user/reg">reg</router-link>
    </div>
    </div>

命名视图

  • 命名视图可以在同一级(同一个组件)中展示更多的路由视图,而不是嵌套显示。 命名视图可以让一个组件中具有多个路由渲染出口,这对于一些特定的布局组件非常有用。 命名视图的概念非常类似于“具名插槽”,并且视图的默认名称也是 default
  • 一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
    const routes: Array<RouteRecordRaw> = [
    {
    path: "/",
    components: {
    default: () => import('../components/layout/menu.vue'),
    header: () => import('../components/layout/header.vue'),
    content: () => import('../components/layout/content.vue'),
    }
    },
    ]
    const router = createRouter({
    history: createWebHistory(),
    routes
    })
    export default router
  • 对应Router-view 通过name 对应组件
    1
    2
    3
    4
    5
    <div>
    <router-view></router-view>
    <router-view name="header"></router-view>
    <router-view name="content"></router-view>
    </div>

Pinia

发表于 2022-03-22 | 分类于 tool

Pinia

  • 官方文档Pinia
  • git地址
  • ~more

    Pinia.js

  • 有如下特点:
    • 全局状态管理工具
    • 完整的 ts 的支持;
    • 足够轻量,压缩后的体积只有1kb左右;
    • 去除 mutations,只有 state,getters,actions;
    • actions 支持同步和异步;
    • 代码扁平化没有模块嵌套,只有 store 的概念,store 之间可以自由使用,每一个store都是独立的
    • 无需手动添加 store,store 一旦创建便会自动添加;
    • 支持Vue3 和 Vue2
  • 起步 安装
    • yarn add pinia
    • npm install pinia
  • 引入注册Vue3

    1
    2
    3
    4
    5
    6
    7
    import { createApp } from 'vue'
    import App from './App.vue'
    import {createPinia} from 'pinia'
    const store = createPinia()
    let app = createApp(App)
    app.use(store)
    app.mount('#app')
  • Vue2 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import { createPinia, PiniaVuePlugin } from 'pinia'
    Vue.use(PiniaVuePlugin)
    const pinia = createPinia()
    new Vue({
    el: '#app',
    // other options...
    // ...
    // note the same `pinia` instance can be used across multiple Vue apps on
    // the same page
    pinia,
    })

初始化仓库Store

  • 新建一个文件夹Store
  • 新建文件[name].ts (存储是使用定义的defineStore(),并且它需要一个唯一的名称,作为第一个参数传递)

    1
    2
    3
    4
    // store-namespace.ts
    export const enum Names {
    Test = 'TEST'
    }
  • 定义仓库Store

    • store引入(这个名称,也称为id,是必要的,Pania 使用它来将商店连接到 devtools。将返回的函数命名为use…是可组合项之间的约定,以使其使用习惯)
      1
      2
      3
      4
      5
      import { defineStore } from 'pinia'
      import { Names } from './store-namespce'
      export const useTestStore = defineStore(Names.Test, {

      })
  • 定义值

    • State 箭头函数 返回一个对象 在对象里面定义值

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      import { defineStore } from 'pinia'
      import { Names } from './store-namespce'

      export const useTestStore = defineStore(Names.Test, {
      state:()=>{
      return {
      current:1
      }
      },
      //类似于computed 可以帮我们去修饰我们的值
      getters:{

      },
      //可以操作异步 和 同步提交state
      actions:{

      }
      })
    • .vue使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <template>
      <div>
      pinia: {{ test.current }} ----- {{ test.name }}
      </div>
      </template>

      <script setup lang="ts">
      import { useTestStore } from './store'
      const test = useTestStore();
      </script>

State

  • State 是允许直接修改值的 例如current++
  • 批量修改State的值(在他的实例上有$patch方法可以批量修改多个值)
  • 批量修改函数形式 - 推荐使用函数形式 可以自定义修改逻辑
  • 通过原始对象修改整个实例 - $state您可以通过将store的属性设置为新对象来替换store的整个状态 - 缺点就是必须修改整个对象的所有属性
  • 通过actions修改 - 定义Actions - 在actions 中直接使用this就可以指到state里面的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import { defineStore } from 'pinia'

    import { Names } from './store-name'

    export const useTestStore = defineStore(Names.Test, {
    state:()=>{
    return {
    current: 1,
    name: 'pinia'
    }
    },
    //类似于computed 可以帮我们去修饰我们的值
    getters:{

    },
    //可以操作异步 和 同步提交state
    actions:{
    setCurrent (num: number): void {
    // this.current++; // 不使用箭头函数,this指向有问题
    this.current = num;
    }

    }
    });
  • 使用方法直接在实例调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <template>
    <div>
    <div>
    <button @click="Add">+</button>
    </div>
    pinia: {{ test.current }} ----- {{ test.name }}
    </div>
    </template>

    <script setup lang="ts">
    import { useTestStore } from './store'
    let test = useTestStore();
    // 修改值
    const Add = () => {
    // 1 直接修改
    // test.current++
    // 2
    // test.$patch({
    // current:200,
    // name: 300
    // })
    // 3 推荐使用 自定义逻辑
    // test.$patch((state)=>{
    // state.current++;
    // state.name = 'pini111'
    // })
    // 4
    // test.$state = {
    // current: 10,
    // name: '1111'
    // }
    // 5
    test.setCurrent(567);
    }
    </script>

    <style lang="less" scoped>

    </style>

解构store

  • Pinia是不允许直接解构是会失去响应性的
  • 差异对比
    • 修改Test current 解构完之后的数据不会变
    • 而源数据是会变的
  • 解决方案可以使用 storeToRefs
    • 其原理跟toRefs 一样的给里面的数据包裹一层toref
    • 源码 通过toRaw使store变回原始数据防止重复代理
    • 循环store 通过 isRef isReactive 判断 如果是响应式对象直接拷贝一份给refs 对象 将其原始对象包裹toRef 使其变为响应式对象
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      <template>
      <div>
      <div>origin value {{test.current}}</div>
      <div>
      <button @click="Add">+</button>
      </div>
      pinia: {{ current }} ----- {{ name }}
      </div>
      </template>

      <script setup lang="ts">
      import { useTestStore } from './store'
      import { storeToRefs } from 'pinia'

      let test = useTestStore();
      let { current, name} = storeToRefs(test);
      // 修改值
      // 直接修改
      const Add = () => {
      // console.log(test);
      // test.current++;
      current.value++
      }
      </script>

      <style lang="less" scoped>

      </style>

Actions,getters

  • Actions(支持同步异步)
    • 同步 直接调用即可
    • 异步 可以结合async await 修饰
    • 多个action互相调用getLoginInfo setName
  • getters

    • 使用箭头函数不能使用this this指向已经改变指向undefined 修改值请用state
    • 主要作用类似于computed 数据修饰并且有缓存

      1
      2
      3
      getters:{
      newPrice:(state)=> `$${state.user.price}`
      },
    • 普通函数形式可以使用this

    • getters 互相调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import { defineStore } from 'pinia'

import { Names } from './store-name'

type User = {
name: string,
age: number
};

// 同步修改
let result:User = {
name: 'pinia',
age: 4
};

// 模拟异步
const Login = ():Promise<User> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: 'pinia',
age: 5
})
}, 2000);
})
}

export const useTestStore = defineStore(Names.Test, {
state:() => {
return {
current: 1,
user: <User>{},
name: '凤'
}
},
// 类似于computed 可以帮我们去修饰我们的值,可以相互调用
getters: {
newName():string {
return `$-${this.name}---${this.getUserAge}`
},
getUserAge():number {
return this.user.age
}
},
//可以操作异步 和 同步提交state, 可以相互调用
actions: {
setCurrent (num: number): void {
// this.current++; // 不使用箭头函数,this指向有问题
this.current = num;
},
// setUser() {
// this.user = result;
// },
async setUser() {
const result = await Login();
this.user = result;
this.setName('凤凤');
},
setName(name: string) {
this.name = name;
},

}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<p>action-user: {{ Test.user }}</p>
<hr/>
<p>action-name: {{ Test.name }}</p>
<hr/>
<p>getters:{{ Test.newName }}</p>
<hr/>
<button @click="change">change</button>
</div>
</template>

<script setup lang="ts">
import { useTestStore } from './store'
const Test = useTestStore();
const change = () => {
Test.setUser();
}
</script>

<style lang="less" scoped>

</style>
  • store-name.ts
    1
    2
    3
    export const enum Names {
    Test = 'TEST'
    }

API

  • $reset - 重置store到他的初始状态

    1
    2
    3
    4
    5
    state: () => ({
    user: <Result>{},
    name: "default",
    current: 1
    }),
  • Vue 例如我把值改变到了10

    1
    2
    3
    const change = () => {
    Test.current++
    }
  • 调用$reset();

    • 将会把state所有值 重置回 原始状态
      1
      2
      3
      const reset = () => {
      Test.$reset();
      };
  • 订阅state的改变 - 类似于Vuex 的abscribe 只要有state 的变化就会走这个函数

    1
    2
    3
    4
    Test.$subscribe((args,state)=>{
    console.log(args,state);

    })
    • 第二个参数 - 如果你的组件卸载之后还想继续调用请设置第二个参数
      1
      2
      3
      4
      5
      6
      Test.$subscribe((args,state)=>{
      console.log('=========', args);
      console.log('=========', state);
      },{
      detached: true
      })
  • 订阅Actions的调用 - 只要有actions被调用就会走这个函数

    1
    2
    3
    4
    5
    6
    Test.$onAction((args)=>{
    console.log(args);
    args.after(() => {
    console.log('after');
    })
    })

pinia插件

  • pinia 和 vuex 都有一个通病 页面刷新状态会丢失
  • 写一个pinia 插件缓存他的值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    const __piniaKey = '__PINIAKEY__'

    type OptPinia = Partial<Options>

    const setStorage = (key: string, value: any): void => {
    localStorage.setItem(key, JSON.stringify(value))
    }

    const getStorage = (key: string) => {
    return (localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key) as string) : {})
    }

    const piniaPlugin = (options: OptPinia) => {
    return (context: PiniaPluginContext) => {
    const { store } = context;
    const data = getStorage(`${options?.key ?? __piniaKey}-${store.$id}`)
    store.$subscribe(() => {
    etStorage(`${options?.key ?? __piniaKey}-${store.$id}`, toRaw(store.$state));
    })
    return {
    ...store.$state,
    ...data
    }
    }
    }
    const pinia = createPinia()
    pinia.use(piniaPlugin({
    key: "pinia"
    }))

vite-vue3-03

发表于 2022-03-21 | 分类于 前端框架

vite

依赖注入Provide / Inject

  • Provide / Inject
    • 通常,当我们需要我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。
    • provide 可以在祖先组件中指定我们想要提供给后代组件的数据或方法,而在任何后代组件中,我们都可以使用 inject 来接收 provide 提供的数据或方法。
  • TIPS 你如果传递普通的值 是不具有响应式的 需要通过ref reactive 添加响应式
  • 使用场景
    • 当父组件有很多数据需要分发给其子代组件的时候, 就可以使用provide和inject。
  • 父组件传递数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <template>
    <div class="App">
    <button>我是App</button>
    <A></A>
    </div>
    </template>

    <script setup lang='ts'>
    import { provide, ref } from 'vue'
    import A from './components/A.vue'
    let flag = ref<number>(1)
    provide('flag', flag)
    </script>

    <style>
    .App {
    background: blue;
    color: #fff;
    }
    </style>
  • 子组件接受

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <template>
    <div style="background-color: green;">
    我是B
    <button @click="change">change falg</button>
    <div>{{ flag }}</div>
    </div>
    </template>

    <script setup lang='ts'>
    import { inject, Ref, ref } from 'vue'

    const flag = inject<Ref<number>>('flag', ref(1))

    const change = () => {
    flag.value = 2
    }
    </script>

    <style>
    </style>

兄弟组件传参和Bus

  • 借助父组件传参

    • 父组件为App 子组件为A 和 B他两个是同级的
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      <template>
      <div>
      <A @on-click="getFalg"></A>
      <B :flag="Flag"></B>
      </div>
      </template>

      <script setup lang='ts'>
      import A from './components/A.vue'
      import B from './components/B.vue'
      import { ref } from 'vue'
      let Flag = ref<boolean>(false)
      const getFalg = (flag: boolean) => {
      Flag.value = flag;
      }
      </script>

      <style>
      </style>
  • A 组件派发事件通过App.vue 接受A组件派发的事件然后在Props 传给B组件 也是可以实现的

  • 缺点就是比较麻烦 ,无法直接通信,只能充当桥梁

  • Event Bus

    • 我们在Vue2 可以使用$emit 传递 $on监听 emit传递过来的事件
    • 这个原理其实是运用了JS设计模式之发布订阅模式
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      type BusClass<T> = {
      emit: (name: T) => void
      on: (name: T, callback: Function) => void
      }
      type BusParams = string | number | symbol
      type List = {
      [key: BusParams]: Array<Function>
      }
      class Bus<T extends BusParams> implements BusClass<T> {
      list: List
      constructor() {
      this.list = {}
      }
      emit(name: T, ...args: Array<any>) {
      let eventName: Array<Function> = this.list[name]
      eventName.forEach(ev => {
      ev.apply(this, args)
      })
      }
      on(name: T, callback: Function) {
      let fn: Array<Function> = this.list[name] || [];
      fn.push(callback)
      this.list[name] = fn
      }
      }

      export default new Bus<number>()
  • 挂载到Vue config 全局就可以使用啦

TSX

  • 使用Template去写我们模板。现在可以扩展另一种风格TSX风格
  • vue2 的时候就已经支持jsx写法,只不过不是很友好,随着vue3对typescript的支持度,tsx写法越来越被接受
  • npm install @vitejs/plugin-vue-jsx -D
  • vite.config.ts 配置

    1
    2
    3
    4
    5
    6
    7
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import vueJsx from '@vitejs/plugin-vue-jsx'; // 引入jsx
    // https://vitejs.dev/config/
    export default defineConfig({
    plugins: [vue(),vueJsx()] // 注册vueJsx
    })
  • 修改tsconfig.json 配置文件

    1
    2
    3
    "jsx": "preserve",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment",
  • 使用TSX

  • TIPS tsx不会自动解包使用ref加.vlaue ! ! !
  • tsx支持 v-model 的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
import { ref } from 'vue'
let v = ref<string>('')
const renderDom = () => {
return (
<>
<input v-model={v.value} type="text" />
<div>
{v.value}
</div>
</>
)
}
export default renderDom
  • v-show

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import { ref } from 'vue'
    let flag = ref(false)
    const renderDom = () => {
    return (
    <>
    <div v-show={flag.value}>景天</div>
    <div v-show={!flag.value}>雪见</div>
    </>
    )
    }
    export default renderDom
  • v-if是不支持的(需要修改编写)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import { ref } from 'vue'
    let flag = ref(false)
    const renderDom = () => {
    return (
    <>
    {
    flag.value ? <div>景天</div> : <div>雪见</div>
    }
    </>
    )
    }
    export default renderDom
  • v-for也是不支持的(需要使用Map)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import { ref } from 'vue'
    let arr = [1,2,3,4,5]
    const renderDom = () => {
    return (
    <>
    {
    arr.map(v=>{
    return <div>${v}</div>
    })
    }
    </>
    )
    }
    export default renderDom
  • v-bind使用(直接赋值就可以)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import { ref } from 'vue'
    let arr = [1, 2, 3, 4, 5]
    const renderDom = () => {
    return (
    <>
    <div data-arr={arr}>1</div>
    </>
    )
    }
    export default renderDom
  • v-on绑定事件 所有的事件都按照react风格来

    • 所有事件有on开头
    • 所有事件名称首字母大写
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const renderDom = () => {
      return (
      <>
      <button onClick={clickTap}>点击</button>
      </>
      )
      }
      const clickTap = () => {
      console.log('click');
      }
      export default renderDom
  • Props 接受值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import { ref } from 'vue'
    type Props = {
    title:string
    }
    const renderDom = (props:Props) => {
    return (
    <>
    <div>{props.title}</div>
    <button onClick={clickTap}>点击</button>
    </>
    )
    }
    const clickTap = () => {
    console.log('click');
    }
    export default renderDom
  • Emit派发

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    type Props = {
    title: string
    }
    const renderDom = (props: Props,content:any) => {
    return (
    <>
    <div>{props.title}</div>
    <button onClick={clickTap.bind(this,content)}>点击</button>
    </>
    )
    }
    const clickTap = (ctx:any) => {
    ctx.emit('on-click',1)
    }

深入v-model

  • Vue3自动引入插件
    • unplugin-auto-import/vite
  • vite配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import VueJsx from '@vitejs/plugin-vue-jsx'
    import AutoImport from 'unplugin-auto-import/vite'
    // https://vitejs.dev/config/
    export default defineConfig({
    plugins: [vue(),VueJsx(),AutoImport({
    imports:['vue'],
    dts:"src/auto-import.d.ts"
    })]
    })
  • 配置完成之后使用ref reactive watch 等 无须import 导入 可以直接使用

    • Vue3自动引入插件
  • v-model
  • TIps 在Vue3 v-model 是破坏性更新的
  • v-model在组件里面也是很重要的
    • v-model 其实是一个语法糖 通过props 和 emit组合而成的
    • 默认值的改变
    • prop:value -> modelValue;
    • 事件:input -> update:modelValue;
    • v-bind 的 .sync 修饰符和组件的 model 选项已移除
    • 新增 支持多个v-model
    • 新增 支持自定义 修饰符
  • 子组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    <template>
    <div v-if='propData.modelValue ' class="dialog">
    <div class="dialog-header">
    <div>标题</div><div @click="close">x</div>
    </div>
    <div class="dialog-content">
    内容
    </div>

    </div>
    </template>

    <script setup lang='ts'>

    type Props = {
    modelValue:boolean
    }

    const propData = defineProps<Props>()

    const emit = defineEmits(['update:modelValue'])

    const close = () => {
    emit('update:modelValue',false)
    }

    </script>

    <style lang='less'>
    .dialog{
    width: 300px;
    height: 300px;
    border: 1px solid #ccc;
    position: fixed;
    left:50%;
    top:50%;
    transform: translate(-50%,-50%);
    &-header{
    border-bottom: 1px solid #ccc;
    display: flex;
    justify-content: space-between;
    padding: 10px;
    }
    &-content{
    padding: 10px;
    }
    }
    </style>
  • 父组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <template>
    <button @click="show = !show">开关{{show}}</button>
    <Dialog v-model="show"></Dialog>
    </template>

    <script setup lang='ts'>
    import Dialog from "./components/Dialog/index.vue";
    import {ref} from 'vue'
    const show = ref(false)
    </script>

    <style>
    </style>
  • 绑定多个案例

  • 子组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    <template>
    <div v-if='modelValue ' class="dialog">
    <div class="dialog-header">
    <div>标题---{{title}}</div><div @click="close">x</div>
    </div>
    <div class="dialog-content">
    内容
    </div>

    </div>
    </template>

    <script setup lang='ts'>

    type Props = {
    modelValue:boolean,
    title:string
    }

    const propData = defineProps<Props>()

    const emit = defineEmits(['update:modelValue','update:title'])

    const close = () => {
    emit('update:modelValue',false)
    emit('update:title','我要改变')
    }

    </script>

    <style lang='less'>
    .dialog{
    width: 300px;
    height: 300px;
    border: 1px solid #ccc;
    position: fixed;
    left:50%;
    top:50%;
    transform: translate(-50%,-50%);
    &-header{
    border-bottom: 1px solid #ccc;
    display: flex;
    justify-content: space-between;
    padding: 10px;
    }
    &-content{
    padding: 10px;
    }
    }
    </style>
  • 父组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <template>
    <button @click="show = !show">开关{{show}} ----- {{title}}</button>
    <Dialog v-model:title='title' v-model="show"></Dialog>
    </template>

    <script setup lang='ts'>
    import Dialog from "./components/Dialog/index.vue";
    import {ref} from 'vue'
    const show = ref(false)
    const title = ref('我是标题')
    </script>

    <style>
    </style>
  • 自定义修饰符

    • 添加到组件 v-model 的修饰符将通过 modelModifiers prop 提供给组件。在下面的示例中,我们创建了一个组件,其中包含默认为空对象的 modelModifiers prop
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      <script setup lang='ts'>
      type Props = {
      modelValue: boolean,
      title?: string,
      modelModifiers?: {
      default: () => {}
      }
      titleModifiers?: {
      default: () => {}
      }
      }
      const propData = defineProps<Props>()
      const emit = defineEmits(['update:modelValue', 'update:title'])
      const close = () => {
      console.log(propData.modelModifiers);
      emit('update:modelValue', false)
      emit('update:title', '我要改变')
      }

自定义指令directive

  • directive-自定义指令(属于破坏性更新)
    • Vue中有v-if,v-for,v-bind,v-show,v-model 等等一系列方便快捷的指令 今天一起来了解一下vue里提供的自定义指令
  • Vue3指令的钩子函数
    • created 元素初始化的时候
    • beforeMount 指令绑定到元素后调用 只调用一次
    • mounted 元素插入父级dom调用
    • beforeUpdate 元素被更新之前调用
    • update 这个周期方法被移除 改用updated
    • beforeUnmount 在元素被移除前调用
    • unmounted 指令被移除后调用 只调用一次
    • Vue2 指令 bind inserted update componentUpdated unbind
  • 在setup内定义局部指令

    • 但这里有一个需要注意的限制:必须以 vNameOfDirective 的形式来命名本地自定义指令,以使得它们可以直接在模板中使用
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      <template>
      <button @click="show = !show">开关{{show}} ----- {{title}}</button>
      <Dialog v-move-directive="{background:'green',flag:show}"></Dialog>
      </template>

      const vMoveDirective: Directive = {
      created: () => {
      console.log("初始化====>");
      },
      beforeMount(...args: Array<any>) {
      // 在元素上做些操作
      console.log("初始化一次=======>");
      },
      mounted(el: any, dir: DirectiveBinding<Value>) {
      el.style.background = dir.value.background;
      console.log("初始化========>");
      },
      beforeUpdate() {
      console.log("更新之前");
      },
      updated() {
      console.log("更新结束");
      },
      beforeUnmount(...args: Array<any>) {
      console.log(args);
      console.log("======>卸载之前");
      },
      unmounted(...args: Array<any>) {
      console.log(args);
      console.log("======>卸载完成");
      },
      };
  • 生命周期钩子参数详解

    • 第一个 el 当前绑定的DOM 元素
    • 第二个 binding
      • instance:使用指令的组件实例。
      • value:传递给指令的值。例如,在 v-my-directive=”1 + 1” 中,该值为 2。
      • oldValue:先前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否有更改都可用。
      • arg:传递给指令的参数(如果有的话)。例如在 v-my-directive:foo 中,arg 为 “foo”。
      • modifiers:包含修饰符(如果有的话) 的对象。例如在 v-my-directive.foo.bar 中,修饰符对象为 {foo: true,bar: true}。
      • dir:一个对象,在注册指令时作为参数传递。例如,在以下指令中
  • 第三个 当前元素的虚拟DOM 也就是Vnode
  • 第四个 prevNode 上一个虚拟节点,仅在 beforeUpdate 和 updated 钩子中可用
  • 函数简写

    • 你可能想在 mounted 和 updated 时触发相同行为,而不关心其他的钩子函数。那么你可以通过将这个函数模式实现
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <template>
      <div>
      <input v-model="value" type="text" />
      <A v-move="{ background: value }"></A>
      </div>
      </template>

      <script setup lang='ts'>
      import A from './components/A.vue'
      import { ref, Directive, DirectiveBinding } from 'vue'
      let value = ref<string>('')
      type Dir = {
      background: string
      }
      const vMove: Directive = (el, binding: DirectiveBinding<Dir>) => {
      el.style.background = binding.value.background
      }
      </script>

      <style>
      </style>
  • 自定义拖拽指令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    <template>
    <div v-move class="box">
    <div class="header"></div>
    <div>
    内容
    </div>
    </div>
    </template>

    <script setup lang='ts'>
    import { Directive } from "vue";
    const vMove: Directive = {
    mounted(el: HTMLElement) {
    let moveEl = el.firstElementChild as HTMLElement;
    const mouseDown = (e: MouseEvent) => {
    //鼠标点击物体那一刻相对于物体左侧边框的距离=点击时的位置相对于浏览器最左边的距离-物体左边框相对于浏览器最左边的距离
    console.log(e.clientX, e.clientY, "-----起始", el.offsetLeft);
    let X = e.clientX - el.offsetLeft;
    let Y = e.clientY - el.offsetTop;
    const move = (e: MouseEvent) => {
    el.style.left = e.clientX - X + "px";
    el.style.top = e.clientY - Y + "px";
    console.log(e.clientX, e.clientY, "---改变");
    };
    document.addEventListener("mousemove", move);
    document.addEventListener("mouseup", () => {
    document.removeEventListener("mousemove", move);
    });
    };
    moveEl.addEventListener("mousedown", mouseDown);
    },
    };
    </script>

    <style lang='less'>
    .box {
    position: fixed;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 200px;
    height: 200px;
    border: 1px solid #ccc;
    .header {
    height: 20px;
    background: black;
    cursor: move;
    }
    }
    </style>

自定义Hooks

  • Vue3 自定义Hook
    • 主要用来处理复用代码逻辑的一些封装
      • 这个在vue2 就已经有一个东西是Mixins
      • mixins就是将这些多个相同的逻辑抽离出来,各个组件只需要引入mixins,就能实现一次写代码,多组件受益的效果。
      • 弊端就是 会涉及到覆盖的问题
      • 组件的data、methods、filters会覆盖mixins里的同名data、methods、filters
  • 二点就是 变量来源不明确(隐式传入),不利于阅读,使代码变得难以维护。
    • Vue3 的自定义的hook
    • Vue3 的 hook函数 相当于 vue2 的 mixin, 不同在与 hooks 是函数
    • Vue3 的 hook函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数
    • Vue3 hook 库Get Started | VueUse
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      import { onMounted } from 'vue'
      type Options = {
      el: string
      }

      type Return = {
      Baseurl: string | null
      }
      export default function (option: Options): Promise<Return> {
      return new Promise((resolve) => {
      onMounted(() => {
      const file: HTMLImageElement = document.querySelector(option.el) as HTMLImageElement;
      file.onload = ():void => {
      resolve({
      Baseurl: toBase64(file)
      })
      }

      })
      const toBase64 = (el: HTMLImageElement): string => {
      const canvas: HTMLCanvasElement = document.createElement('canvas')
      const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
      canvas.width = el.width
      canvas.height = el.height
      ctx.drawImage(el, 0, 0, canvas.width,canvas.height)
      console.log(el.width);

      return canvas.toDataURL('image/png')

      }
      })
      }

Vue3定义全局函数和变量

  • globalProperties
    • 由于Vue3 没有Prototype 属性 使用 app.config.globalProperties 代替 然后去定义变量和函数
  • Vue2

    1
    2
    // 之前 (Vue 2.x)
    Vue.prototype.$http = () => {}
  • Vue3

    1
    2
    3
    // 之后 (Vue 3.x)
    const app = createApp({})
    app.config.globalProperties.$http = () => {}
  • 过滤器

    • 在Vue3 移除了
    • 正好可以使用全局函数代替Filters
      1
      2
      3
      4
      5
      app.config.globalProperties.$filters = {
      format<T extends any>(str: T): string {
      return `$${str}`
      }
      }
  • 声明文件 不然TS无法正确类型 推导

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type Filter = {
    format: <T extends any>(str: T) => T
    }
    // 声明要扩充@vue/runtime-core包的声明.
    // 这里扩充"ComponentCustomProperties"接口, 因为他是vue3中实例的属性的类型.
    declare module '@vue/runtime-core' {
    export interface ComponentCustomProperties {
    $filters: Filter
    }
    }
  • setup 读取值

    1
    2
    3
    import { getCurrentInstance, ComponentInternalInstance } from 'vue';
    const { appContext } = <ComponentInternalInstance>getCurrentInstance()
    console.log(appContext.config.globalProperties.$env);

编写Vue3插件

  • 插件
    • 插件是自包含的代码,通常向 Vue 添加全局级功能。你如果是一个对象需要有install方法Vue会帮你自动注入到install 方法 你如果是function 就直接当install 方法去使用
  • 使用插件
    • 在使用 createApp() 初始化 Vue 应用程序后,你可以通过调用 use() 方法将插件添加到你的应用程序中。
  • 实现一个Loading

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <template>
    <div v-if="isShow" class="loading">
    <div class="loading-content">Loading...</div>
    </div>
    </template>

    <script setup lang='ts'>
    import { ref } from 'vue';
    const isShow = ref(false)//定位loading 的开关

    const show = () => {
    isShow.value = true
    }
    const hide = () => {
    isShow.value = false
    }
    //对外暴露 当前组件的属性和方法
    defineExpose({
    isShow,
    show,
    hide
    })
    </script>

    <style scoped lang="less">
    .loading {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.8);
    display: flex;
    justify-content: center;
    align-items: center;
    &-content {
    font-size: 30px;
    color: #fff;
    }
    }
    </style>
  • Loading.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import {  createVNode, render, VNode, App } from 'vue';
    import Loading from './index.vue'
    export default {
    install(app: App) {
    //createVNode vue提供的底层方法 可以给我们组件创建一个虚拟DOM 也就是Vnode
    const vnode: VNode = createVNode(Loading)
    //render 把我们的Vnode 生成真实DOM 并且挂载到指定节点
    render(vnode, document.body)
    // Vue 提供的全局配置 可以自定义
    app.config.globalProperties.$loading = {
    show: () => vnode.component?.exposed?.show(),
    hide: () => vnode.component?.exposed?.hide()
    }
    }
    }
  • Main.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import Loading from './components/loading'
    let app = createApp(App)
    app.use(Loading)
    type Lod = {
    show: () => void,
    hide: () => void
    }
    //编写ts loading 声明文件放置报错 和 智能提示
    declare module '@vue/runtime-core' {
    export interface ComponentCustomProperties {
    $loading: Lod
    }
    }
    app.mount('#app')

了解UI库ElementUI,AntDesigin等

  • vue作为一款深受广大群众以及尤大崇拜者的喜欢,特此列出在github上开源的vue优秀的UI组件库供大家参考
    • 这两套框架主要用于后台管理系统的制作,方便开发者快速开发
  • 安装方法

    1
    2
    3
    4
    5
    6
    7
    8
    # NPM
    $ npm install element-plus --save

    # Yarn
    $ yarn add element-plus

    # pnpm
    $ pnpm install element-plus
  • main ts引入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import { createApp } from 'vue'
    import ElementPlus from 'element-plus'
    import 'element-plus/dist/index.css'
    import App from './App.vue'

    const app = createApp(App)

    app.use(ElementPlus)
    app.mount('#app')
  • volar插件支持

    1
    2
    3
    4
    5
    6
    {
    "compilerOptions": {
    // ...
    "types": ["element-plus/global"]
    }
    }
  • Element UI Plus

    • 一个 Vue 3 UI 框架 | Element Plus
  • Ant Design Vue

    • Ant Design Vue
    • 安装

      1
      2
      $ npm install ant-design-vue@next --save
      $ yarn add ant-design-vue@next
    • 使用

      1
      2
      3
      4
      5
      6
      7
      8
      import { createApp } from 'vue';
      import Antd from 'ant-design-vue';
      import App from './App';
      import 'ant-design-vue/dist/antd.css';

      const app = createApp(App);

      app.use(Antd).mount('#app');

详解Scoped和样式 穿透

  • 主要是用于修改很多vue常用的组件库(element, vant, AntDesigin),虽然配好了样式但是还是需要更改其他的样式
  • 就需要用到样式穿透
  • scoped的原理
    • vue中的scoped 通过在DOM结构以及css样式上加唯一不重复的标记:data-v-hash的方式,以保证唯一(而这个工作是由过PostCSS转译实现的),达到样式私有化模块化的目的。
  • 总结一下scoped三条渲染规则:
    • 给HTML的DOM节点加一个不重复data属性(形如:data-v-123)来表示他的唯一性
    • 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-123])来私有化样式
    • 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的data属性
  • PostCSS会给一个组件中的所有dom添加了一个独一无二的动态属性data-v-xxxx,然后,给CSS选择器额外添加一个对应的属性选择器来选择该组件中dom,这种做法使得样式只作用于含有该属性的dom——组件内部dom, 从而达到了’样式模块化’的效果.
  • 案例修改Element ui Input样式
    • 如果不写Scoped 就没问题
    • 原因就是Scoped 搞的鬼 他在进行PostCss转化的时候把元素选择器默认放在了最后
    • Vue 提供了样式穿透:deep() 他的作用就是用来改变 属性选择器的位置

vite-vue3-02

发表于 2022-03-18 | 分类于 前端框架

vite

插槽slot(占位符)

  • 子组件中的提供给父组件使用的一个占位符,用 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的标签
  • 匿名插槽
  • 在子组件放置一个插槽

    1
    2
    3
    4
    5
    <template>
    <div>
    <slot></slot>
    </div>
    </template>
  • 父组件使用插槽

    • 在父组件给这个插槽填充内容
      1
      2
      3
      4
      5
      <Dialog>
      <template v-slot>
      <div>2132</div>
      </template>
      </Dialog>
  • 具名插槽

    • 具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中
      1
      2
      3
      4
      5
      6
      <div>
      <slot name="header"></slot>
      <slot></slot>

      <slot name="footer"></slot>
      </div>
  • 父组件使用需对应名称

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <Dialog>
    <template v-slot:header>
    <div>1</div>
    </template>
    <template v-slot>
    <div>2</div>
    </template>
    <template v-slot:footer>
    <div>3</div>
    </template>
    </Dialog>
  • 插槽简写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <Dialog>
    <template #header>
    <div>1</div>
    </template>
    <template #default>
    <div>2</div>
    </template>
    <template #footer>
    <div>3</div>
    </template>
    </Dialog>
  • 作用域插槽

    • 在子组件动态绑定参数 派发给父组件的slot去使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <div>
      <slot name="header"></slot>
      <div>
      <div v-for="item in 100">
      <slot :data="item"></slot>
      </div>
      </div>

      <slot name="footer"></slot>
      </div>
    • 通过结构方式取值

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <Dialog>
      <template #header>
      <div>1</div>
      </template>
      <template #default="{ data }">
      <div>{{ data }}</div>
      </template>
      <template #footer>
      <div>3</div>
      </template>
      </Dialog>
  • 动态插槽

    • 插槽可以是一个变量名
      1
      2
      3
      4
      5
      6
      7
      8
      <Dialog>
      <template #[name]>
      <div>
      23
      </div>
      </template>
      </Dialog>
      const name = ref('header')

异步组件&代码分包&suspense

  • 异步组件
  • 在大型应用中,我们可能需要将应用分割成小一些的代码块,并且减少主包的体积
  • 就可以使用异步组件
  • 顶层 await,在setup语法糖里面 使用方法
1
2
3
4
<script setup> 中可以使用顶层 await 结果代码会被编译成 async setup()
<script setup>
const post = await fetch(`/api/post/1`).then(r => r.json())
</script>
  • 父组件引用子组件 通过defineAsyncComponent加载异步配合import 函数模式便可以分包

    1
    2
    3
    <script setup lang="ts">
    import { reactive, ref, markRaw, toRaw, defineAsyncComponent } from 'vue'
    const Dialog = defineAsyncComponent(() => import('../../components/Dialog/index.vue'))
  • suspense

    • 组件有两个插槽。它们都只接收一个直接子节点。default 插槽里的节点会尽可能展示出来。如果不能,则展示 fallback 插槽里的节点
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <Suspense>
      <template #default>
      <Dialog>
      <template #default>
      <div>接口请求动态数据</div>
      </template>
      </Dialog>
      </template>

      <template #fallback>
      <div>loading...</div>
      </template>
      </Suspense>

Teleport传送组件

  • Teleport Vue 3.0新特性之一

    • Teleport 是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;类似于 React 的 Portal
    • 主要解决的问题 因为Teleport节点挂载在其他指定的DOM节点下,完全不受父级style样式影响
    • 使用方法
      • 通过to 属性 插入指定元素位置 to=”body” 便可以将Teleport 内容传送到指定位置
        1
        2
        3
        <Teleport to="body">
        <Loading></Loading>
        </Teleport>
  • 也可以自定义传送位置 支持 class id等 选择器

    1
    2
    3
    4
    5
    6
    <div id="app"></div>
    <div class="modal"></div>

    <Teleport to=".modal">
    <Loading></Loading>
    </Teleport>
  • 也可以使用多个

    1
    2
    3
    4
    5
    6
    <Teleport to=".modal1">
    <Loading></Loading>
    </Teleport>
    <Teleport to=".modal2">
    <Loading></Loading>
    </Teleport>

keep-alive缓存组件

  • 内置组件keep-alive
    • 有时候我们不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。而是希望组件可以缓存下来,维持当前的状态。这时候就需要用到keep-alive组件
  • 开启keep-alive 生命周期的变化

    • 初次进入时: onMounted> onActivated
    • 退出后触发 deactivated
    • 再次进入:
    • 只会触发 onActivated
    • 事件挂载的方法等,只执行一次的放在 onMounted中;组件每次进去执行的方法放在 onActivated中
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <!-- 基本 -->
      <keep-alive>
      <component :is="view"></component>
      </keep-alive>

      <!-- 多个条件判断的子组件 -->
      <keep-alive>
      <comp-a v-if="a > 1"></comp-a>
      <comp-b v-else></comp-b>
      </keep-alive>

      <!-- 和 `<transition>` 一起使用 -->
      <transition>
      <keep-alive>
      <component :is="view"></component>
      </keep-alive>
      </transition>
  • include 和 exclude

    • include 和 exclude prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示
      1
      <keep-alive :include="" :exclude="" :max=""></keep-alive>
  • max

    1
    2
    3
    <keep-alive :max="10">
    <component :is="view"></component>
    </keep-alive>

transition动画组件

  • Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡:
    • 条件渲染 (使用 v-if)
    • 条件展示 (使用 v-show)
    • 动态组件
    • 组件根节点
    • 自定义 transition 过度效果,你需要对transition组件的name属性自定义。并在css中写入对应的样式
    • 过渡的类名
      • 在进入/离开的过渡中,会有 6 个 class 切换。
  • 过渡 class
    • 在进入/离开的过渡中,会有 6 个 class 切换。
    • v-enter-from:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
    • v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
    • v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除。
    • v-leave-from:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
    • v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
    • v-leave-to:离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被移除),在过渡/动画完成之后移除。
  • 自定义过渡 class 类名

    • trasnsition props
    • enter-from-class
    • enter-active-class
    • enter-to-class
    • leave-from-class
    • leave-active-class
    • leave-to-class
    • 自定义过度时间 单位毫秒
      • 你也可以分别指定进入和离开的持续时间:
        1
        2
        <transition :duration="1000">...</transition>
        <transition :duration="{ enter: 500, leave: 800 }">...</transition>
  • 通过自定义class 结合css动画库animate css

  • 安装库 npm install animate.css
  • 引入 import ‘animate.css’
  • 使用方法
  • 官方文档

    1
    2
    3
    4
    5
    6
    <transition
    leave-active-class="animate__animated animate__bounceInLeft"
    enter-active-class="animate__animated animate__bounceInRight"
    >
    <div v-if="flag" class="box"></div>
    </transition>
  • transition 生命周期8个

    • @before-enter=”beforeEnter” //对应enter-from
    • @enter=”enter”//对应enter-active
    • @after-enter=”afterEnter”//对应enter-to
    • @enter-cancelled=”enterCancelled”//显示过度打断
    • @before-leave=”beforeLeave”//对应leave-from
    • @leave=”leave”//对应enter-active
    • @after-leave=”afterLeave”//对应leave-to
    • @leave-cancelled=”leaveCancelled”//离开过度打断
    • 当只用 JavaScript 过渡的时候,在 enter 和 leave 钩子中必须使用 done 进行回调
    • 结合gsap 动画库使用(npm install gsap -S)
      • import gsap from ‘gsap’
      • GreenSock
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        const beforeEnter = (el: Element) => {
        console.log('进入之前from', el);
        }
        const Enter = (el: Element,done:Function) => {
        console.log('过度曲线');
        setTimeout(()=>{
        done()
        },3000)
        }
        const AfterEnter = (el: Element) => {
        console.log('to');
        }
  • appear

    • 通过这个属性可以设置初始节点过度 就是页面加载完成就开始动画 对应三个状态
      1
      2
      3
      4
      appear-active-class=""
      appear-from-class=""
      appear-to-class=""
      appear

transition-group过度列表

  • 单个节点
  • 多个节点,每次只渲染一个
  • 那么怎么同时渲染整个列表,比如使用 v-for?在这种场景下,我们会使用 组件。在我们深入例子之前,先了解关于这个组件的几个特点:
    • 默认情况下,它不会渲染一个包裹元素,但是你可以通过 tag attribute 指定渲染一个元素。
    • 过渡模式不可用,因为我们不再相互切换特有的元素。
    • 内部元素总是需要提供唯一的 key attribute 值。
    • CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <transition-group>
      <div style="margin: 10px;" :key="item" v-for="item in list">{{ item }</div>
      </transition-group>
      const list = reactive<number[]>([1, 2, 4, 5, 6, 7, 8, 9])
      const Push = () => {
      list.push(123)
      }
      const Pop = () => {
      list.pop()
      }
  • 列表的移动过渡
    • 组件还有一个特殊之处。除了进入和离开,它还可以为定位的改变添加动画。只需了解新增的 v-move 类就可以使用这个新功能,它会应用在元素改变定位的过程中。像之前的类名一样,它的前缀可以通过 name attribute 来自定义,也可以通过 move-class attribute 手动设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template>
<div>
<button @click="shuffle">Shuffle</button>
<transition-group class="wraps" name="mmm" tag="ul">
<li class="cell" v-for="item in items" :key="item.id">{{ item.number }}</li>
</transition-group>
</div>
</template>

<script setup lang='ts'>
import _ from 'lodash'
import { ref } from 'vue'
let items = ref(Array.apply(null, { length: 81 } as number[]).map((_, index) => {
return {
id: index,
number: (index % 9) + 1
}
}))
const shuffle = () => {
items.value = _.shuffle(items.value)
}
</script>

<style scoped lang="less">
.wraps {
display: flex;
flex-wrap: wrap;
width: calc(25px * 10 + 9px);
.cell {
width: 25px;
height: 25px;
border: 1px solid #ccc;
list-style-type: none;
display: flex;
justify-content: center;
align-items: center;
}
}

.mmm-move {
transition: transform 0.8s ease;
}
</style>
  • 状态过渡
    • Vue 也同样可以给数字 Svg 背景颜色等添加过度动画 今天演示数字变化
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      <template>
      <div>
      <input step="20" v-model="num.current" type="number" />
      <div>{{ num.tweenedNumber.toFixed(0) }}</div>
      </div>
      </template>

      <script setup lang='ts'>
      import { reactive, watch } from 'vue'
      import gsap from 'gsap'
      const num = reactive({
      tweenedNumber: 0,
      current:0
      })

      watch(()=>num.current, (newVal) => {
      gsap.to(num, {
      duration: 1,
      tweenedNumber: newVal
      })
      })

      </script>

      <style>
      </style>
12…7
yongfeng.peng

yongfeng.peng

(女英

67 日志
13 分类
11 标签
© 2022 yongfeng.peng