简体   繁体   English

编写一个与redux / redux-saga共享数据的HoC

[英]Writing a HoC that shares data with redux/redux-saga

I'm currently trying to create a reusable component, that will pull data from a remote source, and update the table as needed if items change on the backend. 我目前正在尝试创建一个可重用的组件,它将从远程源中提取数据,并在后端更改项目时根据需要更新表。 Here's a simple component: 这是一个简单的组件:

<React.Fragment>
    <Header />
    <table>
      <thead>
        <tr>
          {columns.map(c => (
            <th key={c.header}>{c.header}</th>
          ))}
        </tr>
      </thead>
      <tbody>{data.map(row => children(row))}</tbody>
    </table>
  <Pagination />
</React.Fragment>

The goal was, that from my order page, I could call it like below: 目标是,从我的订单页面,我可以像下面这样称呼它:

<DataTable
  id="app/OrderPage/orders"
  url="http://localhost:8081/api/orders"
  columns={[
    { header: 'Name' },
    { header: 'Actions' },
  ]}
>
  {order => (
      <tr key={order.id}>
        <td>{order.name}</td>
        <td>
          <Link to={`/admin/orders/${order.id}`}>
            View
          </Link>
        </td>
      </tr>
    )}
</DataTable>

Now, the idea is I want to reuse this component in multiple places. 现在,我的想法是想在多个地方重用这个组件。 I currently use redux-saga to do the fetching, and update the datatable when the data is returned. 我目前使用redux-saga来进行提取,并在返回数据时更新数据表。 (Currently I'm providing an datatable ID, which is then being passed to FETCH_DATA, LIMIT_CHANGED, SEARCH_CHANGED, etc..., which I then use to update the parts of the slice that need changing: (目前我正在提供一个数据表ID,然后传递给FETCH_DATA,LIMIT_CHANGED,SEARCH_CHANGED等等,然后我用它来更新需要更改的切片部分:

function dataTableReducer(state = initialState, action) {
  switch (action.type) {
    case INIT: {
      const { id, url, deletedItem } = action;
      return state.setIn(
        ['tables', id],
        initialTableState
          .set('id', id)
          .set('url', url)
          .set('deletedItem', deletedItem),
      );
    }
    case LOAD_DATA_SUCCESS:
      return state.setIn(['tables', action.id, 'data'], action.data.data);
    case LIMIT_CHANGED:
      return state.setIn(['tables', action.id, 'limit'], action.limit);
    case SEARCH_CHANGED:
      return state.setIn(['tables', action.id, 'search'], action.search);
    default:
      return state;
  }
}

My current saga for the DataTable component: 我目前的DataTable组件的传奇:

function* getData({ id }) {
  const dt = yield select(selectDataTableInstance(), { id });
  const { url, limit, search, page } = dt;

  const res = yield call(request, url, {
    qs: {
      limit,
      search,
      page,
    },
  });

  yield put(loadDataSuccess(id, res));
}

// Individual exports for testing
export default function* dataTableSaga() {
  yield all([
    takeEvery(LOAD_DATA, getData),
    takeEvery(LIMIT_CHANGED, getData),
    takeLatest(SEARCH_CHANGED, getData),
  ]);
}

The problem now becomes, how do I add additional logic to this component? 现在的问题是, 如何向该组件添加其他逻辑 For example, when a new order comes in over a websocket, I'd like the <DataTable /> component to add that row to the top of the list. 例如,当一个新订单通过websocket进入时,我希望<DataTable />组件将该行添加到列表顶部。 The problem is, I'd need to connect additional logic to the redux-saga that I have in place. 问题是,我需要将额外的逻辑连接到我现有的redux-saga。 My thought initially was to add a prop to the DataTable, like deletedItem={DELETE_ORDER_SUCCESS} , but this seems like a really nasty workaround. 我的想法最初是为DataTable添加一个prop,比如deletedItem={DELETE_ORDER_SUCCESS} ,但这似乎是一个非常讨厌的解决方法。 My reducer would need to loop through every rendered table in the slice, and check if the action.type === the one that was initialized when the table displayed. 我的reducer需要遍历切片中的每个渲染表,并检查action.type ===是否是在显示表时初始化的那个。

I'm currently using React Boilerplate . 我目前正在使用React Boilerplate

TLDR: TLDR:

  • Have a reusable DataTable component 拥有可重用的DataTable组件
  • Needs to fetch data/populate table. 需要获取数据/填充表。 Has search/limit/pagination functionality 具有搜索/限制/分页功能
  • Want to add additional hooks (Like addItem), that will add the item to the top of the list, if one comes from a websocket 想要添加额外的钩子(比如addItem),这会将项目添加到列表的顶部,如果一个来自websocket

You can have a set of reducers for web sockets related action. 您可以为Web套接字相关操作设置一组Reducer。 Let's say when a new order is received on web sockets, you can dispatch an NEW_ORDER action. 假设在Web套接字上收到新订单时,您可以发送NEW_ORDER操作。

In your reducers, you can update the existing state to include the new order (at the top of the list if that is what you need). 在reducers中,您可以更新现有状态以包含新订单(如果您需要,则在列表顶部)。 This should trigger a re render since the table data is being read from the Redux store. 这应该触发重新渲染,因为正在从Redux存储中读取表数据。

case LOAD_DATA_SUCCESS:
  return state.setIn(['tables', action.id, 'data'], [action.data.data, state.getIn(['tables', 'action.id', 'data']);

You can have different actions for different things you need to perform. 您可以针对需要执行的不同操作执行不同的操作。

I would setup the websocket inside your saga after yield put(loadDataSuccess(id, res)); 我会在yield put(loadDataSuccess(id, res));之后在你的saga中设置websocket yield put(loadDataSuccess(id, res)); and add a handler like this: 并添加这样的处理程序:

var ws = new WebSocket(wsAddress);
ws.onmessage = function (event) {
 const newItem = event.data;
 res = {...res, data:[newItem ,...res.data]};
 yield put(loadDataSuccess(id, res));
}

and you would need to change your const res to let res 你需要改变你的const reslet res

then on the component unmount you should send an action to allow closing the websocket. 然后在组件卸载时,您应该发送一个动作以允许关闭websocket。 (you could keep a map of open websockets keyed by the id) (你可以保留一个由id键入的开放式websockets的地图)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM