因为对 Rxjs 的好感玩上了 Cycle.js,该系列用于记录使用该框架的一些笔记。本文我们从了解 Driver 驱动开始,完成 Input 值的设置set value
,然后实现 Input 的双向绑定。
# Input 输入流
有小伙伴们把 Cycle.js 翻译中文了,大家可以看看中文文档 (opens new window)。
# Driver 驱动
这里我们先来看看什么是 Driver。 Driver 其实是一些函数,它用来监听输入流,然后执行必要的副作用操作,最后可能会返回输出流。
至于抽象想象什么的,本骚年已经在《Cycle.js 学习笔记 5--关于框架设计和抽象》 (opens new window)中发挥过脑洞的力量了。
Driver 应该始终与某些I/O effect
联系在一起。
大多数 Driver,比如DOM Driver
,以sink
(为了描述write
)作为输入,以source
(为了捕获read
)作为输出。
像我们要实现一个 Websocket 的 Driver:
import {adapt} from '@cycle/run/lib/adapt';
function makeSockDriver(peerId) {
let sock = new Sock(peerId);
function sockDriver(outgoing$) {
// 添加流监听
outgoing$.addListener({
next: outgoing => {
sock.send(outgoing));
},
error: () => {},
complete: () => {},
});
// 创建流
const incoming$ = xs.create({
start: listener => {
sock.onReceive(function (msg) {
listener.next(msg);
});
},
stop: () => {},
});
return adapt(incoming$);
}
return sockDriver;
}
# 实现流创建和监听
我们希望能通过set value
的方式注入流,我们可以创建一个流,然后调用监听器:
- 创建流,提取出监听器。
- 将 1 步骤创建的流作为输入更新 Input 的 value。
- 在调用
setValue()
方法时触发监听器。 - 将 Input 的
change
或者keyup
事件流,合并 3 步骤更新的流,作为输出。
我们大概能得到这样一个 Input:
@bindMethods
export class InputComponent {
id;
listener;
DOM;
inputGet;
inputSet;
constructor(domSource, type) {
this.id = id++;
// 初始化流,并提取出监听器
this.inputSet = xs
.create({
start: listener => {
this.listener = listener;
},
stop: () => {}
})
.startWith(undefined);
// 接上设置流
this.DOM = xs
.merge(this.inputSet)
.map(val => (
<input
type={type}
id={"input" + this.id}
className="form-control"
value={val}
/>
));
// 合并输入流和源流,然后输出更新值
this.inputGet = xs
.merge(
domSource
.select("#input" + this.id)
.events("keyup")
.map(ev => ev.target.value),
this.inputSet
)
.startWith("");
}
getDOM() {
return this.DOM;
}
getValue() {
return this.inputGet;
}
setValue(val) {
// 触发监听器
this.listener.next(val);
}
}
# 检验实现
检验的时候到了,我们通过设置一个定时器,触发 Input 输入的自动更新,当然,你也可以自己手动输入验证。
export function LoginComponent(sources) {
const domSource = sources.DOM;
// 登录点击和路由切换
const loginClick$ = domSource.select("#submit").events("click");
// 通过InputComponent注册的Input和值
const unameInputSource = new InputComponent(domSource, "text");
const unameInputDOM$ = unameInputSource.getDOM();
const unameInputValue$ = unameInputSource.getValue();
// 设计一个定时器,每秒自增1,并输入到username的input
let a = 1;
setInterval(() => {
unameInputSource.setValue(a++);
}, 1000);
// 合流生成最终DOM流
const loginView$ = xs
.combine(unameInputDOM$, unameInputValue$)
.map(([unameDOM, unameValue]) => {
return (
<form>
<h1>System</h1>
<div>{unameDOM}</div>
{unameValue}
<div>
<a className="btn btn-default" id="submit">
Login
</a>
</div>
</form>
);
});
return {
DOM: loginView$,
router: loginClick$.mapTo("/app")
};
}
# 双向绑定
我们能看到,在定时器的作用下,Input 值每秒自增,同时获取到的值也触发更新。 当我们讲输入和输出连接到一起的时候,我们就实现了简单的双向绑定。前面也说过了,双向绑定只是个语法糖而已,拆开来说也就是能设置输入,并获取输出。
这时候,我们巧妙地使用get
和set
,就可以实现双向绑定:
@bindMethods
export class InputComponent {
// 其他没有改变
// 将 getValue 和 setValue 调整为 get 和 set 的方式
get value() {
return this.inputGet;
}
set value(val) {
// 触发监听器
this.listener.next(val);
}
}
这样,我们在使用的时候:
export function LoginComponent(sources) {
// ...其他
// 通过InputComponent注册的Input和值
const unameInputSource = new InputComponent(domSource, "text");
const unameInputDOM$ = unameInputSource.getDOM();
// 通过获取值的方式
const unameInputValue$ = unameInputSource.value;
// 设计一个定时器,每秒自增1,并输入到username的input
let a = 1;
setInterval(() => {
// 通过设置的方式
unameInputSource.value = a++;
}, 1000);
}
塔嗒!是不是好了。
# 结束语
这节主要简单(真的很简单)介绍了驱动 Driver,并成功地完成了之前没有完成的部分,完成 Input 的输入,并将 Input 的输入和输出衔接在一起,对外呈现出一种双向绑定的方式。
此处查看项目代码 (opens new window)
此处查看页面效果 (opens new window)