Nautil 中使用双向数据绑定的实现

虽然是基于 react 的框架,但是在 nautil 中可以使用双向数据绑定,这得益于基于观察者模式的开发思路。在 react 中使用双向绑定并非没有需求,react 严格的单向数据流,严重影响了开发者的发挥空间,特别是在表单组件的使用中,很容易陷入回调地狱,即使 redux 也无法避免。

现有状态管理的问题

我们都知道,react 是单向数据流的,数据只能从外部通过 props 传入,再通过 props 上面传入的回调函数再传出去,直接修改 props 或者上面的对象,不会带来界面的更新,而且会导致数据不可预期。

基于这种单向数据流的 flux 思想,redux 还遵循了函数式编程的规范,保证了数据的干净。同时,它提供了自顶向下的分发机制,修改 redux store 中的数据,会触发所有connected 的组件。而触发过程是,调用 connected 组件 props.dispatch 方法。

虽然单向数据流的方式保证了数据流干净,但 redux 的编程方式太复杂了。它不仅增加了数据构造本身的逻辑代码,而且 action 代码也是分散的,当你需要进行修改时,有的时候会在好几个文件之间转晕。虽然有很多优化 redux 样板代码的库,但受限于它的编程思想,仍然不好在项目中节省更多时间。

新的思维方式

出于节省更多时间成本的目的,我在开发 nautil 中没有使用 flux 那一套,而是另辟蹊径,做了很像 mobx 但又更简单的事。

我们来看一下如何在 nautil 中创建一个 store:

import { Store } from 'nautil'
const store = new Store({
 some: 123,
})

这样我们就创建了一个 store,非常简单,只传入了默认值。而没有各种 reducer 的样板代码。

Store 实例是一个可观察的对象,通过 watch 方法,可以监听 store 中数据的变化。但凡能监听到数据变化,我们就可以在数据变化时,更新界面渲染。所以,在 nautil 中,观察者模式是核心思想,是实现 nautil 中各种响应式效果的前提条件。

如果你用过 vue 的话,你一定喜欢 vue 中操作数据的方式。在 vue 中要将输出框组件和数据绑定非常容易:

<input type="text" v-model="name" />

当用户在输入框中输入内容时,this.name 也会随之变化。而由于 vue 的响应式是自主绑定的,this.name 发生变化的同时,也会触发 vue 内部对整个组件的重新渲染机制。这种将数据映射到视图,再由视图重新映射会数据的编程方式,在 angular 1.x 中随处可见。

在 angular 中,通过 ng-click 等事件绑定,或者控制器中调用 $http 实现数据请求,在响应结束的时候,都会自动触发 angular 内部的 digest,并通过脏检查机制,从顶至底的去完成界面重新渲染,由于脏检查的特质,根本不需要 react 那种要求数据是 immutable 的,即使原始数据被修改,新的界面也会被按照新的数据进行渲染。

我并不是说 angular 这种直接修改数据的方式更好,但起码,在面对开发者时,它更直接,更容易理解,更符合编程习惯。

双向绑定

从某些角度讲,vue 是很容易让人费解的。在 vue 的组件里,需要在组件内内置很多状态来控制,这里的状态指通过 data() 绑定到 this 上的各种响应式属性。在组件内部,修改 this.name 可以触发组件的重新渲染。但是,奇怪的是,vue 不能通过这种方式修改 props 中传入的数据。

这一点很让人费解,对比 react,react 虽然支持组件内 state,但是比较强调组件的可控性,通过 props 来完全掌控 UI 界面的展示,也就是一个状态对应一个 UI 界面。因此,react 提供了函数式组件,这种组件没有自己的 state,这种组件最符合 react 主流思想的口味,而且,整个 react 编程也一以贯之,遵循这种 props 控制一切的理念。

但是,vue 明显更强调 this 上面属性的响应式特性。却又不提供 props 反写的能力,让人百思不解。另一个让人百思不解的是,既然 vue 推崇它的属性响应式特点,为何 vuex 却要像 redux 那样编程?甚至还要分 state, mutaion, action 三种东西,却不继续发挥属性更新形式的响应式编程特点。

Nautil 在这条路上一走到底,将响应式编程发挥到极致。

简单的讲,“双向绑定”是要做到组件内和组件外数据的双向修改,外部修改数据时,组件内部即时响应变化,组件内部修改数据时,外部整个应用的对应部分也随即发生更新。这一点在 angular 1.x 中已经实现了,为何新的框架反而不实现呢?

因此,我要在 nautil 实现的双向绑定方案,更加彻底,更符合开发者想要的方式。

但是,如何在 react 里面实现双向绑定呢?

vue 的 v-model 给了我启示。我们去看 v-model 指令,实质上,它是一个将 v-bind 和 v-on 动作简化的语法糖。

<input type="text" :value="name" @input="name = $event.target.value" />

一个双向绑定的语法,实际上是一个数据绑定和一个事件响应的结合体。不过 vue 有一个优势,它是基于模板解析的,所以写法上非常有优势。而 react 如果要依靠编译的话,非常不稳定,因为不知道其他人打算怎么用。最后,我找到一种特别的语法,用来表达双向绑定这种数据传递方式。

我们先来看下一个实现的效果:

import { Component, Store } from 'nautil'
import { createTwoWayBinding } from 'nautil/utils'
import { initialize, pipe, observe } from 'nautil/operators'
import { Section, Text, Input } from 'nautil/components'

export class OneComponet extends Component {
 static props = {
  store: Store,
 }
 render() {
  const { store } = this.attrs
  const { state } = store
  const $state = createTwoWayBinding(state) // 创建一个可用于双向绑定的宿主对象
  return (
   <Section>
    <Text>name: {state.name}</Text>
    <Input $value={$state.name} />
   </Section>
  )
 }
}

export default pipe([
 initialize('store', Store, { name: 'tomy' }),
 observe('store'),
])(OneComponent)

上面的代码利用了比较多的东西,例如 nautil 中的 Store 和指令。但单纯双向绑定这个点,你只需要注意 Input 组件的 $value 属性。在 nauti 中,$ 开头的属性表示双向绑定属性,它的值必须是一个特定结构,而非普通值。

从原理上将,nautil 中的双向绑定基于一个特定结构。在这个特定结构中,包含了值本身,和一个值改变时的回调函数,当组件内部的该值发生变化时,这个回调函数会被执行,更新界面的动作,在回调函数中被执行。而这个特定结构,被 createTwoWayBinding 抹平了结构在视觉上的差异。它的原始结构实际上是:

$value={[state.value, value => state.value = value]}

之所以 state.value = value 可以更新界面的渲染,是因为我们通过 observe 指令观察了 store 的变化,从而在外层就让界面可以根据 store 的变化而更新。

利用双向绑定

对于组件本身而言,如何利用双向绑定完成一些事情呢?我们来看Input 组件的源码:

export class Input extends Component {
 render() {
  const { type, placeholder, value, ...rest } = this.attrs

  const onChange = (e) => {
   const value = e.target.value
   this.attrs.value = value // 主要是这一句
   this.onChange$.next(e)
  }

  return <input
   {...rest}

   type={type}
   placeholder={placeholder}
   value={value}

   onChange={onChange}
   onFocus={e => this.onFocus$.next(e)}
   onBlur={e => this.onBlur$.next(e)}
   onSelect={e => this.onSelect$.next(e)}

   className={this.className}
   style={this.style}
  />
 }
}

对于 Input 组件而言,中间比普通 react 组件多了一句 this.attrs.value = value,这句话利用了双向绑定特殊结构的第二个值,进行值的回传和反写。也就是说,在 nautil 中,双向绑定具有兼容性,你可以这样写:

<Input value={state.value} onChange={e => state.value = e.target.value} />

也可以这样写(标准写法):

<Input $value={[state.value, value => state.value = value]}

当然,如果你知道 nautil 里面的内置规则,甚至还可以这样写:

<Input $value={state} />

或者也可以利用前面提到的 createTwoWayBinding 函数(推荐用法):

const $state = createTwoWayBinding(state)

<Input $value={$state.value} />

这样写可能更容易理解一些。

Input, Textarea 等表单组件都有双向绑定功能。但是,假如现在你自己想写一个组件,使用双向绑定功能,你需要怎么写?其实很简单,只需要直接操作 this.attrs 上的属性即可:

import { Component } from 'nautil'
import { Button } from 'nautil/components'

export class Some extends Component {
 static props = {
  $age: Number,
 }
 render() {
  return (
   <Button onHint={() => this.attrs.age ++}>grow</Button>
  )
 }
}

这样的写法比较严格,要求外部传入的时候,必须传入 $age 这个属性,而不允许传入 age 属性。为了兼容,你可以学习 Input 组件的做法,在 onHint 的回调函数中,增加一个回调函数的调用。

需要注意,this.attrs.age ++ 这个语句,不会真的修改 this.attrs.age 的值,这个修改动作会被拦截,它只是在编程上顺延了 js 语法,但实际上,它的效果是调用双向绑定特定结构的第二个参数,至于 this.attrs.age 的值是否真的变化,取决于双向绑定特定结构第二个参数是否修改外部传入的 age 值发生变化。

  1. 组件内直接使用 this.attrs.age ++ 修改外部传来的属性,目标是反写外部数据
  2. 外部组件在往子组件传递双向绑定属性时,需要传入一个特定结构

createTwoWayBinding

该函数用于基于传入的对象,创建一个用于双向绑定的对象。它的传入参数是任意的,但是我推荐使用 store 或 model 的 state,这样就不用自己构造第二个参数。

但是,如果你想让一个普通对象也可以实现响应式,你可以利用第二个参数:

const { state } = this // react 的 state 本质上是一个普通对象
const $state = createTwoWayBinding(state, ([state, keyPath, value], [target, key]) => {
 this.setState({ [key]: value })
})

<Input $value={$state.name} />

目的上,createTwoWayBinding 最终是为双向绑定服务的,所以不应该用它所创建的对象去读取值。

小结

本文主要介绍了为什么要在 Nautil 中实现双向绑定,怎么实现,以及如何使用的问题。虽然本文主要是介绍 Nautil 中的双向数据绑定,但是也讨论了 react, vue, angular 的一些数据状态管理的东西,如果你对这些问题也有自己的想法,不妨在下方给我留言一起讨论。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Vue父子组件双向绑定传值的实现方法

    父子组件之间的双向绑定非常简单,但是很多人因为是从Vue 2+开始使用的,可能不知道如何双向绑定,当然最简单的双向绑定(单文件中)就是表单元素的 v-model 了,例如 <input type="text" /> 它会响应表单元素的 value 属性,当输入框文本改变时,会将 value 值传给 v-model 所绑定的变量,如果同时设置 v-model 和 value , value 属性无效. 父子组件的自定义双向 v-model 当若干dom封装成组件时,在父组件中

  • Vue 实现双向绑定的四种方法

    1. v-model 指令 <input v-model="text" /> 上例不过是一个语法糖,展开来是: <input :value="text" @input="e => text = e.target.value" /> 2. .sync 修饰符 <my-dialog :visible.sync="dialogVisible" /> 这也是一个语法糖,剥开来是: <my

  • Vue实现双向绑定的方法

    本文能帮你做什么? 1.了解vue的双向数据绑定原理以及核心代码模块 2.缓解好奇心的同时了解如何实现双向绑定 为了便于说明原理与实现,本文相关代码主要摘自vue源码, 并进行了简化改造,相对较简陋,并未考虑到数组的处理.数据的循环依赖等,也难免存在一些问题,欢迎大家指正.不过这些并不会影响大家的阅读和理解,相信看完本文后对大家在阅读vue源码的时候会更有帮助< 本文所有相关代码均在github上面可找到 https://github.com/DMQ/mvvm 相信大家对mvvm双向绑定应该都不

  • Vuejs第一篇之入门教程详解(单向绑定、双向绑定、列表渲染、响应函数)

    什么是组件? 组件(Component)是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能.在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展. 接下来给大家介绍vuejs单向绑定.双向绑定.列表渲染.响应函数基础知识,具体详情如下所示: (一)单向绑定 <div id="app"> {{ message }} </div> <sc

  • 深入理解vue.js双向绑定的实现原理

    前言 大家都知道Vue.js最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统.本文仅探究几乎所有Vue的开篇介绍都会提到的hello world双向绑定是怎样实现的.先讲涉及的知识点,再参考源码,用尽可能少的代码实现那个hello world开篇示例. 一.访问器属性 访问器属性是对象中的一种特殊属性,它不能直接在对象中设置,而必须通过defineProperty()方法单独定义. var obj = { }; // 为obj定义一个名为hello的访问器属性 Object.defin

  • Vue.js每天必学之数据双向绑定

    Vue.js 的模板是基于 DOM 实现的.这意味着所有的 Vue.js 模板都是可解析的有效的 HTML,且通过一些特殊的特性做了增强.Vue 模板因而从根本上不同于基于字符串的模板,请记住这点. 插值 文本 数据绑定最基础的形式是文本插值,使用 "Mustache" 语法(双大括号): <span>Message: {{ msg }}</span> Mustache 标签会被相应数据对象的 msg 属性的值替换.每当这个属性变化时它也会更新. 你也可以只处理

  • 浅谈angular.js中实现双向绑定的方法$watch $digest $apply

    Angular.js 中的特性,双向绑定. 多么神奇的功能,让视图的改变直接反应到数据中,数据的改变又实时的通知到视图,如何做到的? 这要归功于 scope 下面3个重要的方法: $watch $digest $apply 他们的区别是什么,我们来介绍下: $watch 这是一个监听 scope 上数据的监听器 方法说明: $scope.$watch('参数',function(newValue,oldValue){ //逻辑处理 }) 上面我们就是创建了一个监听器. '参数' 就是$scope

  • Vue2实现组件props双向绑定

    Vue学习笔记-3 前言 Vue 2.x相比较Vue 1.x而言,升级变化除了实现了Virtual-Dom以外,给使用者最大不适就是移除的组件的props的双向绑定功能. 以往在Vue1.x中利用props的twoWay和.sync绑定修饰符就可以实现props的双向绑定功能,但是在Vue2中彻底废弃了此功能,如果需要双向绑定需要自己来实现. Vue2的组件props通信方式 在Vue2中组件的props的数据流动改为了只能单向流动,即只能由组件外(调用组件方)通过组件的DOM属性attribu

  • Nautil 中使用双向数据绑定的实现

    虽然是基于 react 的框架,但是在 nautil 中可以使用双向数据绑定,这得益于基于观察者模式的开发思路.在 react 中使用双向绑定并非没有需求,react 严格的单向数据流,严重影响了开发者的发挥空间,特别是在表单组件的使用中,很容易陷入回调地狱,即使 redux 也无法避免. 现有状态管理的问题 我们都知道,react 是单向数据流的,数据只能从外部通过 props 传入,再通过 props 上面传入的回调函数再传出去,直接修改 props 或者上面的对象,不会带来界面的更新,而且

  • vue3.0中的双向数据绑定方法及优缺点

    熟悉vue的人都知道在vue2.x之前都是使用object.defineProperty来实现双向数据绑定的 而在vue3.0中这个方法被取代了 1. 为什么要替换Object.defineProperty 替换不是因为不好,是因为有更好的方法使用效率更高 Object.defineProperty的缺点: 1. 在Vue中,Object.defineProperty无法监控到数组下标的变化, 导致直接通过数组的下标给数组设置值,不能实时响应. push() pop() shift() unsh

  • vue中的双向数据绑定原理与常见操作技巧详解

    本文实例讲述了vue中的双向数据绑定原理与常见操作技巧.分享给大家供大家参考,具体如下: 什么是双向数据绑定? vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化.这也是算是vue的精髓之处了.值得注意的是,我们所说的数据双向绑定,一定是对于UI控件来说的,非UI控件不会涉及到数据双向绑定.单向数据绑定是使用状态管理工具的前提,如果我们使用vuex,那么数据流也是单向的,这时就会和双向数据绑定有冲突,我们可以这么解决.

  • AngularJS框架中的双向数据绑定机制详解【减少需要重复的开发代码量】

    本文实例讲述了AngularJS框架双向数据绑定机制.分享给大家供大家参考,具体如下: 之前写的一篇<AngularJS入门示例之Hello World详解> ,介绍ng-model的时候提到:使用AngularJS的双向数据绑定机制,不需要我们编写繁琐的代码来实现同样的功能.现在我们看一个比较震撼的例子,看看angularJS是如何减少我们在前端开发中的繁琐劳动的.越是感受到框架功能的强大,越是能够激发学习的兴趣和动力. 假如我们有一个学生信息列表,包含学生的姓名.地址和年龄信息.假如这个数

  • 浅谈Angularjs中不同类型的双向数据绑定

    Angularjs1.X中两种不同的双向数据绑定 聊聊 Angularjs1.x中那些活见鬼的事情. 一. html与Controller中的双向数据绑定 html-Controller的双向数据绑定,在开发中非常常见,也是Angularjs1.x的宣传点之一,使用中并没有太多问题. 1.1数据从html流向controller 也就是从 视图层 流向 模型层 ,原生html中需要使用表单元素(例如 input 标签)来收集用户输入信息,Angularjs中通过在表单元素上使用 ng-model

  • vue.js自定义组件实现v-model双向数据绑定的示例代码

    我们都清楚v-model其实就是vue的一个语法糖,用于在表单控件或者组件上创建双向绑定. //表单控件上使用v-model <template> <input type="text" v-model="name" /> <input type="checkbox" v-model="checked"/> <!--上面的input和下面的input实现的效果是一样的--> <

  • JavaScript中双向数据绑定详解

    双向数据绑定指的是将对象属性变化绑定到UI,或者反之.换句话说,如果我们有一个拥有name属性的user对象,当我们给user.name赋予一个新值是UI也会相应的显示新的名字.同样的,如果UI包括了一个输入字段用来输入用户名,输入一个新的值会导致user对象中的那么属性发生变化. 许多流行的客户端JavaScript框架例如Ember.js,AngularJS以及KnockoutJS都将双向数据绑定作为自己的头号特性.但是这并不意味着从零开始实现双向数据绑定就很困难,同样的当我们需要双向数据绑

  • js项目中双向数据绑定的简单实现方法

    目录 前言 发布订阅者模式 结果 调用 总结 前言 双向数据绑定 指的是当对象的属性发生变化时能够同时改变对应的UI,反之亦然.换句话说,如果我们有一个user对象,这个对象有一个name属性,无论何时你对user.name设置了一个新值,UI也会展示这个新的值.同样的,如果UI包含一个用于数据用户名字的输入框,输入一个新值也会导致user对象的name属性发生相应的改变. 许多流行的javascript框架,像Ember.js,Angular.js或者KnockoutJS都会把双向数据绑定作为

  • 深入了解Vue中双向数据绑定原理

    目录 数据的变化反应到视图 命令式操作视图 声明式操作视图 小结 视图的变化反应到数据 现存的问题 数据的变化反应到视图 前面我们了解到数据劫持之后,我们可以在数据发生修改之后做任何我们想要做的事情,操作视图当然也是OK的 命令式操作视图 目标:我们通过原始的操作dom的方式让每一次的name的最新值都能显示到p元素内部 <div id="app"> <p></p> </div> <script> let data = { n

  • 深入浅析AngularJS中的一次性数据绑定 (bindonce)

    一.理解双向数据绑定和监听器 为了实现双向数据绑定,AngularJS使用了$watch API来观察期作用域中的模型变化.具体的作用域取决于你的代码如何编写.如果你没有创建一个自作用域,就是说没有使用ngController指令在你的DOM和你的控制器代码之间创建一个关联,你可能处理的是跟作用域$rootScope,这个作用域由ngApp自动创建,并且是应用中所有作用域的父作用域,当然,如果你选择手动启动AngularJS,那情况就另当别论了.每当你创建了一个数据绑定时,AngularJS就会

随机推荐