2016-07-19 10 views
5

Próbuję zamienić dwoje dzieci elementu z przejściem za pomocą przycisku React.Dlaczego rekoncyliacja DOM w React nie działa zgodnie z oczekiwaniami?

<div style={{position: 'relative'}}> 
    {this.state.items.map((item, index) => (
    <div 
     key={item} 
     style={{position: 'absolute', 
       transform: `translateY(${index * 20}px)`, 
       transition: '1s linear transform'}}> 
     {item} 
    </div> 
))} 
</div> 

state.items jest tablicą dwóch przedmiotów. Gdy zostanie zmieniony, dwójka dzieci powinna przejść do swoich nowych pozycji.

To, co dzieje się w rzeczywistości, podczas gdy drugi element zmienia się zgodnie z oczekiwaniami, pierwszy odskakuje natychmiast.

O ile mogę powiedzieć, React uważa, że ​​może ponownie użyć jednego z elementów podrzędnych, ale nie drugi, chociaż dokumenty mówią, że jeśli użyjemy atrybutu key, powinien on zawsze ponownie użyć elementów: https://facebook.github.io/react/docs/reconciliation.html (przynajmniej , tak to rozumiem).

Co należy zmienić w moim kodzie, aby działało zgodnie z oczekiwaniami? Czy jest to błąd w React?

żywo Przykład: http://codepen.io/pavelp/pen/jAkoAG

+0

React zachowuje się tak jak powinien, nie ma gwarancji, że element będzie * Pobyt w DOM *, tylko że to będzie zoptymalizować co to da. To, czego potrzebujesz, to React's [ReactCSSTransitionGroup] (https://facebook.github.io/react/docs/animation.html). –

+0

ReactCSSTransitionGroup służy do tworzenia animacji Enter/Leave. Z dokumentacji biblioteki: "[To] ... jest łatwym sposobem na wykonywanie przejść i animacji CSS, gdy komponent React wchodzi na DOM lub z niego wychodzi". Nie jestem pewien, czy jest to przydatne do zmiany kolejności animacji. –

+0

Całkiem dobrze, myślałem, że użyłem go do wcześniejszego zamawiania. Przepraszam za błąd :) –

Odpowiedz

2

zastrzeżenie: Robię pewne założenia w tej odpowiedzi, mimo to świeci światło niektóre z moich (i wcześniej) pytania. Również moje rozwiązanie można prawie na pewno uprościć, ale w celu udzielenia odpowiedzi na to pytanie powinno być ono odpowiednie.


To jest świetne pytanie. Byłem nieco zaskoczony, otwierając narzędzia dla programistów i sprawdzając, co się dzieje podczas zamiany przedmiotów.

Jeśli spojrzysz, możesz sortować Zobacz, co React zamierza. Drugi element w ogóle nie zmienia swojego stylu i po prostu zamienia wewnętrzny węzeł tekstowy, podczas gdy pierwszy element jest upuszczany do dom jako świeży element.

Gdybym musiał zgadnąć, wynika to ze sposobu zamiany dwóch elementów w szyku, w którym co najmniej jeden element jest kopiowany do zmiennej temp i umieszczany z powrotem w tablicy.

Pomyślałem, że może jeśli zrobisz tłumaczenie losowo, oba elementy otrzymają rekwizyty nowego stylu i ożywią, ale to tylko sprawiło, że stało się jasne, że nie było to zamierzone zachowanie.

W drodze do znalezienia rozwiązania:

jako eksperyment, co jeśli stworzyliśmy węzły z wyprzedzeniem, i przekazać wskaźnik prop w renderowania poprzez React.cloneElement. Podczas gdy jesteśmy na to, w przeciwnym wypadku, wyrenderujmy: span ifi div. Bez kluczy do obaw.

http://codepen.io/alex-wilmer/pen/pbaXzQ?editors=1010

Otwarcie narzędzia dev ilustruje teraz dokładnie co React zamierza. React zachowuje elementy i zmienia tylko odpowiednią część, w tym przypadku węzeł innerText i typ elementu. Ponieważ style są zamieniane dokładnie na 1: 1, nie jest wymagana aktualizacja stylu.

Rozwiązanie:

Można wygenerować elementy reagować z wyprzedzeniem, zachować te w tablicy, i jako takie nie istnieją żadne klucze shuffle wokół i dowiedzieć się, jak umieścić z powrotem do DOM. Następnie użyj innej tablicy, aby śledzić zamierzoną kolejność. Prawdopodobnie bardzo zawiłe, ale działa!

http://codepen.io/alex-wilmer/pen/kXZKoN?editors=1010

const Item = function (props) { 
    return (
    <div 
     style={{position: 'absolute', 
     transform: `translateY(${props.index * 20}px)`, 
     transition: '0.5s linear transform'}}> 
     {props.children} 
    </div> 
) 
} 

const App = React.createClass({ 
    getInitialState() { 
    return { 
     items: [ 
     {item: 'One', C: <Item>One</Item>}, 
     {item: 'Two', C: <Item>Two</Item>} 
     ], 
     order: ['One', 'Two'] 
    }; 
    }, 
    swap() { 
    this.setState({ 
     order: [this.state.order[1], this.state.order[0]] 
    }); 
    }, 
    render: function() { 
    return <div> 
     <button onClick={this.swap}>Swap</button> 
     <div style={{position: 'relative'}}> 
     {this.state.items.map(x => 
      React.cloneElement(x.C, { 
      index: this.state.order.findIndex(z => z === x.item) 
      })) 
     } 
     </div> 
    </div>; 
    } 
}); 
+1

Dziękuję bardzo, Alex! Właściwie to uprościłem to nieco bardziej: http://codepen.io/pavelp/pen/OXQKLd - kluczem wydaje się po prostu nie zmieniać kolejności elementów. –

Powiązane problemy