因为对 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. 创建流,提取出监听器。
  2. 将 1 步骤创建的流作为输入更新 Input 的 value。
  3. 在调用setValue()方法时触发监听器。
  4. 将 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 值每秒自增,同时获取到的值也触发更新。 当我们讲输入和输出连接到一起的时候,我们就实现了简单的双向绑定。前面也说过了,双向绑定只是个语法糖而已,拆开来说也就是能设置输入,并获取输出。

这时候,我们巧妙地使用getset,就可以实现双向绑定:

@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)

部分文章中使用了一些网站的截图,如果涉及侵权,请告诉我删一下谢谢~
温馨提示喵