1

what is it

It is Vue3 . It is a new way of writing vue components, which realizes the React hooks , the usage is flexible and simple, and the type inference ability is enhanced, allowing Vue to build There is also a place for large-scale applications.

Why use

Because OptionsAPI , developers who are satisfied with the JQ era can quickly get started because the writing method is very similar to the previous writing method.

However, the current Web applications are becoming more and more complex, the front end will do more things, using OptionsAPI will bring a lot of confusion, just use data、 computed、 methods、 watch these, in most cases, it is understandable, because there is no mental burden It's so heavy, it's easy to understand. However, when the components become larger, the business becomes more complicated, the area of logical focus becomes longer, and the screen display is limited. I need to jump back and forth in different areas. When I add a part of the logic, I need to jump repeatedly and shuttle back and forth in the code.

动画演示

Reuse

Perhaps you will say, ah pulled out the code, we have mixins , we have extends , we not only have a combination, as well as inheritance, however, since this is vue to achieve, not native , so could not even peek , cmmand + left click will only tell you that the definition cannot be found

this.productList = this.generateProducts(productGroups); such as this line of code, is very common, but if I tell you that generateProducts is not in the current file, how do you search?

Believe that you will be smart, you will directly search globally. If this is a micro front-end application, your method comes from the base application, do you have to search the application or open a workspace?

Or you are familiar with the file structure, go extends in the search base class, go mixins array searching one by one, and finally go global install in search, even to search Vue.prototype.xx , data sources completely blurred, this is a black box, and finally, you Found that method, you know its logic, but it's late at night. The girlfriend is asleep.

Naming conflict

Smart you, you have solved the above problems, and at the same time, you have also learned to use mixins , the code can be reused, and you are excited to write a mixins

export default {
  data() {
    return {
      x: 0,
    };
  },
  methods: {
    // ...
  },
  computed: {
    double() {
      return this.x * 2;
    },
  },
};

Then the other one you quoted from your teammate’s mixin also has this. The end result is that there is a problem with the program operation and the naming conflict needs to be resolved. So in order to check this issue, it was found late the next night that your girlfriend fell asleep again.

In Vue 2, mixin is the main tool for abstracting some component logic into reusable blocks. However, they have several problems:

  1. Mixins are prone to conflicts: because the attributes of each feature are merged into the same component, in order to avoid property name conflicts and debugging, you still need to understand every other feature.
  2. Reusability is limited: we cannot mixin to change its logic, which reduces their flexibility in abstract logic

to sum up

Problems with object API

  • The code is organized by API type, and the complexity is high and repeated horizontally
  • Not conducive to reuse
  • Potential naming conflict
  • Context loss

Capabilities provided by the combined API

  • Organized by function/logic
  • Extremely easy to reuse
  • Can be flexibly combined
  • Provide better context support

Basic usage

In summary, it would be better if we could configure the code related to the same logical concern together. Vue has launched CompositionAPI to help us do this.

tu

setup

  • Execution mechanism
    setup are creating component instance and completed props executed after initialization, also in after beforeCreate hook, created before execution, can not access the option (data, comupted, methods, etc.) option, option can use setup variable returned. In other words, that the use of nuxt , we nuxt given those options such as under fetch、asyncData , can also be used this.xxx access to setup returned xxx

    It will only be executed once, and its return value is used as the connection point between data and logic.

  • Without this: setup() has been called before parsing other component options
  • Two parameters are accepted:

    1. props: component passing parameters
    2. context: execution context, including three attribute methods: attrs , slots , emit
    import { toRefs } from "vue";
    export default {
      props: {
        title: String,
      },
      setup(props) {
        console.log(props.title);
        const { title } = toRefs(props);
        console.log(title.value);
      },
    };

Among them, props is responsive. Of course, don't deconstruct it. Once deconstructed, the response state will be lost. If you want to deconstruct it, you need toRefs to complete the operation safely.

And context is a normal JavaScript object, that is, it is not responsive, which means you can safely use ES6 deconstruction context

  • Ref/Reactive

    Use these two functions to make the data responsive, but there is a difference between the two. If you are using a value type, you'd better use ref , if it is an object type, use reactive .

    Which, ref use, need .value , and reactive object is automatically UnWrap , without the use of .value , however, can not be deconstructed, because it would lose the response status, if you must, then, toRefs

    import { ref, reactive } from "vue";
    
    let foo = 0;
    let bar = ref(0);
    
    foo = 1;
    bar = 1; // ts-error
    bar.value = 1;
    // 关于.value,官方说法就是可以利用它,良好的区分数据是ref的还是普通的,当然,使用起来确实烦了一点
    
    const foo = { prop: 0 };
    const bar = reactive({ prop: 0 });
    
    foo.prop = 1;
    bar.prop = 1;

Of course, in template and watch , .value is not needed, it will automatically unwrap

const counter = ref(0);

watch(counter, (count) => {
  console.log(count);
});

Of course, sometimes you will say that if I have a function, it can receive both responsive data and non-responsive data. How to write this? Fortunately, the official also provides a unref operation

If you pass in a Ref, return this .value , otherwise, return it as it is

function unref(r) {
  return isRef(r) ? r.value : r;
}
  • Life cycle
Option APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered

Actually add on

export default {
  setup() {
    // mounted
    onMounted(() => {
      console.log("Component is mounted!");
    });
  },
};
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 setup function.
  • Provide / Inject
    We can also use provide/inject in combined APIs. Both can only be called during setup() of the currently active instance

    Because they are used in setup , they are also more flexible and can be turned into responsive.

    Of course, one thing to note is that once it becomes responsive, the place to modify the corresponding value should be modified at the location provided by the data. The convention is greater than the configuration. Because you can change it inside, but that will make the data uncontrollable.

    <!-- src/components/MyApp.vue -->
    <template>
     <Marker />
    </template>
    <script>
    import { provide, reactive, ref } from 'vue'
    import MyMarker from './MyMarker.vue;
    export default {
       setup() {
             const location = ref("North Pole");
             const geolocation = reactive({
                 longitude: 90,
                 latitude: 135,
             });
    
             const updateLocation = () => {
                 location.value = "South Pole";
             };
    
             provide("location", location);
             provide("geolocation", geolocation);
             provide("updateLocation", updateLocation);
       }
    }
    </script>
    
    <!-- src/components/MyMarker.vue -->
    import { inject } from 'vue'
    export default {
     setup() {
         const userLocation = inject('location', 'The Universe')
         const userGeolocation = inject('geolocation')
         const updateUserLocation = inject('updateLocation')
    
         return {
             userLocation,
             userGeolocation,
             updateUserLocation
         }
     }
    }

Advantages of logical areas together

If in vue2.x, if we write a page with request loading and error views, we might write like this

<template>
  <div>
    <div v-if="error">failed to load</div>
    <div v-else-if="loading">loading...</div>
    <div v-else>hello {{ fullName }}!</div>
  </div>
</template>

<script>
import { createComponent, computed } from 'vue'

export default {
 data() {
   // 集中式的data定义 如果有其他逻辑相关的数据就很容易混乱
   return {
       data: {
           firstName: '',
           lastName: ''
       },
       loading: false,
       error: false,
   },
 },
 async created() {
     try {
       // 管理loading
       this.loading = true
       // 取数据
       const data = await this.$axios('/api/user')
       this.data = data
     } catch (e) {
       // 管理error
       this.error = true
     } finally {
       // 管理loading
       this.loading = false
     }
 },
 computed() {
     // 没人知道这个fullName和哪一部分的异步请求有关 和哪一部分的data有关 除非仔细阅读
     // 在组件大了以后更是如此
     fullName() {
         return this.data.firstName + this.data.lastName
     }
 }
}
</script>

Data and logic are scattered in each option , this is just a logic, if there is more logic, more data, computed, methods? If you are a new person to take over this file, how can you quickly distinguish that method is associated with data

So, if we use vue3, with a library called vue-request

<template>
  <div>
    <div v-if="error">failed to load</div>
    <div v-else-if="loading">loading...</div>
    <div v-else>hello {{ fullName }}!</div>
  </div>
</template>

<script>
import { computed } from "vue";
import useSWR from "vue-request";

export default {
  setup() {
    // useRequest帮你管理好了取数、缓存、甚至标签页聚焦重新请求、甚至Suspense...
    const { data, loading, error } = useRequest("/api/user", fetcher);
    // 轻松的定义计算属性
    const fullName = computed(() => data.firstName + data.lastName);
    return { data, fullName, loading, error };
  },
};
</script>

How simple and straightforward. However, if there is still a lot of logic, some people will approve, or write spaghetti code, then it depends on the powerful multiplexing operation.

Reuse

As we mixins , the shortcomings of using 060dd341932e7e, then after using the combined API, it is much easier for us to reuse.

import { ref, onMounted, onUnmounted } from "vue";

export function useMousePosition() {
  const x = ref(0);
  const y = ref(0);

  function update(e) {
    x.value = e.pageX;
    y.value = e.pageY;
  }

  onMounted(() => {
    window.addEventListener("mousemove", update);
  });

  onUnmounted(() => {
    window.removeEventListener("mousemove", update);
  });

  return { x, y };
}

This is an example of mouse movement. In the custom hook , we returned the x,y . After all, in fact, we only care about the values of x and y

Or a simple example of counting down seconds. I pass in a value and return the remaining seconds.

import { ref, onMounted, onUnmounted } from "vue";

export function useCountDown(initSec = 60) {
  const sec = ref(initSec);

  function tiktok() {
    sec.value--;
  }

  let timer = null;

  onMounted(() => {
    timer = setInterval(() => {
      tiktok();
    }, 1000);
  });

  onUnmounted(() => {
    timer && clearInterval(timer);
  });

  return sec;
}

Use them in components

import { useMousePosition } from "./mouse";

export default {
  setup() {
    const { x, y } = useMousePosition();
    const sec = useCountDown(100);
    return { x, y, sec };
  },
};

It's simply too easy. The code is also very simple, there is no evil this. Similarly, you can even use useB in useA and useC in useB

Compare React Hooks

First of all, let’s look at the useCountDown written in the multiplexing above and write it out with react hooks .

import { useState, useEffect } from "react";

function useCount() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => {
      setCount((v) => v - 1);
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  return {
    count,
  };
}

export default function App() {
  const { count } = useCount();

  return <div>{count}</div>;
}

It's like right or wrong.

In fact, there are many problems with React Hook

  1. Do not call Hook in a loop, condition or nested function, because it is a linked list structure
  2. The dependency of useEffect is easy to cause mental burden. Everyone who reads this code needs to read the place where these dependencies are triggered.
  3. Due to closures, internally captured variables such as useEffect are all obsolete variables.

And Vue does not have the above three problems. And because the setup function is only called once, the performance is superior. Of course, the fundamental reason is because its data is responsive, and I can read the latest value directly by changing it.

VueUse

A hooks library that uses a combined API, you will definitely want to use. From the official team.

image.png

Do I still need vuex?

Because we use the combined API, you don't actually need vuex , because I reuse it very easily, just create a reactive it's done, right?

It's a bit similar to the recoil state management from React, which is mainly concurrent mode.

// react
export const state = atom({
  key: "testState",
  default: {
    foo: 1,
    bar: "hello",
  },
});

const [state, setState] = useRecoilState(state);

setState({
  ...state,
  foo: 2,
});

// vue
// shared.ts
export const state = reactive({
  foo: 1,
  bar: "hello",
});

// a.vue
import { state } from "./shared.ts";
state.foo = 2;
// b.vue
import { state } from "./shared.ts";
console.log(state.foo); // 2

But vue write state sharing scheme is not supported ssr of

If you want to be compatible with ssr , you have to write a plug-in and use the application-level provide to do it

export const globalStateKey = Symbol();
export function providerGlobalState() {
  const state = {};
  return {
    install(app) {
      app.provide(globalStateKey, reactive(state));
    },
  };
}
export function useGlobalState() {
  return inject(globalStateKey);
}

// main.ts
app.use(providerGlobalState());

// xx.vue
import { useGlobalState } from "";
const state = useGlobalState();

Two functions, the idea is actually quite createContext / useContext in react hooks. react-context
Including vue-routerV4 in useRouter and createRouter also in a similar manner. createRouter source code location
useRouter source code location

But the program can only be vue3 use, vue2.x in the case, could not do without vuex , but combined to a certain extent have been solved much of the problem of the state.

How to use in Vue 2.x

Install @vue/composition-api

composition-api README

When migrating to Vue 3 , simply replace @vue/composition-api vue . There are almost no additional changes to your existing code.

Almost zero cost, because it optionsAPI , so use it boldly.

script-setup

The latest script setup rfc proposal

Its motivation is "The main goal of the proposal is to reduce the verbosity of Composition API used in SFC by directly exposing the context of <script setup>

The simple explanation is that if you use the setup method

<script>
import { ref, computed } from 'vue'
export default {
   setup () {
      const a = ref(3)
      const b = computed(() => a.value + 2)

      const changeA = () => { a.value = 4 }
      return { a, b, changeA } // have to return everything!
   }
}
</script>

And to use <script setup> , you only need

<script setup>
import { ref, computed } from "vue";
import Foo from './Foo.vue'
import MyComponent from './MyComponent.vue'

const a = ref(3);
const b = computed(() => a.value + 2);

const changeA = () => {
  a.value = 4;
};
</script>

<template>
  <Foo />
  <!-- kebab-case also works -->
  <my-component />
  <div>{{ a }}</div>
  <div @click="changeA">{{ b }}</div>
</template>

It will automatically take all the things you declare, not only data, calculated properties and methods, even instructions and components can also be automatically obtained template

The advantage of this is that you don’t need to write those tedious things, your code is more streamlined, and you don’t need to write return

However, two problems I found, first, lint problem, it is unused , and second, I setup function I can choose return who, but with this, I may just be a temporary variable declarations computing, but also It will be exposed. If this boundary problem cannot be solved, I think it is only used for the use of some custom Hooks, so that I can deconstruct what I want when getting it.

Final summary

  1. compostionAPI appear, so use vue write complex applications no longer become stretched
  2. With compostionAPI , we can make our code better than what we wrote before (familiar with the design patterns) and write more elegantly
  3. For a newcomer, if you start from setup , it will be easier options
  4. Future vue and react more like, you know, react order to launch concurrentmode done a lot of groundwork. I don't know how vue will follow up with similar strategies.

Reference article

  1. VueUse author Anthony Fu shares composable Vue
  2. The Vue3 animation that has been busy all night is very good, but it is too short
  3. What is so good about (Detailed comparison with React Hook)
  4. introduce Vue Conf 21 conference: especially mentioned script setup syntax!
  5. Vue CompositionAPI trap
  6. Vue Conf 21 script setup
  7. Vue2.x vueuse demo
  8. Vueuse document
  9. Vue-Demi help you automatically introduce vue2 or vue3 library, developing plug-ins necessary

jansen
130 声望16 粉丝

学习不能止步,学习就是兴趣!终生学习是目标