what happen
The most advanced project was moved from vue-cli to vite because it is a vue2 project, and it is used.vue-class-component Class components are supported by ts.
Of course, the migration process was not so smooth. The browser console reported a lot of errors, which roughly means that a certain method is undefined and cannot be called. The current method of this is undefined is printed from the method under the vuex-class decorator. This is a very magical thing, why is the only method under the vuex-class decorator undefined?
Explore
I searched online and there was no similar problem. I could only break the point step by step in node_modules to see what went wrong. The first thing I thought was that there was a problem was vuex-class. I debugged the code under /node_modules/vuex-class/lib/ and found that vuex-class only made a layer of method replacement and saved it into the __decorators__ array under vue-class-component through the createDecorator method.
import { createDecorator } from "vue-class-component"; function createBindingHelper(bindTo, mapFn) { function makeDecorator(map, namespace) { // Save it into the __decorators__ array of vue-class-component return createDecorator(function (componentOptions, key) { if (!componentOptions[bindTo]) { componentOptions[bindTo] = {}; } var mapObject = ((_a = {}), (_a[key] = map), _a); componentOptions[bindTo][key] = namespace !== undefined ? mapFn(namespace, mapObject)[key] : mapFn(mapObject)[key]; var _a; }); } function helper(a, b) { if (typeof b === "string") { var key = b; var proto = a; return makeDecorator(key, undefined)(proto, key); } var namespace = extractNamespace(b); var type = a; return makeDecorator(type, namespace); } return helper; }
Then we can only look at vue-class-component.
The @Component decorator of vue-class-component returns a constructor for a vue object.
// vue-class-component/lib/ function Component (options: ComponentOptions<Vue> | VueClass<Vue>): any { if (typeof options === 'function') { return componentFactory(options) } return function (Component: VueClass<Vue>) { return componentFactory(Component, options) } } // Class components@Component export default class HelloWorld extends Vue { ... }
The Component method will pass class HelloWorld in componentFactory , register the name lifecycle methods computed and other in options, and then pass in, returning a constructor of a vue object.
export function componentFactory( Component: VueClass<Vue>, options: ComponentOptions<Vue> = {} ): VueClass<Vue> { // . . . No code = || (Component as any)._componentTag || (Component as any).name; const proto = ; ( || ( = {}))[key] = ; // typescript decorated data ( || ( = [])).push({ data(this: Vue) { return { [key]: }; }, }); // computed properties ( || ( = {}))[key] = { get: , set: , }; // add data hook to collect class properties as Vue instance's data ( || ( = [])).push({ data(this: Vue) { return collectDataFromConstructor(this, Component); }, }); // The vuex-class wrapper method will be injected here const decorators = (Component as DecoratedClass).__decorators__; if (decorators) { ((fn) => fn(options)); delete (Component as DecoratedClass).__decorators__; } const Super = superProto instanceof Vue ? ( as VueClass<Vue>) : Vue; const Extended = (options); // . . . No code return Extended; }
There is basically no problem at this point, so the pressure comes to vue. The returned Extended is the generated vue object constructor.
= function (extendOptions) { // . . . No code var Sub = function VueComponent(options) { this._init(options); }; // . . . No code return Sub; };
When new Extended, _init will be called to initialize the vm object.
._init = function (options) { // . . . No code initLifecycle(vm); initEvents(vm); initRender(vm); callHook(vm, "beforeCreate"); initInjections(vm); // resolve injections before data/props initState(vm); initProvide(vm); // resolve provide after data/props callHook(vm, "created"); // . . . No code};
Next is the boring breakpoint debugging. Finally, after executing the initState method, some methods in vm become undefined. The function of initState is to register data methods, etc. to vm.
function initState(vm) { vm._watchers = []; var opts = vm.$options; if () { initProps(vm, ); } if () { initMethods(vm, ); } if () { initData(vm); } else { observe((vm._data = {}), true /* asRootData */); } if () { initComputed(vm, ); } if ( && !== nativeWatch) { initWatch(vm, ); } }
The problem arises after breaking the point and finding the initData method. The function of the initData method is to register the data object to vm. If data is a function, the function will be called, and the problem occurs in the sentence (vm, vm) in getData.
function initData(vm) { var data = vm.$; data = vm._data = typeof data === "function" ? getData(data, vm) : data || {}; // . . . No code} function getData(data, vm) { // #7573 disable dep collection when invoking data getters pushTarget(); try { const a = (vm, vm); return a; } catch (e) { handleError(e, vm, "data()"); return {}; } finally { popTarget(); } }
The (vm, vm) called is the method registered by vue-class-component. Well, back to vue-class-component, let's take a look at the code of vue-class-component.
export function componentFactory( Component: VueClass<Vue>, options: ComponentOptions<Vue> = {} ): VueClass<Vue> { // . . . No code ( || ( = [])).push({ data(this: Vue) { return collectDataFromConstructor(this, Component); }, }); // . . . No code}
In the componentFactory method above, data returns a collectDataFromConstructor method. In collectDataFromConstructor we should be able to solve the puzzle.
function collectDataFromConstructor(vm, Component) { ._init = function () { var _this = this; // proxy to actual vm var keys = (vm); // 2.2.0 compat (props are no longer exposed as self properties) if (vm.$) { for (var key in vm.$) { if (!(key)) { (key); } } } (function (key) { (_this, key, { get: function get() { return vm[key]; }, set: function set(value) { vm[key] = value; }, configurable: true, }); }); }; // should be acquired class property values var data = new Component(); // restore original _init to avoid memory leak (#209) // . . . No code return data; }
function Vue(options) { this._init(options); }
The passed Component parameter is export default class HelloWorld extends Vue { ... }, new Component() will get all parameters in HelloWorld. Component inherits from Vue, so when new Component(), the _init method will be called first like Vue, and collectDataFromConstructor replaces Component's _init.
In the permuted _init method, all properties on vm are traversed and these properties are pointed back to vm through. The reason is that initProps initMethods is meant that when new Component() is detected, it will point to vm, and the remaining data value is the data value.
There seems to be no problem with the whole process. But since you use get set, will it have anything to do with the set method? A breakpoint was hit in the set method, and it was triggered, and the triggering conditions were a bit strange.
@Component export default class HelloWorld extends Vue { // vuex @ count: number; @("increment") increment: () => void; @("setCount") setCount: () => void = () => { = + 1; }; // data msg: string = "Hello Vue 3 + TypeScript + Vite"; // methods incrementEvent() { (this); (); = + " + " + ; } // life cycle beforeCreate() {} created() { (this); = + " + " + ; } }
The above is a very basic class component. The set of increment setCount triggers, one is passed in undefined and the other is passed in () => { = + 1 }. Both belong to methods, but both are not assigned the initial value in the form of fn(){}, so the set of incrementEvent is not triggered, increment is passed in undefined, and setCount is passed in a function.
class A { increment; setCount = () => {}; incrementEvent() {} }
increment and setCount are a variable, and incrementEvent will be regarded as a method
Strangely, there is no problem in vue-cli, the set method will not trigger, why does it trigger after switching to vite set reset the initial value of some variables. I wondered if it was a problem with the compilation of both. I compared the compiled files of the two, and sure enough.
vue-cli
export default class HelloWorld { constructor() { = () => { = + 1; }; // data = "Hello Vue 3 + TypeScript + Vite"; } // methods incrementEvent() { (this); (); = + " + " + ; } // life cycle beforeCreate() {} created() { (this); = + " + " + ; } }
vite
export default class HelloWorld { // vuex count; increment; setCount = () => { = + 1; }; // data msg = "Hello Vue 3 + TypeScript + Vite"; // methods incrementEvent() { (this); (); = + " + " + ; } // life cycle beforeCreate() {} created() { (this); = + " + " + ; } }
You can see that the compilation results of vue-cli vite are not consistent. vite has two more default values than vue-cli, and the default values of these two values are undefined, and they are not compiled in vue-cli. I can only search for the vite document below, and one attribute attracted me.
Checked thisuseDefineForClassFields In short, if useDefineForClassFields is false, ts willSkip the variable as undefined, if true, the default value is undefined variable attributeStill compiled. Under normal circumstances, there will be no problem, but vue-class-component will hijack the props methods attributes. When new initialization, these values will be triggered. If there is no default value, it will be assigned to undefined.
solve
It is very simple to solve the problem. Just add the useDefineForClassFields property to tsconfig and set it to false.
{ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": false, "module": "ESNext", "lib": ["ESNext", "DOM"], "moduleResolution": "Node", "strict": true, "sourceMap": false, "resolveJsonModule": true, "esModuleInterop": true, "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true }, "include": ["./src"] }
Summarize
In the process of transferring to vite, there are still many pitfalls to be stepped on. Sometimes it is not a problem with vite, but a problem from multiple parties.useDefineForClassFields The changes brought about are not just the properties that will be compiled into undefined. You can learn more about them and broaden your knowledge.
This is the end of this article about the detailed explanation of the pitfall record of vue-class migration vite. For more related content on vue-class migration vite, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!