Three functions to create responsive data: reactive, toRefs, ref
-
reactive: convert the object into a responsive object, that is, a Proxy object.
-
toRefs: it can also convert all attributes in a proxy object into responsive objects. When dealing with the attributes of this object, toRefs() is similar to ref.
-
ref: convert basic types of data into responsive objects.
Let's start with a problem. After the deconstruction of a responsive object, it will no longer be a responsive object, because when we create a responsive object, we use the reactive function to encapsulate the data into a Proxy object
For example, the following code does not work properly:
[case code online presentation address]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document 01 for Vue 3.0 Composition API</title> </head> <body> <div id="app"> x: {{ x }} <br /> y: {{ y }} </div> <script type="module"> // The functions extracted from the current refactoring can be put into a module and can be used in any component in the future. You can try to implement other logic in the same way function useMousePosition() { const position = reactive({ x: 0, y: 0, }); const update = (e) => { position.x = e.pageX; position.y = e.pageY; }; onMounted(() => { window.addEventListener('mousemove', update); }); onUnmounted(() => { window.removeEventListener('mousemove', update); }); return position; } import { createApp, reactive, onMounted, onUnmounted, } from './node_modules/vue/dist/vue.esm-browser.js'; const app = createApp({ setup() { // The first parameter, props, receives external incoming parameters. It is a responsive object, which cannot be deconstructed. // The second parameter, context, is an object with three members attrs, emit and slots // An object needs to be returned. The object returned in setup can be used in templates, methods, computed, and hook functions of life cycle // const position = useMousePosition(); const { x, y } = useMousePosition(); return { x, y, }; }, }); console.log(app); app.mount('#app'); </script> </body> </html>
Because the return value position of useMousePosition was originally a responsive Proxy object, when accessing its X and Y in the future, it will call the getter in the Proxy object to intercept and collect dependencies. When x and y change, it will call the setter in the Proxy object to intercept and trigger updates. When we deconstruct the Proxy object, const {x, y} = useMousePosition(), which is equivalent to defining two variables X and y to receive position X and position y. The assignment of basic types is equivalent to copying the value in memory. Therefore, the deconstruction results in two basic types of variables, which have nothing to do with the Proxy proxy object. When assigning values to X and Y again, the setter of the Proxy object will not be called, and the update operation cannot be triggered. Therefore * * cannot deconstruct the current responsive object * *.
After babel downgrades the deconstruction code, it actually defines two variables, X and y, to receive position.x and position.y. As shown in the figure:
If we really want to do this, there is still a way.
toRefs()
This requires a new API called toRefs(). Let's demonstrate how to use toRefs():
[Online demonstration code]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document 01 for Vue 3.0 Composition API</title> </head> <body> <div id="app"> x: {{ x }} <br /> y: {{ y }} </div> <script type="module"> import { createApp, reactive, onMounted, onUnmounted, toRefs, } from './node_modules/vue/dist/vue.esm-browser.js'; // The functions extracted from the current refactoring can be put into a module and can be used in any component in the future. You can try to implement other logic in the same way function useMousePosition() { const position = reactive({ x: 0, y: 0, }); const update = (e) => { position.x = e.pageX; position.y = e.pageY; }; onMounted(() => { window.addEventListener('mousemove', update); }); onUnmounted(() => { window.removeEventListener('mousemove', update); }); return toRefs(position); } const app = createApp({ setup() { // The first parameter, props, receives external incoming parameters. It is a responsive object, which cannot be deconstructed. // The second parameter, context, is an object with three members attrs, emit and slots // An object needs to be returned. The object returned in setup can be used in templates, methods, computed, and hook functions of life cycle // const position = useMousePosition(); const { x, y } = useMousePosition(); // x and y structured here are both responsive objects with a value, which can be omitted in the use of the template; But when used in code, value cannot be omitted. return { x, y, }; }, }); console.log(app); app.mount('#app'); </script> </body> </html>
We just need to use the toRefs() function to convert the responsive Proxy object into a reference before useMousePosition() returns.
The role of the toRefs() function is to convert all attributes in a responsive object into responsive.
The principle is:
- toRefs(proxyObj) requires that the parameter we pass in must be a Proxy object. If not, it will warn that the Proxy object needs to be passed.
- Next, it will create a new object internally, and then traverse all the attributes of the incoming Proxy object to convert the values of all attributes into Proxy objects.
- Note: toRefs() converts the values of all attributes of the incoming proxyObj proxy object into responsive objects, then mounts them to the newly created object, and finally returns the newly created object. It internally creates an object with value attribute for each attribute of the proxy object, which is responsive. The value attribute has getters and setters. This is similar to the * * ref() function to be discussed below * *. getter returns the value of the corresponding attribute in the proxy object; Assign values to the properties of the proxy object in the setter. So every attribute we return is responsive.
So we can deconstruct the responsive object after using the toRefs() transformation, and every attribute of the structure is also responsive.
ref()
- The function of ref() is to convert ordinary data into responsive data, that is, to wrap basic types of data into responsive objects with value values.
- Unlike reactive(), reactive() converts an object into responsive data.
Use case demonstration of ref(): [online case address]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document 01 for Vue 3.0 Composition API</title> </head> <body> <div id="app"> <button @click="increase">count value➕1</button> <span>{{count}}</span> </div> <script type="module"> import { createApp, ref, } from './node_modules/vue/dist/vue.esm-browser.js'; function useCount() { const count = ref(0); return { count, increase: () => { count.value++; }, }; } const app = createApp({ setup() { // The first parameter, props, receives external incoming parameters. It is a responsive object, which cannot be deconstructed. // The second parameter, context, is an object with three members attrs, emit and slots // An object needs to be returned. The object returned in setup can be used in templates, methods, computed, and hook functions of life cycle return { ...useCount() }; }, }); app.mount('#app'); </script> </body> </html>
Case analysis: we know that the basic data type stores values, so it cannot be responsive data. We know that the responsive data should collect dependencies through getter s and trigger updates through setter s. Then:
- If the parameter passed in by ref() is an object, it will internally call reactive to return a responsive object.
- If the parameter passed in ref() is a value of basic type, such as 0 passed in our case, it will create an object with value attribute internally, and the value attribute of the object has getter s and setters, which collect dependencies; Trigger update in setter.