Правильное дерево компонентов для Array в Array Rerender при изменении - PullRequest
0 голосов
/ 03 мая 2018

Я ломаю голову, пытаясь понять структуру компонентов, которую я должен использовать. Я чувствую, что это то, что я абсолютно хочу получить правильно, потому что в будущем важно понять, как должно выглядеть приложение ReactJS и как правильно разделить проблемы. Я знаю, что это самоуверенный фронт, но то, как я сейчас это делаю, неверно, и я искал какую-то идею.

Модель данных: это большой набор рецептов, каждый из которых содержит другой набор ингредиентов. Я хочу позволить пользователю «пометить» (ингредиент удален из массива) ингредиентами, когда они их покупают / приобретают.

[
    ...
    {
        "title": "Recipe 1"
        "ingredients": [
            { "title": "Flour", "measurement": "300g" },
            { "title": "Sesame Seeds", "measurement": "1 Tblsp" }
        ]
    },
    ...
]

Текущее дерево компонентов Psuedo:

// RecipeList is my "HOC" (higher-order component) and contains all the functions/data required for the rest of the tree, and simply passes them down as props. This is `connect()`ed to the store here.
<RecipeList>
    // Map on the highest level array to display a <Recipe> component for each
    <Recipe data={recipe[mappedIndex]}>
        // Map on each ingredient to create a list item for each
        <RecipeIngredient data={recipe[mappedIndex].ingredient[mappedIndex]>
            <IngredientCheckBox onChange={ //remove this ingredient from the array } />
        </RecipeIngredient>
    </Recipe>
</RecipeList>

Все хорошо с вышесказанным, данные отображаются именно так, как я и ожидал.

Однако, и это главная проблема , когда дело доходит до onChange Я называю действие, COMPLETE_INGREDIENT, которое в основном удаляет его из массива ingredients (я вижу это в действии, используя redux-logger, next-state не содержит его).

К сожалению, мои компоненты не перерисовываются. Он больше не находится в массиве, но все еще отображается на экране. Я понимаю, что это может быть одной из следующих причин:

  • connect() сравнивает только мелкие состояния, поэтому не запускает рендеринг, потому что это значение в массиве, внутри свойства объекта в массиве.
  • Мой connect() слишком далек от действия и должен быть connect() отредактирован на более глубоком уровне компонента, скажем, компонент <Recipe>, и прикрепить его только к той части магазина, которая ему нужна (может даже быть <RecipeIngredient>).
  • Мой редуктор не изменяет состояние неизменным образом. Это тот, на котором я потратил больше всего времени, однако, даже используя slice() и все такое, я все равно не могу заставить его перерисовать

Редактировать: Мой редуктор для действия COMPLETE_INGREDIENT. Я понимаю, что это может быть проблемой, так как это напрямую мутирует государство. Каков был бы правильный путь для такого глубокого изменения в государстве?

case COMPLETE_INGREDIENT:
    // state is from a level above the recipe's, that contains a timestamp etc
    state.recipes[action.payload.recipeIndex].ingredients.splice(action.payload.ingredientIndex, 1)

    return Object.assign({
        ...state
    })

Ответы [ 2 ]

0 голосов
/ 03 мая 2018

Проблема здесь в мутировании существующего состояния

case COMPLETE_INGREDIENT:
// state is from a level above the recipe's, that contains a timestamp etc
state.recipes[action.payload.recipeIndex].ingredients.splice(action.payload.ingredientIndex, 1)

return Object.assign({
    ...state
})

Метод splice изменяет существующий массив, но вам нужно создать новый массив. Вот пример

const obj = { a: { b: 5 } };
const copyObj = { ...obj };
copyObj.a.b = 1;
console.log(obj.a.b); // 1
console.log(copyObj.a.b); // 1

Вы скопировали state объект, но массив ingredients остался прежним. Так что вам нужно скопировать массив.

case COMPLETE_INGREDIENT:
return {
  ...state,
  recipes: state.recipes.map(
    (item, index) =>
      index === action.payload.recipeIndex
        ? { ...item, ingredients: item.ingredients.filter((item, index) => index !== action.payload.ingredientIndex) }
        : item
  ),
};
0 голосов
/ 03 мая 2018

Редактировать: Мой редуктор для действия COMPLETE_INGREDIENT. Я понимаю это может быть проблемой, поскольку это непосредственно мутирует государство. Что будет правильный путь для такого глубокого изменения состояния?

Да, вы изменяете состояние с этим Object.assign. В качестве первого аргумента он должен иметь новый объект для копирования значений и возврата:

return Object.assign({}, {
    ...state
})

На основе вашего кода я создал функцию обновления, которую я, вероятно, создал бы:

case COMPLETE_INGREDIENT: {
  const { recipeIndex, ingregientIndex } = action.payload;
  const recipesListCopy = [...state.recipes];
  const recipeCopy = {
    ...recipesListCopy[recipeIndex],
    ingredients: recipesListCopy[recipeIndex].ingredients.filter(
      (e, index) => index !== ingredientIndex
    )
  };
  recipesListCopy[recipeIndex] = recipeCopy;
  return {
    ...state,
    recipes: recipesListCopy
  };
}

Edit:

на основе вашего комментария - "удалить рецепт из массива рецептов верхнего уровня, если массив ингредиентов теперь пуст" *

case COMPLETE_INGREDIENT: {
  const { recipeIndex, ingregientIndex } = action.payload;
  const recipesListCopy = [...state.recipes];
  const updatedIngredientsList = recipesListCopy[recipeIndex].ingredients.filter(
    (e, index) => index !== ingredientIndex
  );

  if(updatedIngredientsList.length > 0) { 
    // update ingredients
    const recipeCopy = {
      ...recipesListCopy[recipeIndex],
      ingredients: updatedIngredientsList
    };
    recipesListCopy[recipeIndex] = recipeCopy;
  } else { 
    // remove recipe because no igridients
    recipesListCopy.splice(recipeIndex, 1);
  }

  return {
    ...state,
    recipes: recipesListCopy
  };
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...