与Redux反应:道具更改后,子组件不会重新渲染(即使它们不相等)

时间:2018-08-26 07:09:32

标签: reactjs redux react-redux rerender

我正在使用Redux用于状态管理的React Native构建应用程序。我将在下面将所有涉及到的组件和化简器的代码发布下来,但是由于数量很多,所以让我首先用几句话来描述问题。

我的对象有一个不变的化简器,称为“ waitercalls”。我有一个呈现两个组件的屏幕(HomeScreen)。每个组件都是<FlatList />个对象。对象(waitercalls由其父对象(HomeScreen)通过道具给予它们。 HomeScreen通过React-Redux'connect()连接到Redux,并通过用Re-Select创建的选择器获取对象(“ waitercalls”)。

可以按下左侧列表中的项目,并在按下时将事件发送给减速器。问题来了。按下左侧列表的项目后,左侧列表会正确更新(调用render())。正确的列表虽然会得到相同的道具,但不会重新呈现。

为什么左边的列表会重新显示,而右边的列表却不会? reducer是不可变的,选择器也是如此,甚至列表的长度也从1变为0,这应该消除了产生浅相等的可能性。

现在输入代码:

waitercallsReducer:

import { createSelector } from "reselect";

const initialState = {};

const waitercallsReducer = (state = initialState, action) => {
  if (action.payload && action.payload.entities && action.payload.entities.waitercalls) {
    return {
      ...state,
      ...action.payload.entities.waitercalls
    };
  } else {
    return state;
  }
};

export default waitercallsReducer;

export const getAllWaitercallsNormalizedSelector = state => state.waitercalls;
export const getAllWaitercallsSelector = createSelector(
  getAllWaitercallsNormalizedSelector,
  waitercalls => Object.values(waitercalls)
);

export const getAllActiveWaitercallsSelector = createSelector(
  getAllWaitercallsSelector,
  waitercalls => waitercalls.filter(waitercall => !waitercall.done)
);

动作创建者:

import { setValues } from "../core/core";

// feature name
export const WAITERCALLS = "[Waitercalls]";

// action creators
export const setValues = (values, type) => ({
  type: `SET ${type}`,
  payload: values,
  meta: { feature: type }
});
export const setWaitercalls = waitercalls => setValues(waitercalls, WAITERCALLS);

主屏幕:

import React, { Component } from "react";
import { View, TouchableOpacity } from "react-native";
import { SafeAreaView } from "react-navigation";
import { connect } from "react-redux";
import { Icon } from "react-native-elements";
import PropTypes from "prop-types";

// ... I've omitted all the imports so that it's shorter

export class HomeScreen extends Component {
  // ... I've omitted navigationOptions and propTypes

  render() {
    const {
      checkins,
      customChoiceItems,
      menuItemPrices,
      menuItems,
      orders,
      pickedRestaurant,
      tables,
      waitercalls
    } = this.props;
    console.log("Rendering HomeScreen");
    return (
      <SafeAreaView style={styles.container}>
        <View style={styles.activeOrders}>
          <OrdersList
            checkins={checkins}
            customChoiceItems={customChoiceItems}
            menuItemPrices={menuItemPrices}
            menuItems={menuItems}
            orders={orders}
            restaurantSlug={pickedRestaurant.slug}
            tables={tables}
            waitercalls={waitercalls}
          />
        </View>
        <View style={styles.tableOvewView}>
          <TableOverview
            checkins={checkins}
            orders={orders}
            tables={tables}
            waitercalls={waitercalls}
          />
        </View>
      </SafeAreaView>
    );
  }
}

const mapStateToProps = state => ({
  checkins: getAllCheckinsSelector(state),
  customChoiceItems: getAllCustomChoiceItemsNormalizedSelector(state),
  menuItemPrices: getAllMenuItemPricesNormalizedSelector(state),
  menuItems: getAllMenuItemsNormalizedSelector(state),
  orders: getActiveOrdersSelector(state),
  pickedRestaurant: getPickedRestaurantSelector(state),
  tables: getAllTablesSelector(state),
  waitercalls: getAllActiveWaitercallsSelector(state)
});

export default connect(mapStateToProps)(HomeScreen);

OrdersList(您可以看到,OrdersList还允许按订单,这显示出没有重新呈现TableOverView的错误行为),这是带有可单击的<ListItem />的左侧列表。

import React, { PureComponent } from "react";
import { FlatList, Image, Text } from "react-native";
import { ListItem } from "react-native-elements";
import { connect } from "react-redux";
import PropTypes from "prop-types";

// ... omitted imports

export class OrdersList extends PureComponent {
  // omitted propTypes

  keyExtractor = item => item.uuid;

  registerItem = item => {
    // Remember the order status, in case the request fails.
    const { restaurantSlug, setOrders } = this.props;
    const itemStatus = item.orderStatus;
    const data = {
      restaurant_slug: restaurantSlug,
      order_status: "registered",
      order_uuid: item.uuid
    };

    setOrders({
      entities: { orders: { [item.uuid]: { ...item, orderStatus: data.order_status } } }
    });
    postOrderStatusCreate(data)
      .then(() => {})
      .catch(err => {
        // If the request fails, revert the order status change and display an alert!
        alert(err);
        setOrders({ entities: { orders: { [item.uuid]: { ...item, orderStatus: itemStatus } } } });
      });
  };

  answerWaitercall = item => {
    const { restaurantSlug, setWaitercalls } = this.props;
    const data = {
      done: true,
      restaurant_slug: restaurantSlug
    };
    setWaitercalls({ entities: { waitercalls: { [item.uuid]: { ...item, done: true } } } });
    putUpdateWaitercall(item.uuid, data)
      .then(() => {})
      .catch(err => {
        alert(err);
        setWaitercalls({ entities: { waitercalls: { [item.uuid]: { ...item, done: false } } } });
      });
  };

  renderItem = ({ item }) => {
    const { checkins, customChoiceItems, menuItemPrices, menuItems, tables } = this.props;
    return item.menuItem ? (
      <ListItem
        title={`${item.amount}x ${menuItems[item.menuItem].name}`}
        leftElement={
          <Text style={styles.amount}>
            {tables.find(table => table.checkins.includes(item.checkin)).tableNumber}
          </Text>
        }
        rightTitle={`${
          menuItemPrices[item.menuItemPrice].label
            ? menuItemPrices[item.menuItemPrice].label
            : menuItemPrices[item.menuItemPrice].size
              ? menuItemPrices[item.menuItemPrice].size.size +
                menuItemPrices[item.menuItemPrice].size.unit
              : ""
        }`}
        subtitle={`${
          item.customChoiceItems.length > 0
            ? item.customChoiceItems.reduce((acc, customChoiceItem, index, arr) => {
                acc += customChoiceItems[customChoiceItem].name;
                acc += index < arr.length - 1 || item.wish ? "\n" : "";
                return acc;
              }, "")
            : null
        }${item.wish ? "\n" + item.wish : ""}`}
        onPress={() => this.registerItem(item)}
        containerStyle={styles.alignTop}
        bottomDivider={true}
      />
    ) : (
      <ListItem
        title={
          item.waitercallType === "bill"
            ? SCREEN_TEXT_HOME_BILL_CALLED
            : SCREEN_TEXT_HOME_SERVICE_ASKED
        }
        leftElement={
          <Text style={styles.amount}>
            {
              tables.find(table =>
                table.checkins.includes(
                  checkins.find(checkin => checkin.consumer === item.consumer).uuid
                )
              ).tableNumber
            }
          </Text>
        }
        rightIcon={{
          type: "ionicon",
          name: item.waitercallType === "bill" ? "logo-euro" : "ios-help-circle-outline"
        }}
        onPress={() => this.answerWaitercall(item)}
        bottomDivider={true}
      />
    );
  };

  render() {
    const { orders, waitercalls } = this.props;
    return (
      <FlatList
        keyExtractor={this.keyExtractor}
        data={[...orders, ...waitercalls]}
        renderItem={this.renderItem}
        // ... omitted ListHeader and ListEmpty properties
      />
    );
  }
}

export default connect(
  null,
  { setOrders, setWaitercalls }
)(OrdersList);

TableOverview,它是正确的<FlatList />

import React, { Component } from "react";
import { FlatList } from "react-native";
import PropTypes from "prop-types";

// ... omitted imports

export class TableOverview extends Component {
  // ... omitted propTypes

  keyExtractor = item => item.uuid;

  renderItem = ({ item }) => {
    const { checkins, orders, waitercalls } = this.props;
    if (item.invisible) return <Table table={item} />;
    console.log("Rendering TableOverview");
    return (
      <Table
        table={item}
        hasActiveOrders={orders.some(order => item.userOrders.includes(order.uuid))}
        billWanted={item.checkins.some(checkin =>
          waitercalls.some(
            waitercall =>
              waitercall.waitercallType === "bill" &&
              waitercall.consumer ===
                checkins.find(checkinObj => checkinObj.uuid === checkin).consumer
          )
        )}
        serviceWanted={item.checkins.some(checkin =>
          waitercalls.some(
            waitercall =>
              waitercall.waitercallType === "waiter" &&
              waitercall.consumer ===
                checkins.find(checkinObj => checkinObj.uuid === checkin).consumer
          )
        )}
      />
    );
  };

  formatData = (data, numColumns) => {
    const numberOfFullRows = Math.floor(data.length / numColumns);

    let numberOfElementsLastRow = data.length - numberOfFullRows * numColumns;
    while (numberOfElementsLastRow !== numColumns && numberOfElementsLastRow !== 0) {
      data.push({ uuid: `blank-${numberOfElementsLastRow}`, invisible: true });
      numberOfElementsLastRow++;
    }

    return data;
  };

  render() {
    const { tables } = this.props;
    return (
      <FlatList
        style={styles.container}
        keyExtractor={this.keyExtractor}
        data={this.formatData(tables, NUM_COLUMNS)}
        renderItem={this.renderItem}
        numColumns={NUM_COLUMNS}
      />
    );
  }
}

export default TableOverview;

1 个答案:

答案 0 :(得分:0)

我找到了解决方法!

列表未重新呈现,因为public class ItemController : ApiController { // GET: api/Item [HttpGet] public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } [ActionName("testBring")] [HttpGet] public string testBring() { return "value"; } // GET: api/Item/5 public string Get(int id) { return "value"; } // POST: api/Item [HttpPost] public void CreateItem([FromBody]string value) { } // PUT: api/Item/5 public void Put(int id, [FromBody]string value) { } // DELETE: api/Item/5 public void Delete(int id) { } } 仅查看表而不查看服务员呼叫。

我必须将以下属性添加到<FlatList />

<FlatList />