使用typescript搭建前端框架InDiv
时间:2023-04-06 00:13:52
HTML5
同事跟我说:需求还是不够,还有时间造轮子。..前言这个轮子是2018年4月22日到2018年10月12日造的,看了一篇关于前端框架路由实现原理的文章,想尝试一下路由。结果越写越多,最后莫名其妙的变成了mvvm框架。顺便写了一个比较草率的文档和服务端渲染。..当前版本:v1.2.0项目地址文档npmInDiv介绍这个名字其实是随便起的,因为要将组件包裹在一个div中,所以叫InDiv。整个项目都是用typescript写的,真心的说ts真的很优雅。在思考怎么写的时候,参考了很多ngreactvue的架构和实践,用自己能想到的最好的方法实现了。喜欢ng,也许我真的很喜欢angular)。后来实现了服务端渲染,但是有点简陋。..此刻,向三大框架的开发者致敬。造轮子并不容易,主要分为模块(NvModule)、组件(Component)、服务。该模板使用字符串模板。我定义了一些指令如:nv-class,nv-repeat等,然后在模板中只能使用来自状态的值($.)和来自组件实例的实例方法(@),所以它看起来很丑(先创建它)。目前没有命令和管道。其实在字符串模板中,可以使用组件上带返回值的方法(nv-src="@buildSrc($.src)"),返回值会被渲染到模板中,并被认为即暂时没有管道Replenish。内置路由,使用基于虚拟DOM的异步渲染,但是目前还没有路由的懒加载。模块负责导入和导出组件,导入其他模块和注册服务。当前模块中的组件可以使用根模块和当前模块中的任何服务和组件,以及从导入模块导出的组件。如果没有特殊声明,任何模块中声明的服务都会成为全局单例,但组件或服务只能注入当前模块中的服务或根模块中的服务;组件中声明的服务会跟着组件实例走,每个组件实例都有一个独立的服务实例。(实际上实现了一个3级注入器)。组件实现多个生命周期。在ts中可以传implements类型,而在js中只能手写生命周期方法。通过Object.defineProperty监控状态。任何直接改变state的属性,通过setState改变state的操作都是同步操作,会导致当前组件重新渲染;而在子组件中,通过在props中调用父组件的方法来改变parent状态,当组件处于状态时,子组件不会立即获取更新的props,因为渲染是异步的,props只能在之后获取渲染。因为Object.defineProperty是用来监听状态的,无法监听状态中数组项的添加、插入、移除,所以如果要改变数组结构,请只使用setState来重置状态中的项。ts中实现了依赖构造函数的参数类型作为token的依赖注入,需要通过@Injected声明注入;在js中,只实现了依赖静态属性injectTokens:string[]将字符串声明为token的服务。axios被封装成一个http服务,我平时用到的一些工具都收集在了Utils类中。使用组件通过注解Component来提供元数据,并声明选择器、模板和组件提供者。通过注解Injected,声明以下需要注入服务的类。使用implements实现生命周期钩子函数。在组件中提供SetState、GetLocation、SetLocation等类型可以使用ths.setState、this.getLocation、this.setLocation等内置方法改变状态、获取路由状态、设置路由状态等。如果你使用JavaScript开发,不能使用Injected声明需要注入服务,使用类的静态属性injectTokens:String[]和其他注入服务几乎一样。import{Component,SetState,GetLocation,SetLocation,Injected,OnInit,RouteChange,OnDestory}from'indiv';importTestServicefrom'../../service/test';@Injected@Component({selector:'app-container-component',template:(`名称:{{test.name}}id:{{test.id}}
`),providers:[{provide:TestService,useClass:TestService,},//也可以直接接入TestService,TestService当做指令牌],})exportdefaultclassAppContainerComponentimplementsOnInit,RouteChange,OnDestory{publicstate:{showSideBar:string;测试列表:{id:数字;名称:字符串;}[];}公共setState:SetState;constructor(privatetestS:TestService,){this.subscribeToken=this.testS.subscribe(this.subscribe);}publicnvOnInit(){this.state={showSideBar:'open',testList:[{id:0,name:'dima'},{id:1,name:'xxx'}]};}publicnvRouteChange(lastRoute?:string,newRoute?:string):void{}publicnvOnDestory(){this.subscribeToken.unsubscribe();}publicchangeShowSideBar(){if(this.state.showSideBar==='open'){this.state.showSideBar='close';}else{//this.state.showSideBar='open';setState也可以使用this.setState({showSideBar:'close'});ServiceandDependencyInjection和angular服务类似,默认是全局单例,但是可以在@Injectable({isSingletonMode:false})中指定isSingletonMode为false,这样就不会在IOC中创建服务实例容器,每次注入都会重新通过工厂函数通过注解Injected创建一个新的服务实例,服务也可以注入到其他服务中';import{Injectable,Injected}from'indiv';@Injected@Injectable()导出默认类TestService{公共数据:数字;公共主题:主题<任何>;构造函数(私有测试服务2:测试服务2){this.data=1;this.subject=newSubject();}公共订阅(乐趣:(值:任意)=>void):订阅{returnthis.subject.subscribe({next:fun,});}publicupdate(value:any){this.subject.next({next:value,});}publicunsubscribe(){this.subject.subscribe();}}module模块通过@NvModule装饰器接收五个参数,声明某些组件(components)和服务(services)属于这个模块。该模块可以将组件导出到其他模块需要一个根模块才能使用整个应用程序,如果不使用路由,该组必须定义bootstrapimport{NvModule}from'indiv';从'../pages/app.container.component'导入AppContainerComponent;从'../service/test.service';importTestService2from'../service/test2.service';@NvModule({imports:[],//导入其他模块providers:[{provide:TestService,useClass:TestService,},TestService2,],components:[AppContainerComponent,],exports:[AppContainerComponent,],bootstrap:AppContainerComponent,//如果路由不适用,需要在根模块中声明bootstrap组件})exportdefaultclassAppModule{}从'indiv'导入{InDiv};constinDiv=newInDiv();inDiv.bootstrapModule(AppModule);//inDiv.use(router);使用路由inDiv.init();lifecyclehook只实现了以下几种类型,这里借鉴了很多reactexcept类的settergetter也可以作为生命周期构造函数()nvOnInit():void;nvBeforeMount():无效;nvAfterMount():无效;nvHasRender():void;nvOnDestory():无效;nvWatchState(oldState?:State):void;nvRouteChange(lastRoute?:string,newRoute?:string):void;nvReceiveProps(nextProps:State):void;虚拟DOM将DOM结构转换为VNode,比较差异并将其应用于真实DOM。其实和react的diff算法类似。diff子元素只比较同一级别的子元素。禁止跨层diff优先匹配新旧VNode中tagName和key相同的元素,计算位置差。如果没有匹配到子元素,找到它的位置,放入插入队列中,匹配新旧VNode。如果不是InDiv自定义组件元素,则开始比较两个匹配元素的属性事件,继续比较下一层。如果子元素匹配的新旧两个VNode是InDiv自定义组件元素,则跳过匹配下一层子元素,将diff交给组件编译器,最后更新每个组件内的每个队列,跟随第一个shift移除、插入和替换属性等之后做支持自定义指令路由延迟加载使用Proxy代替Object.defineProperty或实现脏检查取消状态和props@indiv/cli最后,其他字符串模板、http、utils路由等。文档里都有,写的不好还请见谅(估计没人看得懂)。不明白的可以去看文档的源码,完全是用indiv实现的。(吹一波牛逼)其实整个项目都是心血来潮写的,并没有写单元测试。估计bug不少。作为一个前端菜鸟,我还是知道自己的很多不足,明白好记性不如烂笔头的道理,多造轮子永远不会错。最后感谢大家观看最后