溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Nautil 中使用雙向數(shù)據(jù)綁定的實現(xiàn)

發(fā)布時間:2020-10-14 18:46:24 來源:腳本之家 閱讀:143 作者:否子戈 欄目:web開發(fā)

雖然是基于 react 的框架,但是在 nautil 中可以使用雙向數(shù)據(jù)綁定,這得益于基于觀察者模式的開發(fā)思路。在 react 中使用雙向綁定并非沒有需求,react 嚴格的單向數(shù)據(jù)流,嚴重影響了開發(fā)者的發(fā)揮空間,特別是在表單組件的使用中,很容易陷入回調(diào)地獄,即使 redux 也無法避免。

現(xiàn)有狀態(tài)管理的問題

我們都知道,react 是單向數(shù)據(jù)流的,數(shù)據(jù)只能從外部通過 props 傳入,再通過 props 上面?zhèn)魅氲幕卣{(diào)函數(shù)再傳出去,直接修改 props 或者上面的對象,不會帶來界面的更新,而且會導致數(shù)據(jù)不可預期。

基于這種單向數(shù)據(jù)流的 flux 思想,redux 還遵循了函數(shù)式編程的規(guī)范,保證了數(shù)據(jù)的干凈。同時,它提供了自頂向下的分發(fā)機制,修改 redux store 中的數(shù)據(jù),會觸發(fā)所有connected 的組件。而觸發(fā)過程是,調(diào)用 connected 組件 props.dispatch 方法。

雖然單向數(shù)據(jù)流的方式保證了數(shù)據(jù)流干凈,但 redux 的編程方式太復雜了。它不僅增加了數(shù)據(jù)構造本身的邏輯代碼,而且 action 代碼也是分散的,當你需要進行修改時,有的時候會在好幾個文件之間轉暈。雖然有很多優(yōu)化 redux 樣板代碼的庫,但受限于它的編程思想,仍然不好在項目中節(jié)省更多時間。

新的思維方式

出于節(jié)省更多時間成本的目的,我在開發(fā) nautil 中沒有使用 flux 那一套,而是另辟蹊徑,做了很像 mobx 但又更簡單的事。

我們來看一下如何在 nautil 中創(chuàng)建一個 store:

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

這樣我們就創(chuàng)建了一個 store,非常簡單,只傳入了默認值。而沒有各種 reducer 的樣板代碼。

Store 實例是一個可觀察的對象,通過 watch 方法,可以監(jiān)聽 store 中數(shù)據(jù)的變化。但凡能監(jiān)聽到數(shù)據(jù)變化,我們就可以在數(shù)據(jù)變化時,更新界面渲染。所以,在 nautil 中,觀察者模式是核心思想,是實現(xiàn) nautil 中各種響應式效果的前提條件。

如果你用過 vue 的話,你一定喜歡 vue 中操作數(shù)據(jù)的方式。在 vue 中要將輸出框組件和數(shù)據(jù)綁定非常容易:

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

當用戶在輸入框中輸入內(nèi)容時,this.name 也會隨之變化。而由于 vue 的響應式是自主綁定的,this.name 發(fā)生變化的同時,也會觸發(fā) vue 內(nèi)部對整個組件的重新渲染機制。這種將數(shù)據(jù)映射到視圖,再由視圖重新映射會數(shù)據(jù)的編程方式,在 angular 1.x 中隨處可見。

在 angular 中,通過 ng-click 等事件綁定,或者控制器中調(diào)用 $http 實現(xiàn)數(shù)據(jù)請求,在響應結束的時候,都會自動觸發(fā) angular 內(nèi)部的 digest,并通過臟檢查機制,從頂至底的去完成界面重新渲染,由于臟檢查的特質(zhì),根本不需要 react 那種要求數(shù)據(jù)是 immutable 的,即使原始數(shù)據(jù)被修改,新的界面也會被按照新的數(shù)據(jù)進行渲染。

我并不是說 angular 這種直接修改數(shù)據(jù)的方式更好,但起碼,在面對開發(fā)者時,它更直接,更容易理解,更符合編程習慣。

雙向綁定

從某些角度講,vue 是很容易讓人費解的。在 vue 的組件里,需要在組件內(nèi)內(nèi)置很多狀態(tài)來控制,這里的狀態(tài)指通過 data() 綁定到 this 上的各種響應式屬性。在組件內(nèi)部,修改 this.name 可以觸發(fā)組件的重新渲染。但是,奇怪的是,vue 不能通過這種方式修改 props 中傳入的數(shù)據(jù)。

這一點很讓人費解,對比 react,react 雖然支持組件內(nèi) state,但是比較強調(diào)組件的可控性,通過 props 來完全掌控 UI 界面的展示,也就是一個狀態(tài)對應一個 UI 界面。因此,react 提供了函數(shù)式組件,這種組件沒有自己的 state,這種組件最符合 react 主流思想的口味,而且,整個 react 編程也一以貫之,遵循這種 props 控制一切的理念。

但是,vue 明顯更強調(diào) this 上面屬性的響應式特性。卻又不提供 props 反寫的能力,讓人百思不解。另一個讓人百思不解的是,既然 vue 推崇它的屬性響應式特點,為何 vuex 卻要像 redux 那樣編程?甚至還要分 state, mutaion, action 三種東西,卻不繼續(xù)發(fā)揮屬性更新形式的響應式編程特點。

Nautil 在這條路上一走到底,將響應式編程發(fā)揮到極致。

簡單的講,“雙向綁定”是要做到組件內(nèi)和組件外數(shù)據(jù)的雙向修改,外部修改數(shù)據(jù)時,組件內(nèi)部即時響應變化,組件內(nèi)部修改數(shù)據(jù)時,外部整個應用的對應部分也隨即發(fā)生更新。這一點在 angular 1.x 中已經(jīng)實現(xiàn)了,為何新的框架反而不實現(xiàn)呢?

因此,我要在 nautil 實現(xiàn)的雙向綁定方案,更加徹底,更符合開發(fā)者想要的方式。

但是,如何在 react 里面實現(xiàn)雙向綁定呢?

vue 的 v-model 給了我啟示。我們?nèi)タ?v-model 指令,實質(zhì)上,它是一個將 v-bind 和 v-on 動作簡化的語法糖。

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

一個雙向綁定的語法,實際上是一個數(shù)據(jù)綁定和一個事件響應的結合體。不過 vue 有一個優(yōu)勢,它是基于模板解析的,所以寫法上非常有優(yōu)勢。而 react 如果要依靠編譯的話,非常不穩(wěn)定,因為不知道其他人打算怎么用。最后,我找到一種特別的語法,用來表達雙向綁定這種數(shù)據(jù)傳遞方式。

我們先來看下一個實現(xiàn)的效果:

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) // 創(chuàng)建一個可用于雙向綁定的宿主對象
  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 中的雙向綁定基于一個特定結構。在這個特定結構中,包含了值本身,和一個值改變時的回調(diào)函數(shù),當組件內(nèi)部的該值發(fā)生變化時,這個回調(diào)函數(shù)會被執(zhí)行,更新界面的動作,在回調(diào)函數(shù)中被執(zhí)行。而這個特定結構,被 createTwoWayBinding 抹平了結構在視覺上的差異。它的原始結構實際上是:

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

之所以 state.value = value 可以更新界面的渲染,是因為我們通過 observe 指令觀察了 store 的變化,從而在外層就讓界面可以根據(jù) 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 里面的內(nèi)置規(guī)則,甚至還可以這樣寫:

<Input $value={state} />

或者也可以利用前面提到的 createTwoWayBinding 函數(shù)(推薦用法):

const $state = createTwoWayBinding(state)

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

這樣寫可能更容易理解一些。

Input, Textarea 等表單組件都有雙向綁定功能。但是,假如現(xiàn)在你自己想寫一個組件,使用雙向綁定功能,你需要怎么寫?其實很簡單,只需要直接操作 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 的回調(diào)函數(shù)中,增加一個回調(diào)函數(shù)的調(diào)用。

需要注意,this.attrs.age ++ 這個語句,不會真的修改 this.attrs.age 的值,這個修改動作會被攔截,它只是在編程上順延了 js 語法,但實際上,它的效果是調(diào)用雙向綁定特定結構的第二個參數(shù),至于 this.attrs.age 的值是否真的變化,取決于雙向綁定特定結構第二個參數(shù)是否修改外部傳入的 age 值發(fā)生變化。

  1. 組件內(nèi)直接使用 this.attrs.age ++ 修改外部傳來的屬性,目標是反寫外部數(shù)據(jù)
  2. 外部組件在往子組件傳遞雙向綁定屬性時,需要傳入一個特定結構

createTwoWayBinding

該函數(shù)用于基于傳入的對象,創(chuàng)建一個用于雙向綁定的對象。它的傳入?yún)?shù)是任意的,但是我推薦使用 store 或 model 的 state,這樣就不用自己構造第二個參數(shù)。

但是,如果你想讓一個普通對象也可以實現(xiàn)響應式,你可以利用第二個參數(shù):

const { state } = this // react 的 state 本質(zhì)上是一個普通對象
const $state = createTwoWayBinding(state, ([state, keyPath, value], [target, key]) => {
 this.setState({ [key]: value })
})

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

目的上,createTwoWayBinding 最終是為雙向綁定服務的,所以不應該用它所創(chuàng)建的對象去讀取值。

小結

本文主要介紹了為什么要在 Nautil 中實現(xiàn)雙向綁定,怎么實現(xiàn),以及如何使用的問題。雖然本文主要是介紹 Nautil 中的雙向數(shù)據(jù)綁定,但是也討論了 react, vue, angular 的一些數(shù)據(jù)狀態(tài)管理的東西,如果你對這些問題也有自己的想法,不妨在下方給我留言一起討論。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI