Vue3 – Composition API(二)

1. Use of computed function

1.1、computed

        We explained the computed attribute in the previous section: when some of our attributes depend on other states, we can use the computed attribute to handle it

  • In the previous Options API, we used the computed option to complete;
  • In the Composition API, we can use the computed method in the setup function to write a computed property;

How to use computed?

  • Method 1 : Receive a getter function and return an unchanged ref object for the value returned by the getter function;
  • Method 2 : Receive an object with get and set , and return a mutable (readable and writable) ref object;

1.2. Examples 

app.vue

<template>
  <h2>{
   
   { fullname }}</h2>
  <button @click="setFullname">设置fullname</button>
  <h2>{
   
   { scoreLevel }}</h2>
</template>

<script>
  import {reactive, computed, ref} from 'vue'

  export default {
    setup() {
      // 1.定义数据
      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
        }
      })

      console.log(fullname)  // 是一个ref对象

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


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

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

<style scoped>
</style>

2. Use ref in setup

How to use ref to get elements or components in setup?

  • In fact, it is very simple. We only need to define a ref object and bind it to the ref attribute of an element or component;

Example: 

ShowInfo.vue

<template>
  <div>ShowInfo</div>
</template>

<script>
  export default {
    // methods: {
    //   showInfoFoo() {
    //     console.log("showInfo foo function")
    //   }
    // }
    setup() {
      function showInfoFoo() {
        console.log("showInfo foo function")
      }

      return {
        showInfoFoo
      }
    }
  }
</script>

<style scoped>
</style>

app.vue

<template>
  <!-- 1.获取元素 -->
  <h2 ref="titleRef">我是标题</h2>
  <button ref="btnRef">按钮</button>

  <!-- 2.获取组件实例 -->
  <show-info ref="showInfoRef"></show-info>

  <button @click="getElements">获取元素</button>
</template>

<script>
  import { ref, onMounted } from 'vue'
  import ShowInfo from './ShowInfo.vue'

  export default {
    components: {
      ShowInfo
    },
    setup() {
      const titleRef = ref()
      const btnRef = ref()
      const showInfoRef = ref()

      // mounted的生命周期函数
      onMounted(() => {
        console.log(titleRef.value)
        console.log(btnRef.value)
        console.log(showInfoRef.value)

        showInfoRef.value.showInfoFoo()
      })

      function getElements() {
        console.log(titleRef.value)
      }

      return {
        titleRef,
        btnRef,
        showInfoRef,
        getElements
      }
    }
  }
</script>

<style scoped>
</style>

3. Component lifecycle functions

3.1, life cycle hook

  • We said earlier that setup can be used to replace data, methods, computed, etc. options, as well as life cycle hooks .
  • So how to use lifecycle functions in setup?
    • Lifecycle hooks can be registered using directly imported onX functions;

Optional API

Hook inside setup

beforecreate

none

created

none

beforeMount

onBeforeMount

mounted

onMounted

beforeupdate

onBeforeupdate

updated

onUpdated

beforeUnmount

onBeforeUnmount

unmounted

onUnmounted

activated

onActivated

deactivated

onDeactivated

Tip: Since setup operates around the beforeCreate and created lifecycle hooks, there is no need to define them explicitly. In other words, any code abbreviated in these hooks should be written directly in the setup function.

3.2. Examples 

app.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>

<style scoped>
</style>

4, using Provide/Inject

4.1, Provide function

  • In fact, we have also learned Provide and Inject before, and the Composition API can also replace the previous Provide and Inject options.
  • We can provide data through provide:
    • Each Property can be defined by the provide method;
  • Provide can pass in two parameters:
    • name : the provided property name ;
    • value : the provided attribute value ;

4.2. Inject function 

  • In descendant components , the required properties and corresponding values ​​can be injected through inject:
  • The required content can be injected through inject;
  • inject can pass in two parameters :
    • The name of the property to inject;
    • Defaults;

4.3. Responsive data 

In order to increase the reactivity between provide value and inject value, we can use ref and reactive when provide value.

4.4. Examples 

ShowInfo.vue

<template>
  <div>ShowInfo: {
   
   { name }}-{
   
   { age }}-{
   
   { height }}</div>
</template>

<script>
  import {inject} from 'vue'

  export default {
    // inject的options api注入, 那么依然需要手动来解包
    // inject: ["name", "age"],
    setup() {
      const name = inject("name")
      const age = inject("age")
      const height = inject("height", 1.88)

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

<style scoped>
</style>

app.vue

<template>
  <div>AppContent: {
   
   { name }}</div>
  <button @click="name = 'kobe'">app btn</button>
  <show-info></show-info>
</template>

<script>
  import { provide, ref } from 'vue'
  import ShowInfo from './ShowInfo.vue'

  export default {
    components: {
      ShowInfo
    },
    setup() {
      const name = ref("why")

      provide("name", name)
      provide("age", 18)

      return {
        name
      }
    }
  }
</script>

<style scoped>
</style>

5、watch/watchEffect

5.1, listening to data changes

  • In the previous Options API, we can use the watch option to listen to data changes in data or props , and perform certain operations when the data changes.
  • In Composition API, we can use watchEffect and watch to listen to responsive data;
    • watchEffect : a dependency for automatically collecting responsive data;
    • watch : You need to manually specify the data source to listen to;

5.2, the use of Watch

The API of the watch is completely equivalent to the Property of the component watch option:

  • Watch needs to listen to a specific data source and execute its callback function;
  • It is lazy by default , the callback will only be executed when the source being listened to changes;

5.3, listening to multiple data sources 

Listeners can also listen to multiple sources simultaneously using an array :

5.4, ​​watch options 

        If we want to listen to a deep listening, we still need to set deep to true: we can also pass in immediate to execute immediately;

5.5、watchEffect 

  • When listening to some responsive data changes, we want to perform some operations, and watchEffect can be used at this time .
  • Let's look at a case:
    • First, the function passed in by watchEffect will be executed once immediately , and dependencies will be collected during execution;
    • Secondly, only when the collected dependencies change , the function passed in by watchEffect will be executed again;

5.6, stop listening of watchEffect 

  • If in some cases, we want to stop listening, at this time we can get the return value function of watchEffect, just call this function.
  • For example, in the above case, when our age reaches 20, we stop listening:

5.7. Examples 

App-watch.vue

<template>
  <div>AppContent</div>
  <button @click="message = '你好啊,李银河!'">修改message</button>
  <button @click="info.friend.name = 'james'">修改info</button>
</template>

<script>
  import {reactive, ref, watch} from 'vue'

  export default {
    setup() {
      // 1.定义数据
      const message = ref("Hello World")
      const info = reactive({
        name: "why",
        age: 18,
        friend: {
          name: "kobe"
        }
      })

      // 2.侦听数据的变化
      watch(message, (newValue, oldValue) => {
        console.log(newValue, oldValue)
      })

      watch(info, (newValue, oldValue) => {
        console.log(newValue, oldValue)
        console.log(newValue === oldValue) // true,两者为同一个对象(浅拷贝)
      }, {
        // 这个属性作用就是,加载后默认就会执行一次这个console.log回调方法
        immediate: true
      })


      // 3.监听reactive数据变化后, 获取普通对象
      watch(() => ({...info}), (newValue, oldValue) => {
        console.log(newValue, oldValue)
      }, {
        immediate: true,
        deep: true
      })

      return {
        message,
        info
      }
    }
  }
</script>

<style scoped>
</style>

app.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")

      // watch(counter, (newValue, oldValue) => {})

      // 1.watchEffect传入的函数默认会直接被执行
      // 2.在执行的过程中, 会自动的收集依赖(依赖哪些响应式的数据)
      const stopWatch = watchEffect(() => {
        console.log("-------", counter.value, name.value)

        // 判断counter.value > 10
        if (counter.value >= 10) {
          // 停止监听
          stopWatch()
        }
      })

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

<style scoped>
</style>

6. Script setup syntactic sugar

6.1, script setup syntax

  • <script setup> is a compile-time syntactic sugar for using the combined API in a single-file component (SFC), and is recommended when using both the SFC and the combined API.
    • Less boilerplate content, more concise code;
    • Ability to declare props and throw events using pure Typescript;
    • better runtime performance;
    • Better IDE type inference performance;
  • To use this syntax, add the setup attribute to the <script> code block :

  • The code inside will be compiled into the content of the component setup() function:
    • This means that unlike normal <script> which is only executed once when the component is first introduced;
    • The code in <script setup> will be executed every time a component instance is created. 

6.2. Top-level bindings will be exposed to templates

        When using <script setup>, any bindings at the top level of the <script setup> declaration (including variables, function declarations, and imports) can be used directly in the template:

Responsive data needs to be created through ref and reactive. 

6.3. The imported components are used directly

Values ​​in the <script setup> scope can also be used directly as tag names for custom components:

6.4、defineProps() 和 defineEmits() 

        To get full type inference support when declaring props and emits options, we can use the defineProps and defineEmits APIs, which will automatically be available in <script setup>:

6.5、defineExpose() 

  • Components using <script setup> are disabled by default :
    • The public instance of the component obtained through the template ref or $parent chain will not expose any bindings declared in <script setup>;
  • Explicitly specify the property to be exposed in the <script setup> component through the defineExpose compiler macro

6.6. Examples 

ShowInfo.vue

<template>
  <div>ShowInfo: {
   
   { name }}-{
   
   { age }}</div>
  <button @click="showInfoBtnClick">showInfoButton</button>
</template>

<script setup>

  // 定义props
  const props = defineProps({
    name: {
      type: String,
      default: "默认值"
    },
    age: {
      type: Number,
      default: 0
    }
  })

  // 绑定函数, 并且发出事件
  const emits = defineEmits(["infoBtnClick"])

  function showInfoBtnClick() {
    emits("infoBtnClick", "showInfo内部发生了点击")
  }

  // 定义foo的函数
  function foo() {
    console.log("foo function")
  }

  // 暴露实例
  defineExpose({
    foo
  })

</script>

<style scoped>
</style>

app.vue

<template>
  <div>AppContent: {
   
   { message }}</div>
  <button @click="changeMessage">修改message</button>
  <show-info name="why"
             :age="18"
             @info-btn-click="infoBtnClick"
             ref="showInfoRef">
  </show-info>
  <show-info></show-info>
  <show-info></show-info>
</template>

<script setup>
  // 1.所有编写在顶层中的代码, 都是默认暴露给template可以使用
  import {ref, onMounted} from 'vue'

  // 组件不在需要注册,直接导入使用即可
  import ShowInfo from './ShowInfo.vue'

  // 2.定义响应式数据
  const message = ref("Hello World")
  console.log(message.value)

  // 3.定义绑定的函数
  function changeMessage() {
    message.value = "你好啊, 李银河!"
  }

  function infoBtnClick(payload) {
    console.log("监听到showInfo内部的点击:", payload)
  }

  // 4.获取组件实例
  const showInfoRef = ref()
  onMounted(() => {
    showInfoRef.value.foo()
  })

</script>

<style scoped>
</style>

7. Custom Hook practice

useCounter.js

import { ref, onMounted } from 'vue'

export default function useCounter() {
  const counter = ref(0)
  function increment() {
    counter.value++
  }
  function decrement() {
    counter.value--
  }
  onMounted(() => {
    setTimeout(() => {
      counter.value = 989
    }, 1000);
  })

  return {
    counter,
    increment,
    decrement
  }
}

useScrollPosition.js

import { reactive } from 'vue'

export default function useScrollPosition() {
  // 1.使用reative记录位置
  const scrollPosition = reactive({
    x: 0,
    y: 0
  })

  // 2.监听滚动
  document.addEventListener("scroll", () => {
    scrollPosition.x = window.scrollX
    scrollPosition.y = window.scrollY
  })


  return {
    scrollPosition
  }
}

useTitle.js

import { ref, watch } from "vue";

export default function useTitle(titleValue) {
  // document.title = title

  // 定义ref的引入数据
  const title = ref(titleValue)

  // 监听title的改变
  watch(title, (newValue) => {
    document.title = newValue
  }, {
    immediate: true
  })

  // 返回ref值
  return {
    title
  }
}

About.vue

<template>
  <h2>About计数: {
   
   { counter }}</h2>
  <button @click="increment">+1</button>
  <button @clcik="decrement">-1</button>
</template>

<script>
  import { onActivated } from 'vue'
  import useCounter from '../hooks/useCounter'
  import useTitle from '../hooks/useTitle'

  export default {
    setup() {

      // 切换标题
      useTitle("关于")

      return {
        ...useCounter()
      }
    }
  }
</script>

<style scoped>
</style>

Home.vue

<template>
  <h2>Home计数: {
   
   { counter }}</h2>
  <button @click="increment">+1</button>
  <button @click="decrement">-1</button>

  <button @click="popularClick">首页-流行</button>
  <button @click="hotClick">首页-热门</button>
  <button @click="songClick">首页-歌单</button>

  <div class="scroll">
    <h2>x: {
   
   { scrollPosition.x }}</h2>
    <h2>y: {
   
   { scrollPosition.y }}</h2>
  </div>
</template>

<script>
  import { onMounted, ref } from 'vue'
  import useCounter from '../hooks/useCounter'
  import useTitle from '../hooks/useTitle'
  import useScrollPosition from '../hooks/useScrollPosition'

  export default {
    setup() {
      // 1.counter逻辑
      const { counter, increment, decrement } = useCounter()

      // 2.修改标题
      const { title } = useTitle("首页")

      // 3.监听按钮的点击
      function popularClick() {
        title.value = "首页-流行"
      }
      function hotClick() {
        title.value = "首页-热门"
      }
      function songClick() {
        title.value = "首页-歌单"
      }

      // 4.获取滚动位置
      const { scrollPosition } = useScrollPosition()
      console.log(scrollPosition)

      return {
        counter,
        increment,
        decrement,
        popularClick,
        hotClick,
        songClick,
        scrollPosition
      }
    }
  }
</script>

<style scoped>
</style>

app.vue

<template>
  <div>AppContent</div>
  <button @click="changeTitle">修改title</button>

  <!-- 1.计数器 -->
  <!-- <hr>
  <home></home>
  <hr>
  <about></about> -->

  <!-- 2.home和about页面的切换 -->
  <button @click="currentPage = 'home'">home</button>
  <button @click="currentPage = 'about'">about</button>

  <component :is="currentPage"></component>

  <div class="content"></div>

  <br><br><br><br><br><br>
  <br><br><br><br><br><br>
  <br><br><br><br><br><br>
  <br><br><br><br><br><br>
  <br><br><br><br><br><br>
  <br><br><br><br><br><br>
  <br><br><br><br><br><br>
  <br><br><br><br><br><br>
  <br><br><br><br><br><br>
  <br><br><br><br><br><br>
  <br><br><br><br><br><br>
</template>

<script>
  import { ref } from 'vue'
  import Home from './views/Home.vue'
  import About from './views/About.vue'

  import useTitle from './hooks/useTitle'

  export default {
    components: {
      Home,
      About
    },
    setup() {
      const currentPage = ref("home")

      function changeTitle() {
        useTitle("app title")
      }

      return {
        changeTitle,
        currentPage
      }
    }
  }
</script>

<style scoped>
  .content {
    width: 3000px;
    height: 100px;
    background-color: orange;
  }
</style>

Note: This case just shows how to use other functions in setup. 

Guess you like

Origin blog.csdn.net/weixin_52851967/article/details/128749380