vue3中逻辑复用

一、组件

1、自定义组件

1、全局注册

  • 全局注册通过
javascript 复制代码
import MyComponent from './App.vue'
app.component('MyComponent', MyComponent)
  • 全局注册使用方便,但有两个缺点:如果未使用此组件,仍然会进行打包,无法自动移除(tree-shaking);依赖关系不明确,影响长期维护

2、局部注册

  • 局部注册通过父组件引用子组件,<script setup> 的单文件组件中,导入的组件可以直接在模板中使用,无需注册;其他需要用 components 选项来显式注册。
xml 复制代码
<script setup>
import ComponentA from './ComponentA.vue'
</script>

<template>
  <ComponentA />
</template>
javascript 复制代码
import ComponentA from './ComponentA.js'

export default {
  components: {
    ComponentA
  },
  setup() {
    // ...
  }
}

3、命名

命名使用PascalCase,模板中可以使用PascalCase或kebab-case

2、组件间传值方式?

1、父传子props

  1. 父传子,使用props,单向数据流 ,修改父组件的内容,子组件会同步。如果子组件需要修改父组件传过来的值,可以将props的值先赋值给其他变量,再进行修改;如果需要进一步转换prop,可以使用computed计算属性来更改

  2. <script setup>中使用defineProps定义prop,其他使用props,setup(props)传入prop

  3. 推荐prop写法:kebab-case写法,为了和 HTML attribute 对齐,使用驼峰命名法没有太大优势

  4. prop中类型声明为Boolean时,可以将值省略,例如

xml 复制代码
defineProps({
  disabled: Boolean
})
<!-- 等同于传入 :disabled="true" -->
<MyComponent disabled />

<!-- 等同于传入 :disabled="false" -->
<MyComponent />

2、子传父 $emits

  1. 监听事件,子传父,使用$emits,子组件向父组件传递信息,组件触发的事件没有冒泡机制
  2. <script setup>中使用defineEmits定义emit,其他使用emits,setup(props,ctx)第二个参数为ctx.emit('事件名')或者setup(props,{emit})解构赋值出emit,添加事件参数,$emit('事件名', 参数1, 参数2)
js 复制代码
<!-- 父组件 -->  
<template>  
    <div>订单信息:{{text}}</div>  
        <div class="app">  
        <v-text  
            v-bind:text="text"  
            v-on:onChangeText="onChangeText"  
        />  
    </div>  
</template>  
<script setup >  
    import { ref } from 'vue';  
    import VText from './text.vue'  
    const text = ref('环城东路888号');  
    const onChangeText = (newText) => {  
        text.value = newText;  
    }  
</script>
js 复制代码
<!-- 子组件 -->  
<template>  
    <div class="v-text">  
        <span>地址:</span>  
        <input :value="props.text" @input="onInput" />  
    </div>  
</template>  
<script setup>
    const props = defineProps({  
        text: String,  
    });  
    const emits = defineEmits(['onChangeText'])  
    const onInput = (e) => {  
        emits('onChangeText', e.target.value)  
    }  
</script>

3、父组件获取子组件内容$childrenref

  • 一般不用$children,因为不能保证顺序,需要使用下标值来取值,要是改变需求时就需要经常改动,不方便
  • 组件上的ref,可以在父组件中调用子组件的内容,在<script setup>中访问是私有的,默认访问不到子组件的内容,子组件需要使用defineExpose暴露出来;其他情况是可以直接访问

4、子组件获取父组件内容$parent$root

一般不用$parent,因为在开发中,一个子组件可能有好几个父组件,使用$parent耦合性太高,所以一般不使用;可以使用$root来访问根组件的实例

5、兄弟,多个组件依赖注入provideinject

1. 依赖注入是什么?

当有多个深层组件时,祖先组件-父组件-子组件,想从子组件中获取到祖先组件的数据,这时候可以使用props来一层一层传递,在这里边父组件可能就是充当一个传递的功能,并不会用到要传递的数据,这就形成了'prop透传问题',可以使用依赖注入进行传递

2. 依赖注入用法?
  1. provide有两个参数,第一个是名称 ,可以是字符串或者Symbol;第二个是数值 ,可以是任何类型,包括响应式。在大型项目或者编写插件提供给其他使用者时,一般使用Symbol。provide可以有局部,也可以有全局(应用层)

  2. inject注入参数,获取到祖先组件中的数据,inject第一个参数是provide提供的参数名;如果不用provide提供值,可以只在inject中写参数名,但是需要第二个参数加上默认值,否则会报错

  3. 第二个参数是响应式时,尽量将响应式数据放在provide提供方,这样方便后续管理;如果inject注入方需要修改数据,可以在provide提供方编写一个修改数据的方法;如果不想inject注入方修改传入的值,可以传入只读属性的provide

祖先组件:

xml 复制代码
<script setup>
import { ref, provide } from 'vue'
import Child from './Child.vue'

const message = ref('hello')
provide('message', message)
</script>

<template>
  <input v-model="message">
  <Child />
</template>

父组件Child.vue:

xml 复制代码
<script setup>
import GrandChild from './GrandChild.vue'
</script>

<template>
  <GrandChild />
</template>

子组件GrandChild.vue:

xml 复制代码
<script setup>
import { inject } from 'vue'

const message = inject('message')
</script>

<template>
  <p>
    Message to grand child: {{ message }}
  </p>
</template>

6、使用第三方库pinia

使用Vue3中基于Proxy实现的公共状态管理工具Pinia,读数据使用内置的getter和state属性;写数据使用action方法 。使用Pinia需要在挂载DOM实例前引入

javascript 复制代码
import { createApp } from 'vue';  
import { createPinia } from 'pinia';  
import App from './app.vue';  
const app = createApp(App);  
const pinia = createPinia();  
// 加载pinia插件  
app.use(pinia);  
app.mount('#app');

例如实现:

Pinia 定义的独立 store 文件:

javascript 复制代码
import { defineStore } from 'pinia';  
export const useMyStore = defineStore('my-store', {  
    state: () => ({  
        text: '环城东路888号',  
        list: [  
            { name: '苹果', price: 20, count: 0 },  
            { name: '香蕉', price: 12, count: 0 },
            { name: '梨子', price: 15, count: 0 },  
        ]  
    }),  
    getters: {  
        totalPrice(state) {  
            let total = 0;  
            state.list.forEach((item) => {  
                total += item.price * item.count;  
            });  
            return total;  
        },  
    },  
    actions: {  
        updateText(text) {  
            this.text = text;
        },  
        increase(index) {  
            this.list[index].count += 1;  
        },  
        decrease(index) {  
            if (this.list[index].count > 0) {  
                this.list[index].count -= 1;  
            }  
        }    
    }
})

父组件(最外层组件):

xml 复制代码
<template>  
    <div class="app">  
        <v-info />  
        <v-text />  
        <v-list/>  
    </div>  
</template>  
<script setup >  
    import { reactive } from 'vue'; 
    import VInfo from './components/info.vue'  
    import VText from './components/text.vue'  
    import VList from './components/list.vue';  
</script>

子组件(订单显示信息):

xml 复制代码
<template>  
    <div class="v-info">  
        <div>订单信息:</div>  
        <div>收货地址:{{myStore.text}}</div>  
        <div>总金额:<span class="v-info-value">{{myStore.totalPrice}}</span></div>  
    </div>  
</template>  
<script setup>  
    import { useMyStore } from '../store';  
    const myStore = useMyStore();  
</script>

子组件(文本编辑器):

xml 复制代码
<template>  
    <div class="v-text">  
        <span>地址:</span>  
        <input :value="myStore.text" v-on:input="onInput" />  
    </div>  
</template>  
<script setup >  
    import { useMyStore } from '../store';  
    const myStore = useMyStore();  
    const onInput = (e) => {  
        myStore.updateText(e.target.value);  
    }  
</script>

子组件(商品规格选择器):

xml 复制代码
<template>  
    <div class="v-list">  
        <div class="v-list-item" v-for="(item, index) in myStore.list">  
            <span class="text">{{item.name}}</span>  
            <span class="text">单价: {{item.price}}</span>  
            <button class="btn" v-on:click="myStore.decrease(index)">-</button>  
            <span class="count"> {{item.count}}</span>  
            <button class="btn" v-on:click="myStore.increase(index)">+</button>  
        </div>  
    </div>  
</template>  
<script setup>  
    import { useMyStore } from '../store';  
    const myStore = useMyStore();  
</script>

7、pinia与vuex区别?

pinia与vuex的区别:

  • 包含内容:pinia没有mutation,只有getter,state,action来读写数据;vuex需要通过getter,state,mutation,action来读写数据,其中还需要dispatch来分发

  • 写法:pinia的写法简单;vuex需要dispatch;pinia没有modules配置,每个独立仓库都是defineStore出来的

  • 存储方式:pinia默认存于内存中,如果要本地存储,写法比vuex复杂

  • 关于TS:与在 Vuex 中添加 TypeScript 相比,添加 TypeScript 更容易

  • 优缺点:pinia不能用于时间旅行和编辑功能;vuex可以

  • 用法:pinia体积小,适合中小型项目,管理简单;vuex适用于大型项目

3、透传attributes

1、什么是透传?

  • 子组件是单根节点,父组件中除了props,emits外的所有属性,例如class,style,v-on等属性可以向下透传到子组件的根节点上

  • 如果是多根节点,不会自动透传,$attrs 没有被显式绑定,将会抛出一个运行时警告

2、透传的写法?

  • 会自动透传到根节点中,比如 父组件:
ini 复制代码
<MyButton class="large" />

子组件:

xml 复制代码
<!-- <MyButton> 的模板 -->
<button>click me</button>

DOM渲染结果:

arduino 复制代码
<button class="large">click me</button>
  • 如果不想透传到根节点中,先禁用透传inheritAttrs: false ,再使用$attrs显示绑定到想要的元素上,例如透传到button上

父组件:

ini 复制代码
<MyButton class="large" />

子组件:

xml 复制代码
<div class="btn-wrapper">
  <button class="btn" v-bind="$attrs">click me</button>
</div>
<script setup>
defineOptions({
  inheritAttrs: false
})
// ...setup 逻辑
</script>

没有参数的 v-bind会将一个对象的所有属性都作为 attribute 应用到目标元素上。

DOM渲染结果:

arduino 复制代码
<button class="btn large">click me</button>
  • 使用js写法:
xml 复制代码
<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>
javascript 复制代码
export default {
  setup(props, ctx) {
    // 透传 attribute 被暴露为 ctx.attrs
    console.log(ctx.attrs)
  }
}

4、插槽slot

1、slot是什么?

slot插槽是在子组件中预留位置,可以让父组件向子组件传递数据,数据可以是字符串内容,可以是HTML元素等

2、slot用法?

  1. 父组件传入的内容会渲染到子组件中,子组件的<slot></slot>是出口

  2. 渲染作用域:因为是在父组件中填写内容,所以作用域是父组件,默认情况下,不能获取到子组件的内容

  3. 默认插槽 :子组件slot中,默认写的内容,如果父组件不传递内容,会默认展出子组件内容

  4. 具名插槽 :子组件中有多个插槽的时候,父组件向子组件传递,不知道需要传递到那个里边,这时候就需要有个命名的插槽,name具名插槽,父组件传入v-slot:header(子组件slot的name),简写#header。当子组件中默认插槽与具名插槽一起使用时,父组件传入默认插槽内容使用#default

xml 复制代码
<BaseLayout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>

  <template #default> 
     <p>A paragraph for the main content.</p>
     <p>And another one.</p> 
  </template>
  <!-- 或者隐式的默认插槽 -->
  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</BaseLayout>
  1. 动态插槽名:动态插槽
xml 复制代码
<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>

  <!-- 缩写为 -->
  <template #[dynamicSlotName]>
    ...
  </template>
</base-layout>
  1. 作用域插槽:上边说了,默认情况下的作用域是父组件,如果想用子组件的数据的话,也可以实现,使用作用域插槽,获取子组件的数据

父组件:

xml 复制代码
<script setup>
import FancyList from './FancyList.vue'
</script>

<template>
  <FancyList :api-url="url" :per-page="10">
    <template #item="{ body, username, likes }">
      <div class="item">
        <p>{{ body }}</p>
        <p class="meta">by {{ username }} | {{ likes }} likes</p>
      </div>
    </template>
  </FancyList>
</template>

子组件:

xml 复制代码
<script setup>
import { ref } from 'vue'

const props = defineProps(['api-url', 'per-page'])

const items = ref([])

// mock remote data fetching
setTimeout(() => {
  items.value = [
    { body: 'Scoped Slots Guide', username: 'Evan You', likes: 20 },
	  { body: 'Vue Tutorial', username: 'Natalia Tepluhina', likes: 10 }
  ]
}, 1000)
</script>

<template>
  <ul>
    <li v-if="!items.length">
      Loading...
    </li>
    <li v-for="item in items">
      <slot name="item" v-bind="item"/>
    </li>
  </ul>
</template>

3、slot原理?

子组件实例化时,获取到父组件内容存放到$slot中,等遇到slot,将$slot替换到slot中;也可以理解为,调用一个传参的函数,返回slot中内容

javascript 复制代码
MyComponent({
  // 类比默认插槽,将其想成一个函数
  default: (slotProps) => {
    return `${slotProps.text} ${slotProps.count}`
  }
})

function MyComponent(slots) {
  const greetingMessage = 'hello'
  return `<div>${
    // 在插槽函数调用时传入 props
    slots.default({ text: greetingMessage, count: 1 })
  }</div>`
}

5、动态组件

1、动态组件是什么?

在项目中经常会遇到,切换tab键,这个时候可以获取动态组件,根据切换值渲染不同的组件,被切换时,组件会被卸载,想保持存活状态,可以使用keepAlive

2、动态组件用法?

通过is属性

xml 复制代码
<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>

6、异步组件

1、异步组件是什么?

  • 当需要从服务器加载组件时,就需要使用异步组件,使用defineAsyncComponent来注册组件,返回一个Promise函数,满足需求,使用resolve引入组件,错误的话也可以使用错误的组件;
javascript 复制代码
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...从服务器获取组件
    resolve('./components/MyComponent.vue')
  })
})
// ... 像使用其他一般组件一样使用 `AsyncComp`
  • ES动态模块导入也是返回Promise函数,所以就可以直接使用ES引入
javascript 复制代码
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

二、组合式函数

1、组合式函数

1、是什么?

我们经常会封装一个组件,用来复用这个组件,这时候,可以将封装的组件内容,写为一个组合式函数;组合式函数主要是返回有状态逻辑的函数(有状态逻辑是时间改变会改变状态,比如移动鼠标值改变);组合式函数重点在于逻辑,封装的复用组件会包含样式的内容

2、组合式函数写法?

  • 一般定义一个js或者ts文件
  • 导出一个函数,函数命名以use开头,使用驼峰命名
javascript 复制代码
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'

// 按照惯例,组合式函数名以"use"开头
export function useMouse() {
  // 被组合式函数封装和管理的状态
  const x = ref(0)
  const y = ref(0)

  // 组合式函数可以随时更改其状态。
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  // 一个组合式函数也可以挂靠在所属组件的生命周期上
  // 来启动和卸载副作用
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  // 通过返回值暴露所管理的状态
  return { x, y }
}

3、异步状态

当请求异步数据时,需要处理异步数据,经常有加载中,加载完成,加载失败的状态,每次都处理会有点繁琐,这时候就可以使用组合式函数

javascript 复制代码
// fetch.js
import { ref } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)

  fetch(url)
    .then((res) => res.json())
    .then((json) => (data.value = json))
    .catch((err) => (error.value = err))

  return { data, error }
}
xml 复制代码
<script setup>
import { useFetch } from './fetch.js'

const { data, error } = useFetch('...')
</script>

参数也可以传入响应式参数,或者getter函数,这样的话就可以使用watchEffect、toValue来处理响应式参数与getter函数

toValue是将响应式,getter进行规范化值,是响应式的话,会返回响应式值,getter函数的话会调用函数,返回函数的返回值。注意 toValue(url) 是在 watchEffect 回调函数的 内部调用的。这确保了在 toValue() 规范化期间访问的任何响应式依赖项都会被侦听器跟踪。

javascript 复制代码
// fetch.js
import { ref, watchEffect, toValue } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)

  const fetchData = () => {
    // reset state before fetching..
    data.value = null
    error.value = null

    fetch(toValue(url))
      .then((res) => res.json())
      .then((json) => (data.value = json))
      .catch((err) => (error.value = err))
  }

  watchEffect(() => {
    fetchData()
  })

  return { data, error }
}

4、使用限制

只能在<script setup>或者setup()中使用,也必须是同步调用

5、抽取为组合式函数的好处?

  • 可以复用代码,直接调用文件就可以
  • 代码结构方面,拆分为更小的函数,方便管理理解

6、与mixin对比?

  • mixin是vue2中的函数,在vue3中虽然还保留,但是不推荐使用,只是为了兼容vue2
  • mixin的数据来源不清晰,组合式函数只要ref+解构就可以;命名可能会有冲突,组合式函数可以通过解构变量重新命名;隐式的耦合在一起,组合式函数可以将结果作为另一个组合式函数的参数传递,跟普通函数一样

7、与无渲染组件对比?

  • 无渲染组件:额外的组件会有实例开销,进而影响性能开销;复用逻辑+视图布局使用无渲染组件
  • 组合式函数:则不会有额外实例开销;只包含逻辑,视图布局给消费组件

2、组合式API与其他对比?

1、是什么?

组合式API是api的合集,包含响应式:ref,reactive,计算属性,侦听器;生命周期:onMounted等;

2、为什么要用组合式API?

  • 更好的复用逻辑:可以使用组合式函数,复用各个逻辑功能,通过逻辑组合到一起,避免了vue2中mixin的问题
  • 更好的代码结构:使用选项式API或者vue2时,一个功能的代码会分散到各个钩子中,经常需要上下滚动来查看逻辑代码,很不方便;使用组合式,一个功能的代码会放到同一区域,在大型项目中,如果重构的话,可以直接提取功能代码,不用再重新组织结构,降低了重构成本,在维护代码方面很重要
  • 打包为更小的体积:组合式API一般与<script setup>一起使用,<script setup>中可以压缩变量名,如果是选项式的话,需要通过this访问下上文对象属性,但是对象属性名不能被压缩
  • 更好的类型推导:与ts使用方便,组合式API使用ts与js差别不大

3、与选项式API对比?

  • 选项式API规定了代码放置位置,组合式API更灵活,并且更适用于js的写法
  • 组合式API是否覆盖了所有场景:能够覆盖所有状态逻辑方面的内容
  • 同一个组件可以使用两种API吗:可以在选项式API中通过setup()使用组合式API,但是不提倡,除非是原旧项目想添加组合式内容
  • 选项式API会被废弃吗:不会,还有一部分人喜欢使用选项式API

4、与React Hooks对比?

  • 组合式函数:灵感来自React Hooks,逻辑方面也相似。

    • <script setup>只会执行一次,与函数一样,不用担心闭包问题,并且可以有条件的调用
    • 计算属性,侦听器可以直接使用,vue组合式函数会满足,无需手动声明依赖
    • 不用手动缓存回调函数来避免不必要的组件更新,大多数情况下,vue响应式系统仅执行必要的更新
  • React Hooks:

    • 只能按顺序执行,不能写在条件分支中
    • 变量会被一个钩子函数闭包捕获,如果传入了错误的依赖数组,会"过期"。这样得非常依赖Eslint,但是边缘问题会经常产生错误内容
    • 昂贵计算的话,需要useMemo,需要传入正确依赖数组
    • 传递给子组件事件处理函数会导致子组件进行不必要的更新,这也需要传入正确的依赖数组

三、指令

1、自定义指令

1、为什么要自定义指令?

重用代码的方式有:组件组合式函数组件 主要是为了构建模块;组合式函数 是为了复用有状态的逻辑内容。自定义指令 是为了复用 只能通过操作DOM来实现的 普通元素 ,比如,autofocus 在vue动态插入元素后不会触发,而自定义的自动聚焦 vFocus 就可以触发

2、自定义指令是什么样的?局部指令,全局指令?

自定义指令是一个包含类似组件生命周期钩子的对象。在 vue 中以 v 开头的驼峰命名都是自定义指令。

  • 自定义局部指令
xml 复制代码
<script setup>
// 在模板中启用 v-focus
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

<template>
  <input v-focus />
</template>
  • 自定义全局指令
less 复制代码
const app = createApp({})

// 使 v-focus 在所有组件中都可用
app.directive('focus', {
  /* ... */
})

3、自定义指令的钩子函数?有哪些周期?

javascript 复制代码
const myDirective = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {}
}

一个很常见的情况是仅仅需要在 mountedupdated 上实现相同的行为,除此之外并不需要其他钩子

css 复制代码
<div v-color="color"></div>
app.directive('color', (el, binding) => {
  // 这会在 `mounted` 和 `updated` 时都调用
  el.style.color = binding.value
})

4、指令也可以接收任何合法的 JavaScript 表达式

less 复制代码
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
app.directive('demo', (el, binding) => {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text) // => "hello!"
})

5、不推荐在组件上使用自定义指令

四、插件

1、插件

1、为什么要自定义插件?组件和插件的区别?

插件是为vue添加全局功能的工具代码,与组件不同的是,组件需要每次引入,而插件一般情况引入一次就行;组件一般是依赖于项目的,而插件定义好后可以在不同项目中引入安装,开箱即用;自定义插件的过程其实是理解vue实例化的过程。

2、插件的使用范围?

插件没有明确的使用范围,一般可以使用3种方式进行注册:

  • app.component()或者app.directive()注册全局组件或指令
  • app.provide()app.inject()通过依赖注入到整个应用
  • app.config.globalProperties通过全局属性/方法,多个插件同时用全局属性方法时,很容易让应用变得难以理解和维护

3、自定义插件写法?

  • 插件是可以包含install()方法的对象,也可以是安装函数本身

例如自定义一个国际化:

  • 单独js文件
javascript 复制代码
// plugins/i18n.js
export default {
  install: (app, options) => {
    // 注入一个全局可用的 $translate() 方法
    app.config.globalProperties.$translate = (key) => {
      // 获取 `options` 对象的深层属性
      // 使用 `key` 作为索引
      return key.split('.').reduce((o, i) => {
        if (o) return o[i]
      }, options)
    }
  }
}
  • 引入文件
css 复制代码
import i18nPlugin from './plugins/i18n'

app.use(i18nPlugin, {
  greetings: {
    hello: 'Bonjour!'
  }
})
  • 使用插件
bash 复制代码
<h1>{{ $translate('greetings.hello') }}</h1>

4、vue.use()是什么,怎么用?注意点及场景?

vue.use() 是将vue进行实例化,

相关推荐
NightCyberpunk4 分钟前
HTML、CSS
前端·css·html
xcLeigh14 分钟前
HTML5超酷响应式视频背景动画特效(六种风格,附源码)
前端·音视频·html5
zhenryx16 分钟前
前端-react(class组件和Hooks)
前端·react.js·前端框架
ZwaterZ18 分钟前
el-table-column自动生成序号&&在序号前插入图标
前端·javascript·c#·vue
zhangjr05752 小时前
【HarmonyOS Next】鸿蒙实用装饰器一览(一)
前端·harmonyos·arkts
不爱学习的YY酱2 小时前
【操作系统不挂科】<CPU调度(13)>选择题(带答案与解析)
java·linux·前端·算法·操作系统
木子七3 小时前
vue2-vuex
前端·vue
麻辣_水煮鱼3 小时前
vue数据变化但页面不变
前端·javascript·vue.js
BY—-组态3 小时前
web组态软件
前端·物联网·工业互联网·web组态·组态
一条晒干的咸魚3 小时前
【Web前端】实现基于 Promise 的 API:alarm API
开发语言·前端·javascript·api·promise