vue3细节的改变以及unocss 原子化

(一)异步组件(新增)

以前,异步组件是通过将组件定义为返回 Promise 的函数来创建的,例如:

const asyncModal = () => import('./Modal.vue')

在 Vue 3 中,由于函数式组件被定义为纯函数,因此异步组件需要通过将其包裹在新的 defineAsyncComponent 助手方法中来显式地定义:

<template>
  <div>
    -----异步组件-----
    <button @click="onClick">点击显示</button>
    <asyncChild v-if="isShow" />
  </div>
</template>

<script setup>
  import { defineAsyncComponent } from 'vue';
  let isShow = ref(false);
  const asyncChild = defineAsyncComponent(() => import('./child.vue'));
  const onClick = () => {
    isShow.value = true;
  };
</script>
子组件:
<template>
  <div> Child: {{ test }} </div>
</template>
<script setup>
  let test = ref('你好!');
</script>

点击显示 后才加载child组件
在这里插入图片描述

(二)$attrs 包括class&style

(三)自定义指令

Vue 2.x & Vue 3.x

(1)Vue 2.x 自定义指令的声明周期

  • bind:指令绑定到元素时触发,只触发一次;
  • inserted:绑定元素被插入父DOM时触发
  • update:当元素更新而子元素还没有更新时触发;
  • componentUpdated:组件和子组件更新完成后触发;
  • unbind:一旦指令被移除,就会调用这个钩子。也只调用一次。
<p v-highlight="'yellow'">以亮黄色高亮显示此文本</p>
Vue.directive('highlight', {
  bind(el, binding, vnode) {
    el.style.background = binding.value
  }
})

(2)Vue 3.x 自定义指令的声明周期

  • created - 新增!在元素的 attribute 或事件监听器被应用之前调用。
  • beforeMount:替代bind
  • mounted:替代inserted
  • beforeUpdate:移除Vue2.x 中的update,用beforeUpdate和updated来替代
  • updated
  • beforeUnmount:新增!与组件的生命周期钩子类似,它将在元素被卸载之前调用。
  • unmounted:替代unbind
<p v-highlight="'yellow'">以亮黄色高亮显示此文本</p>
const app = Vue.createApp({})
app.directive('highlight', {
  beforeMount(el, binding, vnode) {
    el.style.background = binding.value
  }
})

自定义指令

全局自定义指令/访问组件实例

<el-button type="primary" v-hasPermi="['system-access:list:query']" >搜索</el-button >
app.directive('hasPermi', {
	mounted(el, binding, vnode) {
		// 编写
	}
})

局部自定义

<template>
	<div>
		<p v-highlight="'yellow'">以亮黄色高亮显示此文本</p>
	</div>
</template>

<script>
export default {
	directives: {
		highlight: {
			beforeMount(el, binding, vnode, prevVnode) {
				el.style.background = binding.value;
			}
		}
	}
};
</script>

(四) is属性

vue3.0.0+ 中 禁止在 HTML 元素上使用is属性(is 已弃用)

<div is="foo"></div><div :is="foo"></div>  //错误

保留的 标签上使用时,它的行为将与 2.x 中完全相同

<component is="bar" />

当在不同组件标签上使用is时,is会被当做一个不同的prop;

<bar is="plastic-button" />

使用 vue: 前缀来解决 DOM 内模板解析问题

<template>
    <select>   
        <option is="vue:optioncomp"> </option>
    </select>
</template>
<script>
export default {
  components:{
    'optioncomp':{
        template: '<option >下拉选择</option>'
    }
  }
};
</script>

新增v-is来实现在普通的 HTML 元素渲染组件

<template>
    <div v-is="'child'">渲染 child 组件</div>
    <!--  等同于<child>渲染 child 组件</child>  -->
</template>
<script>
import child from "./child.vue";
export default {
  components:{
    child
  }
};
</script>

(五)Data 选项

在 2.x 中,开发者可以通过 object 或者是 function 定义 data 选项

<!-- Object 声明 -->
<script>
  const app = new Vue({
    data: {
      apiKey: 'a1b2c3'
    }
  })
</script>

<!-- Function 声明 -->
<script>
  const app = new Vue({
    data() {
      return {
        apiKey: 'a1b2c3'
      }
    }
  })
</script>

3.x 中,data 选项已标准化为只接受返回 object 的 function

createApp({
    data() {
      return {
        apiKey: 'a1b2c3'
      }
    }
  }).mount('#app')

Mixin 合并行为变更

此外,当来自组件的 data() 及其 mixin 或 extends 基类被合并时,合并操作现在将被浅层次地执行:

const Mixin = {
  data() {
    return {
      user: {
        name: 'Jack',
        id: 1
      }
    }
  }
}

const CompA = {
  mixins: [Mixin],
  data() {
    return {
      user: {
        id: 2
      }
    }
  }
}

在 Vue 2.x 中,生成的 $data 是:

{
  "user": {
    "id": 2,
    "name": "Jack"
  }
}

在 3.0 中,其结果将会是:

{
  "user": {
    "id": 2
  }
}

(六)emits(新增)

emits: 列表申明从父组件继承来的事件
$emit: 抛出事件, 告诉父组件解决

<!-- 父组件 App -->
<template>
  <div>
    <div> 父组件:{{num}}</div>
    <child  @numchange="getNum" :num="num" ></child>
  </div>
</template>
<script>
import { ref}from 'vue'
import child from "./child.vue";
export default {
  components:{
    child,
    },
    setup() {
        let num =ref(0);
        const getNum = (res)=>{
            num.value = res;
        }
        return{
            num,
            getNum
        }
    }
};
</script>




<!-- 子组件 Count -->
<template>
	<div>
       <p>子组件:{{num}}</p> 
        <button type="button" class="btn btn-danger" @click="add">+1</button>
	</div>
</template>

<script>
export default {
    props: ['num'],
    emits: ['numchange'],
    setup(props,ctx){
        const add=()=> {
            ctx.emit('numchange', props.num + 1)
        }
        return {
            add
        }
    }
};
</script>

(七)vue3 移除过滤器

在 3.x 中,过滤器已移除,且不再支持。官网建议用方法调用或计算属性来替换它们。
全局过滤器

// main.js
const app = createApp(App)

app.config.globalProperties.$filters = {
  currencyUSD(value) {
    return '$' + value
  }
}
使用的时候: <p>{{ $filters.currencyUSD(accountBalance) }}</p>

(八)片段(新增)

在 3.x 中,组件可以包含多个根节点!

<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

(九) 全局 API

一个新的全局 API:createApp

import { createApp } from 'vue'
const app = createApp({})

在这里插入图片描述注意

  • app.mixin() 不推荐,推荐使用组合函数

(十)全局 API Treeshaking

Vue 3.x 对 部分全局 API 实现了 tree shacking 功能。

什么是 tree shaking?

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。
新的 webpack 4 正式版本,扩展了这个检测能力,通过 package.json 的 “sideEffects” 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 “pure(纯的 ES2015 模块)”,由此可以安全地删除文件中未使用的部分。
摘抄自——《webpack——tree shaking》

作用:tree shaking 的作用是移除项目代码中上下文中的未引用代码(dead-code),已达到实现项目打包文件的精简。
前提:tree shaking 基于 ES2015模块系统。也就是项目中对模块的应用和导出需要用到import、export。

比如:在Vue2中,无论我们使用什么功能,它们最终都会出现在生产代码中。主要原因是Vue实例在项目中是单例的,捆绑程序无法检测到该对象的哪些属性在代码中被使用到

import Vue from 'vue'
 
Vue.nextTick(() => {})

而Vue3源码引入tree shaking特性,将全局 API 进行分块。如果您不使用其某些功能,它们将不会包含在您的基础包中

import { nextTick, observable } from 'vue'
 
nextTick(() => {})

在这里插入图片描述
在这里插入图片描述

作用

通过脚手架vue-cli安装Vue2与Vue3项目。
vue2

<template>
  <div id="app">{{count}}--{{double}}
  </div>
</template>

<script>

export default {
	name: 'App',
	data(){
		return{
			count: 1,
			question:'',
			answer:''
		}
	},
	computed: {
		double: function () {
			return this.count * 2;
		},
	},
	watch: {
		question: function () {
			this.answer = 'xxxx'
		}
	}
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

在这里插入图片描述
vue3

<template>
  <div id="app">{{state.count}}--{{double}}
  </div>
</template>

<script>

import { reactive ,computed, watch} from "vue";
export default {
  name: 'App',
  setup() {
    const state = reactive({
      count: 1,
    });
    const double = computed(() => {
      return state.count * 2;
    });

    watch(
      () => state.count,
      (count, preCount) => {
        console.log(count);
        console.log(preCount);
      }
    );
    return {
      state,
      double,
    };
  },

}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

在这里插入图片描述
对项目进行打包,通过Tree shaking,Vue3给我们带来的好处是:

  • 减少程序体积(更小)
  • 减少程序执行时间(更快)
  • 便于将来对程序架构进行优化(更友好)

(十一)$listeners 移除,谁来接手

Vue3 已经不支持 $listeners 了,从文档中可以知道 Vue2 的 $listeners 在 Vue3 中是合并到了 $attrs 里。

<template>
<div>--------父组件--------{{num}}--</div>
  <Child @call="handleEmit" />
</template>
<script>
import { ref}from 'vue'
import Child from "./Child.vue";
export default {
    components:{
        Child
    },
    setup() {
        let num = ref(0)
        const  handleEmit =(res)=> {
            num.value = res;
            console.log('父组件:',res)
        }
        return{
            num,
            handleEmit
        }
    }
};
</script>

<template>
    <div>---子组件-----</div>
  <!-- 注意传参方式 -->
  <Grandchild v-bind="$attrs" />
</template>
<script>
import { ref}from 'vue'
import Grandchild from './Grandchild.vue'

export default ({
  components: { Grandchild },
  
  setup(props, { attrs }) {
    console.log('Child 组件:',attrs) // 可以看到 Parent 传入的 call 事件,且前面加上了 on,变成了 onCall
  }
})
</script>

<template>
  <button @click="handleClick">孙组件:点击</button>
</template>
<script>
import { ref}from 'vue'

export default ({
   emits: ['call'],
    setup(props, { emit}) {
        const handleClick= ()=> {
            emit('call','1') // 成功触发 Parent 里面的 call 事件回调,在控制台1的值
        }
        return {
            handleClick
        }
    }
})
</script>


(十二)在 prop 的默认函数中访问this

在vue2 中 props里的参数是可以用this.去访问的。但是vue3不行了,取而代之 组件接收到的原始 prop 将作为参数传递给默认函数。


export default{
	props: {
		iconClass: {
		  type: String,
		  required: true
		}
	},
	setup(props) {
	console.log(props.iconClass)
	}
}<script setup>
 const props = defineProps({
    info: {
      type: Object,
      default: null,
    }
  });
  props.info
</script>

(十三)渲染函数

h() 渲染函数

在 2.x 中,render 函数会自动接收 h 函数 (它是 createElement 的惯用别名) 作为参数:

// Vue 2 渲染函数示例
export default {
  render(h) {
    return h('div')
  }
}

在 3.x 中,h 函数现在是全局导入的,而不是作为参数自动传递。

// Vue 3 渲染函数示例
import { h } from 'vue'

export default {
  render() {
    return h('div')
  }
}

<h1>{{ blogTitle }}</h1> 等于
render() {
  return h('h1', {}, this.blogTitle)
}

h() 到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为 VNode。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。

h() 参数

h(
  // {String | Object | Function | null} tag
  // 一个 HTML 标签名、一个组件、一个异步组件,或者 null。
  // 使用 null 将会渲染一个注释。
  //
  // 必需的。
  'div',


  // {Object} props
  // 与 attribute、prop 和事件相对应的对象。
  // 我们会在模板中使用。
  //
  // 可选的。
  {},


  // {String | Array | Object} children
  // 子 VNodes, 使用 `h()` 构建,
  // 或使用字符串获取 "文本 Vnode" 或者
  // 有 slot 的对象。
  //
  // 可选的。
  [
    'Some text comes first.',
    h('h1', 'A headline'),
    h(MyComponent, {
      someProp: 'foobar'
    })
  ]
)

约束

VNodes 必须唯一

render() {
  const myParagraphVNode = Vue.h('p', 'hi')
  return Vue.h('div', [
    // 错误 - 重复的Vnode!
    myParagraphVNode, myParagraphVNode
  ])
}
如果你真的需要重复很多次的元素/组件,你可以使用工厂函数来实现。例如,下面这渲染函数用完全合法的方式渲染了 20 个相同的段落:

render() {
  return Vue.h('div',
    Array.apply(null, { length: 20 }).map(() => {
      return Vue.h('p', 'hi')
    })
  )
}

注册组件

在 3.x 中,由于 VNode 是上下文无关的,不能再用字符串 ID 隐式查找已注册组件。取而代之的是,需要使用一个导入的 resolveComponent 方法

// 3.x
import { h, resolveComponent } from 'vue'

export default {
  setup() {
    const ButtonCounter = resolveComponent('button-counter')
    return () => h(ButtonCounter)
  }
}

(十四)Suspense作用

等待异步组件时渲染一些额外内容,让应用有更好的用户体验。

试验性:
Suspense 是一个试验性的新特性,其 API 可能随时会发生变动。特此声明,以便社区能够为当前的实现提供反馈。
生产环境请勿使用

使用步骤

异步引入组件

import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))

使用Suspense包裹组件,并配置好default 与 fallback

<template>
    <div class="app">
        <h3>我是App组件</h3>
        <Suspense>
            <template v-slot:default>
                <Child/>
            </template>
            <template v-slot:fallback>
                <h3>加载中.....</h3>
            </template>
        </Suspense>
    </div>
</template>

其中 fallback是由于网速或者其他原因没有加载成功时显示的组件,当加载成功后显示default(default 与 fallback 不可改名,因为Suspense相当于定义好的slot具名插槽)
在这里插入图片描述

Suspense搭配async函数的setup

App.vue(异步加载组件的父组件)
<template>
  <div class="app">
    <h3>我是App组件</h3>
    <Suspense>
      <template v-slot:default>
        <Child />
      </template>
      <template v-slot:fallback>
        <h3>稍等,加载中...</h3>
      </template>
    </Suspense>
  </div>
</template>
 
<script>
// import Child from './components/Child'//静态引入
import { defineAsyncComponent } from "vue";
const Child = defineAsyncComponent(() => import("./components/Child")); //异步引入
export default {
  name: "App",
  components: { Child },
};
</script>
 
<style>
.app {
  background-color: gray;
  padding: 10px;
}
</style>
定义setup为async的组件Child.vue
<template>
  <div class="child">
    <h3>我是Child组件</h3>
    {{ sum }}
  </div>
</template>
 
<script>
import { ref } from "vue";
export default {
  name: "Child",
  async setup() {
    let sum = ref(0);
    let p = new Promise((resolve) => {
      setTimeout(() => {
        resolve({ sum });
      }, 3000);
    });
    return await p;
  },
};
</script>
 
<style>
.child {
  background-color: skyblue;
  padding: 10px;
}
</style>

在这里插入图片描述

(十五)移除v-on.native修饰符

<my-component
  v-on:click.native="handleNativeClickEvent"
/>
改成
<my-component
  v-on:click="handleNativeClickEvent"
/>

删除 .native 修饰符的所有实例。确保所有组件都使用 emits 选项记录其事件。

(十六)样式

1. 穿透

scoped 在 DOM 结构、CSS 样式上,增加唯一标识(形如 data-v-hash)达到样式私有化、样式模块化(PostCSS 转译实现)。
不使用样式穿透,属性选择器就永远在最后,无法直接在 scoped 中修改 ElementPlus 类名;使用样式穿透,就能改变属性选择器的位置;
Vue2 中使用 /deep/ 进行样式穿透

/deep/ .el-input__icon{
 color: red;
}

Vue3 中使用 :deep() 或者**::v-deep** 进行样式穿透

:deep(.el-input__icon) {
      color: red;
    }
::v-deep .el-input__icon{
 color: red;
}

在这里插入图片描述

2.样式新特性

插槽选择器 :slotted()

带插槽的子组件:

  • 默认情况下,子组件内无法修改 未来 slot 中出现的类名
  • 可以使用 插槽选择器 :slotted() 修改未来 slot 中出现的类名
子组件:
<template>
  <div>
      插槽组件
      <slot></slot>
  </div>
</template>
<style scoped>
  // 没效果
  .parent-custom {
    color: red;
  }
 
  // 有效果
  :slotted(.parent-custom) {
    color: red;
  }
</style>
父组件
  <template>
  <div>
	<A> <div class="parent-custom"> 父组件插入了内容 </div></A>
  </div>
</template>

<script setup>
  import A from '@/views/a.vue';
</script>

在这里插入图片描述

注意:父组件中的类名,会覆盖子组件中的插槽选择器:

父组件
<style>
.parent-custom {
    color:#666;
  }
</style>

全局选择器 :global()

如果想在单个 .vue 文件里增加全局样式,通过删除 scoped 实现,但这样不够优雅。:global() 全局选择器就可以在不删除 scoped 的情况下,增加全局样式,

<style scoped>
  /* 
     通过 :global() 全局选择器
     在不删除 scoped 的情况下
     增加全局样式
  */
  :global(input) {
    width: 100px;
    height: 100px;
    background: red;
  }
</style>

动态样式绑定 v-bind

如下所示:
新增响应式变量 color,点击按钮后,改变 color 的值,.test 类名的 div 随之改变背景色

<template>
  <div>
    <div class="test"></div>
  <el-button @click="changeColor">颜色切换</el-button>
  </div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const color = ref('red');
const changeColor = () => {
 color.value = color.value === 'red' ? 'green' : 'red'
};
</script>
<style scoped>
 .test {
    width: 88px;
    height: 88px;
    /* 通过 v-bind 可以绑定变量,实现动态 css */
    background: v-bind(color)
  }
</style>

在这里插入图片描述
如果响应式变量是对象形式,则需要在 v-bind 中加单引号

<template>
  <div>
    <div class="test"></div>
  </div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const color = ref({
  one: 'red',
  two: 'yellow',
});
</script>
<style scoped>
 .test {
    width: 88px;
    height: 88px;
   background: v-bind('color.two')
  }
</style>

(十七) css module

使用场景 —— 此功能一般用于 tsx、render 函数

在 style 标签上,新增 module;在样式处,新增默认名字 $style,在他的后面加真正的类名;

<template>
  <div class="$style.test"></div>
</template>
 
<style module>
  .test {
    background: red;
  }
</style>

如果有多个类名,则用数组

<template>
  <!-- 多个使用数组分割 -->
  <div class="[$style.test, $style.uat]"></div>
</template>
 
<style module>
  .test {
    background: red;
  }
  .uat {
    border: 1px solid red;
  }
</style>

可以给模块指定名字,并通过 Vue3 内置 hoos useCssModule() 获取类名列表

<template>
  <div class="$mmm.test"></div>
</template>
 
<script lang="ts" setup>
import { useCssModule } from 'vue'
const classList = useCssModule('mmm')
</script>
 
<style module="mmm">
  .test {
    background: red;
  }
</style>

(十八)unocss 原子化

什么是 unocss 原子化

原子化 CSS 是一种 CSS 的架构方式,它倾向于小巧且用途单一的 class,并且会以视觉效果进行命名。

优点

  • 减少了 css 体积,提高了 css 复用(重复的样式不用写了,直接用现有类名)
  • 减少起名的复杂度(比如 margin-top,就简写成mt)
    增加了记忆成本,将 css 拆分为原子之后,就必须要记住一些 class 才能书写(比如 background,开头是 bg)

安装

1. 安装命令
npm i -D unocss

2.vite.config.ts 配置

import unocss from 'unocss/vite'

 plugins: [vue(), vueJsx(),unocss({
      rules:[
        //配置一些规则
      ]
  })],
  
3.main.ts 引入 

import 'uno.css'

自定义css–》vite.config.ts

配置动态css(使用正则表达式)
m-参数*10   例如 m-10 就是 margin:100px

rules: [
  [/^m-(\d+)$/, ([, d]) => ({ margin: `${Number(d) * 10}px` })],
  ['flex', { display: "flex" }]
]
在style模块中@apply(减少类名)

unocss可以直接在class中写原子化css,但有时候可能我不是很想把很多css的样式都堆在tag上,导致tag的class很长,或者tag属性过多。
官方提供了插件实现在style中写原子化css,官方文档 #apply

// vite.config.ts
import { defineConfig } from "vite";
import Unocss from "unocss/vite";
import transformerDirective from "@unocss/transformer-directives";

export default defineConfig({
  plugins: [
    vue(),
    Unocss({
      transformers: [transformerDirective()],
    })
  ],
});

@unocss/transformer-directives插件后允许使用@apply指令在style中写原子化css

<template>
  <div class="cnt">@apply</div>
</template>

<style lang="scss">
.cnt {
  @apply p-5 bg-pink c-white;
}
</style>

在这里插入图片描述

shortcuts 可以自定义组合样式
 plugins: [vue(), vueJsx(), unocss({
    rules: [
      [/^m-(\d+)$/, ([, d]) => ({ margin: `${Number(d) * 10}px` })],
      ['flex', { display: "flex" }],
      ['pink', { color: 'pink' }]
    ],
    shortcuts: {
      btn: "pink flex"
    }
  })],

在这里插入图片描述

unocss 预设

presets:[presetIcons(),presetAttributify(),presetUno()]
presetIcons Icon图标预设

图标集合安装:npm i -D @iconify-json/ic
首先我们去icones官网(方便浏览和使用iconify)浏览我们需要的icon
在这里插入图片描述
使用:

<i i-subway-admin /> 
button可以用自定义图标了
<el-button type="primary">
          <template #icon>
            <i i-subway-admin />
          </template>
          登录
        </el-button>

在这里插入图片描述

presetAttributify 属性化模式支持

属性语义化 无须class

<div font="black">
     btn
</div>
presetUno 工具类预设

默认的 @unocss/preset-uno 预设(实验阶段)是一系列流行的原子化框架的 通用超集,包括了 Tailwind CSS,Windi CSS,Bootstrap,Tachyons 等。

例如,ml-3(Tailwind),ms-2(Bootstrap),ma4(Tachyons),mt-10px(Windi CSS)均会生效。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值