ソースを参照

Merge pull request #141 from SimoTod/bug/nested-proxies

Fix nested proxies issue
Caleb Porzio 5 年 前
コミット
f7ef5e61f3
4 ファイル変更57 行追加5 行削除
  1. 0 0
      dist/alpine.js
  2. 0 0
      dist/alpine.js.map
  3. 14 4
      src/component.js
  4. 43 1
      test/data.spec.js

ファイルの差分が大きいため隠しています
+ 0 - 0
dist/alpine.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/alpine.js.map


+ 14 - 4
src/component.js

@@ -73,7 +73,11 @@ export default class Component {
 
         const proxyHandler = {
             set(obj, property, value) {
-                const setWasSuccessful = Reflect.set(obj, property, value)
+                // If value is an Alpine proxy (i.e. an element returned when sorting a list of objects),
+                // we want to set the original element to avoid a matryoshka effect (nested proxies).
+                const setWasSuccessful = value['$isAlpineProxy']
+                    ? Reflect.set(obj, property, value['$originalTarget'])
+                    : Reflect.set(obj, property, value)
 
                 // Don't react to data changes for cases like the `x-created` hook.
                 if (self.pauseReactivity) return setWasSuccessful
@@ -90,14 +94,20 @@ export default class Component {
                 return setWasSuccessful
             },
             get(target, key) {
+                // Provide a way to determine if this object is an Alpine proxy or not.
+                if (key === "$isAlpineProxy") return true
+
+                // Provide a hook to access the underlying "proxied" data directly.
+                if (key === "$originalTarget") return target
+
                 // If the property we are trying to get is a proxy, just return it.
                 // Like in the case of $refs
-                if (target[key] && target[key].isRefsProxy) return target[key]
+                if (target[key] && target[key].$isRefsProxy) return target[key]
 
                 // If property is a DOM node, just return it. (like in the case of this.$el)
                 if (target[key] && target[key] instanceof Node) return target[key]
 
-                // If accessing a nested property, retur this proxy recursively.
+                // If accessing a nested property, return this proxy recursively.
                 // This enables reactivity on setting nested data.
                 if (typeof target[key] === 'object' && target[key] !== null) {
                     return new Proxy(target[key], proxyHandler)
@@ -317,7 +327,7 @@ export default class Component {
         // For this reason, I'm using an "on-demand" proxy to fake a "$refs" object.
         return new Proxy({}, {
             get(object, property) {
-                if (property === 'isRefsProxy') return true
+                if (property === '$isRefsProxy') return true
 
                 var ref
 

+ 43 - 1
test/data.spec.js

@@ -1,5 +1,5 @@
 import Alpine from 'alpinejs'
-import { fireEvent, wait } from '@testing-library/dom'
+import { wait } from '@testing-library/dom'
 
 global.MutationObserver = class {
     observe() {}
@@ -63,3 +63,45 @@ test('functions in x-data are reactive', async () => {
 
     await wait(() => { expect(document.querySelector('span').innerText).toEqual('baz') })
 })
+
+test('Proxies are not nested and duplicated when manipulating an array', async () => {
+    document.body.innerHTML = `
+        <div x-data="{ list: [ {name: 'foo'}, {name: 'bar'} ] }">
+            <span x-text="list[0].name"></span>
+            <button x-on:click="list.sort((a, b) => (a.name > b.name) ? 1 : -1)"></button>
+            <h1 x-on:click="list.sort((a, b) => (a.name < b.name) ? 1 : -1)"></h1>
+        </div>
+    `
+
+    Alpine.start()
+
+    // Before this fix: https://github.com/alpinejs/alpine/pull/141
+    // This test would create exponentially slower performance and eventually stall out.
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('foo') })
+    document.querySelector('button').click()
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('bar') })
+    document.querySelector('h1').click()
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('foo') })
+    document.querySelector('button').click()
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('bar') })
+    document.querySelector('h1').click()
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('foo') })
+    document.querySelector('button').click()
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('bar') })
+    document.querySelector('h1').click()
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('foo') })
+    document.querySelector('button').click()
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('bar') })
+    document.querySelector('h1').click()
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('foo') })
+    document.querySelector('button').click()
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('bar') })
+    document.querySelector('h1').click()
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('foo') })
+    document.querySelector('button').click()
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('bar') })
+    document.querySelector('h1').click()
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('foo') })
+    document.querySelector('button').click()
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('bar') })
+})

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません