简体   繁体   English

React-Redux重构容器逻辑

[英]React-Redux Refactoring Container Logic

I got one container connected to one component. 我将一个容器连接到一个组件。 Its a select-suggestion component. 它是一个选择建议组件。 The problem is that both my container and component are getting too much repeated logic and i want to solve this maybe creating a configuration file or receiving from props one config. 问题是我的容器和组件都获得了太多重​​复的逻辑,我想解决这个问题,可能创建一个配置文件或从props接收一个配置。

This is the code: 这是代码:

import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { goToPageRequest as goToPageRequestCompetitions  } from '../ducks/competitions/index';
import { getSearchParam as getSearchCompetitionsParam, getCompetitionsList } from '../ducks/competitions/selectors';
import { goToPageRequest as goToPageRequestIntermediaries } from '../ducks/intermediaries/index';
import { getSearchParam as getSearchIntermediariesParam, getIntermediariesList } from '../ducks/intermediaries/selectors';
import SelectBox2 from '../components/SelectBox2';


export const COMPETITIONS_CONFIGURATION = {
    goToPageRequest: goToPageRequestCompetitions(),
    getSearchParam: getSearchCompetitionsParam(),
    suggestions: getCompetitionsList()
};

export const INTERMEDIARIES_CONFIGURATION = {
    goToPageRequest: goToPageRequestIntermediaries(),
    getSearchParam: getSearchIntermediariesParam(),
    suggestions: getIntermediariesList()
};

const mapStateToProps = (state, ownProps) => ({
    searchString: ownProps.reduxConfiguration.getSearchParam(state),
});

const mapDispatchToProps = (dispatch, ownProps) => ({
    dispatchGoToPage: goToPageRequestObj =>
        dispatch(ownProps.reduxConfiguration.goToPageRequest(goToPageRequestObj)),
});

const mergeProps = (stateProps, dispatchProps, ownProps) => ({
    ...ownProps,
    search: searchParam => dispatchProps.dispatchGoToPage({
        searchParam
    }),
    ...stateProps
});

export default withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(SelectBox2));






import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Flex, Box } from 'reflexbox';
import classname from 'classnames';
import styles from './index.scss';
import Input from '../Input';
import { AppButtonRoundSquareGray } from '../AppButton';
import RemovableList from '../RemovableList';

const MIN_VALUE_TO_SEARCH = 5;
const NO_SUGGESTIONS_RESULTS = 'No results found';


class SelectBox extends Component {
    /**
     * Component setup
     * -------------------------------------------------------------------------*/
    constructor(props) {
        super(props);
        this.state = {
            displayBox: false,
            selection: null,
            value: '',
            items: [],
            suggestions: [],
        };
    }
    /**
     * Component lifecycle
     * -------------------------------------------------------------------------*/
    componentWillMount() {
        console.log(this.props);
        document.addEventListener('mousedown', this.onClickOutside, false);
        if (this.props.suggestionsType){
            if (this.props.suggestionsType === 'competition'){
                this.state.suggestions = this.props.competitionsSuggestions;
            }
            if (this.props.suggestionsType === 'intermediaries'){
                this.state.suggestions = this.props.intermediariesSuggestions;
            }
        }
    }
    componentWillUnmount() {
        console.log(this.props);
        document.removeEventListener('mousedown', this.onClickOutside, false);
    }
    componentWillReceiveProps(nextProps){
        console.log(this.props);
        if (this.props.suggestionsType === 'competition') {
             this.state.suggestions = nextProps.competitionsSuggestions;
         }
        if (this.props.suggestionsType === 'intermediaries') {
            this.state.suggestions = nextProps.intermediariesSuggestions;
        }
     }
    /**
     * DOM event handlers
     * -------------------------------------------------------------------------*/
    onButtonClick = (ev) => {
        ev.preventDefault();
        const itemIncluded = this.state.items.find(item => item.id === this.state.selection);
        if (this.state.selection && !itemIncluded) {
            const item =
                this.state.suggestions.find(suggestion => suggestion.id === this.state.selection);
            this.setState({ items: [...this.state.items, item] });
        }
    };
    onChangeList = (items) => {
        const adaptedItems = items
            .map(item => ({ label: item.name, id: item.itemName }));
        this.setState({ items: adaptedItems });
    };
    onClickOutside = (ev) => {
        if (this.wrapperRef && !this.wrapperRef.contains(ev.target)) {
            this.setState({ displayBox: false });
        }
    };
    onSuggestionSelected = (ev) => {
        this.setState({
            displayBox: false,
            value: ev.target.textContent,
            selection: ev.target.id });
    };
    onInputChange = (ev) => {
        this.generateSuggestions(ev.target.value);
    };
    onInputFocus = () => {
        this.generateSuggestions(this.state.value);
    };
    /**
     * Helper functions
     * -------------------------------------------------------------------------*/
    setWrapperRef = (node) => {
        this.wrapperRef = node;
    };
    executeSearch = (value) => {
        if (this.props.suggestionsType === 'competition'){
            this.props.searchCompetitions(value);
        }
        if (this.props.suggestionsType === 'intermediaries'){
            this.props.searchIntermediaries(value);
        }
    };
    generateSuggestions = (value) => {
        if (value.length > MIN_VALUE_TO_SEARCH) {
            this.executeSearch(value);
            this.setState({ displayBox: true, value, selection: '' });
        } else {
            this.setState({ displayBox: false, value, selection: '' });
        }
    };
    renderDataSuggestions = () => {
        const { listId } = this.props;
        const displayClass = this.state.displayBox ? 'suggestions-enabled' : 'suggestions-disabled';
        return (
            <ul
                id={listId}
                className={classname(styles['custom-box'], styles[displayClass], styles['select-search-box__select'])}
            >
                { this.state.suggestions.length !== 0 ?
                    this.state.suggestions.map(suggestion => (<li
                        className={classname(styles['select-search-box__suggestion'])}
                        onClick={this.onSuggestionSelected}
                        id={suggestion.get(this.props.suggestionsOptions.id)}
                        key={suggestion.get(this.props.suggestionsOptions.id)}
                    >
                        <span>{suggestion.get(this.props.suggestionsOptions.label)}</span>
                    </li>))
                    :
                    <li className={(styles['select-search-box__no-result'])}>
                        <span>{NO_SUGGESTIONS_RESULTS}</span>
                    </li>
                }
            </ul>
        );
    };

    renderRemovableList = () => {
        if (this.state.items.length > 0) {
            const adaptedList = this.state.items
                .map(item => ({ name: item.name, itemName: item.id }));
            return (<RemovableList
                value={adaptedList}
                className={classname(styles['list-box'])}
                onChange={this.onChangeList}
                uniqueIdentifier="itemName"
            />);
        }
        return '';
    };

    render() {
        const input = {
            onChange: this.onInputChange,
            onFocus: this.onInputFocus,
            value: this.state.value
        };
        return (
            <Flex className={styles['form-selectBox']}>
                <Box w={1}>
                    <div
                        ref={this.setWrapperRef}
                        className={styles['div-container']}
                    >
                        <Input
                            {...this.props}
                            input={input}
                            list={this.props.listId}
                            inputStyle={classname('form-input--bordered', 'form-input--rounded', styles.placeholder)}
                        />
                        { this.renderDataSuggestions() }
                    </div>
                </Box>
                <Box>
                    <AppButtonRoundSquareGray type="submit" className={styles['add-button']} onClick={this.onButtonClick}>
                        Add
                    </AppButtonRoundSquareGray>
                </Box>
                <Box>
                    { this.renderRemovableList() }
                </Box>
            </Flex>
        );
    }
}


SelectBox.propTypes = {
    items: PropTypes.instanceOf(Array),
    placeholder: PropTypes.string,
    listId: PropTypes.string,
    className: PropTypes.string
};

SelectBox.defaultProps = {
    items: [],
    placeholder: 'Choose an option...',
    listId: null,
    className: ''
};

export default SelectBox;

As you see, in many places i am validating the type of suggestions and do something with that. 如您所见,在许多地方,我正在验证建议的类型并对此进行处理。 Its suppose to be a reusable component, and this component could accept any kind of type of suggestions. 它假定是可重用的组件,并且该组件可以接受任何类型的建议。 If this grows, if will have very big validations and i don't want that. 如果增长的话,是否会有很大的验证,我不希望这样。 So i think that i want something similar to this: 所以我认为我想要类似的东西:

const mapStateToProps = (state, ownProps) => ({
    searchString: ownProps.reduxConfiguration.getSearchParam(state),
});

const mapDispatchToProps = (dispatch, ownProps) => ({
    dispatchGoToPage: goToPageRequestObj =>
        dispatch(ownProps.reduxConfiguration.goToPageRequest(goToPageRequestObj)),
});

const mergeProps = (stateProps, dispatchProps, ownProps) => ({
    ...ownProps,
    search: searchParam => dispatchProps.dispatchGoToPage({
        searchParam
    }),
    ...stateProps
});

How can i make something similar to that? 我该如何做类似的事情?

Here are a few things to consider: 这里有几件事情要考虑:

The purpose of using Redux is to remove state logic from your components. 使用Redux的目的是从组件中删除状态逻辑。

What you've currently got has Redux providing some state and your component providing some state. 您目前拥有的是Redux提供某种状态, 您的组件提供某种状态。 This is an anti-pattern (bad) : 这是一个反模式(不好)

// State from Redux: (line 22 - 24)
const mapStateToProps = (state, ownProps) => ({
  searchString: ownProps.reduxConfiguration.getSearchParam(state),
});


// State from your component: (line 65 - 71)
this.state = {
  displayBox: false,
  selection: null,
  value: '',
  items: [],
  suggestions: [],
};

If you take another look at your SelectBox component - a lot of what it is doing is selecting state : 如果您再看一下SelectBox组件,那么它所做的很多事情就是选择状态

// The component is parsing the state and choosing what to render (line 79 - 86)
if (this.props.suggestionsType){
  if (this.props.suggestionsType === 'competition'){
    this.state.suggestions = this.props.competitionsSuggestions;
  }
  if (this.props.suggestionsType === 'intermediaries'){
    this.state.suggestions = this.props.intermediariesSuggestions;
  }
}

Turns out, this is precisely what mapStateToProps() is for . 事实证明, 这正是mapStateToProps()目的 You should move this selection logic to mapStateToProps() . 您应该将此选择逻辑移动到mapStateToProps() Something like this: 像这样:

const mapStateToProps = (state) => {
  let suggestions = null;
  switch (state.suggestionType) {
    case 'competition':
      suggestions = state.suggestions.competition;
      break;
    case 'intermediaries':
      suggestions = state.suggestions.intermediaries;
      break;
    default:
      break;
  }

  return {
    suggestions
  };
};

Every time the state updates (in Redux) it will pass new props to your component. 每次状态更新(在Redux中)时,都会将新的props传递给您的组件。 Your component should only be concerned with how to render its part of the state. 您的组件仅应关注如何呈现其状态的一部分。 And this leads me to my next point: When your application state is all being managed by Redux and you don't have state logic in your components, your components can simply be functions (functional components). 这引出我的下一个观点:当您的应用程序状态全部由Redux管理并且您的组件中没有状态逻辑时, 您的组件可以只是功能 (功能组件)。

const SelectBox3 = ({ suggestions }) => {
  const onClick = evt => { console.log('CLICK!'); };
  const list = suggestions.map((suggestion, index) => {
    return (
      <li key={index} onClick={onClick}>suggestion</li>
    );
  });

  return (
    <ul>
      {list}
    </ul>
  );
};

Applying these patterns, you get components that are very easy to reason about , and that is a big deal if you want to maintain this code into the future. 应用这些模式,您将获得非常易于推理的组件,而如果您想在将来维护此代码,那将是非常重要的。

Also, by the way, you don't need to use mergeProps() in your example. 另外,顺便说一句,您无需在示例中使用mergeProps() mapDispatchToProps can just return your search function since connect() will automatically assemble the final props object for you.: mapDispatchToProps只能返回您的搜索功能,因为connect()会自动为您组装最终的props对象。

const mapDispatchToProps = (dispatch, ownProps) => ({
  // 'search' will be a key on the props object passed to the component!
  search: searchParam => {
    dispatch(ownProps.reduxConfiguration.goToPageRequest({ searchParam });
    // (also, your 'reduxConfiguration' is probably something that belongs in
    // the Redux state.) 
  }
}); 

I highly recommend giving the Redux docs a good read-through. 我强烈建议给Redux文档一个很好的通读。 Dan Abramov (and crew) have done a great job of laying it all out in there and explaining why the patterns are the way they are. Dan Abramov(和工作人员)在将所有内容都放在其中并解释了模式为何如此的方式方面做得很出色。

Here's the link: Redux . 这是链接: Redux

Also, look into async actions and redux-thunk for dealing with asynchronous calls (for performing a search on a server, for example). 另外,研究异步操作和redux-thunk以处理异步调用(例如,用于在服务器上执行搜索)。

Finally let me say: you're on the right track. 最后,我要说:您步入正轨。 Keep working on it, and soon you will know the joy of writing elegant functional components for your web apps. 继续努力,很快您就会知道为Web应用程序编写优雅的功能组件的乐趣。 Good luck! 祝好运!

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

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