React 中的双向绑定

4,890 阅读2分钟

 

在开发 Nautil 中我想做些不一样的尝试。

和主流的 react 生态格格不入,nautil 像是对 react 的反目,但实际上我希望在现有优秀的 UI 框架上,做一些不一样的东西。nautil 基于“观察者模式”,在 react 中实现类似 vue 一样的属性更新的响应式模式,以及今天要讲的双向绑定。

单向数据流

React 推崇单向数据流,组件通过 props 接收数据,控制 UI 展示。当用户对组件进行操作时,在每一个操作上,组件调用 props 上传递的函数,并将内部的状态数据作为参数传递给这些回调函数。而这些回调函数,必须修改原来传入的 props 值,才能保证组件的 UI 以新的界面展示。

单向数据流的好处,是保证了组件的纯粹性,用户不用担心副作用,而且严格按照 immutable 数据的方式控制 UI 展示。

双向绑定

在早期的 angular1.x 版本中,一个 directive 声明 scope 属性为 = 时,angular 会将该属性和外部传入的属性进行双向绑定。外部对应的属性发生变化时,该 directive 内部也会由于对应属性的变化而重新渲染。当然,这也是因为 angular 是脏检查机制,从顶往下全量检查。

在 vue 中,表单组件 input 等支持 v-model 属性实现双向绑定,这给单向数据流的应用框架提供了一种思路。vue 的实现,实际上是用一个 v-model 指令,完成单向的数据传入和 @input 事件响应。而正好因为 vue 基于模板语法,属性路径(keyPath)的使用非常方便,所以 v-model 的实现非常优雅。

在 react 中双向绑定

基于 v-model 的解决思路,一个双向绑定,实质是一个单向数据传入,和一个事件的响应。因此,我们在 react 中,也可以模拟这种实现。

class Input extends Component {
  static props = {
    $value: [String, Function],
  }

  render() {
    const { $value } = this.props
    const [value, reflect] = $value
    return <input type="text" value={value} onChange={e => reflect(e.target.value)} />
  }
}

这样的代码非常容易理解。无非是迫使一个 prop 传入的值是一个特定数组结构。

class App extends Component {
  state = {
    value: 'xxx',
  }
  render() {
    return <Input $value={[this.state.value, value => this.setState({ value })]} />
  }
}

按照这样的思路,我们基本可以实现 v-model 在 react 中的运行模式。但是显然,这样还不够简单,有没有更简洁的写法?

在 nautil 中实现了更简单的内部双向绑定操作,可在组件内像 vue 一样,写 this.attrs ++ 这样的语法。如果你感兴趣,不妨一试。