indiv v1.2.1
InDiv
一个 web mvvm 库。 A web mvvm library.
current version: v1.2.1
demo
npm run startnpm run start-js- open
http://localhost:1234/demo
Basic Usage
- tsconfig: "emitDecoratorMetadata": true
- please use version: v1.2.0+
Create a root DOM for route which id is root:
<div id="root"></div>Create a InDiv:
Use
bootstrapModuleto bootstrap a root moduleNvModuleconst indiv = new InDiv(); indiv.bootstrapModule(M1); indiv.use(router); // if u are using a router indiv.init();
Create a router:
Routes must an
ArrayincludesObject- Routes must incloude rootPath
'/' - Routes must set an component name like
component: 'R1',R1is aComponentselectorfromRoot NvModule.You can use thisComponentfromexportsof otherNvModuleinRoot NvModule - Routes can assign redirectTo and use
redirectTo: Path - Routes can assign children and use
children: Array - Routes can set a path like
path: '/:id', but route of the same level can't be set
- Routes must incloude rootPath
If u are using
Router, u must need torouter.setRootPath('/RootPath')to set an root path.router.routeChange = (old, next)can listen route changerouter.init(routes);can init Array routesIf u want to watch routes changes, plz use
router.routeChange=(old.new) => {}1. Use `this.setLocation(path: String, query: Object, params: Object)` to go to Path or `location.href` 2. Use `this.getLocation()` to get location states 3. `Router` : `http://localhost:1234/R1` 4. **From v1.2.1, we have removed `setState, setLocation, getLocation`, and you can use `setState, setLocation, getLocation` from `import { setState, getLocation, setLocation } from 'indiv'`**type TRouter = { path: string; redirectTo?: string; component?: string; children?: TRouter[]; }; const router = new Router(); const routes: TRouter = [ { path: '/', // redirectTo: '/R1', component: 'container-wrap', children: [ { path: '/R1', component: 'R1', // redirectTo: '/R2', children: [ { path: '/C1', component: 'R2', children: [ { path: '/D1', redirectTo: '/R2', }, ], }, { path: '/C2', redirectTo: '/R2', }, ], }, { path: '/R2', component: 'R2', children: [ { path: '/:id', component: 'R1', children: [ { path: '/D1', redirectTo: '/R1/C1', }, ], }, ], }, ], }, ]; router.setRootPath('/demo'); // so routes:Array => `/` is `/demo` router.init(routes); router.routeChange = (old, next) => { console.log('nvRouteChange', old, next); }; const indiv = new InDiv(); indiv.bootstrapModule(M1); const routerIndex = indiv.use(router); indiv.init();
Create a Component:
- Create a
class - Use decorator
Componentin typescript or use functionComponentin javascript - Component types:
type TComponentOptions<State> = { selector: string; template: string; providers: (Function | { provide: any; useClass: Function; } | { provide: any; useValue: any; })[]; }; - Recommend to use
ServicewithRXjson communication betweenComponents selector: string;is your component tag name for HTML and used in template.template: stringonly accepts$.as value ofnv-directivefromthis.state, andnv-on:eventonly accepts@eventHandlerfrom method which belongs to Class instance$.in template isthis.state.providersdeclaresServiceforComponentProviders is an
Arraywhich has three modes:- Mode1:
Functionis equal to mode2:{ provide: Function; useClass: Function; } - Mode2:
{ provide: any; useClass: Function; } - Mode3:
{ provide: any; useValue: any; }usesuseValuefor injector
- Mode1:
providecan bestring,functionorClassin TypeScript- In TypeScript these three modes can be used and provide can be
functionorClass - In javascript please use
{ provide: string; useClass: Function; }[]or{ provide: string; useValue: any; }[]
templateonly accepts$.XXXfrom this.state, and nv-on:event only accepts@eventHandlerfrom method which belongs to Class instance- Please use
setStateafter lifecycleconstructor()andnvOnInit, and you can change or set value forthis.statewithoutsetStatein lifecycleconstructor()andnvOnInit - From v1.2.1, we have removed
setState, setLocation, getLocation, and you can usesetState, setLocation, getLocationfromimport { setState, getLocation, setLocation } from 'indiv' - After
Class'sconstructor, u can usethis.propsin any lifecycle - Use
nvOnInitandnvReceiveProps(nextProps: any): void;to receive props and setpropsinstate - Use
setLocationandgetLocationto controll route or get route info Use
Componentintemplate- Use like a HTML element tag:
<test-component></test-component> - Set props like HTML attributes, but use
{}to include props value:<test-component man="{@countState(man.name)}" women="{man.name}" handler="{@getProps}"></test-component> Please use UnderScoreCase to set Props in
template, and use CamelCase to use props<p-component props-value="$.a"></p-component> this.props.propsValueProps can use three types:
- value from
this.stateand value from repeat data:women="{$.name}"women="{man.name}" - value use function from
Componentinstance with return value:man="{@countState(man.name)}" - Function form
Componentinstance (must use@):handler="{@getProps}"
- value from
- Use like a HTML element tag:
About
stateandprops- action of set value of
stateor usingsetStateis a synchronous action - action of using callback function from
propsto changestatewhich comes fromparent Componentinchild Componentis a synchronous action - rerender of Component is an asynchronous action, and after rerendering
propscan be changed inchild Component - so after using callback function from
propsto changestatewhich comes fromparent Componentinchild Component,propsinchild Componentcan't be changed immediately because of out render mechanism is asynchronous render.You should usenvReceiveProps(nextProps: any): void;to watchpropschanges - from v1.2.1 we has remove
setStateinComponentinstance, and u can usesetStatefromimport { setState } from 'indiv'
- action of set value of
typescript
- To use decorator
Componentdeclaretemplateandstate - To implements interface
OnInit, BeforeMount, AfterMount, HasRender, OnDestory, ReceiveProps, WatchState, RouteChangeto use lifecycle hooks - To use decorator
Injectedto declare which need to be injectedServiceinconstructor's arguments ofComponent**
import { Injected, Component, OnInit, AfterMount, ReceiveProps, SetLocation, GetLocation, SetState, setLocation, getLocation, setState } from 'indiv'; @Injected @Component({ selector: 'container-wrap', template: (` <div> <p nv-on:click="@go()">container: {{$.a}}</p> <input nv-model="$.a" /> <router-render></router-render> </div> `), providers: [ HeroSearchService, { provide: HeroSearchService1, useClass: HeroSearchService1, } ] }) class Container implements OnInit, AfterMount, ReceiveProps { public ss: HeroSearchService; public state: any; public setLocation: SetLocation; public getLocation: GetLocation; public setState: SetState; constructor( private hss: HeroSearchService, ) { this.ss = hss; this.ss.test(); this.state = { a: 1, }; this.setLocation = setLocation; this.getLocation = getLocation; this.setState = setState; } public nvOnInit() { this.state.propsTest = this.props.test; } public nvAfterMount() { console.log('nvAfterMount Container'); } public nvReceiveProps(nextProps: any) { this.state.a = nextProps.test; } public go() { this.setLocation('/R1', { b: '1' }); } public show(a: any, index?: string) { console.log('aaaa', a); console.log('$index', index); } }- To use decorator
javascript
- To use function
Componentdeclaretemplateandstate - Lifecycle hooks are equal in TypeScript, and have no use
implementsinterface - Use Class static attribution
injectTokens: string[]and every item isprovide: stringof provider inNvModule - Class static attribution
injectTokens: string[]must beprovideof providers, and arguments of constructor will be instances ofuseClassor value ofuseValueof providers
import { Injected, Component, setLocation, getLocation, setState } from 'indiv'; class Container { static injectTokens = [ 'heroSearchService', ]; constructor( heroSearchService ) { this.ss = heroSearchService; this.ss.test(); this.state = { a: 1, }; this.setLocation = setLocation; this.getLocation = getLocation; this.setState = setState; } nvOnInit() { this.state.a = this.props.test; console.log('nvOnInit Container'); } nvReceiveProps(nextProps) { this.state.a = nextProps.test; } go() { this.setLocation('/R1', { b: '1' }); } show(a, index) { console.log('aaaa', a); console.log('$index', index); } } Component({ selector: 'container-wrap', template: (` <div> <p nv-on:click="@go()">container: {{$.a}}</p> <input nv-model="$.a" /> <router-render></router-render> </div>`), providers: [ { provide: 'heroSearchService', useClass: HeroSearchService, }, { provide: 'heroSearchService1', useClass: HeroSearchService1, } ] })(Container);- To use function
props: Objectis data whichclass Controllersends toclass Componentprops: Objectcan only be changed or used after lifecycleconstructor()
NvModule
- InDiv apps are modular and InDiv has its own modularity system called
NvModule. - An
NvModuleis a container for a cohesive block of code dedicated to an application domain, a workflow, or a closely related set of capabilities. - It can contain components, service providers, and other code files whose scope is defined by the containing
NvModule. It can import functionality that is exported from other
NvModule, and export selected functionality for use by otherNvModule.You need to declare
imports?: Function[]components: Function[]providers?: (Function | { provide: any; useClass: Function; } | { provide: any; useValue: any; }[])[]exports?: Function[]bootstrap?: Functioninoptions
imports?: Function[]imports otherNvModuleand can use it'sexports?: Function[]components: Function[]declaresComponentsprovidersdeclaresServiceforComponentorServiceProviders is an
Arraywhich has three modes:- Mode1:
Functionis equal to mode2:{ provide: Function; useClass: Function; } - Mode2:
{ provide: any; useClass: Function; } - Mode3:
{ provide: any; useValue: any; }usesuseValuefor injector
- Mode1:
providecan bestring,functionorClassin TypeScript- In TypeScript these three modes can be used and provide can be
functionorClass - In javascript please use
{ provide: string; useClass: Function; }[]or{ provide: string; useValue: any; }[]
exports?: Function[]exportsComponentsorNvModule(v1.2.1 add, actually useexportsfromNvModule) for otherNvModulesbootstrap?: FunctiondeclaresComponentfor Module bootstrap only if u don'tRoutertypescript
@NvModule({ imports: [ ShareModule, M2, ], components: [ Container, PComponent, R1, ], providers: [ HeroSearchService, { provide: HeroSearchService1, useClass: HeroSearchService1 }, { provide: HeroSearchService3, useValue: 123, }, ], exports: [ Container, PComponent, ShareModule, M2, ] }) class M1 {}javascript
class M1 {} NvModule({ imports: [ ShareModule, M2, ], components: [ Container, PComponent, R1, ], providers: [ { provide: 'heroSearchService', useClass: HeroSearchService1 }, { provide: 'heroSearchService1', useClass: HeroSearchService1 }, { provide: 'heroSearchService3', useValue: 123, }, ], exports: [ Container, PComponent, ShareModule, M2, ] })(M1);
Template Syntax
- Now we support: nv-model nv-text nv-html nv-if nv-class nv-repeat nv-key nv-on:Event
- And also support some directive which can use
DOM.directiveNameto set value, likenv-srcnv-hrefnv-alt Expect value from this.state and value from nv-repeat, and some directives can also use Fuction form this which must return a value like
nv-text="@addCount($.a, $.b)"nv-modelnv-on:eventcan't use Fuction as value- Fuction must from
Componentinstance - Arguments in Function can include:
- Element:
nv-text="@addCount($element)" - String:
nv-text="@addCount('xxx')" - Number:
nv-text="@addCount(123)" - Index:
$index, you can only use this in repeat DOM :<input nv-repeat="let b in $.testArray2" nv-class="@addCount($index)" /> - Variable:
nv-text="@addCount($.a)" - Boolean:
nv-text="@addCount(true, false)" - For nv-repeat: items like:
nv-repeat="let data in $.repeatData" nv-text="@addCount(data.show)"
- Element:
Event directive, like
nv-on:click- Text:
<p nv-text="$.b"></p>; - Template:
<p>this.$.b是:{{$.b}}</p>; - HTML:
<p nv-html="$.c"></p>; - Model for input:
'<input nv-model="$.c"/>';if input is a repeat DOM, and intem of Array is'nt an object, please use$index - Class:
<p class="b" nv-class="$.a"></p>'; - Directives: ues
nv-on:event<p nv-on:click="@componentClick()"></p>;
- If:
- Now
nv-ifwill remove this DOM - Because continually change DOM structure will lead the waste of performance, so please reduce use this template syntax
<p id="aa" nv-if="$.a" nv-on:click="@changeInput()">{{$.a}}</p>
- Now
- Repeat:
<p class="b" nv-class="$.a" nv-repeat="let a in $.b" nv-if="a.f">{{a.z}}</p> - Key:
- If u are repeating an
Componentor need continually change DOM structure, please usenv-keywithnv-repeat. nv-keyis a key for InDiv to mark repeatComponentorDOM, and it must be an unique value or index$index.- If u are not use
nv-repeatwithoutnv-key, InDiv will render this with a newComponent, andstateinComponentwill be reset. <test-component nv-repeat="let man in $.testArray" nv-key="man.id" man="{man.name}"></test-component>
- If u are repeating an
- Text:
About event function in Template Syntax
- Arguments in Function can include:
- Element:
nv-on:click="@click($element)" - Event:
nv-on:click="@click($event)" - String:
nv-on:click="@click('xxx')" - Number:
nv-on:click="@click(123)" - Index:
$index, you can only use this in repeat DOM :<input nv-on:click="this.show(b, $index)" nv-repeat="let b in $.testArray2" nv-model="b" nv-class="b" /> - Variable:
nv-on:click="@click($.a)" - Boolean:
nv-on:click="@click(true, false)" - For nv-repeat: items like:
nv-repeat="let data in $.repeatData" nv-if="data.show"
- Element:
- Arguments in Function can include:
Data monitor: this.state && this.setState
- Use
this.state: Objectandthis.setState(parmars: Function || Object) - If u have some variable, u can set
this.stateinconstructor(){} - If u want to change State, plz use
this.setState, parmars can beObjectorFunctionwhich must return anObject - And u can recive this change in life cycle
nvWatchState(oldState), but we need to use a deep clone forthis.state, so please minimize the use of life cycleWatchState
Watcher and KeyWatcher
import {Watcher, KeyWatcher}
Watcher
- Watcher expects two arguments:
data, watcher - data is an Object
- watcher is a function which has an arguments:
newDatanew Watcher(this.object, (newData) => {})
KeyWatcher
- Watcher expects there arguments:
data, key, watcher - data:
Object - key is a key in Object and type is
String - watcher is a function which has an arguments:
newDatanew KeyWatcher(this.object, key, (newData) => {})
Service
- Components shouldn't fetch or save data directly and they certainly shouldn't knowingly present fake data. They should focus on presenting data and delegate data access to a service.
- And u can use
Serviceto send communication betweenComponent, because we have realized singleton. Serviceaccepts an object{ isSingletonMode?: boolean }to decide use singleton or not, and default value ofisSingletonModeistrue- If you don't set
isSingletonModetofalseand use it inprovidersofNvModule, then allServicewill be singleton instance in an InDiv app and it will only have one instance in this app - If you use it in
providersofComponent,Servicewill be a singleton instance only in one instance ofComponent, whateverisSingletonModeofServiceis true or not. Injectedcan only inject service in module which own this service or component and in root module in TypeScriptClass static attribution
injectTokens: string[]acceptsprovide: stringofprovidersin JavaScript- typescript
- usr decorator
Injectableto declare service to use decorator
Injectedto injectServiceinconstructor's arguments ofService@Injected @Injectable({ isSingletonMode: false }) class HeroSearchService { public hsr: HeroSearchService1; constructor( private hsrS: HeroSearchService1, ) { this.hsr = hsrS; this.hsr.test(); } public test() { console.log('HeroSearchService !!!000000000'); } }
- javascript
- use
Injectable for inject dependences for service, please use Class static attribution
injectTokens: string[]likeComponentclass HeroSearchService { static injectTokens = [ 'heroSearchService1' ]; constructor(heroSearchService1) { this.hsr = heroSearchService1; this.hsr.test(); } test() { console.log('HeroSearchService !!!000000000'); } } Injectable({ isSingletonMode: false, })(HeroSearchService);
- Service
NVHttp: import NVHttp from 'indiv' and use it like aServicewhichisSingletonModeis false (@Injectable({ isSingletonMode: false })) - Service
Utils: import Utils from 'indiv' and use it like aServicewhichisSingletonModeis false (@Injectable({ isSingletonMode: false }))
Dependency Injection
- Dependency injection is an important application design pattern. It's used so widely that almost everyone just calls it DI.
Difference between
providersofComponentandprovidersofNvModuleprovidersofComponentwill be injected to component injector and will be the most preferential.providersofComponentis a singleton service only in one instance ofComponent, and the instance will only belong toComponentprovidersofNvModulewill be injected to root injector. If dependency can't be found inprovidersofComponent, it will be found inprovidersofNvModule- If a service in the
providersofNvModuleis a singleton service or not only depends onisSingletonMode
Basic Usage
Use Typescript
- If u are using
Typescriptto build an app, u can easily use our Dependency Injection. - use
@Injectedbefore theClasswhich need to use other services, that which are declarated inprovidersof it'sNvModuleor rootNvModule. - Use
this.names of constructor arguments to directly useService. - If you don't set
isSingletonModeofServicetofalse, allServicein an InDiv app will be a singleton instance and it will only have one instance in this app
import { Injected, Injectable, Component, NvModule, HasRender } from 'indiv'; @Injectable() class HeroSearchService1 { constructor() {} public test() { console.log('HeroSearchService !!!1111'); } } @Injected @Injectable() class HeroSearchService { public hsr: HeroSearchService1; constructor( private hsrS: HeroSearchService1, ) { this.hsrS.test(); this.state = { a: 1 }; } public test() { console.log('HeroSearchService !!!000000000'); } } @Injected @Component({ selector: 'pc-child', template: (` <div> <p nv-on:click="@go()">container: {{$.a}}</p> <input nv-model="$.a" /> <router-render></router-render> </div> `), providers: [ { provide: HeroSearchService1, useClass: HeroSearchService1, } ] }) class PCChild implements HasRender { public props: any; constructor( private hsrS: HeroSearchService, private hsrS1: HeroSearchService1, ) { this.hsrS.test(); this.state = { a: 1 }; } public nvHasRender() {} } @NvModule({ imports: [ M2, ], components: [ PCChild, ], providers: [ HeroSearchService, { provide: HeroSearchService1, useClass: HeroSearchService1, }, { provide: ValueService, useClass: 3333, }], }) class M1 {}- If u are using
Use Javascript
- A little diffrence between javascript and typescript.
use constructor arguments to directly use
Injectable, and assign them to a variable**class HeroSearchService1 { constructor() { super(); } test() { console.log('HeroSearchService !!!1111'); } } Injectable({ isSingletonMode: true })(HeroSearchService1); class HeroSearchService { static injectTokens = [ 'heroSearchService1' ]; constructor( heroSearchService1, ) { super(); this.hsr = heroSearchService1; this.hsr.test(); } test() { console.log('HeroSearchService !!!000000000'); } } Injectable({ isSingletonMode: false })(HeroSearchService); class Container { static injectTokens = [ 'heroSearchService', 'heroSearchService1', ]; constructor( heroSearchService, HeroSearchService1 ) { this.hsrS = heroSearchService; this.hsrS.test(); this.state = { a: 'a', }; } } Component({ selector: 'container-wrap', template: (` <div> <p>1232{{$.a}}</p> </div> `), providers: [ { provide: 'heroSearchService1', useClass: HeroSearchService1, } ] })(Container) class M1 {} NvModule({ imports: [ M2, ], components: [ Container, ], providers: [ { provide: 'heroSearchService', useClass: HeroSearchService, }, { provide: 'heroSearchService1', useClass: HeroSearchService1, }, { provide: 'value', useClass: 3333, }], })(M1);
LifeCycle hooks which from the beginning to the end:
NvModule
constructor()Components
- You can initialize state in life cycle hooks
constructor nvOnInit, after that u must usesetStateadd listener for additional attributes on state - After
constructoru can get this.props and use it - On
nvReceivePropsu can receive new props in argumentnextPropsand use old props withthis.props
constructor() nvOnInit(): void; nvBeforeMount(): void; nvAfterMount(): void; nvHasRender(): void; nvOnDestory(): void; nvWatchState(oldState?: any): void; nvRouteChange(lastRoute?: string, newRoute?: string): void; nvReceiveProps(nextProps: any): void; setter getter- You can initialize state in life cycle hooks
Router
routeChange((lastRoute?: string, newRoute?: string): void;
Server Side Render (@indiv/ssr-renderer: v1.1.0+)
- [npm](https://www.npmjs.com/package/@indiv/ssr-renderer)
- @indiv/ssr-renderer: v1.1.0+, indiv: v1.2.0+, NodeJs v6+
- export your InDiv instance and routes
- use `renderToString(url: string, routes: TRouter[], inDiv: InDiv): string`, render your app to string with `request.url`, `routes` and `InDiv instance`
- at last render string to your template
- details in [@indiv/ssr-renderer](https://github.com/DimaLiLongJi/indiv-ssr-renderer#readme)Architecture
route => NvModule => component
To do
- 类分离,通过use来绑定方法
- 无需route渲染
- 子路由(2/2)
- 组件化(3/3)
- 模块化 + NvModule
- 双向绑定
- 公共类提取
- 数据劫持
- 双向绑定模板
- Template Syntax: nv-text nv-html nv-model nv-class nv-repeat nv-key nv-if nv-src nv-href(7/7)
- 组件props
- 组件渲染
- 组件中使用组件
- 改用 history的pushState
- 监听路由变化动态渲染(2/2)
- Virtual DOM
- Service
- Route bug
- ts (强类型赛高)
- DI
- SSR 服务端渲染
- route lazy load
- async template render
- optimize setState