头图

Composition API also called combined API, which is a new feature of Vue 3.x.

By creating Vue components, we can extract the repeatable parts of the interface and its functions into reusable code segments. This alone can make our application go further in terms of maintainability and flexibility. However, our experience has proven that this alone may not be enough, especially when your application becomes very large—think hundreds of components. When dealing with such a large application, sharing and reusing code becomes particularly important

's terms:

Composition API , the code of the vue-related business needs to be configured to the specific area of the option. Small and medium-sized projects are no problem, but in large-scale projects, the later maintenance will be more complicated, and the code reusability is not high. The composition-api in Vue3.x was born to solve this problem

compositon api provides the following functions:

  • setup
  • ref
  • reactive
  • watchEffect
  • watch
  • computed
  • toRefs
  • The life cycle of hooks

One, setup component options

The new setup component options in before creating a component execution, once props is resolved, and acts as a synthetic API entry point

prompt:

Since the component instance has not been created when setup is executed, there is no this setup option. This means that, with the exception of props , you will not be able to access any properties declared in the component-local state, calculated properties or methods.

When using the setup function, it will accept two parameters:

  1. props
  2. context

Let's look deeper into how to use each parameter

1. Props

The first parameter in the setup props . As a standard component as desired, setup function of props is responsive when a new incoming prop , it will be updated
// MyBook.vue

export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}

Note:

However, because props is responsive, you cannot use ES6 deconstruct because it will eliminate the responsiveness of prop

If desired deconstruction prop, by using setup function in toRefs to safely complete the operation.

// MyBook.vue

import { toRefs } from 'vue'

setup(props) {
    const { title } = toRefs(props)

    console.log(title.value)
}

2. Context

Passed to setup function of the second parameter is context . context is an ordinary JavaScript object that exposes the properties of three components
// MyBook.vue

export default {
  setup(props, context) {
    // Attribute (非响应式对象)
    console.log(context.attrs)

    // 插槽 (非响应式对象)
    console.log(context.slots)

    // 触发事件 (方法)
    console.log(context.emit)
  }
}
context is a normal JavaScript object, that is, it is not reactive, which means you can safely use ES6 deconstruction context
// MyBook.vue
export default {
  setup(props, { attrs, slots, emit }) {
    ...
  }
}
attrs and slots are stateful objects, they are always updated with the update of the component itself. This means that you should avoid deconstructing them and always refer to properties attrs.x or slots.x Please note that, unlike props , attrs and slots are not responsive. If you plan to change the application side effects attrs or slots onUpdated lifecycle hook.

3. The property of the setup component

When setup is executed, the component instance has not been created yet. Therefore, you can only access the following properties:
  • props
  • attrs
  • slots
  • emit

In other words, your will not be able to access the following component options of

  • data
  • computed
  • methods

4. Use ref reactive and setup in combination with templates

Before looking at the setup combination with the template, we must first know the ref and reactive methods.

If setup returns an object, you can bind the properties and methods of the object in the template, but when you want to define responsive data, you can use the ref , reactive method to define the responsive data

wrong writing:
<template>
{{msg}}
<br>

<button @click="updateMsg">改变etup中的msg</button>

<br>
</template>

<script>
export default {
    data() {
        return {

        }
    },
    setup() {
        let msg = "这是setup中的msg";
        let updateMsg = () => {
            alert("触发方法")
            msg = "改变后的值"
        }
        return {
            msg,
            updateMsg
        }
    },

}
</script>

<style lang="scss">
.home {
    position: relative;
}
</style>
correct writing one:
ref used to define the string, number, array, Bool type of response
import {  
    ref
} from 'vue'
<template>
{{msg}}
<br>
<br>
<button @click="updateMsg">改变etup中的msg</button>
<br>
<br>
<ul>
    <li v-for="(item,index) in list" :key="index">
        {{item}}
    </li>
</ul>

<br>
</template>

<script>
import {

    ref
} from 'vue'

export default {
    data() {
        return {

        }
    },
    setup() {
        let msg = ref("这是setup中的msg");

        let list = ref(["马总", "李总", "刘总"])

        let updateMsg = () => {
            alert("触发方法");
            msg.value = "改变后的值"
        }
        return {
            msg,
            list,
            updateMsg
        }
    },

}
</script>

<style lang="scss">
.home {
    position: relative;
}
</style>
correct writing two:

reactive used to define reactive objects

import {
    reactive   
} from 'vue'
<template>
{{msg}}
<br>
<br>
<button @click="updateMsg">改变setup中的msg</button>
<br>
<br>
<ul>
    <li v-for="(item,index) in list" :key="index">
        {{item}}
    </li>
</ul>
<br>
{{setupData.title}}
<br>
<button @click="updateTitle">更新setup中的title</button>
<br>
<br>
</template>

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

export default {
    data() {
        return {

        }
    },
    setup() {
        let msg = ref("这是setup中的msg");

        let setupData = reactive({
            title: "reactive定义响应式数据的title",
            userinfo: {
                username: "张三",
                age: 20
            }

        })

        let updateMsg = () => {
            alert("触发方法");
            msg.value = "改变后的值"
        }
        let updateTitle = () => {
            alert("触发方法");
            setupData.title = "我是改变后的title"

        }
        return {
            msg,
            setupData,
            updateMsg,
            updateTitle
        }
    },

}
</script>

<style lang="scss">
.home {
    position: relative;
}
</style>

Description: To change the attribute name defined by ref, you need to modify the attribute name.value. To change the object name defined in reactive

5. Use this

in setup() inside, this will not be a reference to the active instance of , because setup() is being called before parsing other component options, so setup() internal this behavior with other options this completely different. This may cause confusion when setup() with other optional APIs

Two, toRefs-Deconstruct responsive object data

Convert a responsive object into an ordinary object. Each property the ordinary object is a ref , which property one-to-one
<template>
<div>
    <h1>解构响应式对象数据</h1>
    <p>Username: {{username}}</p>
    <p>Age: {{age}}</p>
</div>
</template>

<script>
import {
    reactive,
    toRefs
} from "vue";

export default {
    name: "解构响应式对象数据",
    setup() {
        const user = reactive({
            username: "张三",
            age: 10000,
        });

        return {
            ...toRefs(user)
        };
    },
};
</script>
When you want to return a responsive object from a combinatorial logic function, toRefs is very effective. This API allows the consumer component to deconstruct/expand the operator) without losing responsiveness:
function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2,
  })

  // 对 state 的逻辑操作
  // ....

  // 返回时将属性都转为 ref
  return toRefs(state)
}

export default {
  setup() {
    // 可以解构,不会丢失响应性
    const { foo, bar } = useFeatureX()

    return {
      foo,
      bar,
    }
  },
}

Three, calculated-calculated attributes

<template>
<div>
    <h1>解构响应式对象数据+computed</h1>

    <input type="text" v-model="firstName" placeholder="firstName" />
    <br>
    <br>
    <input type="text" v-model="lastName" placeholder="lastName" />

    <br>
    {{fullName}}
</div>
</template>

<script>
import {
    reactive,
    toRefs,
    computed
} from "vue";

export default {
    name: "解构响应式对象数据",
    setup() {
        const user = reactive({
            firstName: "",
            lastName: "",
        });

        const fullName = computed(() => {
            return user.firstName + " " + user.lastName
        })

        return {
            ...toRefs(user),
            fullName
        };
    },
};
</script>

Four, readonly "deep" read-only proxy

Pass in an object (responsive or ordinary) or ref, and return a read-only proxy of the original object. A read-only proxy is "deep", and any nested properties within the object are also read-only
<template>
  <div>
    <h1>readonly - “深层”的只读代理</h1>
    <p>original.count: {{original.count}}</p>
    <p>copy.count: {{copy.count}}</p>
  </div>
</template>

<script>
import { reactive, readonly } from "vue";

export default {
  name: "Readonly",
  setup() {
    const original = reactive({ count: 0 });
    const copy = readonly(original);

    setInterval(() => {
      original.count++;
      copy.count++; // 报警告,Set operation on key "count" failed: target is readonly. Proxy {count: 1}
    }, 1000);


    return { original, copy };
  },
};
</script>

Five, watchEffect

Run a function immediately when tracking its dependencies responsively, and re-run it when changing dependencies.
<template>
<div>
    <h1>watchEffect - 侦听器</h1>
    <p>{{data.count}}</p>
    <button @click="stop">手动关闭侦听器</button>
</div>
</template>

<script>
import {
    reactive,
    watchEffect
} from "vue";
export default {
    name: "WatchEffect",
    setup() {
        const data = reactive({
            count: 1,
            num: 1
        });
        const stop = watchEffect(() => console.log(`侦听器:${data.count}`));
        setInterval(() => {
            data.count++;
        }, 1000);
        return {
            data,
            stop
        };
    },
};
</script>

Six, the difference between watch, watch and watchEffect

Contrast watchEffect , watch allows us :

  • Lazy execution, which means that the callback is executed only when the source of the listener changes;
  • It is more clear which state changes will trigger the listener to re-run;
  • Access the values before and after the listening state changes

More clearly which state changes will trigger the listener to re-run

<template>
<div>
    <h1>watch - 侦听器</h1>
    <p>count1: {{data.count1}}</p>
    <p>count2: {{data.count2}}</p>
    <button @click="stopAll">Stop All</button>
</div>
</template>

<script>
import {
    reactive,
    watch
} from "vue";
export default {
    name: "Watch",
    setup() {
        const data = reactive({
            count1: 0,
            count2: 0
        });
        // 侦听单个数据源
        const stop1 = watch(data, () =>
            console.log("watch1", data.count1, data.count2)
        );
        // 侦听多个数据源
        const stop2 = watch([data], () => {
            console.log("watch2", data.count1, data.count2);
        });
        setInterval(() => {
            data.count1++;
        }, 1000);
        return {
            data,
            stopAll: () => {
                stop1();
                stop2();
            },
        };
    },
};
</script>

Access the value before and after the listening state change

<template>
<div>
    <h1>watch - 侦听器</h1>
    <input type="text" v-model="keywords" />
</div>
</template>

<script>
import {
    ref,
    watch
} from "vue";
export default {
    name: "Watch",
    setup() {
        let keywords = ref("111");
        // 侦听单个数据源
        watch(keywords, (newValue, oldValue) => {
            console.log(newValue, oldValue)
        });

        return {
            keywords
        };
    },
};
</script>

is executed only when the source of the listening changes.

<template>
<div>
    <h1>watch - 侦听器</h1>
    <p>num1={{num1}}</p>
    <p>num2={{num2}}</p>
</div>
</template>

<script>
import {
    ref,
    watch,
    watchEffect
} from "vue";
export default {
    name: "Watch",
    setup() {
        let num1 = ref(10);
        let num2 = ref(10);
        // 侦听单个数据源
        watch(num1, (newValue, oldValue) => {
            console.log(newValue, oldValue)
        });

        watchEffect(() => console.log(`watchEffect侦听器:${num2.value}`));

        return {
            num1,
            num2
        };
    },
};
</script>

Seven, combined api life cycle hook

You can access the life cycle hook of the component by adding "on" in front of the life cycle hook.

The following table contains how to call life cycle hooks inside setup ()

Option APIHook inside setup
beforeCreateunnecessary*
createdunnecessary*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
Because setup around the beforeCreate and created lifecycle hooks, there is no need to explicitly define them. In other words, any code written in these hooks should be written setup
export default {
  setup() {
    // mounted
    onMounted(() => {
      console.log('Component is mounted!')
    })
  }
}

8. Provider Inject

Usually, when we need to pass data from the parent component to the child component, we use props . Imagine a structure like this: you have some deeply nested components, and you only need some content from the parent component in the deeply nested child components. In this case, you still need to pass the prop to the entire component chain, which can be annoying

For this situation, we can use provide and inject for the parent component to be the dependency provider for all its child components, no matter how deep the component hierarchy is. This feature has two parts: the parent component has a provide option to provide data, and the child component has a inject option to start using this data

1. non-combined api writing

<!-- src/components/MyMap.vue -->
<template>
  <MyMarker />
</template>

<script>
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  provide: {
    location: 'North Pole',
    geolocation: {
      longitude: 90,
      latitude: 135
    }
  }
}
</script>
<!-- src/components/MyMarker.vue -->
<script>
export default {
  inject: ['location', 'geolocation']
}
</script>

2. combined api in wording

Provider:
In setup() use provide , we first from vue explicitly import provide method. This allows us to call provide time to define each property

provide function allows you to define property with two parameters:

  1. property of name ( <String> type)
  2. property of value

Using the MyMap component, the value we provide can be reconstructed as follows:

<!-- src/components/MyMap.vue -->
<template>
  <MyMarker />
</template>

<script>
import { provide } from 'vue'
import MyMarker from './MyMarker.vue

export default {
  components: {
    MyMarker
  },
  setup() {
    provide('location', 'North Pole')
    provide('geolocation', {
      longitude: 90,
      latitude: 135
    })
  }
}
</script>
Inject:
In setup() use inject when needed from vue it explicitly import. Once we do this, we can call it to define how to expose it to our components.

inject function has two parameters:

  1. The name property to be injected
  2. A default value ( optional )

Using the MyMarker component, it can be refactored with the following code:

<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'

export default {
  setup() {
    const userLocation = inject('location', 'The Universe')
    const userGeolocation = inject('geolocation')

    return {
      userLocation,
      userGeolocation
    }
  }
}
</script>

Provider Inject Responsiveness

parent component:

import {
    provide,
    ref,
    reactive
} from 'vue'

setup() {
        const location = ref('北京')
        const geolocation = reactive({
            longitude: 90,
            latitude: 135
        })
        const updateLocation = () => {
            location.value = '上海'
        }
        provide('location', location);
        provide('geolocation', geolocation);
        return {
            updateLocation
        }
    }
<button @click="updateLocation">改变location</button>

sub-component:

import { inject } from 'vue'

export default {
  setup() {
    const userLocation = inject('location', 'The Universe')
    const userGeolocation = inject('geolocation')

    return {
      userLocation,
      userGeolocation
    }
  }
}
</script>


前端进阶之旅
332 声望20 粉丝