vue3学习 --- Composition API (3)

343 阅读2分钟

生命周期钩子

setup 可以用来替代 data 、 methods 、 computed 、watch 等等这些选项,也可以替代 生命周 期钩子

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

IUy4Dp.png

可以看到的是 在composition api中,是没有created和beforeCreate这两个钩子函数的

因为setup函数会在组件被创建完成的时候就被调用

所以原本在created和beforeCreate中需要执行的逻辑,直接写在setup函数内部即可

<template>
  <div>
    <button @click="changeCount">{{ count }}</button>
  </div>
</template>

<script>
import {
  ref,
  onMounted,
  onUpdated,
  onBeforeUnmount
} from 'vue'

export default {
  name: 'Home',

  setup() {
    let count = ref(0)

    console.log('setup 函数会最先被调用')

    // 在Vue3中生命周期函数可以被多次调用
    onMounted(() => console.log('onMounted1'))
    onMounted(() => console.log('onMounted2'))
    
    onUpdated(() => console.log('onUpdated'))
    onBeforeUnmount(() => console.log('onBeforeUnmount'))

    const changeCount = () => count.value++

    return {
      count,
      changeCount
    }
  }
}
</script>

provide和inject

事实上我们之前还使用过Provide和Inject,Composition API也可以替代之前的 Provide 和 Inject 的选项

我们可以通过 provide 方法来定义每个 Property(提供数据),

在 后代组件 中可以通过 inject 来注入需要的属性和对应的值(注入需要的属性和对应的值)

provide可以传入两个参数:

  • name:提供的属性名称
  • value:提供的属性值

inject可以传入两个参数:

  • 要 inject 的 property 的 name
  • 默认值

父组件 --- 数据提供者

<template>
  <div>
    <h2>Home组件中的name {{ name }}</h2>
    <h2>Home组件中的age {{ age }}</h2>

    <button @click="changeAgeInHome">修改Home组件中的age</button>

    <Home />
  </div>
</template>

<script>
import { ref, readonly, provide } from 'vue'
import Home from './components/Home.vue'

export default {
  name: 'App',

  components: {
    Home
  },

  setup() {
    const name = ref('Klaus')
    const age = ref(23)

    // 提供数据
    // 参数1 --- key
    // 参数2 --- value

    // provide给后代组件的数据最好修改为readonly
    // 这样后代组件就无法修改父组件provide过来的数据
    // 如果要修改, 后代组件只能emit一个事件,让父组件(数据提供者)这里修改
    // 这么做的目的是为了确保单向数据流,即数据都是从父组件传递给子组件的
    // 这样后期调试的时候,数据来源会变得统一,便于调试和维护
    provide('name', readonly(name))
    provide('age', readonly(age))

    const changeAgeInHome = () => age.value++

    return {
      name,
      age,
      changeAgeInHome
    }
  }
}
</script>

子组件 --- 数据使用者

<template>
  <div>
    <h2>{{ name }}</h2>
    <h2>{{ age }}</h2>
  </div>
</template>

<script>
import { readonly, ref, inject } from 'vue'

export default {
  name: 'Home',

  setup() {
    // inject用于接收provide提供的值 --- 函数的返回值就是我们所需要使用的那个值
    // inject函数有两个参数
    // 参数1: 接收的数据名称
    // 参数2: 默认值  --- 可选参数
    const name = inject('name')
    
    // 默认值设置为readonly也是为了避免子组件随意修改父组件传入的数据
    const age = inject('age', readonly(ref(18))

    return {
      name,
      age
    }
  }
}
</script>

阶段练习

计数器

使用

<template>
  <div>
    <p>count: {{ count }}</p>
    <p>doubleCount: {{ doubleCount }}</p>

    <button @click="increment">increment</button>
    <button @click="decrement">decrement</button>
  </div>
</template>

<script>
// 传入的hook函数的名称可以任意取,但是推荐参考react中的hook函数,取名的时候使用use开头的小驼峰
import useCount from './hooks/useCount'

export default {
  name: 'Home',

  setup() {
    const {
      count,
      doubleCount,
      increment,
      decrement
    } = useCount() // 引入的hook是函数,所以要先调用

    return {
      count,
      doubleCount,
      increment,
      decrement
    }
  }
}
</script>

hook -- /src/hooks/useCount.js

import { ref, computed } from 'vue'

export default function() {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)

  const increment = () => count.value++
  const decrement = () => count.value--

  return {
    count,
    doubleCount,
    increment,
    decrement
  }
}

另一种使用方式

<template>
  <div>
    <p>count: {{ count }}</p>
    <p>doubleCount: {{ doubleCount }}</p>

    <button @click="increment">increment</button>
    <button @click="decrement">decrement</button>
  </div>
</template>

<script>
// 可以直接对hook函数的返回值直接解构挂载到模板中进行使用
import useCount from './hooks/useCount'

export default {
  name: 'Home',
	
  setup() {
    return {
      // 不推荐 --- 解构出的数据需要使用在模板上
      // 如果引入的hook函数过多,那么就无法方便的获知,
      // 那个数据来源于哪一个hook函数,不利于调试
      ...useCount()
    }
  }
}
</script>

监听滚动位置

使用

<template>
  <div class="screen">
    <div class="hint">
      <p>scrollX: {{ scrollX }}</p>
      <p>scrollY: {{ scrollY }}</p>
    </div>
  </div>
</template>

<script>
import useScrollPostion from './hooks/useScrollPostion'

export default {
  name: 'App',

  setup() {
    const { scrollX, scrollY } = useScrollPostion()

    return {
      scrollX,
      scrollY
    }
  }
}
</script>

<style scoped>
.screen {
  width: 300vw;
  height: 300vh;
}

.hint {
  position: fixed;
  right: 30px;
  bottom: 30px;
}
</style>

hook

import { ref } from 'vue'

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

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

  return {
    scrollX,
    scrollY
  }
}

监听鼠标滚动

import { ref } from 'vue'

export default function() {
  const mouseX = ref(0)
  const mouseY = ref(0)

  window.addEventListener('mousemove', e => {
    // 可以使用event中的pageX和pageY来获取到鼠标移动的位置
    mouseX.value = e.pageX
    mouseY.value = e.pageY
  })

  return {
    mouseX,
    mouseY
  }
}

localStorage

hook

import { ref, watch } from 'vue'

export default function(key, value) {
  const data = ref(value)

  if (value) {
    localStorage.setItem(key, JSON.stringify(value))
  } else {
    data.value = JSON.parse(localStorage.getItem(key))
  }

  // data是一个ref对象,所以监听的时候,要监听data.value
  // v 是 newValue的值,也就是data的value属性值
  
  // tips: 如果ref的值是一个对象,那么ref对象的value值所对应的那个对象默认是一个reactive对象
  watch(data.value, v =>  localStorage.setItem(key, JSON.stringify(v)))

  return data
}

使用

<template>
  <div>
    <h2>{{ storage.name }}</h2>
    <button @click="changeStorage">change</button>
  </div>
</template>

<script>
import useLocalStorage from './hooks/useLocalStorage'

export default {
  name: 'App',

  setup() {
    const storage = useLocalStorage('info', { name: 'Klaus' })

    console.log(useLocalStorage('info').value?.name)

    const changeStorage = () => {
      storage.value.name = 'Alex'
    }

    return {
      storage,
      changeStorage
    }
  }
}
</script>

setup顶层编写

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖

最后还是会被编译为使用setup函数的形式编写的vue组件

父组件

<template>
  <div>
    <Cpn :message="msg" @changeMsg="changeMsg" />
  </div>
</template>

<script setup>
 import { ref } from 'vue'
 
 // script setup中无须定义组件的name属性,其值会被命名为SFC的文件名称

 // 在这里 组件引入以后直接使用即可,不需要在进行任何的注册操作
 import Cpn from './components/Cpn.vue'

 const msg = ref('Hello World')

 const changeMsg = v => msg.value = v

 // 所有需要在模板中使用的方法和数据不需要在return出来
</script>

子组件

<template>
  <div>
    <h2>{{ message }}</h2>
    <button @click="change">change</button>
  </div>
</template>

<script setup>
 import { defineProps, defineEmits } from 'vue'

  // 通过defineProps来接收父组件传入的props
  defineProps({
    message: {
      type: String,
      required: true
    }
  })

  // 通过defineEmits方法来定义需要触发的事件
  // 该方法会返回一个函数, 所有在注册过的方法都可以使用这个方法触发一些事件
  const emit = defineEmits(['changeMsg'])

  const change = function() {
    emit('changeMsg', 'Hello Vue')
  }
</script>