Redux: Bagaimana cara membuat daftar generik sebagai peredam dan penambah komponen?

Dibuat pada 30 Sep 2015  ·  50Komentar  ·  Sumber: reduxjs/redux

Halo. Apa cara yang baik untuk memperluas contoh penghitung ke daftar penghitung independen yang dinamis?

Dengan dinamis maksud saya bahwa di UI di akhir daftar penghitung akan ada tombol + dan - untuk menambahkan penghitung baru ke daftar atau menghapus yang terakhir.

Idealnya counter reducer dan komponen akan tetap seperti apa adanya. Bagaimana cara membuat daftar toko+komponen yang digeneralisasi untuk mengumpulkan segala jenis entitas? Apakah mungkin untuk menggeneralisasi daftar toko+komponen lebih jauh untuk mengambil penghitung dan item-todo dari todomvc-example ?

Akan sangat bagus untuk memiliki sesuatu seperti ini dalam contoh.

examples

Komentar yang paling membantu

Bagaimana cara membuat daftar toko+komponen yang digeneralisasi untuk mengumpulkan segala jenis entitas? Apakah mungkin untuk menggeneralisasi daftar toko+komponen lebih jauh untuk mengambil penghitung dan item-todo dari contoh-todomvc?

Ya, pasti mungkin.
Anda ingin membuat peredam orde lebih tinggi dan komponen orde lebih tinggi.

Untuk peredam orde tinggi lihat pendekatannya di sini:

function list(reducer, actionTypes) {
  return function (state = [], action) {
    switch (action.type) {
    case actionTypes.add:
      return [...state, reducer(undefined, action)];
    case actionTypes.remove:
      return [...state.slice(0, action.index), ...state.slice(action.index + 1)];
    default:
      const { index, ...rest } = action;
      if (typeof index !== 'undefined') {
        return state.map(item => reducer(item, rest));
      }
      return state;
    }
  }
}

function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return counter + 1;
  case 'DECREMENT':
    return counter - 1;
  }
}

const listOfCounters = list(counter, {
  add: 'ADD_COUNTER',
  remove: 'REMOVE_COUNTER'
});

const store = createStore(listOfCounters);
store.dispatch({
  type: 'ADD_COUNTER'
});
store.dispatch({
  type: 'ADD_COUNTER'
});
store.dispatch({
  type: 'INCREMENT',
  index: 0
});
store.dispatch({
  type: 'INCREMENT',
  index: 1
});
store.dispatch({
  type: 'REMOVE_COUNTER',
  index: 0
});

(Saya belum menjalankannya tetapi seharusnya berfungsi dengan sedikit perubahan.)

Semua 50 komentar

Saya setuju ini adalah contoh yang baik.
Redux di sini mirip dengan Arsitektur Elm jadi jangan ragu untuk mendapatkan inspirasi dari contoh di sana.

Bagaimana cara membuat daftar toko+komponen yang digeneralisasi untuk mengumpulkan segala jenis entitas? Apakah mungkin untuk menggeneralisasi daftar toko+komponen lebih jauh untuk mengambil penghitung dan item-todo dari contoh-todomvc?

Ya, pasti mungkin.
Anda ingin membuat peredam orde lebih tinggi dan komponen orde lebih tinggi.

Untuk peredam orde tinggi lihat pendekatannya di sini:

function list(reducer, actionTypes) {
  return function (state = [], action) {
    switch (action.type) {
    case actionTypes.add:
      return [...state, reducer(undefined, action)];
    case actionTypes.remove:
      return [...state.slice(0, action.index), ...state.slice(action.index + 1)];
    default:
      const { index, ...rest } = action;
      if (typeof index !== 'undefined') {
        return state.map(item => reducer(item, rest));
      }
      return state;
    }
  }
}

function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return counter + 1;
  case 'DECREMENT':
    return counter - 1;
  }
}

const listOfCounters = list(counter, {
  add: 'ADD_COUNTER',
  remove: 'REMOVE_COUNTER'
});

const store = createStore(listOfCounters);
store.dispatch({
  type: 'ADD_COUNTER'
});
store.dispatch({
  type: 'ADD_COUNTER'
});
store.dispatch({
  type: 'INCREMENT',
  index: 0
});
store.dispatch({
  type: 'INCREMENT',
  index: 1
});
store.dispatch({
  type: 'REMOVE_COUNTER',
  index: 0
});

(Saya belum menjalankannya tetapi seharusnya berfungsi dengan sedikit perubahan.)

Terima kasih - Saya akan mencoba agar pendekatan ini berhasil.

Saya masih bertanya-tanya apakah saya dapat menggunakan kembali fungsionalitas daftar melalui combineReducers untuk memiliki daftar penghitung dan daftar item yang harus dilakukan. Dan jika itu masuk akal. Tapi saya pasti akan mencobanya.

Saya masih bertanya-tanya apakah saya dapat menggunakan kembali fungsionalitas daftar melalui combineReducers untuk memiliki daftar penghitung dan daftar item yang harus dilakukan. Dan jika itu masuk akal. Tapi saya pasti akan mencobanya.

Ya, sepenuhnya:

const reducer = combineReducers({
  counterList: list(counter, {
    add: 'ADD_COUNTER',
    remove: 'REMOVE_COUNTER'
  }),
  todoList: list(counter, {
    add: 'ADD_TODO',
    remove: 'REMOVE_TODO'
  }),
});

@gaearon bagaimana seharusnya tindakan daftar mendapatkan index ?

Kami berhasil membuat peredam urutan yang lebih tinggi dengan instruksi Anda, tetapi kami berjuang dengan komponen urutan yang lebih tinggi. Saat ini komponen kami tidak cukup umum untuk digunakan dengan komponen lain selain Counter. Masalah kami adalah bagaimana menambahkan indeks ke tindakan dengan cara yang umum.

Anda dapat melihat solusi kami di sini: https://github.com/Zeikko/redux/commit/6a222885c8c93950dbdd0d4cf3532cd99a32206c

Saya menambahkan beberapa komentar ke komit untuk menyoroti bagian yang bermasalah.

Akan sangat bagus untuk memiliki komponen + peredam daftar umum yang dapat melakukan daftar daftar penghitung.

Saat ini komponen kami tidak cukup umum untuk digunakan dengan komponen lain selain Counter. Masalah kami adalah bagaimana menambahkan indeks ke tindakan dengan cara yang umum.

Bisakah Anda menjelaskan apa yang Anda maksud dengan "menambahkan indeks secara umum"? Apakah maksud Anda bahwa Anda ingin memiliki nama yang berbeda untuk kunci index dalam tindakan?

Saya pikir saya mengerti apa yang Anda maksud sekarang.

Maaf baru bisa komentar banyak. Aku akan kembali besok.

Saya mengerti masalahnya sekarang. Melihat ke dalamnya.

Saya dengan cepat menemukan beberapa batasan yang melekat pada bagaimana Redux menyimpang dari arsitektur Elm.
Sayang sekali saya tidak memahami mereka sebelumnya!

  • Pada saat komponen dibungkus, ia perlu menerima prop dispatch dan bukan callback. Ini berarti Anda harus menghindari menjadikan pembuat tindakan sebagai alat peraga Anda dan hanya menggunakan dispatch() dalam komponen yang disusun, atau kami perlu memperkenalkan bindActionCreators(Component, actionCreators) => Component yang persis seperti connect() tetapi tidak benar-benar terhubung ke toko, alih-alih hanya mengganti action-creator-as-props-wanting-component dengan this.props.dispatch -wanting-component.
  • Anda tidak dapat mengirimkan tindakan yang membutuhkan middleware dari komponen yang dibungkus. Ini mengecewakan! Jika saya membungkus penghitung dengan daftar, saya tidak dapat lagi mengirim incrementAsync() karena function (dispatch, getState) { ... } yang baru saja saya kirim diubah menjadi { action: function (dispatch, getState) { ... } } oleh daftar—dan bam! middleware thunk tidak lagi mengenalinya.

Mungkin ada solusi yang tidak canggung untuk ini, tetapi saya belum melihatnya.
Untuk saat ini, silakan lihat komit ini sebagai contoh (dengan batasan yang dijelaskan di atas).

Berikut kodenya:

komponen/Counter.js

import React, { Component, PropTypes } from 'react';
import { increment, incrementIfOdd, incrementAsync, decrement } from '../actions/counter';

class Counter extends Component {
  render() {
    const { dispatch, counter } = this.props;
    return (
      <p>
        Clicked: {counter} times
        {' '}
        <button onClick={() => dispatch(increment())}>+</button>
        {' '}
        <button onClick={() => dispatch(decrement())}>-</button>
        {' '}
        <button onClick={() => dispatch(incrementIfOdd())}>Increment if odd</button>
        {' '}
        <button onClick={() => dispatch(incrementAsync())}>Increment async</button>
      </p>
    );
  }
}

Counter.propTypes = {
  dispatch: PropTypes.func.isRequired,
  counter: PropTypes.number.isRequired
};

export default Counter;

komponen/daftar.js

import React, { Component, PropTypes } from 'react';
import { addToList, removeFromList, performInList } from '../actions/list';

export default function list(mapItemStateToProps) {
  return function (Item) {
    return class List extends Component {
      static propTypes = {
        dispatch: PropTypes.func.isRequired,
        items: PropTypes.array.isRequired
      };

      render() {
        const { dispatch, items } = this.props;
        return (
          <div>
            <button onClick={() =>
              dispatch(addToList())
            }>Add counter</button>

            <br />
            {items.length > 0 &&
              <button onClick={() =>
                dispatch(removeFromList(items.length - 1))
              }>Remove counter</button>
            }
            <br />
            {this.props.items.map((item, index) =>
              <Item {...mapItemStateToProps(item)}
                    key={index}
                    dispatch={action =>
                      dispatch(performInList(index, action))
                    } />
            )}
          </div>
        )
      }
    }
  };
}

tindakan/counter.js

export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';

export function increment() {
  return {
    type: INCREMENT_COUNTER
  };
}

export function decrement() {
  return {
    type: DECREMENT_COUNTER
  };
}

export function incrementIfOdd() {
  return (dispatch, getState) => {
    const { counter } = getState();

    if (counter % 2 === 0) {
      return;
    }

    dispatch(increment());
  };
}

export function incrementAsync(delay = 1000) {
  return dispatch => {
    setTimeout(() => {
      dispatch(increment());
    }, delay);
  };
}

tindakan/daftar.js

export const ADD_TO_LIST = 'ADD_TO_LIST';
export const REMOVE_FROM_LIST = 'REMOVE_FROM_LIST';
export const PERFORM_IN_LIST = 'PERFORM_IN_LIST';

export function addToList() {
  return {
    type: ADD_TO_LIST
  };
}

export function removeFromList(index) {
  return {
    type: REMOVE_FROM_LIST,
    index
  };
}

export function performInList(index, action) {
  return {
    type: PERFORM_IN_LIST,
    index,
    action
  };
}

reduksi/penghitung.js

import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions/counter';

export default function counter(state = 0, action) {
  switch (action.type) {
  case INCREMENT_COUNTER:
    return state + 1;
  case DECREMENT_COUNTER:
    return state - 1;
  default:
    return state;
  }
}

reduksi/daftar.js

import { ADD_TO_LIST, REMOVE_FROM_LIST, PERFORM_IN_LIST } from '../actions/list';

export default function list(reducer) {
  return function (state = [], action) {
    const {
      index,
      action: innerAction
    } = action;

    switch (action.type) {
    case ADD_TO_LIST:
      return [
        ...state,
        reducer(undefined, action)
      ];
    case REMOVE_FROM_LIST:
      return [
        ...state.slice(0, index),
        ...state.slice(index + 1)
      ];
    case PERFORM_IN_LIST:
      return [
        ...state.slice(0, index),
        reducer(state[index], innerAction),
        ...state.slice(index + 1)
      ];
    default:
      return state;
    }
  }
}

reduksi/index.js

import { combineReducers } from 'redux';
import counter from './counter';
import list from './list'

const counterList = list(counter);

const rootReducer = combineReducers({
  counterList
});

export default rootReducer;

container/App.js

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

import Counter from '../components/Counter';
import list from '../components/list';

const CounterList = list(function mapItemStateToProps(itemState) {
  return {
    counter: itemState
  };
})(Counter);

export default connect(function mapStateToProps(state) {
  return {
    items: state.counterList
  };
})(CounterList);

cc @acdlite — inilah contoh middleware saat ini + desain React Redux yang agak rusak.
Kami dapat mendeklarasikan ini sebagai wontfix tetapi mungkin Anda ingin melihat apakah ada cara untuk menghindari ini.

Saya telah bereksperimen dengan penggunaan wadah layanan (IoC) untuk React, dan membuat test-repo ini kemarin: https://github.com/magnusjt/react-ioc

Saya pikir itu berpotensi memecahkan sebagian masalah, karena Anda dapat mewariskan pembuat tindakan ke Counter tanpa CounterList mengetahuinya. Ini dimungkinkan karena pembuat tindakan masuk ke konstruktor untuk Penghitung, bukan di alat peraga.

Untuk setiap komponen Penghitung baru yang Anda buat, Anda dapat meneruskan pembuat tindakan yang berbeda (mungkin dengan mengikat nilai indeks ke pembuat tindakan). Tentu saja Anda masih memiliki masalah dengan mendapatkan data ke konter. Saya belum yakin apakah itu sesuatu yang bisa diselesaikan dengan wadah layanan.

@gaearon , contoh Anda terlihat tepat bagi saya. Anda harus melewati pembuat aksi dan mengirimkan semuanya ke bawah. Dengan cara ini Anda dapat melakukan tindakan altar dengan fungsi tingkat tinggi.

Saya tidak begitu yakin poin kedua Anda diperlukan. Anda akan kehilangan middleware karena format pesan baru, tetapi masalah yang lebih besar dengan performInList adalah Anda membatasi abstraksi hanya pada satu daftar. @pe3 menyebutkan daftar daftar penghitung. Untuk abstraksi sewenang-wenang seperti itu, saya pikir Anda harus membuat sarang tindakan.

Dalam tiket ini, saya menemukan cara untuk membuat sarang jenis:
https://github.com/rackt/redux/issues/897

Tapi saya pikir lebih mendasar, Anda akan ingin menyarangkan tindakan sepenuhnya....

Oke, saya baru saja mencobanya.

Saya menyederhanakan banyak hal. Inilah aplikasi penghitung sebelum melakukan hal-hal mewah ini:

https://github.com/ccorcos/redux-lifted-reducers/blob/80295a09c4d04654e6b36ecc8bc1bfac4ae821c7/index.js#L49

Dan ini setelah:

https://github.com/ccorcos/redux-lifted-reducers/blob/1fdafe8ed29303822018cde4973fda3305b43bb6/index.js#L57

Saya tidak yakin "angkat" adalah istilah yang tepat -- saya tahu itu berarti sesuatu dalam pemrograman fungsional, tetapi saya merasa baik-baik saja.

Pada dasarnya, dengan mengangkat suatu tindakan, Anda membuat suatu tindakan di dalam tindakan lain.

const liftActionCreator = liftingAction => actionCreator => action => Object.assign({}, liftingAction, { nextAction: actionCreator(action) })

Dan tindakan bersarang dihilangkan dengan mengangkat peredam. Peredam pengangkatan pada dasarnya menerapkan sub-peredam (yang diterapkan sebagian dengan tindakan yang sesuai) ke beberapa substatus.

const liftReducer = liftingReducer => reducer => (state, action) => liftingReducer(state, action)((subState) => reducer(subState, action.nextAction))

Jadi untuk daftar peredam komponen, saya memiliki tindakan yang menentukan komponen di mana indeks sub-tindakan berlaku.

// list actions
const LIST_INDEX = 'LIST_INDEX'
function actOnIndex(i) {
  return {
    type: LIST_INDEX,
    index: i
  }
}

Dan saya memiliki "tingkat tinggi" (istilah mewah lain yang baru saja terasa benar, haha;) peredam yang menerapkan sub-pereduksi ke sub-negara bagian yang sesuai.

const list = (state=[], action) => (reduce) => {
  switch (action.type) {
    case LIST_INDEX:
      let nextState = state.slice(0)
      nextState[action.index] = reduce(nextState[action.index])
      return nextState
    default:
      return state;
  }
}

Dan yang tersisa hanyalah "mengangkat" peredam hitungan ke dalam peredam daftar.

const reducer = combineReducers({
  counts: liftReducer(list)(count)
});

Dan sekarang untuk daftar penghitung, kita hanya perlu mengangkat tindakan saat kita meneruskannya ke penghitung.

class App extends Component {
  render() {
    const counters = [0,1,2,3,4].map((i) => {
      return (
        <Counter count={this.props.state.counts[i]}
                 increment={liftActionCreator(actOnIndex(i))(increment)}
                 decrement={liftActionCreator(actOnIndex(i))(decrement)}
                 dispatch={this.props.dispatch}
                 key={i}/>
      )
    })
    return (
      <div>
        {counters}
      </div>
    );
  }
}

Saya pikir ini bisa lebih diformalkan dengan istilah yang tepat. Saya pikir lensa dapat digunakan di sini untuk reduksi tingkat tinggi juga, tetapi saya tidak pernah berhasil menggunakannya, haha.

Dan saya menarik kembali apa yang saya katakan di komentar terakhir -- @gaearon benar. Dengan menyarangkan tindakan seperti ini, Anda akan kehilangan middleware, dan Anda harus melewati pengiriman sepenuhnya sehingga Anda dapat memanipulasi pembuat tindakan. Mungkin untuk mendukung ini, Redux harus menerapkan semua sub-tindakan melalui middleware. Juga, masalah lain adalah menginisialisasi status dalam daftar...

Apa yang Anda gambarkan dikenal sebagai Arsitektur Elm. Silakan lihat di sini: https://github.com/gaearon/react-elmish-example

Bung, Anda selalu selangkah lebih maju! Mandikan saya dengan tautan ke hal-hal keren :+1:

Anda tidak dapat mengirimkan tindakan yang membutuhkan middleware dari komponen yang dibungkus. Ini mengecewakan! Jika saya membungkus penghitung dengan daftar, saya tidak dapat lagi mengirimkan incrementAsync() karena fungsi (pengiriman, getState) { ... } Saya baru saja mengirim diubah menjadi { action: function (dispatch, getState) { ... } } oleh daftarnya—dan bam! middleware thunk tidak lagi mengenalinya.

@gaearon bagaimana dengan solusi ini? alih-alih peredam generik menyebut dirinya peredam anak seperti ini

case PERFORM_IN_LIST:
      return [
        ...state.slice(0, index),
        reducer(state[index], innerAction),
        ...state.slice(index + 1)
      ];

menyediakan toko dengan beberapa metode khusus dispatchTo(reducer, state, action, callback) yang bertindak seperti dispatch kecuali mengirimkan langsung ke peredam anak (melalui semua middelwares yang dikonfigurasi) dan memberi tahu panggilan balik pada setiap modifikasi status anak

export default function list(reducer, dispatchTo) {
  return function (state = [], action) {
    ...
    case PERFORM_IN_LIST:
      dispatchTo(reducer, state[index], innerAction, newState =>
         [
           ...state.slice(0, index),
           newState,
           ...state.slice(index + 1)
        ]);
       default:
          return state;
    }
  }
}

Saya tidak tahu apakah ini bisa dilakukan di Redux. Idenya adalah untuk mengimplementasikan dispatchTo menggunakan beberapa metode internal store.derive(reducer, state) yang akan mengembalikan penyimpanan anak untuk bagian pohon keadaan yang dikonfigurasi dengan beberapa middlewares sebagai penyimpanan akar. Misalnya

function dispatchTo(reducer, state, action, callback) {
  const childStore = store.derive(reducer, state)
  childStore.subscribe(() => setRootState( callback(getState() ))
  childStore.dispatch(action)
}

Ini hanya ide karena saya katakan saya tidak mengetahui internal Redux jadi mungkin saya melewatkan sesuatu

EDIT
_mungkin ini akan menjadi canggung karena metode peredam seharusnya sinkron. membuat metode async menyiratkan semua rantai harus async juga.
Mungkin solusi terbaik adalah dengan expoer langsung metode store.derive(reducer) dan membangun reduksi generik menggunakan semacam komposisi Store_

Itu terlalu banyak komplikasi dan tidak sepadan dengan IMO. Jika Anda ingin melakukannya dengan cara ini, jangan gunakan middleware (atau gunakan beberapa implementasi alternatif dari applyMiddleware ) dan Anda sudah siap.

Juga, saya tutup karena kami tidak berencana untuk bertindak atas hal ini.

@gaearon demi diskusi:
contoh kode yang Anda lampirkan dalam komit ini: https://github.com/rackt/redux/commit/a83002aed8e36f901ebb5f139dd14ce9c2e4cab4

Jika saya memiliki 2 daftar penghitung (atau bahkan 'model' terpisah), mengirimkan addToList akan menambahkan item ke kedua daftar, karena jenis tindakannya sama.

// reducers/index.js

import { combineReducers } from 'redux';
import counter from './counter';
import list from './list'

const counterList = list(counter);
const counterList2 = list(counter);

const rootReducer = combineReducers({
  counterList,
  counterList2
});

export default rootReducer;

jadi bagaimana reducers/list membantu di sini? Apakah Anda tidak perlu mengawali jenis tindakan atau semacamnya?

Jika saya memiliki 2 daftar penghitung (atau bahkan 'model' terpisah), mengirimkan addToList akan menambahkan item ke kedua daftar, karena jenis tindakannya sama.

Harap perhatikan baik-baik a83002aed8e36f901ebb5f139dd14ce9c2e4cab4. Ini sarang tindakan. dispatch yang diturunkan dari komponen wadah membungkus tindakan menjadi performInList tindakan. Kemudian tindakan dalam diambil di peredam. Ini adalah cara kerja Arsitektur Elm.

@gaearon mungkin saya melewatkan sesuatu, tetapi bersama dengan peredam counterList2 tambahan yang disebutkan di atas, UI ini masih memperbarui kedua daftar pada setiap tindakan (yang diharapkan sesuai dengan cara pembuatannya, tetapi apa solusinya?):

// reducers/index.js

import { combineReducers } from 'redux';
import counter from './counter';
import list from './list'

const counterList = list(counter);
const counterList2 = list(counter);

const rootReducer = combineReducers({
  counterList,
  counterList2
});

export default rootReducer;


// containers/App.js

import React from 'react';

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

import counter from '../components/Counter';
import list from '../components/list';

let CounterList = list(function mapItemStateToProps(itemState) {
  return {
    counter: itemState
  };
})(counter);

CounterList = connect(function mapStateToProps(state) {
  return {
    items: state.counterList
  };
})(CounterList);

let CounterList2 = list(function mapItemStateToProps(itemState) {
  return {
    counter: itemState
  };
})(counter);

CounterList2 = connect(function mapStateToProps(state) {
  return {
    items: state.counterList2
  };
})(CounterList2);


export default class App extends React.Component {
  render() {
    return (
      <div>
        <CounterList />
        <CounterList2 />
      </div>
    )
  }
}

@elado Anda harus membungkusnya dalam daftar lagi sehingga tindakan tidak berbenturan untuk dua daftar, sama seperti yang kami lakukan dengan daftar penghitung

@ccorcos

untuk membungkusnya dalam daftar lagi

membungkus apa sebenarnya?

@ccorcos
Saya mengunggah contoh di sini: http://elado.s3-website-us-west-1.amazonaws.com/redux-counter/
Ini memiliki peta sumber

Masih tidak yakin sepenuhnya apa yang Anda maksud. Seperti yang saya katakan - perilaku saat ini adalah yang diharapkan karena nama tindakannya sama, dan tidak ada indikasi di pembuat tindakan di daftar mana yang harus melakukannya, sehingga menjalankan tindakan pada semua reduksi yang akhirnya memengaruhi kedua daftar.

Jadi saya tidak tahu fungsi Redux yang sebenarnya dengan baik, tapi saya sangat akrab dengan elm.

Dalam contoh baru ini, kami tidak mengikat pembuat tindakan di tingkat atas. Sebagai gantinya, kami meneruskan fungsi pengiriman ke komponen yang lebih rendah dan komponen yang lebih rendah itu dapat meneruskan tindakan ke fungsi pengiriman.

Agar abstraksi berfungsi dengan baik sehingga kita tidak memiliki tabrakan tindakan, ketika komponen "listOf" meneruskan pengiriman ke turunannya, komponen sebenarnya melewati fungsi yang membungkus tindakan dalam format yang dapat dipahami oleh komponen daftar.

children.map((child, i) => {
  childDispatch = (action) => dispatch({action, index: i})
  // ...

Jadi sekarang Anda dapat menulis listOf(counter) atau listOf(listOf(counter)) dan jika Anda ingin membuat komponen bernama pairOf , maka Anda perlu memastikan untuk membungkus tindakan saat Anda meneruskannya. Saat ini, komponen App hanya membuat mereka berdampingan tanpa membungkus fungsi pengiriman, sehingga Anda memiliki tabrakan tindakan.

@ccorcos

Terima kasih. Jadi untuk menyimpulkan, jelas tidak ada "keajaiban" yang terjadi, tindakan memerlukan semua info yang mereka butuhkan untuk memberi tahu peredam di mana untuk melakukan tindakan. Jika itu adalah listOf(listOf(counter)) tindakan akan membutuhkan kedua indeks dari array 2d. listOf(listOf(counter)) dapat berfungsi tetapi memiliki semua penghitung dalam satu daftar datar yang diindeks oleh ID unik yang merupakan satu-satunya hal yang diteruskan dalam suatu tindakan tampaknya lebih fleksibel.

Sepertinya satu-satunya cara untuk membangun aplikasi Redux yang fleksibel dan kompleks adalah dengan membuat semua entitas dalam sistem diindeks oleh ID di toko. Pendekatan lain yang lebih sederhana, yang terkadang diberikan dalam contoh, akan mencapai batasnya dengan cepat. Indeks ini hampir merupakan cerminan dari DB relasional.

tindakan akan membutuhkan kedua indeks dari array 2d

kedengarannya seperti Anda berpikir dan tindakan terlihat seperti:

{type: 'increment', index:[0 5]}

tetapi seharusnya benar-benar terlihat seperti:

{type:'child', index: 0, action: {type: 'child', index: 5, action: {type: 'increment'}}}

Dengan begitu Anda dapat melakukan listOf(listOf(listOf(...listOf(counter)...))) selamanya!

Ini semua berasal dari Elm, btw. Lihat Tutorial Arsitektur Elm

Saya agak lambat ke pesta, tetapi saya tidak melihat di mana ini "tidak berfungsi dengan middleware"? Jika Anda menawarkan sarang tak terbatas sebagai @ccorcos , tidak bisakah Anda memasang middleware untuk menangani sarang? Atau apakah kita berbicara secara eksklusif tentang redux-thunk di mana tindakan bersarang berarti keanehan?

Bagaimana middleware mengetahui apakah akan menafsirkan tindakan apa adanya, atau mencari tindakan bersarang?

Aha.

@gaearon

Hai.

Saya tidak yakin saya memahami resolusi tentang masalah ini dan akan sangat menghargai jawaban sederhana.

Apakah _jangan gunakan middleware (dan pada dasarnya sebagian besar ekosistem Redux), jika Anda memiliki banyak salinan komponen pada halaman yang sama_? Saya juga tidak melihat apakah seseorang menanggapi saran multireducer .

Setiap klarifikasi akan membantu.

Tidak, ini bukan resolusi. Utas ini bukan tentang memiliki banyak identitas komponen di halaman. Untuk menerapkan ini, Anda cukup memberikan ID dalam tindakan. Utasnya adalah tentang _menulis fungsi umum untuk melakukan itu_. Yang sayangnya tidak berbenturan dengan konsep middleware.

Terima kasih.

Halo semuanya,
Mungkin saya memecahkan masalah ini, namun saya lebih suka menggunakan deku daripada react ^^

Saya akan senang jika seseorang memberi saya wawasannya tentang eksperimen ini, terutama tentang ide taskMiddleware . @gaearon apakah Anda punya waktu untuk memeriksanya? :lidah:

Daftar Penghitung

Terima kasih!

Hanya ingin mengklarifikasi bahwa tidak satu pun dari solusi ini bertujuan untuk menangani keadaan awal.
Bisakah ini dicapai entah bagaimana @gaearon ? Mengizinkan peredam Daftar di atas memiliki daftar penghitung awal?

Mengapa itu akan bermasalah? Saya membayangkan Anda dapat memanggil reduksi anak dengan status undefined dan menggunakan nilai-nilai itu.

Ketika toko diinisialisasi dengan pengiriman pertama yang saya butuhkan (untuk logika internal saya) untuk memiliki status awal untuk daftar itu, dan itu harus berupa daftar penghitung yang telah ditentukan sebelumnya (jenis item daftar)

Sesuatu seperti ini?

export default function list(reducer) {
  return function (state = [

    // e.g. 2 counters with default values
    reducer(undefined, {}),
    reducer(undefined, {}),

      ], action) {
    const {
      index,
      action: innerAction
    } = action;
   // ...
  }
}

Anda bisa menjadikan ini sebagai argumen untuk list juga jika Anda mau

Ini tampaknya sangat rumit agar saya dapat memiliki beberapa komponen yang sama pada satu halaman.

Saya masih memikirkan Redux, tetapi membuat banyak toko tampaknya jauh lebih sederhana, meskipun itu bukan pola penggunaan Redux yang disarankan.

@deevus : jangan biarkan beberapa diskusi ini membuat Anda takut. Ada sejumlah orang di komunitas Redux yang sangat berorientasi pada Pemrograman Fungsional, dan sementara beberapa konsep yang dibahas di utas ini dan yang serupa lainnya memiliki nilai, mereka juga cenderung menjadi semacam upaya untuk mencapai "sempurna" , bukan hanya "baik".

Anda benar-benar dapat memiliki beberapa contoh komponen pada halaman, secara umum. Maksud diskusi ini adalah komposisi sewenang-wenang dari komponen bersarang, yang menarik, tetapi juga bukan sesuatu yang perlu dilakukan oleh sebagian besar aplikasi.

Jika Anda memiliki masalah khusus di luar itu, Stack Overflow biasanya merupakan tempat yang baik untuk mengajukan pertanyaan. Juga, komunitas Reactiflux di Discord memiliki banyak saluran obrolan yang didedikasikan untuk membahas React dan teknologi terkait, dan selalu ada beberapa orang yang berkumpul untuk berbicara dan membantu.

@markerikson Terima kasih. Saya akan mencoba dan mendapatkan bantuan dari Reactiflux di Discord.

Menindaklanjuti ini:
Saya menemukan cara yang dapat diskalakan untuk menggunakan kembali reduksi di proyek saya, bahkan menggunakan kembali reduksi di dalam reduksi.

Paradigma namespace

Ini adalah konsep bahwa tindakan yang bertindak atas salah satu dari banyak reduksi "sebagian" memiliki properti "namespace" yang menentukan peredam mana yang akan menangani tindakan yang bertentangan dengan _semua_ reduksi yang menanganinya karena mereka mendengarkan tindakan yang sama type (contoh di https://github.com/reactjs/redux/issues/822#issuecomment-172958967)

Namun tindakan yang tidak memiliki namespace akan tetap disebarkan ke semua reduksi parsial di dalam reduksi lainnya

Katakanlah Anda memiliki peredam parsial A dan dengan keadaan awal A(undefined, {}) === Sa dan Peredam B dengan keadaan awal B(undefined, {}) === { a1: Sa, a2: Sa } di mana kunci a1 dan a2 adalah instance dari A .

Tindakan dengan namespace ['a1'] (* namespace selalu merupakan array berurutan dari string yang menyerupai kunci negara bagian untuk peredam parsial) yang diberikan pada B akan menghasilkan hasil berikut

const action = {
  type: UNIQUE_ID,
  namespace: ['a1']
};

B(undefined, action) == { a1: A(undefined, action*), a2: Sa }

Dan contoh kontra dari tindakan tanpa namespace

const action = {
  type: UNIQUE_ID
};


B(undefined, action) == { a1: A(undefined, action), a2: A(undefined, action) }

Peringatan

  • Jika A tidak menangani tindakan yang diberikan (menghabiskan peredam) itu harus mengembalikan keadaan yang sama, ini berarti bahwa untuk tindakan p yang tidak dapat ditangani untuk peredam A hasil B(undefined, p) seharusnya { a1: Sa, a2: Sa } yang notabene sama dengan keadaan awal B
  • Tindakan yang diturunkan ke reduksi parsial (dilambangkan sebagai action* di atas) harus dilucuti dari namespace yang digunakan untuk mempersempit cakupan. Jadi jika tindakan yang diteruskan ke B adalah { type: UNIQUE_ID, namespace: ['a1'] } maka tindakan yang diteruskan ke A adalah { type: UNIQUE_ID, namespace: [] }
  • Untuk mencapai struktur ini, semua reduksi di toko harus menangani tindakan penspasian nama, jika titik ini terpenuhi, tidak ada batasan pada berapa banyak ruang nama yang Anda gunakan dan berapa banyak reduksi parsial yang dapat Anda sarangkan

Untuk mencapai ini, saya telah membuat beberapa kodesemu untuk menangani ruang nama di peredam Anda. Agar ini berfungsi, kita harus tahu sebelumnya apakah peredam dapat menangani suatu tindakan dan jumlah reduksi parsial yang ada di peredam.

(state = initialState, { ...action, namespace = [] }) => {
    var partialAction = { ...action, namespace: namespace.slice(1) };
    var newState;
    if (reducerCanHandleAction(reducer, action) and namespaceExistsInState(namespace, state)) {
        // apply the action to the matching partial reducer
        newState = {
            ...state,
            [namespace]: partialReducers[namespace](state[namespace], partialAction)
        };
    } else if (reducerCantHandleAction(reducer, action) {
        // apply the action to all partial reducers
        newState = Object.assign(
            {},
            state,
            ...Object.keys(partialReducers).map(
                namespace => partialReducers[namespace](state[namespace], action)
            )
        );
    } else {
        // can't handle the action
        return state;
    }

    return reducer(newState, action);
}

Terserah Anda bagaimana memutuskan apakah peredam dapat atau tidak dapat menangani tindakan sebelumnya, saya menggunakan peta objek di mana jenis tindakan adalah kunci dan fungsi pengendali adalah nilainya.

Saya mungkin agak terlambat ke permainan tetapi saya menulis beberapa reduksi tujuan umum yang mungkin membantu:
https://Gist.github.com/crisu83/42ecffccad9d04c74605fbc75c9dc9d1

Saya pikir mutilreducer adalah implementasi yang bagus

@jeffhtli multireducer bukan solusi yang baik karena tidak mengizinkan jumlah reduksi yang tidak ditentukan, alih-alih meminta Anda untuk membuat daftar peredam statis terlebih dahulu
Alih-alih, saya membuat proyek kecil untuk menyelesaikan masalah ini menggunakan UUID untuk setiap instance komponen dan status unik untuk setiap UUID
https://github.com/eloytoro/react-redux-uuid

Apakah halaman ini membantu?
0 / 5 - 0 peringkat