indiv
v1.2.1
Published
一个 web mvvm 库。A web mvvm library.
Downloads
18
Maintainers
Readme
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) => {}
- Use
this.setLocation(path: String, query: Object, params: Object)
to go to Path orlocation.href
- Use
this.getLocation()
to get location states Router
:http://localhost:1234/R1
- From v1.2.1, we have removed
setState, setLocation, getLocation
, and you can usesetState, setLocation, getLocation
fromimport { setState, getLocation, setLocation } from 'indiv'
- Use
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 javascriptComponent 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 TypeScriptIn 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 instancePlease 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 lifecycleUse
nvOnInit
andnvReceiveProps(nextProps: any): void;
to receive props and setprops
instate
Use
setLocation
andgetLocation
to controll route or get route infoUse
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
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` declare `template` and `state`
- 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 injected `Service` in `constructor`'s arguments of `Component`**
```typescript
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);
}
}
```
- javascript
- To use function `Component` declare `template` and `state`
- Lifecycle hooks are equal in TypeScript, and have no use `implements` interface
- **Use Class static attribution `injectTokens: string[]` and every item is `provide: string` of provider in `NvModule`**
- **Class static attribution `injectTokens: string[]` must be `provide` of providers, and arguments of constructor will be instances of `useClass` or value of `useValue` of providers**
```javascript
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);
```
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 TypeScriptIn 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 appIf 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 the `Class` which need to use other services, that which are declarated in `providers` of it's `NvModule` or root `NvModule`. - Use `this.` names of constructor arguments to directly use `Service`. - If you don't set `isSingletonMode` of `Service` to `false`, all `Service` 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 {}
- 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
- [x] 类分离,通过use来绑定方法
- [x] 无需route渲染
- [x] 子路由(2/2)
- [X] 组件化(3/3)
- [x] 模块化 + NvModule
- [X] 双向绑定
- [x] 公共类提取
- [x] 数据劫持
- [x] 双向绑定模板
- [x] Template Syntax: nv-text nv-html nv-model nv-class nv-repeat nv-key nv-if nv-src nv-href(7/7)
- [x] 组件props
- [x] 组件渲染
- [x] 组件中使用组件
- [x] 改用 history的pushState
- [x] 监听路由变化动态渲染(2/2)
- [x] Virtual DOM
- [x] Service
- [x] Route bug
- [x] ts (强类型赛高)
- [x] DI
- [x] SSR 服务端渲染
- [ ] route lazy load
- [x] async template render
- [x] optimize setState