• 技术文章 >web前端 >Vue.js

    超详细!图文讲解Vue3的组合式API!

    藏色散人藏色散人2022-08-09 09:30:03转载903

    组合式API

    • 组合式api(Composition API)算是vue3对我们开发者来说非常有价值的一个api更新,我们先不关注具体语法,先对它有一个大的感知

    1. composition vs options

    2. 案例对比

    上面我们通过图示简单了解了一下vue3带来的全新的api形式,下面我们通过一个具体的小案例更加深入的体会一下俩种api下的开发模式对比,我们先暂时忽略语法细节,只关注代码编写形式

    2.1 理解需求

    在这里插入图片描述
    俩个独立的功能:

    2.2 vue2.x option Api版本

    <template>
      <div>
        <!-- 功能一模板 -->
        <button @click="show">显示</button>
        <button @click="hide">隐藏</button>
        <div v-if="showDiv">一个被控制显隐的div</div>
      </div>
      <div>
        <!-- 功能二模板 -->
        <button @click="changeRed">红色</button>
        <button @click="changeYellow">蓝色</button>
        <div :style="`color:${fontColor}`">一个被控制字体颜色的的div</div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App',
      data() {
        return {
          showDiv: true, // 功能一数据
          fontColor: '' // 功能二数据
        }
      },
      methods: {
        // 功能一方法
        show() {
          this.showDiv = true
        },
        hide() {
          this.showDiv = false
        },
        // 功能二方法
        changeRed() {
          this.fontColor = 'red'
        },
        changeYellow() {
          this.fontColor = 'blue'
        }
      }
    }
    </script>

    2.3 vue3.0 composition api版本

    <template>
      <div>
        <!-- 功能一模板 -->
        <button @click="show">显示</button>
        <button @click="hide">隐藏</button>
        <div v-if="showDivFlag">一个被控制显隐的div</div>
      </div>
      <div>
        <!-- 功能二模板 -->
        <button @click="changeRed">红色</button>
        <button @click="changeBlue">蓝色</button>
        <div :style="`color:${fontColor}`">一个被控制字体颜色的的div</div>
      </div>
    </template>
    
    <script>
    import { ref } from 'vue'
    export default {
      name: 'App',
      setup() {
        // 功能一
        const showDivFlag = ref(true)
        function show() {
          showDivFlag.value = true
        }
        function hide() {
          showDivFlag.value = false
        }
        // 功能二
    
        const fontColor = ref('')
        function changeRed() {
          fontColor.value = 'red'
        }
        function changeBlue() {
          fontColor.value = 'blue'
        }
        return { showDivFlag, show, hide, fontColor, changeRed, changeBlue }
      }
    }
    </script>

    2.4 composition api版本优化

    在这里可能会有疑惑,那我们现在是把功能相关的所有数据和行为放到一起维护了,如果应用很大功能很多的情况下,setup函数不会变得很大吗?岂不是又会变得比较难维护,接下来我们就来拆解一下庞大的setup函数

    <script>import { ref } from 'vue'function useShow() {
      const showpFlag = ref(true)
      function show() {
        showpFlag.value = true
      }
      function hide() {
        showpFlag.value = false
      }
      return { showpFlag, show, hide }}function useColor() {
      const fontColor = ref('')
      function changeRed() {
        fontColor.value = 'red'
      }
      function changeBlue() {
        fontColor.value = 'blue'
      }
      return { fontColor, changeRed, changeBlue }}export default {
      name: 'App',
      setup() {
        // 功能一
        const { showpFlag, show, hide } = useShow()
        // 功能二
        const { fontColor, changeRed, changeBlue } = useColor()
        return { showpFlag, show, hide, fontColor, changeRed, changeBlue }
      }}</script>

    以上,我们通过定义功能函数,把俩个功能相关的代码各自抽离到一个独立的小函数中,然后通过在setUp函数中再把俩个小功能函数组合起来,这样一来,我们既可以把setup函数变得清爽,又可以方便维护快速定位功能位置

    到此我们没有关注api细节,只是体会组合式api给到我们的好处,接下来我们就要深入到api细节,看看全新的api都该如何使用 ↓

    3. setup入口函数

    1. setup 函数是一个新的组件选项,作为组件中组合式API 的起点(入口)
    2. setup 中不能使用 this, this 指向 undefined
    3. setup函数只会在组件初始化的时候执行一次
    4. setup函数在beforeCreate生命周期钩子执行之前执行
    export default {
      setup () {
        console.log('setup执行了')
        console.log(this)
      },
      beforeCreate() {
        console.log('beforeCreate执行了')
        console.log(this)
      }}

    4. 响应式系统API

    4.1 reactive 函数

    • 作用:reactive是一个函数,接收一个普通的对象传入,把对象数据转化为响应式对象并返回

    使用步骤

    代码落地

    <template>
      <div>{{ state.name }}</div>
      <div>{{ state.age }}</div>
      <button @click="state.name = 'pink'">改值</button>
    </template>
    
    <script>
    import { reactive } from 'vue'
    export default {
      setup () {
        const state = reactive({
          name: 'cp',
          age: 18
        })
        return {
          state
        }
      }
    }
    </script>

    4.2 ref 函数

    • 作用:ref是一个函数,接受一个简单类型或者复杂类型的传入并返回一个响应式且可变的 ref 对象

    使用步骤

    <template>
      <div>{{ money }}</div>
      <button @click="changeMondy">改值</button>
    </template>
    
    <script>
    import { ref } from 'vue'
    export default {
      setup() {
        let money = ref(100)
        console.log(money.value)
        return {
          money
        }
      }
    }
    </script>

    总结说明:

    4.3 toRefs 函数

    • 场景: 经过reactive函数处理之后返回的对象,如果给这个对象解构或者展开,会让数据丢失响应式的能力,为了解决这个问题需要引入toRefs函数,使用 toRefs函数 可以保证该对象展开的每个属性都是响应式的

    4.3.1 问题复现

    还是之前的案例,如果我们想在模板中省略到state,直接书写name和age,你可能会想到,那我在return出去的时候把state中的属性解构出来不就好了

    修改前

    <template>
      <div>{{ state.name }}</div>
      <div>{{ state.age }}</div>
      <button @click="state.name = 'pink'">改值</button>
    </template>
    
    <script>
    import { reactive } from 'vue'
    export default {
      setup() {
        const state = reactive({
          name: 'cp',
          age: 18
        })
        return {
          state
        }
      }
    }
    </script>

    解构修改后

    <template>
      <div>{{ name }}</div>
      <div>{{ age }}</div>
      <button @click="name = 'pink'">改值</button>
    </template>
    <script>
    import { reactive } from 'vue'
    export default {
      setup() {
        const state = reactive({
          name: 'cp',
          age: 18
        })
        return {
          ...state
        }
      }
    }
    </script>

    4.3.2 toRefs包裹处理

    <template>
      <div>{{ name }}</div>
      <div>{{ age }}</div>
      <button @click="name = 'pink'">改值</button>
    </template>
    
    <script>
    import { reactive,toRefs } from 'vue'
    export default {
      setup() {
        const state = reactive({
          name: 'cp',
          age: 18
        })
        return {
          ...toRefs(state)
        }
      }
    }
    </script>

    4.4 computed

    作用:根据现有响应式数据经过一定的计算得到全新的数据

    使用步骤

    <template>
      {{ list }}
      {{ filterList }}  <button @click="changeList">change list</button></template><script>import { computed, ref } from 'vue'export default {
      setup() {
        const list = ref([1, 2, 3, 4, 5])
        // 输入大于3的数字
        const filterList = computed(() => {
          return list.value.filter(item => item > 3)
        })
        // 修改list的函数
        function changeList() {
          list.value.push(6, 7, 8)
        }
        return {
          list,
          filterList,
          changeList    }
      }}</script>

    4.5 watch 侦听器

    作用:基于响应式数据的变化执行回调逻辑,和vue2中的watch的功能完全一致

    • 普通监听

    • 立即执行

    • 深度监听

    使用步骤

    4.5.1 普通监听

    <template>
      {{ age }}  <button @click="age++">change age</button></template><script>import { ref, watch } from 'vue'export default {
      setup() {
        const age = ref(18)
        watch(() => {
          // 返回你想要监听的响应式属性(ref产生的对象必须加.value)
          return age.value    }, () => {
          // 数据变化之后的回调函数
          console.log('age发生了变化')
        })
        return {
          age    }
      }}</script>

    4.5.2 开启立刻执行

    watch的效果默认状态下,只有监听的数据发生变化才会执行回调,如果你需要在一上来的时候就立刻执行一次,需要配置一下immediate属性

    <template>
      {{ age }}  <button @click="age++">change age</button></template><script>import { ref, watch } from 'vue'export default {
      setup() {
        const age = ref(18)
        watch(() => {
          // 返回你想要监听的响应式属性(ref产生的对象必须加.value)
          return age.value    }, () => {
          // 数据变化之后的回调函数
          console.log('age发生了变化')
        },{ immediate: true})
        return {
          age    }
      }}</script>

    4.5.3 开启深度监听

    当我们监听的数据是一个对象的时候,默认状态下,对象内部的属性发生变化是不会引起回调函数执行的,如果想让对象下面所有属性都能得到监听,需要开启deep配置

    <template>
      {{ name }}
      {{ info.age }}  <button @click="name = 'pink'">change name</button>
      <button @click="info.age++">change age</button></template><script>import { reactive, toRefs, watch } from 'vue'export default {
      setup() {
        const state = reactive({
          name: 'cp',
          info: {
            age: 18
          }
        })
        watch(() => {
          return state    }, () => {
          // 数据变化之后的回调函数
          console.log('age发生了变化')
        }, {
          deep: true
        })
        return {
          ...toRefs(state)
        }
      }}</script>

    4.5.4 更好的做法

    使用watch的时候,尽量详细的表明你到底要监听哪个属性,避免使用deep引起的性能问题,比如我仅仅只是想在state对象的age属性变化的时候执行回调,可以这么写

    <template>
      {{ name }}
      {{ info.age }}  <button @click="name = 'pink'">change name</button>
      <button @click="info.age++">change age</button></template><script>import { reactive, toRefs, watch } from 'vue'export default {
      setup() {
        const state = reactive({
          name: 'cp',
          info: {
            age: 18
          }
        })
        watch(() => {
          // 详细的告知你要监听谁
          return state.info.age    }, () => {
          // 数据变化之后的回调函数
          console.log('age发生了变化')
        })
        return {
          ...toRefs(state)
        }
      }}</script>

    5. 生命周期函数

    使用步骤

    <template>
      <div>生命周期函数</div>
    </template>
    
    <script>
    import { onMounted } from 'vue'
    export default {
      setup() {
        // 时机成熟 回调函数自动执行
        onMounted(() => {
          console.log('mouted生命周期执行了')
        })
         onMounted(() => {
          console.log('mouted生命周期函数又执行了')
        })
      }
    }
    </script>
    选项式API组合式API
    beforeCreate不需要(直接写到setup函数中)
    created不需要(直接写到setup函数中)
    beforeMountonBeforeMount
    mountedonMounted
    beforeUpdateonBeforeUpdate
    updatedonUpdated
    beforeDestroyedonBeforeUnmount
    destroyedonUnmounted

    6. 父子通信

    在vue3的组合式API中,父传子的基础套路完全一样,基础思想依旧为:父传子是通过prop进行传入,子传父通过调用自定义事件完成

    实现步骤

    代码落地
    app.vue

    <template>
      <son :name="name" @get-msg="getMsg"></son></template><script>import { ref } from 'vue'import Son from './components/son'export default {
      components: {
        Son  },
      setup() {
        const name = ref('cp')
        function getMsg(msg) {
          console.log(msg)
        }
        return {
          name,
          getMsg    }
      }}</script>

    components/son.vue

    <template>
      <div>
        {{name}}
        <button @click="setMsgToSon">set</button>
      </div>
    </template>
    
    <script>
    export default {
      props: {
        name: {
          type: String
        }
      },
      emits: ['get-msg'], // 声明当前组件触发的自定义事件
      setup(props,{emit}) {
        console.log(props.name)
        function setMsgToSon(){
          emit('get-msg','这是一条来自子组件的msg信息')
        }
        return {
          setMsgToSon
        }
      }
    }
    </script>

    7. provide 和 inject

    通常我们使用props进行父子之间的数据传递,但是如果组件嵌套层级较深,一层一层往下传递将会变的非常繁琐,有没有一种手段可以把这个过程简化一下呢,有的,就是我们马上要学习的provide 和 inject,它们配合起来可以方便的完成跨层传递数据

    在这里插入图片描述

    7.1 基础使用

    来个需求: 爷组件中有一份数据 传递给孙组件直接使用
    在这里插入图片描述

    实现步骤:

    代码落地
    爷爷组件 - app.vue

    <template>
      <father></father></template><script>import Father from '@/components/Father'import { provide } from 'vue'export default {
      components: {
        Father  },
      setup() {
        let name = '柴柴老师'
        // 使用provide配置项注入数据 key - value
        provide('name', name)
      }}</script>

    孙组件 - components/Son.vue

    <template>
      我是子组件
      {{ name }}</template><script>import { inject } from 'vue'export default {
      setup() {
        const name = inject('name')
        return {
          name    }
      }}</script>

    事实上,只要是后代组件,都可以方便的获取顶层组件提供的数据

    7.2 传递响应式数据

    provide默认情况下传递的数据不是响应式的,也就是如果对provide提供的数据进行修改,并不能响应式的影响到底层组件使用数据的地方,如果想要传递响应数据也非常简单,只需要将传递的数据使用ref或者reactive生成即可

    <template>
      <father></father>
      <button @click="changeName">change name</button></template><script>import Father from '@/components/Father'import { provide, ref } from 'vue'export default {
      components: {
        Father  },
      setup() {
        // 使用ref转换成响应式再传递
        let name = ref('柴柴老师')
        function changeName(){
          name.value = 'pink'
        }
        provide('name', name)
        return {
          changeName    }
      }}</script>

    8. 模板中 ref 的使用

    在模板中使用ref,我们都很清楚,它一般有三种使用场景

    • ref + 普通dom标签 获取真实dom对象

    • ref + 组件标签 获取组件实例对象

    • ref + v-for 获取由dom对象(实例对象)组成的数组 (不经常使用)

    实现步骤

    代码落地
    components/RefComponent.vue

    <template>
      我是一个普通的组件</template>

    app.vue

    <template>
      <h1 ref="h1Ref">我是普通dom标签</h1>
      <ref-comoonent ref="comRef"></ref-comoonent></template><script>import { onMounted, ref } from 'vue'import RefComoonent from '@/components/RefComponent'export default {
      components: {
        RefComoonent  },
      setup() {
        const h1Ref = ref(null)
        const comRef = ref(null)
        onMounted(() => {
          console.log(h1Ref.value)
          console.log(comRef.value)
        })
        // 必须return
        return {
          h1Ref,
          comRef    }
      }}</script>

    9. 来个案例吧 - Todos

    核心功能

    <template>
      <section class="todoapp">
        <!-- 头部输入框区域 -->
        <header class="header">
          <h1>todos</h1>
          <input
            class="new-todo"
            placeholder="请输入要完成的任务"
            autofocus
            v-model="curTask"
            @keyup.enter="add"
          />
        </header>
        <section class="main">
          <!-- 全选切换input -->
          <input id="toggle-all" class="toggle-all" type="checkbox" v-model="isAll"/>
          <label for="toggle-all">标记所有已经完成</label>
          <ul class="todo-list">
            <!-- 任务列表 -->
            <li v-for="(item, index) in list" :key="item.id">
              <p class="view">
                <!-- 双向绑定 flag -->
                <input class="toggle" type="checkbox" v-model="item.flag" />
                <label>{{ item.name }}</label>
                <!-- 删除按钮 -->
                <button class="destroy" @click="del(index)"></button>
              </p>
            </li>
          </ul>
        </section>
        <footer class="footer">
          <span class="todo-count"> 还未完成的任务有:<strong>{{count}}</strong>项 </span>
        </footer>
      </section></template><script>import { computed, ref } from 'vue'export default {
      setup() {
        const list = ref([
          { id: 1, name: '吃饭', flag: false },
          { id: 2, name: '睡觉', flag: false },
          { id: 3, name: '打豆豆', flag: true }
        ])
    
        // 删除函数
        function del(index) {
          // index 要删除项的下标值
          // splice
          list.value.splice(index, 1)
        }
    
        const curTask = ref('')
        function add() {
          // 添加逻辑
          list.value.unshift({
            id: new Date(),
            name: curTask.value,
            flag: false
          })
          curTask.value = ''
        }
    
        // 全选取消全选
        // {name:"cp"}  console.log(info.name)  info.name = 'pink'
        const isAll = computed({
          // 获取isAll数据的时候会执行get函数
          get() {
            // 当list列表中所有项的flag属性都为true 就为true
            // every
            return list.value.every(item => item.flag === true)
          },
          set(val) {
            // 拿到isAll最新值 遍历一下list 把里面的flag属性设置为最新值
            list.value.forEach(item => {
              item.flag = val        })
          }
        })
    
        // 计算未完成的任务
        const count = computed(()=>{
          return  list.value.filter(item=>item.flag === false).length    })
       
        return {
          list,
          del,
          curTask,
          add,
          isAll,
          count    }
      }}</script><style>html,
    body {
      margin: 0;
      padding: 0;}button {
      margin: 0;
      padding: 0;
      border: 0;
      background: none;
      font-size: 100%;
      vertical-align: baseline;
      font-family: inherit;
      font-weight: inherit;
      color: inherit;
      -webkit-appearance: none;
      appearance: none;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;}body {
      font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
      line-height: 1.4em;
      background: #f5f5f5;
      color: #111111;
      min-width: 230px;
      max-width: 550px;
      margin: 0 auto;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      font-weight: 300;}:focus {
      outline: 0;}.hidden {
      display: none;}.todoapp {
      background: #fff;
      margin: 130px 0 40px 0;
      position: relative;
      box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);}.todoapp input::-webkit-input-placeholder {
      font-style: italic;
      font-weight: 300;
      color: rgba(0, 0, 0, 0.4);}.todoapp input::-moz-placeholder {
      font-style: italic;
      font-weight: 300;
      color: rgba(0, 0, 0, 0.4);}.todoapp input::input-placeholder {
      font-style: italic;
      font-weight: 300;
      color: rgba(0, 0, 0, 0.4);}.todoapp h1 {
      position: absolute;
      top: -140px;
      width: 100%;
      font-size: 80px;
      font-weight: 200;
      text-align: center;
      color: #b83f45;
      -webkit-text-rendering: optimizeLegibility;
      -moz-text-rendering: optimizeLegibility;
      text-rendering: optimizeLegibility;}.new-todo,
    .edit {
      position: relative;
      margin: 0;
      width: 100%;
      font-size: 24px;
      font-family: inherit;
      font-weight: inherit;
      line-height: 1.4em;
      color: inherit;
      padding: 6px;
      border: 1px solid #999;
      box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
      box-sizing: border-box;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;}.new-todo {
      padding: 16px 16px 16px 60px;
      border: none;
      background: rgba(0, 0, 0, 0.003);
      box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);}.main {
      position: relative;
      z-index: 2;
      border-top: 1px solid #e6e6e6;}.toggle-all {
      width: 1px;
      height: 1px;
      border: none; /* Mobile Safari */
      opacity: 0;
      position: absolute;
      right: 100%;
      bottom: 100%;}.toggle-all + label {
      width: 60px;
      height: 34px;
      font-size: 0;
      position: absolute;
      top: -52px;
      left: -13px;
      -webkit-transform: rotate(90deg);
      transform: rotate(90deg);}.toggle-all + label:before {
      content: "❯";
      font-size: 22px;
      color: #e6e6e6;
      padding: 10px 27px 10px 27px;}.toggle-all:checked + label:before {
      color: #737373;}.todo-list {
      margin: 0;
      padding: 0;
      list-style: none;}.todo-list li {
      position: relative;
      font-size: 24px;
      border-bottom: 1px solid #ededed;}.todo-list li:last-child {
      border-bottom: none;}.todo-list li.editing {
      border-bottom: none;
      padding: 0;}.todo-list li.editing .edit {
      display: block;
      width: calc(100% - 43px);
      padding: 12px 16px;
      margin: 0 0 0 43px;}.todo-list li.editing .view {
      display: none;}.todo-list li .toggle {
      text-align: center;
      width: 40px;
      /* auto, since non-WebKit browsers doesn't support input styling */
      height: auto;
      position: absolute;
      top: 0;
      bottom: 0;
      margin: auto 0;
      border: none; /* Mobile Safari */
      -webkit-appearance: none;
      appearance: none;}.todo-list li .toggle {
      opacity: 0;}.todo-list li .toggle + label {
      background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E");
      background-repeat: no-repeat;
      background-position: center left;}.todo-list li .toggle:checked + label {
      background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E");}.todo-list li label {
      word-break: break-all;
      padding: 15px 15px 15px 60px;
      display: block;
      line-height: 1.2;
      transition: color 0.4s;
      font-weight: 400;
      color: #4d4d4d;}.todo-list li.completed label {
      color: #cdcdcd;
      text-decoration: line-through;}.todo-list li .destroy {
      display: none;
      position: absolute;
      top: 0;
      right: 10px;
      bottom: 0;
      width: 40px;
      height: 40px;
      margin: auto 0;
      font-size: 30px;
      color: #cc9a9a;
      margin-bottom: 11px;
      transition: color 0.2s ease-out;}.todo-list li .destroy:hover {
      color: #af5b5e;}.todo-list li .destroy:after {
      content: "×";}.todo-list li:hover .destroy {
      display: block;}.todo-list li .edit {
      display: none;}.todo-list li.editing:last-child {
      margin-bottom: -1px;}.footer {
      padding: 10px 15px;
      height: 20px;
      text-align: center;
      font-size: 15px;
      border-top: 1px solid #e6e6e6;}.footer:before {
      content: "";
      position: absolute;
      right: 0;
      bottom: 0;
      left: 0;
      height: 50px;
      overflow: hidden;
      box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
        0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
        0 17px 2px -6px rgba(0, 0, 0, 0.2);}.todo-count {
      float: left;
      text-align: left;}.todo-count strong {
      font-weight: 300;}.filters {
      margin: 0;
      padding: 0;
      list-style: none;
      position: absolute;
      right: 0;
      left: 0;}.filters li {
      display: inline;}.filters li a {
      color: inherit;
      margin: 3px;
      padding: 3px 7px;
      text-decoration: none;
      border: 1px solid transparent;
      border-radius: 3px;}.filters li a:hover {
      border-color: rgba(175, 47, 47, 0.1);}.filters li a.selected {
      border-color: rgba(175, 47, 47, 0.2);}.clear-completed,
    html .clear-completed:active {
      float: right;
      position: relative;
      line-height: 20px;
      text-decoration: none;
      cursor: pointer;}.clear-completed:hover {
      text-decoration: underline;}.info {
      margin: 65px auto 0;
      color: #4d4d4d;
      font-size: 11px;
      text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
      text-align: center;}.info p {
      line-height: 1;}.info a {
      color: inherit;
      text-decoration: none;
      font-weight: 400;}.info a:hover {
      text-decoration: underline;}/*
    	Hack to remove background from Mobile Safari.
    	Can't use it globally since it destroys checkboxes in Firefox
    */@media screen and (-webkit-min-device-pixel-ratio: 0) {
      .toggle-all,
      .todo-list li .toggle {
        background: none;
      }
    
      .todo-list li .toggle {
        height: 40px;
      }}@media (max-width: 430px) {
      .footer {
        height: 50px;
      }
    
      .filters {
        bottom: 10px;
      }}</style>

    以上就是超详细!图文讲解Vue3的组合式API!的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:csdn,如有侵犯,请联系admin@php.cn删除

    前端(VUE)零基础到就业课程:点击学习

    清晰的学习路线+老师随时辅导答疑

    快捷开发Web应用及小程序:点击使用

    支持亿级表,高并发,自动生成可视化后台。

    专题推荐:Vue
    上一篇:你了解vue diff算法吗?原理解析 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • ❤️‍🔥共22门课程,总价3725元,会员免费学• ❤️‍🔥接口自动化测试不想写代码?• 聊聊Vue2开发者如何快速上手Vue3• 秒懂Vue3+Vite3源码,只要会这20个库!• Vue3中如何自定义指令?代码讲解• 详解Vue3 Suspense:是什么?能干什么?如何用?• 聊聊Vue3+qrcodejs如何生成二维码并添加文字描述
    1/1

    PHP中文网