indiv v1.2.1
InDiv
一个 web mvvm 库。 A web mvvm library.
current version: v1.2.1
demo
npm run start
npm 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
bootstrapModule
to bootstrap a root moduleNvModule
const indiv = new InDiv(); indiv.bootstrapModule(M1); indiv.use(router); // if u are using a router indiv.init();
Create a router:
Routes must an
Array
includesObject
- Routes must incloude rootPath
'/'
- Routes must set an component name like
component: 'R1'
,R1
is aComponent
selector
fromRoot NvModule
.You can use thisComponent
fromexports
of otherNvModule
inRoot 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
Component
in typescript or use functionComponent
in javascript - Component types:
type TComponentOptions<State> = { selector: string; template: string; providers: (Function | { provide: any; useClass: Function; } | { provide: any; useValue: any; })[]; };
- Recommend to use
Service
withRXjs
on communication betweenComponents
selector: string;
is your component tag name for HTML and used in template.template: string
only accepts$.
as value ofnv-directive
fromthis.state
, andnv-on:event
only accepts@eventHandler
from method which belongs to Class instance$.
in template isthis.state.
providers
declaresService
forComponent
Providers is an
Array
which has three modes:- Mode1:
Function
is equal to mode2:{ provide: Function; useClass: Function; }
- Mode2:
{ provide: any; useClass: Function; }
- Mode3:
{ provide: any; useValue: any; }
usesuseValue
for injector
- Mode1:
provide
can bestring
,function
orClass
in TypeScript- In TypeScript these three modes can be used and provide can be
function
orClass
- In javascript please use
{ provide: string; useClass: Function; }[]
or{ provide: string; useValue: any; }[]
template
only accepts$.XXX
from this.state, and nv-on:event only accepts@eventHandler
from method which belongs to Class instance- Please use
setState
after lifecycleconstructor()
andnvOnInit
, and you can change or set value forthis.state
withoutsetState
in lifecycleconstructor()
andnvOnInit
- From v1.2.1, we have removed
setState, setLocation, getLocation
, and you can usesetState, setLocation, getLocation
fromimport { setState, getLocation, setLocation } from 'indiv'
- After
Class
'sconstructor
, u can usethis.props
in any lifecycle - Use
nvOnInit
andnvReceiveProps(nextProps: any): void;
to receive props and setprops
instate
- Use
setLocation
andgetLocation
to controll route or get route info Use
Component
intemplate
- 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.propsValue
Props can use three types:
- value from
this.state
and value from repeat data:women="{$.name}"
women="{man.name}"
- value use function from
Component
instance with return value:man="{@countState(man.name)}"
- Function form
Component
instance (must use@
):handler="{@getProps}"
- value from
- Use like a HTML element tag:
About
state
andprops
- action of set value of
state
or usingsetState
is a synchronous action - action of using callback function from
props
to changestate
which comes fromparent Component
inchild Component
is a synchronous action - rerender of Component is an asynchronous action, and after rerendering
props
can be changed inchild Component
- so after using callback function from
props
to changestate
which comes fromparent Component
inchild Component
,props
inchild Component
can't be changed immediately because of out render mechanism is asynchronous render.You should usenvReceiveProps(nextProps: any): void;
to watchprops
changes - from v1.2.1 we has remove
setState
inComponent
instance, and u can usesetState
fromimport { setState } from 'indiv'
- action of set value of
typescript
- To use decorator
Component
declaretemplate
andstate
- To implements interface
OnInit, BeforeMount, AfterMount, HasRender, OnDestory, ReceiveProps, WatchState, RouteChange
to use lifecycle hooks - To use decorator
Injected
to declare which need to be injectedService
inconstructor
'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
Component
declaretemplate
andstate
- Lifecycle hooks are equal in TypeScript, and have no use
implements
interface - Use Class static attribution
injectTokens: string[]
and every item isprovide: string
of provider inNvModule
- Class static attribution
injectTokens: string[]
must beprovide
of providers, and arguments of constructor will be instances ofuseClass
or value ofuseValue
of 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: Object
is data whichclass Controller
sends toclass Component
props: Object
can only be changed or used after lifecycleconstructor()
NvModule
- InDiv apps are modular and InDiv has its own modularity system called
NvModule
. - An
NvModule
is 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?: Function
inoptions
imports?: Function[]
imports otherNvModule
and can use it'sexports?: Function[]
components: Function[]
declaresComponents
providers
declaresService
forComponent
orService
Providers is an
Array
which has three modes:- Mode1:
Function
is equal to mode2:{ provide: Function; useClass: Function; }
- Mode2:
{ provide: any; useClass: Function; }
- Mode3:
{ provide: any; useValue: any; }
usesuseValue
for injector
- Mode1:
provide
can bestring
,function
orClass
in TypeScript- In TypeScript these three modes can be used and provide can be
function
orClass
- In javascript please use
{ provide: string; useClass: Function; }[]
or{ provide: string; useValue: any; }[]
exports?: Function[]
exportsComponents
orNvModule
(v1.2.1 add, actually useexports
fromNvModule
) for otherNvModule
sbootstrap?: Function
declaresComponent
for Module bootstrap only if u don'tRouter
typescript
@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.directiveName
to set value, likenv-src
nv-href
nv-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-model
nv-on:event
can't use Fuction as value- Fuction must from
Component
instance - 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-if
will 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
Component
or need continually change DOM structure, please usenv-key
withnv-repeat
. nv-key
is a key for InDiv to mark repeatComponent
orDOM
, and it must be an unique value or index$index
.- If u are not use
nv-repeat
withoutnv-key
, InDiv will render this with a newComponent
, andstate
inComponent
will 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: Object
andthis.setState(parmars: Function || Object)
- If u have some variable, u can set
this.state
inconstructor(){}
- If u want to change State, plz use
this.setState
, parmars can beObject
orFunction
which 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:
newData
new 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:
newData
new 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
Service
to send communication betweenComponent
, because we have realized singleton. Service
accepts an object{ isSingletonMode?: boolean }
to decide use singleton or not, and default value ofisSingletonMode
istrue
- If you don't set
isSingletonMode
tofalse
and use it inproviders
ofNvModule
, then allService
will be singleton instance in an InDiv app and it will only have one instance in this app - If you use it in
providers
ofComponent
,Service
will be a singleton instance only in one instance ofComponent
, whateverisSingletonMode
ofService
is true or not. Injected
can only inject service in module which own this service or component and in root module in TypeScriptClass static attribution
injectTokens: string[]
acceptsprovide: string
ofproviders
in JavaScript- typescript
- usr decorator
Injectable
to declare service to use decorator
Injected
to injectService
inconstructor
'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[]
likeComponent
class 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 aService
whichisSingletonMode
is false (@Injectable({ isSingletonMode: false })
) - Service
Utils
: import Utils from 'indiv' and use it like aService
whichisSingletonMode
is 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
providers
ofComponent
andproviders
ofNvModule
providers
ofComponent
will be injected to component injector and will be the most preferential.providers
ofComponent
is a singleton service only in one instance ofComponent
, and the instance will only belong toComponent
providers
ofNvModule
will be injected to root injector. If dependency can't be found inproviders
ofComponent
, it will be found inproviders
ofNvModule
- If a service in the
providers
ofNvModule
is a singleton service or not only depends onisSingletonMode
Basic Usage
Use Typescript
- If u are using
Typescript
to build an app, u can easily use our Dependency Injection. - use
@Injected
before theClass
which need to use other services, that which are declarated inproviders
of it'sNvModule
or rootNvModule
. - Use
this.
names of constructor arguments to directly useService
. - If you don't set
isSingletonMode
ofService
tofalse
, allService
in 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 usesetState
add listener for additional attributes on state - After
constructor
u can get this.props and use it - On
nvReceiveProps
u can receive new props in argumentnextProps
and 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