簡體   English   中英

Immutable.js關系

[英]Immutable.js relationships

想象一下約翰有兩個孩子愛麗絲和鮑勃的情況,鮑勃有一只貓獵戶座。

var Immutable = require('immutable');

var parent = Immutable.Map({name: 'John'});
var childrens = Immutable.List([
    Immutable.Map({name: 'Alice', parent: parent}),
    Immutable.Map({name: 'Bob', parent: parent})
]);
var cat = Immutable.Map({name: 'Orion', owner: childrens.get(1)});

幾年后,約翰想要重命名為簡。

var renamedParent = parent.set('name', 'Jane');

......讓孩子們了解它。

childrens = childrens.map(function(children) {
    children.set('parent', renamedParent);
});

然后我必須更新cat因為Bob改變了。

cat = cat.set('owner', childrens.get(1));

當一個對象發生變化時,是否可以自動更新所有相關對象? 我看了Cursors ,但我不確定它們是否是解決方案。 如果有可能,你能舉個例子嗎?

解釋這個問題:

當一個對象在不可變集合中更改時,是否可以自動更新所有相關對象?

簡短的回答

沒有。

答案很長

不,但在不可變數據結構中沒有任何變化,所以這不是問題。

甚至更長的答案

它更復雜......

不變性

不可變對象的重點是,如果你有一個不可變對象的引用,你就不必費心去檢查它的任何屬性是否已經改變。 那么,這不是問題嗎? 好...

后果

這有一些后果 - 無論好壞取決於您的期望:

  • pass-by-valuepass-by-reference語義之間沒有區別
  • 一些比較可以更容易
  • 當您將引用傳遞給某個對象時,您不必擔心代碼的其他部分會更改它
  • 當您從某個地方獲得對象的引用時,您知道它永遠不會改變
  • 您可以避免一些並發問題,因為沒有時間變化的概念
  • 當沒有任何變化時,你不必擔心變化是否是原子的
  • 使用不可變數據結構實現軟件事務存儲器 (STM)更容易

但世界是可變的

當然,在實踐中,我們經常處理隨時間變化的價值觀。 似乎不可變狀態不能描述可變世界,但人們有一些方法可以處理它。

以這種方式看待它:如果你在某個ID上有你的地址並且你移動到另一個地址,那么應該改變該ID以與新的真實數據保持一致,因為你不再住在那個地址。 但是,如果您在購買包含地址的物品時收到發票,然后更改地址,則發票保持不變,因為在發票開具時您居住在該地址仍然是正確的。 現實世界中的一些數據表示是不可變的,就像該示例中的發票一樣,並且一些數據表示像ID一樣可變。

現在舉個例子,如果你選擇使用不可變結構來建模數據,你必須考慮一下你從我的例子中考慮發票的方式。 數據可能不是最新的,但在某些時間點它始終是一致的,並且永遠不會改變。

如何應對變化

那么如何使用不可變數據建模​​變更呢? Clojure中使用VarsRefsAtomsAgents解決了一個很好的方法, ClojureScript (針對JavaScript的Clojure編譯器)支持其中一些(特別是Atoms應該像在Clojure中一樣工作但是有no Refs,STM,Vars或Agents - 看看ClojureScript和Clojure在並發功能方面有什么區別

看看Atom是如何在ClojureScript中實現的 ,似乎你可以使用普通的JavaScript對象來實現相同的目標。 它適用於像JavaScript對象本身是可變的,但它有一個屬性是對不可變對象的引用 - 你將無法更改該不可變對象的任何屬性,但你將能夠構造一個不同的不可變對象,並將舊的對象交換到頂級可變對象中的新對象。

Haskell這樣純粹功能性的其他語言可能有不同的方式來處理可變世界,比如monads (一個眾所周知難以解釋的概念--Javass的作者Douglas Crockford JSON 的好部分發現者將其歸因於“monadic curse”在他的談話Monads和Gonads )。

你的問題似乎很簡單,但它觸及的問題實際上非常復雜。 當然,如果在一個對象發生變化時是否有可能自動更新所有相關對象,那么對於回答“否”是錯誤的點,但是它比這更復雜,並且說在不可變對象中沒有任何變化(所以這個問題永遠不會發生)同樣無益。

可能的解決方案

您可以擁有一個頂級對象或變量,您可以從中始終訪問所有結構。 假設你有:

var data = { value: Immutable.Map({...}) }

如果您始終使用data.value (或更好的名稱)訪問您的數據,那么您可以將data傳遞給代碼的其他部分,並且每當您的狀態發生變化時,您只需分配一個新的Immutable.Map({... })您的data.value ,此時您使用data所有代碼都將獲得新值。

如何以及何時將data.value更新為新的不可變結構可以通過從用於更新狀態的setter函數自動觸發來解決。

另一種方法是在每個結構的層次上使用類似的技巧,例如 - 我使用變量的原始拼寫:

var parent = {data: Immutable.Map({name: 'John'}) };
var childrens = {data: Immutable.List([
    Immutable.Map({name: 'Alice', parent: parent}),
    Immutable.Map({name: 'Bob', parent: parent})
])};

但是你必須要記住,你所擁有的值不是不可變結構,而是那些引入了不可變結構的附加對象,這些結構引入了額外的間接層。

一些閱讀

我建議的是看一些項目和文章:

  • Peter Hausel撰寫的不可改變的React文章
  • Morearty.js - 像在Om中一樣使用React中的不可變狀態,但是用純JavaScript編寫
  • react-cursor - 用於Facebook的功能狀態管理抽象
  • Omniscient - 一個為React組件提供抽象的庫,允許快速自上而下的渲染包含不可變數據* Om - ClojureScript接口到React
  • mori - 在JavaScript中使用ClojureScript的持久數據結構的庫
  • Fluxy - Facebook的Flux 架構的實現
  • Facebook的Immutable - 持久的JavaScript數據集合,當與Facebook ReactFacebook Flux結合時,面臨着類似的問題(搜索如何將Immutable與React和Flux結合起來可能會給你一些好主意)

我希望這個答案即使不給出簡單的解決方案也會有所幫助,因為用不可變結構描述可變世界是一個非常有趣和重要的問題。

其他方法

對於不可變性的不同方法,使用代表數據的不可變對象(不一定是常數),請參閱Yegor Bugayenko及其文章中的Immutable Objects vs. Common Sense網絡研討會:

Yegor Bugayenko使用術語“不可變”,其意義略微不同於它在函數式編程環境中的意義。 他沒有使用不可變或持久的數據結構和函數式編程,而是提倡在原始意義上使用面向對象編程,這種方式是你永遠不會實際改變任何對象,但你可以要求它改變一些狀態,或者改變一些狀態。數據,本身被認為是與對象分開的。 很容易想象一個與關系數據庫對話的不可變對象。 對象本身可以是不可變的,但它仍然可以更新存儲在數據庫中的數據。 有點難以想象存儲在RAM中的某些數據可以被認為與數據庫中的數據完全獨立,但實際上並沒有太大區別。 如果您將對象視為暴露某些行為的自治實體並且您尊重它們的抽象邊界,那么將對象視為與數據不同的東西實際上很有意義,然后您可以使用具有可變數據的不可變對象。

如果有任何需要澄清,請評論。

正如rsp澄清的那樣,這個問題反映了不變性提供的固有權衡。

這是一個示例react / flux-ish應用程序演示了解決此問題的一種方法。 這里的關鍵是數字ID用於參考而不是Javascript引用。

根據我的理解,您可能使用不可變對象有點過高。 據我所知,不可變對象對於表示時間捕獲狀態很有用。 所以很容易比較state1 === state2。 但是,在您的示例中,您還擁有州內的時間捕獲關系。 將完整狀態與另一個完整狀態進行比較是很好的,因為它是不變的數據,因此它非常易讀,但也因此難以更新。 我建議你在添加另一個庫來修復這個問題之前,試着看看你是否可以在較低級別實現Immutable,你需要比較時間捕獲狀態。 例如,要知道1個實體(人/貓)是否發生了變化。

因此,在您想要使用不可變對象的地方,請使用它們。 但是如果你想要動態對象,不要使用不可變對象。

可能是這種情況可以通過使用ids來引用父級而不是父級記錄本身來解決。 我在這里尋求SQL(sorta)的指導:一條記錄​​不應該有一個指向內存中另一條記錄的指針,而應該知道另一條記錄的id

在我的應用程序中,我喜歡讓每個對象只知道其引用對象的主鍵,因此我不必擔心諸如對象發生變化之類的問題。 只要它的鍵保持不變(應該如此),我總能找到它:

var Immutable = require('immutable');

var people = Immutable.OrderedMap({
    0: Immutable.Map({name: 'John', id: '0'}),
    1: Immutable.Map({name: 'Alice', id: '1', parentId: '0'}),
    2: Immutable.Map({name: 'Bob', id: '2', parentId: '0'})
});
var cat = Immutable.Map({name: 'Orion', ownerId: '2'});

現在,我可以使用標准的immutable.js工具更改任何記錄的任何特征(除了當然的id ),而無需進行任何額外的更新。

people = people.set('0', people.get('0').set('name', 'Jane'))

在遇到這個完全相同的問題后,我感到沮喪並且無法解決問題,我決定完全將關系從我的對象中移出並管理各種常見關聯的邏輯(一對一,一對多,多對多)分別。

本質上,我正在管理與雙向映射的關系,並將關系邏輯集中在一個對象中,而不是像我傳統的情況那樣在每個建模數據中。 這意味着我需要提出觀察的邏輯,例如,父母已經改變並將更改傳播到子關系(因此是bimaps)。 然而,由於它在自己的模塊中被抽象化,因此可以重復使用它來模擬未來的關系。

我決定不使用ImmutableJS來處理這個問題,而是使用我自己的庫( https://github.com/jameslk/relatedjs )並使用ES6功能,但我想它可以用類似的方式完成。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM