Цепные соединения / mapStateToProps / mapDispatchToProps - функции для повторного использования кода в реаги-редуксе - PullRequest
0 голосов
/ 15 сентября 2018

Скажем, у меня есть два компонента, подключенных к редуксу. Первый - это простой контейнер загрузки / отображения задач со следующими функциями, переданными connect(); mapStateToProps считывает задачи из состояния избыточности, а mapDispatchToProps используется для запроса состояния о предоставлении последнего списка задач с сервера:

TodoWidgetContainer.js

import TodoWidgetDisplayComponent from '...'

function mapStateToProps(state) {
  return {
    todos: todoSelectors.getTodos(state)
  };
}

function mapDispatchToProps(dispatch) {
  return {
    refreshTodos: () => dispatch(todoActions.refreshTodos())
  };
}

connect(mapStateToProps, mapDispatchTo)(TodoWidgetDisplayComponent);

Второй избыточный компонент предназначен для применения к любому компоненту на странице, чтобы компонент мог указать, отображается ли глобальный значок «загрузки». Поскольку это можно использовать где угодно, я создал вспомогательную функцию, которая упаковывает MapDispatchToProps в замыкание и генерирует ID для каждого компонента, который используется для того, чтобы все компоненты, которые запрашивали загрузчик, указывали, что им это больше не нужно, и глобальный загрузчик может быть скрыт.

Функции в основном следующие: mapStateToProps предоставляет компонентам видимость загрузчика, а mapDispatchToProps позволяет им запрашивать загрузчик, чтобы показать или скрыть.

Loadify.js

function mapStateToProps(state) {
  return {
    openLoader: loaderSelectors.getLoaderState(state)
  };
}

function mapDispatchToProps() {
  const uniqId = v4();
  return function(dispatch) {
    return {
      showLoader: () => {
        dispatch(loaderActions.showLoader(uniqId));
      },
      hideLoader: () => {
        dispatch(loaderActions.hideLoader(uniqId));
      }
    };
  };
}

export default function Loadify(component) {
  return connect(mapStateToProps, mapDispatchToProps())(component);
}

Итак, теперь, если у меня есть компонент, которому я хочу предоставить доступ к загрузчику, я могу просто сделать что-то вроде этого:

import Loadify from '...'

class DisplayComponent = new React.Component { ... }

export default Loadify(DisplayComponent);

И он должен дать ему уникальный идентификатор, разрешить ему запрашивать загрузчик, чтобы показать / скрыть, и пока есть один компонент, который запрашивает его показать, значок загрузчика будет отображаться. Пока что все это работает нормально.

У меня вопрос: если бы я хотел применить это к компоненту todos, чтобы этот компонент мог запрашивать / получать свои todos, а также иметь возможность запрашивать загрузчик для отображения во время его обработки, я мог бы просто сделать что-то вроде :

TodoWidgetContainer.js

import Loadify from '...'
import TodoWidgetDisplayComponent from '...'

function mapStateToProps(state) {
  return {
    todos: todoSelectors.getTodos(state)
  };
}

function mapDispatchToProps(dispatch) {
  return {
    refreshTodos: () => dispatch(todoActions.refreshTodos())
  };
}

const TodoContainer = connect(mapStateToProps, mapDispatchTo)(TodoWidgetDisplayComponent);

export default Loadify(TodoContainer);

И будет ли приставка автоматически объединять объекты, чтобы сделать их совместимыми, при условии, что нет повторяющихся ключей? Или потребуется только самый последний набор mapStateToProps / mapDispatchTo, если я не сделаю какое-то ручное слияние? Или есть лучший способ получить такой вид повторного использования, которого я не вижу? Я бы действительно предпочел не создавать пользовательский набор контейнеров для каждого компонента, который нам нужен.

Ответы [ 2 ]

0 голосов
/ 15 сентября 2018

connect автоматически объединит комбинацию «реквизиты, переданные компоненту-обертке», «реквизиты из mapState этого компонента» и «реквизиты из mapDispatch этого компонента». Реализация этой логики по умолчанию просто:

export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
  return { ...ownProps, ...stateProps, ...dispatchProps }
}

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

0 голосов
/ 15 сентября 2018

Хорошо, вот что я хотел бы сделать. Создайте компонент высшего порядка (HOC), который добавляет новую ссылку на счетчик к вашему редуктору. HOC будет инициализировать и уничтожать ссылки на счетчик в редукте, привязываясь к методам жизненного цикла. HOC предоставит два свойства базовому компоненту. Первым является isLoading, который является функцией, которая принимает логический параметр; true включено, false выключено. Второе свойство spinnerState, которое является логическим значением только для чтения текущего состояния счетчика.

Я создал этот пример без создателей или редукторов действий, дайте мне знать, если вам нужен их пример.

loadify.jsx

/*----------  Vendor Imports  ----------*/
import React from 'react';
import { connect } from 'react-redux';
import v4 from 'uuid/v4';

/*----------  Action Creators  ----------*/
import {
  initNewSpinner,
  unloadSpinner,
  toggleSpinnerState,
} from '@/wherever/your/actions/are'


const loadify = (Component) => {
  class Loadify extends React.Component {

    constructor(props) {
      super(props);
      this.uniqueId = v4();
      props.initNewSpinner(this.uniqueId);;
      this.isLoading = this.isLoading.bind(this);
    }

    componentWillMount() {
      this.props.unloadSpinner(this.uniqueId);
    }

    // true is loading, false is not loading
    isLoading(isOnBoolean) {
      this.props.toggleSpinner(this.uniqueId, isOnBoolean);
    }

    render() {
      // spinners is an object with the uuid as it's key
      // the value to the key is weather or not the spinner is on.
      const { spinners } = this.props;
      const spinnerState = spinners[this.uniqueId];
      return (
        <Component isLoading={this.isLoading} spinnerState={spinnerState}  />
      );
    }

  }

  const mapStateTopProps = state => ({
    spinners: state.ui.spinners,
  });

  const mapDispatchToProps = dispatch => ({
    initNewSpinner: uuid => dispatch(initNewSpinner(uuid)),
    unloadSpinner: uuid => dispatch(unloadSpinner(uuid)),
    toggleSpinner: (uuid, isOn) => dispatch(toggleSpinnerState(uuid, isOn))
  })

  return connect(mapStateTopProps, mapDispatchToProps)(Loadify);

};

export default loadify;

Пример использования

import loadify from '@/location/loadify';
import Spinner from '@/location/SpinnerComponent';

class Todo extends Component {

  componentWillMount() {
    this.props.isLoading(true);
    asyncCall.then(response => {
      // process response
      this.props.isLoading(false);
    })
  }

  render() {
    const { spinnerState } = this.props;
    return (
      <div>
        <h1>Spinner Testing Component</h1>
        { spinnerState && <Spinner /> }
      </div>
    );
  }

}

// Use whatever state you need
const mapStateToProps = state => ({
  whatever: state.whatever.youneed,
});

// use whatever dispatch you need
const mapDispatchToProps = dispatch => ({
  doAthing: () => dispatch(doAthing()),
});

// Export enhanced Todo Component
export default loadify(connect(mapStateToProps, mapDispatchToProps)(Todo));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...