Skip to content

一. computed 函数使用


1. computed 方法

  • 在前面我们讲解过计算属性 computed:当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理
    • 在前面的 Options API 中,我们是使用 computed 选项来完成的
    • Composition API 中,我们可以在 setup 函数中使用 computed 方法来编写一个计算属性
  • 如何使用 computed 呢?
    • 方式一:接收一个 getter 函数,并为 getter 函数返回的值,返回一个不变的 ref 对象
    • 方式二:接收一个具有 getset 的对象,返回一个可变的(可读写)ref 对象
vue
<template>
  <h2>{{ fullname }}</h2>
  <button @click="setFullname">设置fullname</button>
  <h2>{{ scoreLevel }}</h2>
</template>
<script>
  import { reactive, computed, ref } from 'vue'
  export default {
    setup() {
      const names = reactive({
        firstName: "kobe",
        lastName: "bryant"
      })

      // const fullname = computed(() => {
      //   return names.firstName + " " + names.lastName
      // })
      const fullname = computed({
        set: function(newValue) {
          const tempNames = newValue.split(" ")
          names.firstName = tempNames[0]
          names.lastName = tempNames[1]
        },
        get: function() {
          return names.firstName + " " + names.lastName
        }
      })

      function setFullname() {
        fullname.value = "coder why"
      }

      const score = ref(89)
      const scoreLevel = computed(() => {
        return score.value >= 60 ? "及格": "不及格"
      })

      return {
        names,
        fullname,
        setFullname,
        scoreLevel
      }
    }
  }
</script>

2. setup 中使用 ref

  • setup 中如何使用 ref 获取元素或者组件?

    • 只需要定义一个 ref 对象,绑定到元素 或组件的 ref 属性上即可
    image-20220803205147419

二. 组件的生命周期函数


  • 我们前面说过 setup 可以用来替代 datamethodscomputed 等这些选项,也可以替代生命周期钩子

  • 那么 setup 中如何使用生命周期函数呢?

    • 可以使用直接导入的 onXXX 函数注册生命周期钩子

    • setup 中没有 beforeCreatecreated 两个生命周期函数

    • 实际业务中直接在 setup 中执行业务就行了

      image-20220803211040728image-20230405185954502
  • vue3 的官方文档中可以得知,其实 setup 执行开始在 beforeCreate 之前

  • 示例如下:

    vue
    <template>
      <div>AppContent</div>
    </template>
    <script>
      import { onMounted, onUpdated, onUnmounted } from 'vue'
      export default {
        // beforeCreate() {},
        // created() {},
        // beforeMount() {},
        // mounted() {},
        // beforeUpdate() {},
        // updated() {}
        setup() {
          // 在执行setup函数的过程中, 你需要注册别的生命周期函数
          onMounted(() => {
            console.log("onmounted")
          })
        }
      }
    </script>

三. Provide / Inject 使用


1. Provide 函数

  • 事实上我们之前还学习过 ProvideInjectComposition API 也可以替代之前的 ProvideInject 的选项
  • 我们可以通过 provide 来提供数据:
    • 可以通过 provide 方法来定义每个 Property
  • provide 可以传入两个参数:
    • provide(name, value)
    • name:提供的属性名称
    • value:提供的属性值

2. Inject 函数

  • 在后代组件中可以通过 inject 来注入需要的属性和对应的值:
    • 可以通过 inject 来注入需要的内容
  • inject 可以传入两个参数:
    • 第一个参数:要 injectpropertyname
    • 第二个参数:默认值

3. 数据的响应式

  • 为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用 refreactive

    image-20220803212343191
  • 有的时候,我们可能需要在注入方组件中更改数据

  • 在这种情况下,我们推荐在供给方组件内声明并提供一个更改数据的方法函数:

    vue
    <!-- 在供给方组件内 -->
    <script setup>
    import { provide, ref } from 'vue'
    
    const location = ref('North Pole')
    
    function updateLocation() {
      location.value = 'South Pole'
    }
    
    provide('location', {
      location,
      updateLocation // 将更改数据的方法函数通过provide第二个参数用对象形式带过去
    })
    </script>
    vue
    <!-- 在注入方组件 -->
    <template>
      <button @click="updateLocation">{{ location }}</button>
    </template>
    <script setup>
    import { inject } from 'vue'
    
    const { location, updateLocation } = inject('location')
    </script>

四. watch / watchEffect


1.侦听数据的变化

  • 在前面的 Options API 中,我们可以通过 watch 选项来侦听 data 或者 props 的数据变化,当数据变化时执行某一些操作
  • Composition API 中,我们可以使用 watchEffectwatch 来完成响应式数据的侦听
    • watchEffect:用于自动收集响应式数据的依赖
    • watch:需要手动指定侦听的数据源

2. Watch 的使用

  • watchAPI 完全等同于组件 watch 选项的 Property

    • watch 需要侦听特定的数据源,并且执行其回调函数
    • 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调
    vue
    <template>
      <button @click="message = '你好啊,李银河!'">修改message</button>
    </template>
    <script>
      import { ref, watch } from 'vue'
      export default {
        setup() {
          const message = ref("Hello World")
          // 侦听 message 数据的变化
          watch(message, (newValue, oldValue) => {
            console.log(newValue, oldValue)
          })
          return {
            message
          }
        }
      }
    </script>

3. 侦听多个数据源

  • 侦听器还可以使用数组同时侦听多个源:

    js
    const name = ref('later')
    const age = ref(18)
    
    const changeName = () => {
      name.value = 'hello'
    }
    
    watch([name, age], (newVals, oldVals) => {
      console.log(newVals, oldVals)
    })

4. watch 的选项

  • 如果我们希望进行一个深层的侦听,那么依然需要设置 deeptrue

    • 也可以传入 immediate 立即执行

      vue
      <template>
        <button @click="info.friend.name = 'james'">修改info</button>
      </template>
      <script>
        import { reactive, watch } from 'vue'
        export default {
          setup() {
            const info = reactive({
              name: "why",
              age: 18,
              friend: {
                name: "kobe"
              }
            })
      
            watch(info, (newValue, oldValue) => {
              console.log(newValue === oldValue) // true
            }, {
              immediate: true
            })
      
            // 监听reactive数据变化后, 获取普通对象
            watch(() => ({ ...info }), (newValue, oldValue) => {
              console.log(newValue, oldValue)
            }, {
              immediate: true,
              deep: true
            })
      
            return {
              info
            }
          }
        }
      </script>

5. watchEffect

  • 当侦听到某些响应式数据变化时,我们希望执行某些操作,这个时候可以使用 watchEffect

  • 我们来看一个案例:

    • 首先,watchEffect 传入的函数会被立即执行一次,并且在执行的过程中会收集依赖
    • 其次,只有收集的依赖发生变化时,watchEffect 传入的函数才会再次执行
    vue
    <template>
      <div>
        <h2>当前计数: {{ counter }}</h2>
        <button @click="counter++">+1</button>
        <button @click="name = 'kobe'">修改name</button>
      </div>
    </template>
    <script>
      import { watchEffect, watch, ref } from 'vue'
      export default {
        setup() {
          const counter = ref(0)
          const name = ref("why")
          // 1. watchEffect传入的函数默认会直接被执行
          // 2. 在执行的过程中, 会自动的收集依赖(依赖哪些响应式的数据)
          watchEffect(() => {
            console.log("----", counter.value, name.value)
          })
    
          return {
            counter,
            name
          }
        }
      }
    </script>

6. watchEffect 的停止侦听

  • 如果在发生某些情况下,我们希望停止侦听,这个时候我们可以获取 watchEffect 的返回值函数,调用该函数即可

  • watch 方法也会返回一个停止侦听的函数

  • 比如在上面的案例中,我们 age 达到 20 的时候就停止侦听:

    vue
    <template>
      <div>
        <h2>当前计数: {{ counter }}</h2>
        <button @click="counter++">+1</button>
        <button @click="name = 'kobe'">修改name</button>
      </div>
    </template>
    <script>
      import { watchEffect, watch, ref } from 'vue'
      export default {
        setup() {
          const counter = ref(0)
          const name = ref("why")
          // 1.watchEffect传入的函数默认会直接被执行
          // 2.在执行的过程中, 会自动的收集依赖(依赖哪些响应式的数据)
          const stopWatch = watchEffect(() => {
            console.log("----", counter.value, name.value)
            if (counter.value >= 10) stopWatch()
          })
    
          return {
            counter,
            name
          }
        }
      }
    </script>

五. 自定义 Hook 练习


1. useCounter

image-20220804011207893

2. useTitle

编写一个修改 titleHook

image-20220804011347130

3. useScrollPosition

完成一个监听界面滚动位置的 Hook

js
import { ref } from 'vue'

export function useScrollPosition() {
  const scrollX = ref(0)
  const scrollY = ref(0)

  document.addEventListener('scroll', () => {
    scrollX.value = window.scrollX
    scrollY.value = window.scrollY
  })

  return { scrollX, scrollY }
}

六. script setup 语法糖


1. script setup 语法

  • <script setup> 是在单文件组件 SFC 中使用组合式 API 的编译时语法糖,当同时使用 SFC 与组合式 API 时则推荐该语法

    • 更少的样板内容,更简洁的代码
    • 能够使用纯 Typescript 声明 prop 和抛出事件
    • 更好的运行时性能
    • 更好的 IDE 类型推断性能
  • 使用这个语法,需要在 <script> 中添加 setup 属性:

    vue
    <script setup>
      // ...
    </script>
  • 里面的代码会被编译成组件 setup() 函数的内容:

    • 这意味着与普通的 <script> 只在组件被首次引入的时候执行一次不同
    • <script setup> 中的代码会在每次组件实例被创建的时候执行

2. 顶层的绑定会被暴露给模板

  • 当使用 <script setup> 的时候,任何在 <script setup> 声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用:

    vue
    <template>
      <div>{{ message }}</div>
      <button @click="changeMessage">修改message</button>
    </template>
    
    <script setup>
      // 1.所有编写在顶层中的代码, 都是默认暴露给template可以使用
      import { ref, onMounted } from 'vue'
      // 2.定义响应式数据
      const message = ref("Hello World")
      console.log(message.value)
      // 3.定义绑定的函数
      function changeMessage() {
        message.value = "你好啊, 李银河!"
      }
    </script>
  • 响应式数据需要通过 refreactive 来创建

3. 导入的组件直接使用

  • <script setup> 范围里的值也能被直接作为自定义组件的标签名使用:

    vue
    <template>
      <show-info></show-info>
    </template>
    
    <script setup>
      // 所有编写在顶层中的代码, 都是默认暴露给 template 可以使用
      import ShowInfo from './ShowInfo.vue'
    </script>

4. defineProps() 和 defineEmits()

  • 为了在声明 propsemits 选项时获得完整的类型推断支持
  • 可以使用 definePropsdefineEmits API,它们将自动地在 <script setup> 中可用
  • defineProps 方法返回 props
image-20220804012229410

注意:

  • 传递给 defineProps 的泛型参数本身不能是一个导入的类型

    typescript
    import { Props } from './other-file'
                                    
    // 不支持!
    defineProps<Props>()

    这是因为 Vue 组件是单独编译的,编译器目前不会抓取导入的文件以分析源类型。官方团队计划在未来的版本中解决这个限制

5. defineExpose()

  • 使用 <script setup> 的组件是默认关闭的:

    • 通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定
  • 可以通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的 property

    vue
    <script setup>
      import { ref } from 'vue'
    
      const a = 1
      const b = ref(2)
    
      defineExpose({
        a,
        b
      })
    </script>
  • 当父组件通过模板引用的方式获取到当前组件的实例,获取到的实例会像这样 { a: number, b: number }ref 会和在普通实例中一样被自动解包)

Released under the MIT License.