MVVM
- mvvm由model -> view, model <- view的原型演化而来(也就是说交互存在两个方向:v -> m, v <- m),在二者之间加入vm解耦。
- v收到用户指令要改动m,不能直接访问m,必须使用vm提供的方法间接访问m,所以v的结点可能需要提供到m中映射数据的索引或引用。
- vm应监控m中的数据,一旦数据有变,vm通知v更新对应的v节点,所以,vm中应有v节点和对应数据的binding。
- 那么v和m中的数据和ui组件如何绑定呢?可将数据结构处理为类似哈希表的形式:生成共同的id,在v和m中对应的对象可使用id提取。
MVC(后端)
- config 配置文件的意义:编写代码时,时常遇到要写死在代码中的常量,其中的大部分常量有自己的生命周期,时常面临变更的需要,它们实际上是一种生命周期更长的状态(state),因此,基于
高内聚、低耦合
的原则,很有必要将它们从逻辑流程中抽离,从实际使用的意义来说,配置文件是杠杆:一点点的修改可以精准地撬动大量的行为变化
react
- 关键词:UI , View,virtual-DOM , 函数式,declarive,生命周期,无状态组件,高阶组件,受控组件
- render中
return ( <> )
return后的括号不可少,否则有风险无法识别tag,括号中的元素必须归于同一个根节点 - state变量位于每个<>实例私有作用域中,相当于在constructor中声明private变量,所以state和节点实例一一对应,多用于根部节点。
- state要用
setState{prop: value}
来改变,改变之后重新调用对应节点的class的render,生成新的virtualDOM。 - 组件抽象成了对象,开发者可以在抽象程度很高的层面上把控view,view几乎相当于自动从data上长出来的;react开发相当于只操作data,更改了data后react自动更新view,省去了对view的操作。
- props是组件上的多层次数据缓存
- react在html节点层和app层之间加了一个组件层,其抽象程度位于二者之间。组件层将逻辑渗透在节点之间。
- render<>就是调用React.createElement()
- 花括号中必须是表达式,不能是语句,因为花括号的内容将作为createElement的参数;不能在其中对数据进行改动——比如push,这样意味着view对data的直接操作,that’s chaos。
- virtualDOM:用R.createElement生成的轻量对象,是对DOM的简化抽象。
- 在改动DOM前,setState会触发render新的virtualDOM,然后与旧的virtualDOM比对,筛出有改动的部分,然后在对那部分进行DOM实际操作,提升了效率。
- setState是异步操作,在render时才触发更改state,可避免多次setState频繁刷新。
- 开发者从始至终没有直接操作view,而是告诉react如何render data to view。
- 受控组件使用于输入直接影响render方式的情况
- 我理解的react流程:0.开发时组件树的结构预先编写在各组件的render中 1.初始化,实例化各个组件代理(一堆方法的集合)。2.将组件树通过组件代理的render映射为虚拟DOM树 3.如果有旧的虚拟DOM树,对比新旧虚拟DOM得出要更新的部分 4.浏览器根据第2/3步的结果渲染出DOM。
- 感想:传统MV*模式,M和V间的交互中问题主要原因是Model非常抽象,而View(DOM)非常具象,这导致这二者的交流需要中介的转译,当二者抽象程度相差巨大时,中介的构造也变得非常复杂,难于操作。react实现了在与Model同样的抽象层级上对View的操作,它含有的抽象层级数量可以由开发者控制,就像一条可长可短的纽带。
- state中的数据应尽可能原始、简单,数据结构可以在render前部构造。
- view对model的更改必须由root组件把method作为参数传给底层组件。
- render是纯函数,组件实例中包含状态与行为,render只是其中的一个行为。
- render中return的对象中必须都是value,不能有直接暴露的语句,因为其中的jsx要编译为React.createElement({}),render中的value都要映射到参数对象的值,如果存在语句就会出错。若又复杂逻辑必须有语句处理的话,可用三元表达式或匿名函数或在class中先声明一个返回组件的方法,再在render中调用这个方法。
- render props 使得children 和 父组件解耦,它们中间多了一层逻辑
- React.createElement: (type: string, props: {}, …children: ReactNode[]) => ReactElement。注意: children是数组。children是props中的一个属性(不同于vue的slot)
- ts在react中往往用于规范props和state的格式
- redux中store的改变如何触发render: store.subscribe(render)
- componentWillReceiveProps(nextProps){}可以用来比较新旧props,如果…则…,可以setState,不会额外调用render。设计的使用场景大概是用来侦测上游state,如果有了xx变化则启动xx流程,只要父组件render被调用^有props接入(不管有无变化),然后就会触发(在其他生命周期之前,甚至在判断是否需要更新组件之前),如果使用redux,当store变化后,很可能马上会调用所有组件的willReceive。在其中发出请求的时候要格外注意判断条件,起码先比对之前的props(props的前后变化相当于一个action),否则容易导致浏览器无限循环请求。
- 不建议在componentWillReceiveProps中篡改reload,因为这么做导致reload难以共享
- 每次render必定由上游的setState或全局Store的dispatch触发。
- 关于diff算法,目前的理解是: 父组件render产生n层的子节点树,diff层层递进(应该是前序遍历),每个子树的每层是个数组,如果同一index对应的节点type有变且没有key,则销毁并更新(如果该节点是组件还会触发willunmount并卸载组件实例对象)
- componentDidUpdate的用法: 如果要依照渲染完成后DOM元素的属性(height等等)实时更新数据,则可考虑用它。需要注意的是,如果在其中setState或更新props则很可能导致死循环,为此必须给更新行为加if条件。
Vue
- 不需要使用ko类似的闭包来感知状态的变化,简单地赋值即可,比起ko方便了很多,简直不能再方便了,这样的方便是如何实现的呢?答案是
Object.defineProperty(o, value,{get(){return -}, set(newV){--}})
- vue的组件在使用前须先注册(两种情况:全局和局部)
- 局部注册:
new Vue({components:{c1: valueOfComponent, c2: valueOfComponent}})
- vm有状态:data&computed,有行为method&watch,有view: template,同react的组件相似,vue的组件也不是单纯的UI,是数据、行为、视图混合的更抽象的组件
- computed在data和view中加了个中间层,某种角度上可以说是渲染数据的第一步。
- Vue-cli组件中,注册模板在script标签中的components属性
- 目前看来,vm中的callback触发时this都指向vm。因此值得注意的是尽量不要使用箭头函数。
- vue-router可在路由/:id中输入参数,通过{{ this.$route.params.id}}提取参数
- 生命周期mounted相当于react的componentDidMount,在mounted更新状态后触发updated生命周期
- 我猜测生命周期应是这样分布于vm的constructor中:
function Vm (){this.beforeCreate = ...; this.lifeCycle = ...; //先写入生命周期钩子,然后在构造函数中合适的点插入调用生命周期 this.xx = xx; this.beforeCreate(); this.xx = xx; this.lifeCycle()}
- 有的生命周期则在contructor之外调用,比如mounted,估计是在父组件的render中调用:
vm.render(); vm.挂载();vm.mounted;
- props的改变不会触发beforeUpdate
- vue更新view也是异步:当一个数据变动后,不会立即更新DOM,而是由eventLoop机制控制,在任务队列空了以后才更新DOM。
- vuex中:1 一个mutation即一个改变state的行为 2 一个action是对一组mutation的抽象,在vuex中以promise的形式执行,以方便异步操作 3 getter用来方便组件取用state,一般置入computed 4 组件中触发action采用如此形式:
this.$store.dispatch('ACTION_NAME', payLoad)
- 感兴趣的功能:.mixin, .use, directive, render, slot,ref
- 在beforeMount和Mounted之间,也就是挂载过程中,vue开始把模板译为DOM,如果遇到子组件,则在注册的组件中搜寻该组件并create和挂载。
- 生命周期围绕这些行为:1 创建组件实例 2 挂载组件vDOM -> DOM 3 更新数据 4 销毁组件
- 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。当组件在 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行
- 可以像router-view一样切换显示的组件
- 异步的action,我曾试着这样处理:把异步的promise放在store里,要用的时候
data.then(innerData => this.data = innerData)
,但是这样有一个缺点就是视图无法或者难以响应promise的变化\ - vue中父组件可通过$refs直接访问子组件的实例和数据。
knockout
- 可能使用了观察者模式,在ko.observable中埋伏了.fire(),每当调用observable对数据进行更改时,fire()会通知viewmodel,以这种方法监控对数据的调用。以下是我理解的实现方式:
function observable(value){
return function(replaceVal){
if(replaceVal){
ko.fire() //通知viewModel有改动
value = replaceVal;
}else{
return value;
}
}
}
- 用data-bind绑定view和model。考虑到表现和行为的分层,data-bind最好在js中的init函数中写入。
- 在octopus模式中,对data的取用往往需要在octopus对象上增添一个相应的method,这很笨拙且浪费资源。ko用obsevable返回的函数来代理data变量,解决了这个问题。
- data-bind="text: xx"可以理解为text(context.xx),text是ko的method{把context.xx写入节点的text},冒号后面的是参数。
- data-bind也可以绑定各种表达式(函数,三元操作之类的).
- data-bind=“click: foo”,回调函数foo调用的方式是context.foo()
- ko.applyBindings(obj)会绑定obj为binding context root,foreach:和with:会使部分节点范围内的binding context变小
- 若要访问binding context范围外的数据,可使用binding context提供的变量$parent 、$data、$root等等
- ko的事件会将触发事件的节点上的binding-context对象作为this参数传给handler
this.a = ko.obsevable(1); this.a = ko.obsevable(1); this.sum = ko.computed(function)(){ return this.a() + this.b() }, this)
ko.computed的原理:当你修改a时,必然是a(newValue),调用a会触发computed中的匿名回调函数从而更新sum的值;computed的监控在a、b是对象的时候对a、b的属性无效,因为此时a、b是指针,当它们的属性变化(比如增加一个属性)时,a、b指向的内存地址并没有变,而且,修改a的属性只需要a().attr = newValue
,没有给a传递val,ko将操作识别为读取而不是写。