因为对Rxjs的好感玩上了Cycle.js,《Cycle.js学习笔记》系列用于记录使用该框架的一些笔记。
本文将继续针对第四节中未完成的input值获取进行debug,详细记录debug过程。
这里我们可以先回放第四节–《Cycle.js学习笔记4–使用Class和装饰器》。
我们使用Class建立的Input组件是酱紫的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import xs from 'xstream'; import { run } from '@cycle/run'; import { makeDOMDriver } from '@cycle/dom'; let id = 0; export class InputComponent{ DOM; value; constructor(domSources, type){ const inputValue$ = domSources.select('#input' + id).events('keyup') .map(ev => {console.log(ev.target.value); return ev.target.value}).startWith('') const input$ = inputValue$.map(val => { return <input type={type} id={'input' + id++} className="form-control" value={val} /> }) this.DOM = input$ this.value = inputValue$ } }
|
我们希望通过new InputComponent
的方式来注册这样一个双向绑定的Input
,第四节中,我们的结果是,每次只有第一次监听和更新状态是生效的。
这里我们通过对比的方式来进行debug:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| export function LoginComponent(sources) { const domSource = sources.DOM; const loginClick$ = domSource.select('#submit').events('click') const inputValue$ = domSource.select('#uname').events('keyup') .map(ev => { console.log(ev.target.value); return ev.target.value }).startWith('') const pwdInputSource = new InputComponent(domSource, 'password') const pwdInputDOM$ = pwdInputSource.DOM const pwdInputValue$ = pwdInputSource.value const loginView$ = xs.combine(inputValue$, pwdInputDOM$, pwdInputValue$).map(([inputValue, pwdDOM, pwdValue]) => { return ( <form> <h1>System</h1> <div> <input type="text" id="uname" className="form-control" value={inputValue} /> </div> {inputValue} <div> {pwdDOM} </div> {pwdValue} <div> <a className="btn btn-default" id="submit">Login</a> </div> </form> ) } ); return { DOM: loginView$, router: loginClick$.mapTo("/app") }; }
|
很明显,手动获取的inputValue
能响应input更新状态,但通过InputComponent
注册的Input和值只有第一次才能更新状态。
开始的时候,本骚年一直以为是Driver驱动的问题,但是这样对比一看,原因一下子出来了[捂脸]:
因为每次更新状态后,pwdDOM
的值更新了,虽然生成一个完全一样的input,但是由于是个变量,因此每次都是移除重新种植。
故原有的input被移除,连同在它上面绑定的监听事件一起。
实现
要拿到Input的实时value,本骚年想到了三种方法:
- 只针对
value
进行绑定,即手动获取的Input值,并且手动写DOM。
- 不更新
InputComponent
的基本DOM,只更新里面的value。
- 将输入和输出拆离,也就是设置输入,以及将状态更新流出。
第一种方法,写了跟没写似的,没多少用处,故我们直接略过实现第二种吧。
第二种方法,我们要实现除了初始化之外,每次更新值不更新对应的DOM,其实也没有多难,我们可以通过缓存的方式来进行:
- 初始化的时候缓存DOM。
- 每次更新获取DOM并更新值。
其实这跟很多的框架是一样的,像Angular、React、Vue等,无非是实现虚拟DOM、或者其他方式的diff过程。
具体大家可以翻阅不同代码的源码,这里就不多说了。有个React的虚拟DOM实现文章可以参考:《深度剖析:如何实现一个 Virtual DOM 算法》。
好吧,我们来做第三种。
输入设置和输出更新
输入输出与双向绑定
其实双向绑定无非是个输入设置和输出更新的语法糖,像Angular里面就是:
1 2 3 4 5
| <my-sizer [(size)]="fontSizePx"></my-sizer> <my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>
|
这里我们的Input则会有三种方法:
- 获取DOM。
- 设置DOM的value。
- 获取DOM的value。
实现输入设置和输出更新
我们根据上面的想法,初步预设这样的思路:
set value
:该流将作为DOM的源,可更新DOM值
get value
:该流以DOM为源,更获取DOM的更新
dom
:以set value
为源,且为get value
的源
这样我们其实可以脱离调用者的sources
了,初步实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| let id = 0; @bindMethods export class InputComponent { id; DOM; inputGet; inputSet; constructor(domSource, type) { this.id = id++ this.inputSet = xs.of(undefined) this.DOM = xs.merge(this.inputSet).map(val => <input type={type} id={'input' + this.id} className="form-control" value={val} /> ) this.inputGet = domSource.select('#input' + this.id).events('keyup') .map(ev => { console.log(ev.target.value); return ev.target.value }).startWith('') } getDOM(){ return this.DOM } getValue(){ return this.inputGet } setValue(val){ } }
|
这里我们将流的入口流和出口流拆开了,这样就能拿到每次input更新的流了。至于设置值的方法还没想好,应该可以参考参考Driver的设计。
这次我们能正常获取手动设置的input,以及通过注册设置的input的值啦。
结束语
这节主要围绕上一次的无法获取到input更新值的问题,进行debug并解决。
但目前还木有完全实现双向绑定,其中缺了一个就是set value
的实现,这块我们后面可以跟driver驱动一起讲。
此处查看项目代码
此处查看页面效果
查看Github有更多内容噢:https://github.com/godbasin
更欢迎来被删的前端游乐场边撸猫边学前端噢