-
Notifications
You must be signed in to change notification settings - Fork 1
Vue HaxeVX Draft version guide
#Vue - HaxeVX User Manual (haxelib release >= 0.7.0)
A Haxe-based Vue Component class, is a Haxe-based class powered by @:autoBuild
macros to inject the necessary combination of data/props (or even store
reference for Vuex) as local class accessible fields with the correct contextual type/access control. It also facilitates the necessary initialization setup to correctly reflect the native Vue/Vue-Component options for VueJS. Finally, it has compile-time checks to ensure your Haxe class follows HaxeVx/VueJS conventions when it comes to accessing/declaring fields.
The current working approach is to create a some sort of eg. CustomVueComponentClass
to either extend VComponent<D,P>
(for standard Vue component) or extend VxComponent<S,D,P>
(for Vuex component that contains an additional store
accessor reference). The standard format is to create a basic class that extends either class with the necessary Type Params.
By convention, type parameters are in the order of: S
:Store type, D
:Data type, P
:Props type for VxComponent
, and conversely, just D
:Data type, P
:Props type for VComponent
only.
Mock Examples:
class MyVueSetup extends VComponent<DataModelClass, NoneT>
class MyVueComponentOfProps extends VComponent<NoneT, MyComponentProps>
class MyVueComponentOfStoreDataProps extends VxComponent<MyAppStore, MyDataDef, MyComponentProps>
NoneT
is an empty marker interface type which you can use to mark out Type Params which are Non-Applicable.
All physical fields from your Data type definition D
and Props type definition P
will be automatically mixed into your class (in the same way Vue proxies all it's data/prop fields to the active Vue/VueComponent instance), ie. data fields that do not start with _
underscore characters will be proxied into the instance, and you can access them within the local class context with proper type-hinting/Intellisense.
Additionally, the mixed-in fields will have the right access control both within and outside the local class context.
For props P
:
-
All prop fields will be read-only within the local class context to conform to VueJS best practices, though you can still "force-set" a property using the private
_props:P
getter field if you need to hack through something. (You should NOT actually under normal circumstances, and there is a compile-time optional flag-D remove_props_accessor
that you can use to remove the_props
field. I might remove the_props
getter entirely by default (though it's a great way to quickly access/inspect "props"), or give a compile-time warning if you mutated any field within it. -
Prop fields that start with underscores
_
are disallowed according to HaxeVX conventions. -
Prop type
P
must ONLY contain raw data physical fields. (ie. No methods/getters/setters are allowed, and the compiler will throw a warning/error if these are found.) -
All props fields are mixed-in as
private
class fields, so they can't be accessed from outside except purely within the class context itself.
For data:
-
For classes, mixed-in data fields will still use the original read/write access signatures (if any) from the
D
data type definition itself by default. For typedefs, all mixed-in data fields will be both read/write. -
The mixed in data-fields may either be public/private, depending on the context. For a generic Vue object definition context, all mixed-in data fields will be private. For an explicit Vue instance context (ie. using native Vue constructor), all mixed-in data fields will keep the original public/private access signatures from the the data
D
class (if using class-based Data), or will be mixed-in as all-public fields ( for typedef-based data).
You can always access the Vue instance methods/fields (eg. this.$emit()
, this.$data
, etc.) with $
prefix replaced by a underscored _v
camel-casing prefix instead (eg. this._vEmit()
, this._vData
within the class itself. However, you should not access these Vue API fields within the class constructor, because the Vue instance hasn't been initialized yet. The Vue API can only be accessed within your class methods.
For data fields that begin with _
and aren't mixed into the local class, you can still access them through this._vData
accessor. As long as your Data instance isn't re-serialized via JSON or replaced through some other untyped javascript means, you can safely access the object-oriented methods from the this._vData
reference as well (eg. this._vData.myOwnCustomClassMethod([32,11])
or any custom Haxe-based getters/setters within it, since it's strongly typed based on the D
Type Param you extended VComponent<D,P>
from.
To declare initial setup options such as lifecycle hook methods, vue component options, etc. you can override the following VComponent
methods:
/**
* Optionally override this to determine starting prop values for Unit Testing only!
* @return
*/
function PropsData():P {
return null;
}
/**
* You must override this to determine starting data values if you explicitly specified a Data Type
* in your component Type Param definition
* @return
*/
function Data():D {
return null;
}
// Lifecycle hook methods
function Created():Void {}
function BeforeCreate():Void {}
function BeforeDestroy():Void {}
function Destroy():Void {}
function BeforeMount():Void {}
function Mounted():Void {}
function BeforeUpdate():Void {}
function Updated():Void {}
function Activated():Void {}
function Deactivated():Void {}
function El():Dynamic { // String or HTMLElement
return null;
}
// override this implementation to use a custom render method instead to render Virtual DOM
function Render(c:CreateElement):VNode {
return null;
}
// override this implementation to use a custom template string reference or markup
function Template():String {
return null;
}
/**
* Override this to register components locally.
* You can optionally return an inline StringMap declaration like: `return [ 'someKey'=>value1, '$someTokenisedKey'=>value2 ]` as well,
* which will allow you to tokenise your string-based component keys to any variables you declared, to avoid mispellings.
* Remember, tokenisation isn't available for regular Dynamic object.
*/
function Components():Dynamic<VComponent<Dynamic,Dynamic>> {
return null;
}
The list above is non-exhaustive, but generally they are represented as Capitalized functions of the existing Vue API that can return the necessary Vue option parameter values dynamically. You can override them and return the necessary typed value, accordingly.
If you implemented a Data type D
that is not NoneT
(eg. MyClassData
, MyTypeDefData
, Dynamic
, etc.) the compiler will throw an error if you did not override the Data():D
method, which defines the initial data values.
Basically, as per standard VueJS practice, leave no data field undefined (unless you wish to have a non-reactive property).
If using classes as Data, ensure all class fields (that you intend to be re-active) have their values pre-initialized beforehand (even "empty" values should be set to =null
), to allow Vue to pick those properties.
For using typedefs as Data, ensure all those fields are set beforehand as well...so it's better to not use @:optional
for such cases in case you miss out defining a field within the typedef.
Note that unlike regular VueJS, defining/listing every prop field manually in your Vue component definition isn't required in HaxeVX (unless you are defining a Dynamic data type for your props which will prevent the compile-time build macro from deriving fields). The compile-time build macro auto-detects the prop field names and their accompanying field-type signature in the P
type you supplied, and will reflect these props (as a base-set of prop settings) automatically in the codebase for VueJS to process. (Thus, the developer doesn't need to supply native js type
settings per prop, since it's already done by the compile-time build macro.)
However, if you need further customization, there are various ways.
You can do so by injecting compile-time prop metadata into your P
class/typedef to modify the base-set of prop settings:
typedef CartProps = {
@:prop({required:true, "default":0}) var a:Float;
var b:Float;
}
The disadvantage of using metadata though, is that you won't get compile-time type-checking with the "default"
value (as of now, this is on low-priority list), and the values are restricted to constant values. However, using it to supply common property settings like required
, may be done.
For more control and better type-safety (and generally a better practice to define prop settings within specific component context), you can supply additional property settings to the base set of props via overriding the below 2 methods.
Example via overrides:
override function GetDefaultPropSettings():Dynamic<VcPropSetting> {
return {
a:{required:true}, // will produce final merged result of a:{required:true, "default":0} from base props
//b:null // Compiler produces "Null supplied. Ignored" warning.
// zz:{required:true} // Compiler produces "Unknown prop" warning, but will still force-set "zz" as a prop
};
}
Each VcPropSetting
is basically a subset typedef to the existing official Vue component prop settings. (eg. contains other api prop settings like required
, type
etc., but excludes default
). You may also force-set the type
to an array of types via an array of Native JS extern or an array of untyped __js__
to overwrite a Dynamic
typed props and allow these props to be validated over multiple types similar to VueJS.
Technical note: If you returned a single plain object in the function body without any other statements, inlining of property assignments will be performed, and compile-time macro checks will be done to ensure each dynamic VCompPropSettings
field-name you defined, matches the field-names found in your P
type. If the above inlining can't be performed, reflection and iteration will be executed to merge the prop settings and compile-time checking will be foregoed.
override function GetDefaultPropValues():CartProps {
return {
a:2,
b:4
};
}
Just return a sample of the P
data type. These props will be override any base default prop value setups (if any).
Technical note: If you returned a single plain object in the function body without any other statements, inlining of property assignments will be performed to override the base prop settings. Otherwise, reflection and iteration will be executed to merge the prop settings. In this way, it's may be advantageous to use plain typedef for Props P
, instead of classes.
The validator
setting in VueJS component props can be set using @:propValidate
metadata.
Just declare a method with @:propValidate
metadata. (Note the colon to indicate it's a compile-time metadata)
Below are some mock example formats of using the metadata to indicate validator functions. name:
parameter or a plain string value can be used for the 1st parameter to indicate the prop name to watch.
@:propValidate({name:"a"}) function a_isValid(value:Float):Bool { return value >0 }
@:propValidate("a") function aValid(value:Float) { return value >0 }
@:propValidate("a") function a_checkValidity(value:Float):Bool { return value >0 }
@:propValidate() function validate_a(value:Float){ return value >0 }
If no valid prop name could be found in the metadata parameters, convention may use the xxxx_textAfterUnderscore
as the prop name to link to.
The compiler macro ensures any prop names (and their value parameter type) match with those in your P
definition.
Standard Haxe class local function declarations will be compiled as Vue methods:
definitions.
Standard Haxe class getter/setters (with their accompanying get_
set_
methods), will be compiled to their respective Vue computed:
definitions. As per VueJS convention, a computed field must always have get
access always, and the Haxe compile-time macro checks to ensure this is the case.
Examples..
// Computed
var products(get, never):Array<ProductInStore>;
function get_products():Array<ProductInStore> {
return store.state.products.allProducts;
}
var items(get, set):Array<PItems>;
function get_items():Array<PItems> {
return store.state.items;
}
function set_items(val:Array<PItems>):Void {
store.state.items = val;
}
// Methods
function addToCart(p:ProductInStore):Void {
actionDispatcher.addToCart(store, p);
}
For brevity, if your computed property is a read-only getter (ie. has no setter), you can optionally annotate the Haxe getter function with @:computed
metadata and the macro will auto-generate the read-only variable for you.
@:computed function get_products():Array<ProductInStore> {
return store.state.products.allProducts;
}
Declare a method with @:watch
metadata. (Note the colon to indicate it's a compile-time metadata)
Below are some mock example formats of using the metadata to indicate watchers. name:
parameter or a plain string value can be used for the 1st parameter to indicate the field name to watch. Optional 2nd parameter can be used for watcher parameters instead.
@:watch({name:"a", deep:true}) function onABC_Change(newValue:Float, oldValue:Float):Void {}
@:watch("b") function onB_Change(newValue:Float, oldValue:Float):Void {}
@:watch("total", {deep:true}) function total_change(newValue:Float, oldValue:Float):Void {}
@:watch( {deep:true}) function watch_checkoutStatus(newValue:String, oldValue:String):Void {}
If no valid field name could be found in the metadata parameters, convention may use the xxxx_textAfterUnderscore
as the field name to link to. Only single field names (from data/computed/prop) are supported (ie. no dot-access) to ensure strict typing. If you wish to access a specific deeply nested property via dot access, then create a computed property getter first representing to retrieve that nested property, and watch that computed property instead.
The compiler macro ensures any watcher field names (and their types) match with those in your generated class' data/computed/prop definition.
As of now, these are the only methods available:
Registering a globalised Vue component. Mock eg.
Vue.component("some-component", new MyVueComponentOfProps() )
Standalone Vue instance. Mock eg.
new Vue(new MyVueSetup())
where the mock classes represent extended VComponent
instances.