Vue3 Composition API (setup function)

Introduction to Composition API

I don’t know if you felt that when the amount of code was small, the logical structure was quite clear when you used Vue to develop. However, as the amount of code becomes larger and larger, even if we extract related functions and turn them into functional components one by one, there will be more and more functional components, and putting all the overall content in it will be quite bloated and essential Various forms of inter-component communication are also used. As a result, the code of each functional module will be scattered in various locations, making it difficult to read and maintain the content of the entire project (especially when the team develops, various naming and communication between components, people can't touch mind).

For example, the most well-known Vue structure is as follows:

This has also led to Vue helping us design many ways to maintain the code, such as Vuex, slots, mixins, EventBus, etc., so that we can simplify the code as much as possible and achieve reuse.

With Vue3, this phenomenon has finally changed. The idea of ​​Vue3 is to organize and divide the code according to the logical function, put all the related codes of the same function together, or put them separately in a function, so as to solve the above-mentioned problem of bloated code. Based on this, the Composition API is also known as an API based on function composition.

Why use this method?

1. First of all, the Composition API organizes the code according to logical dependencies, which can improve the readability and maintainability of the code.

2. This method can better reuse logic code. For example, if you want to reuse logic code in Vue2, naming conflicts and unclear relationships may occur.

At the same time, it should be noted that in Vue3, the Composition API is optional, and it is not necessary to use this new method, which means that we can still use the previous structure and usage.


setup function

Next is the use of Vue3. The main thing is to learn to use the setup function, which is the entry point for using the Composition API.

Points to note:
The setup function is a new function in Vue3, which is the entry point for us to use the Composition API when writing components. At the same time, it is also a new life cycle function in Vue3, which will be called before beforeCreate. Because the data and methods of the component have not been initialized yet, this cannot be used in the setup. Therefore, in order to avoid our wrong use, Vue directly modifies this in the setup function to undefined. Moreover, we can only use the setup function synchronously, and cannot use async to make it asynchronous.


Basic usage


Create data in setup (ref)

First, a simple example of the most basic use of the setup function:

<template>
  <div id='app'>
    <p>{
   
   { name }}</p>
  </div>
</template>
<script>
import { ref } from 'vue'
export default {
  setup() {
    const name = ref('王路飞')
    return {
      name
    }
  }
}
</script>

The role of the ref function here is to create and return a responsive reference.

At this point, it should be noted that this name does not return a value of type string, but a responsive object . Then the attributes in the object returned by return can be used in the template.

(You may be curious about this term. Regardless of the responsiveness, it doesn’t look like an object? Don’t worry, look down slowly, and you will find the answer to this question in the attention points of the ref below)


The methods method in setup

If we want to use the original methods usage in setup, we need to do the following:

<template>
  <div id='app'>
    <p>{
   
   { name }}</p>
    <p>{
   
   { age }}</p>
    <button @click='addOne'>加一</button>
  </div>
</template>
<script>
import { ref } from 'vue'
export default {
  setup() {
    const name = ref('王路飞')
    const age = ref(17)
    function addOne() {
      age.value++
    }
    return {
      name,
      age,
      addOne
    }
  }
}
</script>

Note here: As mentioned earlier, this cannot be used in setup, and age will not be a number type data, but a responsive object, so obviously we cannot directly change its value (age++) . Then the modification method is to use value. Similarly, if we want to modify the value of name, we also need to use value.

(And why you can modify it with value, the answer to this question is also in the attention points of the ref below)


computed method in setup

If we want to use the original computed usage in setup, we need to do the following:

<template>
  <div id="app">
    <p>姓名:{
   
   { name }}</p>
    <p>
      年龄:
      <button @click="changeAge(-1)">-</button>
      {
   
   { age }}
      <button @click="changeAge(1)">+</button>
    </p>
    <p>出生年份:{
   
   { year }}</p>
  </div>
</template>
<script>
// 不要忘记import
import { ref, computed } from "vue";
export default {
  setup() {
    const name = ref("王路飞")
    const age = ref(17)
    const year = computed(() => {
      return 2020 - age.value
    })
    function changeAge(val) {
      age.value += val
    }
    return { name, age, changeAge, year }
  },
};
</script>

The usage is still very similar to before. It should be noted that obviously if we want to change the return value of year, we need to adjust the value of age. If we want to modify the value of year directly, we cannot get it through year.value. Here, we can use getters and setters as before.

<template>
  <div id='app'>
    <p>姓名:{
   
   { name }}</p>
    <p>
      年龄:
      <button @click='changeAge(-1)'>-</button>
      {
   
   { age }}
      <button @click='changeAge(1)'>+</button>
    </p>
    <p>
      出生年份:
      <button @click='changeYear(-1)'>-</button>
      {
   
   { year }}
      <button @click='changeYear(1)'>+</button>
    </p>
  </div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
  setup() {
    const name = ref('王路飞')
    const age = ref(17)
    const year = computed({
      get: () => {
        return 2020 - age.value
      },
      set: (val) => {
        age.value = 2020 - val
      }
    })
    function changeAge(val) {
      age.value += val
    }
    function changeYear(val) {
      year.value += val
    }
    return {
      name,
      age,
      changeAge,
      year,
      changeYear
    }
  }
}
</script>

Create data in setup (reactive)

Now through the above content, we can know that if you want to use data, you need to use ref, and you need to use .value to get the value, and these responsive objects need to be returned through return. Although it is indeed simplified a lot compared to before, and all related content can be managed together, but the operation so far seems to be relatively cumbersome, I don’t know what you think.

Let’s not talk about whether you are tired of writing this way, but at least if there are many responsive objects, then a lot of content needs to be returned in return. In addition, from the current point of view, the ref function can only monitor changes of simple types, and cannot monitor changes of complex types, such as objects and arrays.

To sum up, Vue3 does provide another way to define a responsive object: it allows us to define a responsive object, and then put the values ​​we want to use in the object as object properties.

If we want to use it like this, we need to import a function first: reactive. Its role is to create and return a responsive object.

After filtering, our code can be changed to this:

<template>
  <div id='app'>
    <p>姓名:{
   
   { data.name }}</p>
    <p>
      年龄:
      <button @click='changeAge(-1)'>-</button>
      {
   
   { data.age }}
      <button @click='changeAge(1)'>+</button>
    </p>
    <p>
      出生年份:
      <button @click='changeYear(-1)'>-</button>
      {
   
   { data.year }}
      <button @click='changeYear(1)'>+</button>
    </p>
  </div>
</template>
<script>
import { reactive, computed } from 'vue'
export default {
  setup() {
    const data = reactive({
      name: '王路飞',
      age: 17,
      year: computed({
        get: () => {
          return 2020 - data.age
        },
        set: (val) => {
          data.age = 2020 - val
        }
      })
    })
    function changeAge(val) {
      data.age += val
    }
    function changeYear(val) {
      data.year += val
    }
    return {
      data,
      changeAge,
      changeYear
    }
  }
}
</script>

It should be noted that when using ref before, age.value was used, and a responsive object was returned. Now, after using reactive, it looks a bit like an ordinary object. We can use data.age on it as we used objects before. (No need to use .value anymore)

This part must pay attention to observe the difference. That is to say, in this way, except methods (function), now all content can be placed in data, which is convenient for our reference and simplifies the number of returns.

Don't forget to use the object method to call: data.age in the template.

Notes on reactive

The reactive parameter must be an object (json / arr), otherwise the reactive style cannot be implemented.

  setup() {
    let state = reactive(123)
    function myFn() {
      state = 666 //由于在创建响应式数据的时候传递的不是一个对象,所以无法实现响应式
      console.log(state) //输出666,但是页面无变化
    }
    return {
      state,
      myFn
    }
  }
  setup() {
    let state = reactive({
      age: 17,
    })
    function myFn() {
      state.age = 666
      console.log(state) //输出666,页面变化
    }
    return {
      state,
      myFn
    }
  }
  setup() {
    let state = reactive([1, 3, 5])
    function myFn() {
      state[0] = 100
      console.log(state) // 页面变化
    }
    return {
      state,
      myFn
    }
  }

Besides, after console.log(state), we can find Proxy. That also proves that the responsive data in Vue3 is realized through ES6's Proxy.

It should be noted that if other objects are passed to reactive, the interface will not be updated automatically if the object is modified by default. If you want to update, you need to reassign.

  setup() {
    let state = reactive({
      time: new Date()
    })
    function myFn() {
      // 直接修改,页面不会更新:
      state.time.setDate(state.time.getDate() + 1)
      console.log(state.time) // 日期变更,页面无变化
      // 重新赋值
      const newTime = new Date(state.time.getTime())
      newTime.setDate(state.time.getDate() + 1)
      state.time = newTime
      console.log(state.time) // 日期变更,页面更新
    }
    return {
      state,
      myFn
    }
  }

Note on ref

Although it is mentioned that ref is cumbersome to use, reactive also has some problems.

Since it was just proved that reactive must pass an object, this has led to a lot of trouble in enterprise development if we only want a certain variable to be responsive. So how to choose to use reactive and ref is a question worth pondering.

What needs to be noted here is: ref can only monitor changes of simple types, not complex types, such as objects and arrays.

But in fact,  the underlying essence of ref is still reactive, and the system will automatically convert it into reactive according to the value we pass in to ref.  That is: ref(xx) =>  reactive({value:xx}). So this is why, ref is a so-called responsive object, and the reason why value needs to be used.

That being the case, everyone should remember that when we used reactive before, we needed to use it in the form of an object in the template { {data.age}}. Since ref is also reactive, and it does have a value attribute, how  { {age}} can we just use it directly in the template? Here,  { {age.value}} the reason why we don't need to use it is that Vue will automatically add .value for us.

So how does Vue judge ref and reactive? The method of judging whether the responsive object is ref: there is a __v_isRefvalue in it, which will record whether the data is ref or reactive.

Use Vue2's ref in setup

I believe that you will often use ref in the previous development process of Vue2. And now in Vue3 we use ref to create a responsive object, so how to use Vue2's ref in Vue3?

<template>
<div id="app">
<div ref="box">我是div</div>
</div>
</template>
<script> 
export default {
 setup(){
 console.log(this.$refs.box) } } 
</script>

Through the above code, we found that it is impossible for us to use it directly in the setup as before.

The reason why it cannot be used in this way is very simple. It was mentioned at the beginning of the article: the setup function is a new function in Vue3, and it is the entry point for us to use the Composition API when writing components. At the same time, it is also a new life cycle function in Vue3, which will be called before beforeCreate. Because the data and methods of the component have not been initialized yet, this cannot be used in the setup. Therefore, in order to avoid our wrong use, Vue directly modifies this in the setup function to undefined. So obviously this is not going to work.

Now that you know what the problem is, it's easy to fix it. Because this cannot be used at this time, then we will wait until this can be used to solve the problem. Here we can use onMounted to verify:

<template>
  <div id='app'>
    <div ref='box'>我是div</div>
  </div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
  setup() {
    let box = ref(null) // reactive({value: null})
    onMounted(() => {
      console.log('onMounted', box.value)
    }) //到了相应生命周期才会执行,结果:<div>我是div<div> ;
    console.log(box.value) //虽然放在后侧也会先执行,结果:null
    return { box }
  }
};
</script>

To sum up, the ref of Vue2 and Vue3 will point to the same responsive object, and when you want to use it, just make sure to use it after the corresponding life cycle, that is, do not perform additional operations on the ref in the setup in advance.


Recursive monitoring and non-recursive monitoring

After understanding the above content, here is an introduction to the monitoring principle of ref and reactive.

By default, both ref and reactive are recursive listeners . Recursive monitoring means that no matter how many layers of content there are in the responsive object, each layer will perform proxy monitoring. Therefore, the problem with recursive monitoring is that if the amount of data is relatively large, it will consume a lot of performance. We can see the effect through the output:

    let state = reactive({
      a: 'a',
      b: {
        c: 'c',
        d: {
          e: 'e'
        }
      }
    })


In addition, don't forget what was mentioned in the note of reactive before, if other objects are passed to reactive, the interface will not be updated automatically if the object is modified by default. If you want to update, you need to reassign.


And if we want them to change from recursive listening to non-recursive listening, we can use shallowRef and shallowReactive:

    let state = shallowReactive({
      a: 'a',
      b: {
        c: 'c',
        d: {
          e: 'e'
        }
      }
    })

We can see the effect through the output:


At this time, we can see that, except for the first layer, there is no proxy monitoring.


But ref is a bit special. We have said many times before that ref can only monitor changes of simple types, not complex types, such as objects and arrays. So the existence of shallowRef is a bit awkward? First, let's take a look at the usage:

 setup() {
    let state = shallowRef({
      a: 'a',
      b: {
        c: 'c',
        d: {
          e: 'e'
        }
      }
    })
    function myFn() {
      state.value.a = '1'
      state.value.b.c = '3'
      state.value.b.d.e = '5'
      console.log(state)
      console.log(state.value)
      console.log(state.value.b)
      console.log(state.value.b.d)
    }
    return {
      state,
      myFn
    }
  }

You still need to use .value when using it.

This is because of the previously mentioned conversion between ref and reactive: ref(xx) =>  reactive({value:xx}).

Similarly, shallowRef(xx) =>  shallowReactive({value:xx}).

After testing, we can find that although we have modified state.value.a, it will not be updated on the page. Could it be that after using shallowRef, ref can't even monitor the first layer?

In fact, it is still an old problem. ref cannot monitor the changes of complex types of data, so if we want to use it like this, we can only reassign new objects:

setup() {
    let state = shallowRef({
      a: 'a',
      b: {
        c: 'c',
        d: {
          e: 'e'
        }
      }
    })
    function myFn() {
      state.value = {
        a: '1',
        b: {
          c: '3',
          d: {
            e: '5'
          }
        }
      }
      console.log(state)
      console.log(state.value)
      console.log(state.value.b)
      console.log(state.value.b.d)
    }
    return {
      state,
      myFn
    }
  }

First of all, shallowRef does have its effect, and the value of shallow is used for judgment. Secondly, a problem can be found in this way, it can not only modify the value of the first layer a, but also modify the value of all subsequent contents. So in short, shallowRef seems to have no restrictive effect. Because we set up non-recursive monitoring for it, but can also modify data at other levels, it is really unreasonable.

If we really want to use it like this and need to modify data at other levels, can we still only achieve it by reassignment? To solve this problem, Vue3 provides us with a way: triggerRef. It will look at the data that has changed in the previous state, and then actively help us update it. Look at the code:

<script>
import { shallowRef, triggerRef } from 'vue'
export default {
  setup() {
    let state = shallowRef({
      a: 'a',
      b: {
        c: 'c',
        d: {
          e: 'e'
        }
      }
    })
    function myFn() {
      state.value.b.d.e = '5'
      triggerRef(state)
    }
    return {
      state,
      myFn
    }
  }
}
</script>

This is much easier to use. But the most important contradiction is why use non-recursive monitoring to monitor ref, and let ref store objects or arrays instead of simple data types. And even if it is non-recursive monitoring, it is necessary to reassign a new object or use triggerRef to update its data. For me, I have not found the benefit of this usage. Although this can avoid the efficiency loss of recursive monitoring, it also brings about great changes in encoding. Since it is the final product of all the bosses, doing so must have its advantages, and this advantage needs to be experienced in the follow-up development.

It should be noted that although all the previous content appeared in pairs, there is indeed no triggerReactive here. In other words, under non-recursive monitoring, we really have no way to modify the data in any layer of reactive, which indeed satisfies the essential concept of non-recursive monitoring. What's more, under recursive monitoring, it is very simple to modify the data of the responsive object, and this method does not need to exist.

So to sum up, in the author's opinion, in general, we can use ref and reactive. Only when the amount of data to be monitored is relatively large, should we consider using shallowRef and shallowReactive. However, under what circumstances you need to use shallowRef is still a question that needs to be considered.


toRefs ('modular' for Vue3)

In order to help us make the code more concise, Vue3 also provides a toRefs function. The function of this function is to transform the object of a responsive function into an ordinary object. But the content in this ordinary object is a responsive object, so it needs to be deconstructed, that is, it should be used like this:  …toRefs(data). This step can also verify whether Vue3 really does this.

The test proves the following:

<template>
  <div id='app'>
    <p>姓名:{
   
   { name }}</p>
    <p>
      年龄:
      <button @click='changeAge(-1)'>-</button>
      {
   
   { age }}
      <button @click='changeAge(1)'>+</button>
    </p>
    <p>
      出生年份:
      <button @click='changeYear(-1)'>-</button>
      {
   
   { year }}
      <button @click='changeYear(1)'>+</button>
    </p>
  </div>
</template>
<script>
import { reactive, computed, toRefs } from 'vue'
export default {
  setup() {
    const data = reactive({
      name: '王路飞',
      age: 17,
      year: computed({
        get: () => {
          return 2020 - data.age
        },
        set: (val) => {
          data.age = 2020 - val
        }
      })
    })
    function changeAge(val) {
      data.age += val
    }
    function changeYear(val) {
      data.year += val
    }
    console.log('data', data)
    console.log('toRefs', toRefs(data))
    return {
      ...toRefs(data),
      changeAge,
      changeYear
    }
  }
}
</script>

From the results, after using toRefs, it will convert a reactive function object into an ordinary object. The content in this common object will be a responsive object.

After doing this, there is no need to write in the template  data.age . On this basis, we can also take out the data of the data part and write it in a function, which makes it easier for us to distinguish each functional module:

<script>
import { reactive, computed, toRefs } from 'vue'
export default {
  setup() {
    const { data, changeAge, changeYear } = test()
    return {
      ...toRefs(data),
      changeAge,
      changeYear
    }
  }
}
function test() {
  const data = reactive({
    name: '王路飞',
    age: 17,
    year: computed({
      get: () => {
        return 2020 - data.age
      },
      set: (val) => {
        data.age = 2020 - val
      }
    })
  })
  function changeAge(val) {
    data.age += val
  }
  function changeYear(val) {
    data.year += val
  }
  return {
    data,
    changeAge,
    changeYear
  }
}
</script>

In this way, we can also extract the test function from the vue component and put it in a js file to centrally manage related functions.


In this way, the so-called modularization of Vue3 is achieved, which simplifies the code, and separates each functional module for easy management.


toRef

First look at the conclusion:

setup(){

let obj = {
name: 'zs'};
let state = ref(obj.name);
function myFn(){

state.value = 'ls';
console.log(state.value); // 输出ls,页面更新
console.log(obj.name); // 输出zs
}
return {
 state, myFn };
}

The conclusion is: if we use ref to change the properties in an object into responsive data, then we will not affect the original data when we modify the responsive data.

But when we use toRef to change the properties in an object into responsive data, then we modify the responsive data will affect the original data. But at this time, the update of the page will not be triggered.

setup(){

let obj = {
name: 'zs'};
let state = toRef(obj, 'name');
function myFn(){

state.value = 'ls';
console.log(state.value); // 输出ls,页面不更新
console.log(obj.name); // 输出ls,页面不更新
}
return {
 state, myFn };
}

So the difference between ref and toRef is:
ref is a copy of the original data, modifying the responsive data will not affect the original data, and the interface will be automatically updated if the data changes.
toRef is a reference to the original data. Modifying the responsive data will affect the original data, but the interface will not be automatically updated if the data changes.

The application scenario of toRef is: the responsive data is associated with the original data, but we don't want to update the interface after updating the data, then we can use toRef.


Based on the above content, if there are many attributes in obj and want to use toRef, we can use toRefs at this time. Because toRefs will convert a reactive function object into a normal object. But the contents of this ordinary object are all responsive objects, so it can be used in state like this:

<template>
  <div id='app'>
    <p>{
   
   { name }}</p>
    <button @click='myFn'>按钮</button>
  </div>
</template>
<script>
import { toRefs } from 'vue'
export default {
  setup() {
    let obj = {
      name: 'zs',
      age: 17
    }
    let state = toRefs(obj)
    function myFn() {
      state.name.value = 'ls'
      state.age.value = '16'
    }
    return {
      ...toRefs(state),
      myFn
    }
  }
}
</script>

customRef

Previously we used ref to create a reactive object. If we want to explicitly control dependency tracking and trigger responses, we can use customRef to customize a ref. The function of customRef is to return a ref object. Relevant code:

<template>
  <div id='app'>
    <p>{
   
   { age }}</p>
    <button @click='myFn'>按钮</button>
  </div>
</template>
<script>
import { customRef } from 'vue'
function myRef(value) {
  return customRef((track, trigger) => {
    return {
      get() {
        track() //告诉Vue这个数据需要追踪变化 return value;
      },
      set(newValue) {
        value = newValue
        trigger() //告诉Vue触发界面更新
      }
    }
  })
}
export default {
  setup() {
    let age = myRef(18)
    function myFn() {
      age.value += 1
    }
    return {
      age,
      myFn
    }
  }
}
</script>

So when should you consider using customRef?

First of all, if we want to get json data, we need to use it like this: (I built a data.json in public)

  setup() {
    let state = ref([])
    fetch("../public/data.json")
      .then((res) => {
        return res.json()
      })
      .then((data) => {
        console.log(data)
        state.value = data
      })
      .catch((err) => {
        console.log(err)
      });
    return {
      state
    }
  }

Let me add here, when I used @vue/cli before, this way to call json data will report an error. But when I checked according to the error message, I found that there was no problem with what I wrote, so I pondered for a while here, and I didn't find a solution after consulting the information. Later, I switched to Vite to create a project and found that the problem was solved directly. I don’t know the reason for now, but I mentioned before that Vite is a tool developed by the author of Vue to replace webpack. Maybe Vite helps us process json data. So if you encounter json data error when using @vue/cli, but you can't find the reason, you can consider using Vite to create a project to try)

Closer to home, as mentioned before, setup cannot be used asynchronously, that is, async and await cannot be used to obtain json data here. Then after getting more data, it is obviously inconvenient to write so many callback functions in it. At this point, we can consider using customRef:

<script>
import { customRef } from 'vue'
function myRef(value) {
  return customRef((track, trigger) => {
    fetch(value)
      .then((res) => {
        return res.json()
      })
      .then((data) => {
        console.log(data)
        value = data
        trigger()
      })
      .catch((err) => {
        console.log(err)
      })
    return {
      get() {
        track() //告诉Vue这个数据需要追踪变化 return value;
      },
      set(newValue) {
        value = newValue
        trigger() //告诉Vue触发界面更新
      }
    }
  })
}
export default {
  setup() {
    let state = myRef('../public/data.json')
    return {
      state
    }
  }
}
</script>

Parameter props and context

The setup function, it can actually set some parameters, one is called props, and the other is called context.

The props parameter is used to get the props defined in the component. As follows:


export default {
  props: {
    title: String,
  },
  setup(props) {
    const data = reactive({
      name: "王路飞",
      age: 17,
      year: computed({
        get: () => {
          return 2020 - data.age;
        },
        set: (val) => {
          data.age = 2020 - val;
        }
      })
    })
    function changeAge(val) {
      data.age += val
      console.log(props.title)
    }
    function changeYear(val) {
      data.year += val
    }
    return {
      ...toRefs(data),
      changeAge,
      changeYear
    }
  }
}

It should be noted that props.title, because we know that the values ​​passed through props are only readable and cannot be modified.

In addition, all props we define are responsive, we can monitor the value of props, and we will respond to changes. For example, if we want to monitor the value of title, we need to use watch, the following code:

<script>
import { reactive, computed, toRefs, watch } from 'vue'
export default {
  props: {
    title: String,
  },
  setup(props) {
    const data = reactive({
      name: '王路飞',
      age: 17,
      year: computed({
        get: () => {
          return 2020 - data.age
        },
        set: (val) => {
          data.age = 2020 - val
        },
      })
    })
    function changeAge(val) {
      data.age += val
      console.log(props.title)
    }
    function changeYear(val) {
      data.year += val
    }
    watch(
      () => props.title,
      (newTitle, oldTitle) => {
        console.log(newTitle, oldTitle)
      }
    );
    return {
      ...toRefs(data),
      changeAge,
      changeYear
    }
  }
}
</script>

For the second parameter context, we said before that this cannot be used in setup, but if we need to use this for some functions, we can use context to get attributes, get slots, or send events. for example:

 setup(props, context) {
    const data = reactive({
      name: "王路飞",
      age: 17,
      year: computed({
        get: () => {
          return 2020 - data.age;
        },
        set: (val) => {
          data.age = 2020 - val;
        }
      })
    })
    function changeAge(val) {
      data.age += val
      console.log(props.title)
    }
    function changeYear(val) {
      data.year += val
    }
    watch(
      () => props.title,
      (newTitle, oldTitle) => {
        console.log(newTitle, oldTitle);
        context.emit("title-change");
      }
    )
    return {
      ...toRefs(data),
      changeAge,
      changeYear
    }
  }

The use of context is the same as before, such as using emit.


toRaw

We now know that in the setup function, only when the responsive data changes can the page be updated, that is, if a common object changes in the setup function, it cannot cause the page to be updated anyway. Now let's do these operations:

 setup() {
    let obj = {
      name: 'zs',
      age: 17
    }
    let state = reactive(obj)
    console.log(obj === state)// false
    function myFn() {
      // 对obj操作无法让页面发生更新,但是会修改obj数据
      obj.name = 'ls'
      console.log(obj)
      // 对state操作才能让页面发生更新,同时会修改obj数据
      // state.name = 'ls';
      // console.log(state);
    }
    return {
      state,
      myFn
    }
  }

So what is the relationship between obj and state here?

They are reference relationships. The essence of state is a Proxy object, and obj is referenced in this Proxy object.


Speaking of the role of toRaw, it will get the original data (reference) from reactive or ref. That is to say:

let obj = {
name: 'zs', age: 17};
let state = reactive(obj);
let obj2 = toRaw(state);
// let state = ref(obj);
// let obj2 = toRaw(state.value);
console.log(obj === state); // false
console.log(obj === obj2); // true

So what's the use?

Because every modification of ref and reactive will be tracked and the UI interface will be updated, so if there are some operations that do not need to be tracked and the interface does not need to be updated, then the role of toRaw will be reflected at this time. Because it gets the original data, modifications to the original data will not be tracked, which improves performance.

In addition to this there is a markRaw. If some data never wants to be tracked, you can use markRaw.

let obj = {
name: 'zs', age: 17};
obj = markRaw(obj);
let state = reactive(obj);

readonly

readonly Like its name, the data can only be read-only, and it is recursively read-only. That is, all levels in it are read-only. For example the following code:

<template>
  <div id='app'>
    <p>{
   
   { state.name }}</p>
    <p>{
   
   { state.attr.age }}</p>
    <p>{
   
   { state.attr.height }}</p>
    <button @click='myFn'>按钮</button>
  </div>
</template>
<script>
import { readonly, isReadonly, shallowReadonly } from 'vue'
export default {
  setup() {
    let state = readonly({
      name: 'ls',
      attr: {
        age: 18,
        height: 1.88
      }
    })
    function myFn() {
      state.name = 'zs'
      state.attr.age = 16
      state.attr.height = 1.66
    }
    return {
      state,
      myFn
    }
  }
}
</script>

When we try to modify the state data, a prompt will appear:


shallowReadonly makes only the first layer read-only:

  setup() {
    let state = shallowReadonly({
      name: "ls",
      attr: {
        age: 18,
        height: 1.88
      }
    })
    function myFn() {
      state.name = "zs"
      state.attr.age = 16
      state.attr.height = 1.66
      console.log(state)
    }
    return {
      state,
      myFn
    }
  }

Although setting shallowReadonly only the first layer is read-only, modifying other content will not allow the view to update.


isReadonly is very simple, used to determine whether it is read-only data:

function myFn(){

console.log(isReadonly(state));
}

For read-only type data, you can immediately think of const. The difference between const and readonly:

We know that if const declares an object, the internal data of the object can still be modified after reassignment, then what const does is actually assignment protection, that is, the variable cannot be reassigned. But readonly is an attribute protection, that is, the attribute cannot be reassigned.


Supplementary note: After understanding the Composition API, it needs to be said that its essence is actually the Option API of Vue2.x. When the setup function returns, it will write the responsive object into data, and write the methods method into methods, and the other functions are the same.

Guess you like

Origin blog.csdn.net/qq_47443027/article/details/126348286