reactJs redux:如何在用户事件上调度操作并将操作与reducers和store

时间:2016-04-27 21:31:20

标签: javascript reactjs store redux react-redux

对于反应和新闻来说是一个新手redux和尽管有教程(包括来自redux的todolist示例),我仍然难以理解如何实际触发将改变状态的动作。

我已经构建了一些非常简单的东西,装得很好。有人可以帮我分发一个动作,最终会改变商店数据吗?

我希望在用户点击li.module时调用togglePriceModule函数。 我是否需要调用作为道具传递给孩子的主要定价组件的功能?什么是正确的方法?

非常感谢!

我的app.js:

//Importing with braces imports a specific export of the file
import { createDevTools } from 'redux-devtools'
//Importing without braces imports the default export of the file
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'

import React from 'react'
import ReactDOM from 'react-dom'
//Redux helps manage a single state which can be updated through actions call pure reducers
//Importing muliple items injects them into the current scope
import { applyMiddleware, compose, createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux'
//React-router helps switch between components given a specific route
import { Router, Route, Link } from 'react-router'
import createHistory from 'history/lib/createHashHistory'
import { syncHistory, routeReducer } from 'react-router-redux'

//Imports an object of elements correspondings to every export of the file
import * as reducers from './reducers';

import Pricing from './components/pricing/pricing_main.js';

const history = createHistory();
const middleware = syncHistory(history);
const reducer = combineReducers({
    ...reducers,
    routing: routeReducer
});

const DevTools = createDevTools(
    <DockMonitor toggleVisibilityKey="ctrl-q"
                 changePositionKey="ctrl-alt-q"
                 defaultIsVisible={false}>
        <LogMonitor theme="tomorrow" preserveScrollTop={false} />
    </DockMonitor>
);

const finalCreateStore = compose(
    applyMiddleware(middleware),
    DevTools.instrument()
)(createStore);

const store = finalCreateStore(reducer);

middleware.listenForReplays(store);

var renderComponent = function(component, id) {
    var reactContainer = document.getElementById(id);
    if (null !== reactContainer) {
        ReactDOM.render(
            <Provider store={store}>
                <div>
                    <Router history={history}>
                        <Route path="/" component={component} />
                    </Router>
                    <DevTools />
                </div>
            </Provider>,
            reactContainer
        );
    }
};

renderComponent(Pricing, 'react-pricing');

我的定价组件:

import React from 'react';
var _ = require('lodash');

const Pricing = React.createClass({
    getInitialState: function(){
        return {
            modules: {
                'cms' : {
                    title: 'Fiches techniques & Mercuriale',
                    subtitle: 'Gérez votre connaissance sous un format structuré',
                    price: 15,
                    details: [
                        'première ligne',
                        'deuxième ligne',
                        'troisième ligne'
                    ],
                    'activated': true
                },
                'cycle' : {
                    title: 'Cycle de menus',
                    subtitle: 'Programmez votre production dans le temps',
                    price: 20,
                    details: [
                        'première ligne',
                        'deuxième ligne',
                        'troisième ligne'
                    ],
                    'activated': false
                },
                'organigram' : {
                    title: 'Organigramme de production',
                    subtitle: "Optimisez l'affectation de votre main d'oeuvre",
                    price: 20,
                    details: [
                        'première ligne',
                        'deuxième ligne',
                        'troisième ligne'
                    ],
                    'activated': false
                },
                'teams' : {
                    title: 'Planning des équipes',
                    subtitle: "Gérez les temps de présence de vos salariés",
                    price: 20,
                    details: [
                        'première ligne',
                        'deuxième ligne',
                        'troisième ligne'
                    ],
                    'activated': false
                },
                'orders' : {
                    title: 'Commandes et stocks',
                    subtitle: "Commandez en un clic auprès de vos fournisseurs",
                    price: 20,
                    details: [
                        'première ligne',
                        'deuxième ligne',
                        'troisième ligne'
                    ],
                    'activated': false
                }
            },
            options : {
                users: {
                    title: "Nombre d'utilisateurs",
                    subtitle: "Distribuez des accès sécurisés",
                    price: 5,
                    unit: "5€ par utilisateur",
                    type: 'quantity',
                    value: 1
                },
                sites: {
                    title: 'Sites de vente ou de production',
                    subtitle: "Gérez vos multiples sites dans la même interface",
                    unit: "50€ par site",
                    type: 'quantity',
                    value: 1
                },
                backup: {
                    title: 'Sauvegarde',
                    subtitle: "Recevez une copie Excel de vos données tous les jours",
                    type: 'switch',
                    value: 'day'
                }
            }
        }
    },
    componentWillMount: function(){
        this.setState(this.getInitialState());
    },
    render: function () {
        return (
            <div className="wrapper">
                <h1>Paramétrez votre offre</h1>
                <div id="elements">
                    <ul id="module-container" className="flex-container col">
                        {_.map(this.state.modules, function(module, key) {
                            return <Module key={key} data={module} />
                        })}
                    </ul>
                    <ul id="param_container">
                        {_.map(this.state.options, function(option, key) {
                            return <Option key={key} data={option} />
                        })}
                    </ul>
                </div>
                <div id="totals" className="flex-container sp-bt">
                    <span>Total</span>
                    <span>{calculatePrice(this.state)}</span>
                </div>
            </div>
        );
    }
});

function calculatePrice(state) {
    var modulePrices = _.map(state.modules, function(item){
        return item.price;
    });
    modulePrices = _.sum(modulePrices);

    return modulePrices;
}

var Module = React.createClass({
    render: function(){
        var data = this.props.data;
        return <li className="module">
            <div className="selection">
                <i className={data.activated ? 'fa fa-check-square-o' : 'fa fa-square-o'} />
            </div>
            <div className="title">
                <h3>{data.title}</h3>
                <h4>{data.subtitle}</h4>
            </div>
            <div className="price">
                <div className="figure">{data.price}</div>
                <div className="period">par mois</div>
            </div>
            <ul className="details">{
                data.details.map(function(item, key){
                    return <li key={key}><i className="fa fa-check" />{item}</li>
                })}
            </ul>
        </li>
    }
});

var Option = React.createClass({
    render: function(){
        var data = this.props.data;
        return <li className="param">
            <div className="title">
                <h3>{data.title}</h3>
                <h4>{data.subtitle}</h4>
            </div>
            <div className="config">
                <span className="figure"><i className="fa fa-minus" /></span>
                <input value="1"/>
                <span className="plus"><i className="fa fa-plus" /></span>
            </div>
        </li>
    }
});

export default Pricing;

我的行动:

import { TOGGLE_PRICE_MODULE, INCREASE_PRICE_OPTION, DECREASE_PRICE_OPTION } from '../constants/constants.js'

export function increasePriceOption(value) {
    return {
        type: INCREASE_PRICE_OPTION,
        value: value
    }
}

export function decreasePriceOption(value) {
    return {
        type: DECREASE_PRICE_OPTION,
        value: value
    }
}

export function togglePriceModule(activated) {
    return {
        type: TOGGLE_PRICE_MODULE,
        activated: activated
    }
}

我的减速机:

import { TOGGLE_PRICE_MODULE, INCREASE_PRICE_OPTION, DECREASE_PRICE_OPTION } from '../constants/constants.js'


export default function updateModule(state = false, action) {
    if(action.type === TOGGLE_PRICE_MODULE) {
        return !state;
    }
    return state
}

export default function updateOption(state = 1, action) {
    if(action.type === INCREASE_PRICE_OPTION) {
        return state + 1;
    }
    else if(action.type === DECREASE_PRICE_OPTION) {
        if (state < 2) {
            return 1;
        } else {
            return state + 1;
        }
    }
    return state

编辑1

我已经隔离了模块组件,并尝试从下面的第一个答案进行调整:模块正确加载,但视图中根本没有效果。想念什么?

第一个错误: 我需要改变

  

导入*作为来自&#39; ./ reducer&#39;;

的reducer

进入

  

从&#39; ./ reducers / pricing.js&#39;;

中导入*作为减速器

用于实际显示我的reducer的控制台日志。

为什么?

第二: console.log显示确实正在调用该操作 减速机中的相同表明它不是。 如何在reducer和action之间建立链接? 我应该使用mapStateToProps并以某种方式连接吗?

import React from 'react';
import { togglePriceModule } from '../../actions/pricing.js';

var Module = React.createClass({
    handleClick: function(status){
        this.context.store.dispatch(togglePriceModule(status));
    },
    render: function(){
        console.log(this.props);
        var data = this.props.data;
        return <li className="module" onClick={this.handleClick}>
            <div className="selection">
                <i className={data.activated ? 'fa fa-check-square-o' : 'fa fa-square-o'} />
            </div>
            <div className="title">
                <h3>{data.title}</h3>
                <h4>{data.subtitle}</h4>
            </div>
            <div className="price">
                <div className="figure">{data.price}</div>
                <div className="period">par mois</div>
            </div>
            <ul className="details">{
                data.details.map(function(item, key){
                    return <li key={key}><i className="fa fa-check" />{item}</li>
                })}
            </ul>
        </li>
    }
});

Module.contextTypes = {
    store: React.PropTypes.object
};

export default Module;
    }

EDIT2

我按照建议进行了更改,现在调用reducer。 但是我没有更改用户界面,所以我做错了。 我处理州/商店/道具的方式是对吗?

捆绑包有效,但我在控制台中收到以下错误:

  

warning.js:45警告:setState(...):在现有状态期间无法更新   状态转换(例如在render内)。渲染方法应该是一个   道具和国家的纯粹功能。

另外,我应该将定价组件(容器)中的函数传递给模块组件并将逻辑放在上面而不是在子模块组件中调度操作吗?

我点击的更新模块组件,希望更改UI:

import React from 'react';
import { connect } from 'react-redux';
import { togglePriceModule } from '../../actions/pricing.js';

var Module = React.createClass({
    handleClick: function(status){
        this.context.store.dispatch(togglePriceModule(status));
    },
    render: function(){
        var data = this.props.data;
        return <li className="module" onClick={this.handleClick(!data.activated)}>
            <div className="selection">
                <i className={data.activated ? 'fa fa-check-square-o' : 'fa fa-square-o'} />
            </div>
            <div className="title">
                <h3>{data.title}</h3>
                <h4>{data.subtitle}</h4>
            </div>
            <div className="price">
                <div className="figure">{data.price}</div>
                <div className="period">par mois</div>
            </div>
            <ul className="details">{
                data.details.map(function(item, key){
                    return <li key={key}><i className="fa fa-check" />{item}</li>
                })}
            </ul>
        </li>
    }
});

Module.contextTypes = {
    store: React.PropTypes.object
};

function mapStateToProps(state) {
    return {
        data: state.updateModule.data
    }
}

export default connect(mapStateToProps)(Module)

export default Module;

我的行动:

export function togglePriceModule(status) {
    return {
        type: TOGGLE_PRICE_MODULE,
        activated: status
    }
}

我的减速机:

import { TOGGLE_PRICE_MODULE, INCREASE_PRICE_OPTION, DECREASE_PRICE_OPTION } from '../constants/constants.js'

export function updateModule(state = {}, action) {
    console.log('updateModule reducer called');
    if(action.type === TOGGLE_PRICE_MODULE) {
        return {...state, activated : action.activated };
    }
    return state
}

export function updateOption(state = {}, action) {
    if(action.type === INCREASE_PRICE_OPTION) {
        return {...state, value: state.value + 1};
    } else if(action.type === DECREASE_PRICE_OPTION) {
        if (state.value < 2) {
            return {...state, value : 1};
        } else {
            return {...state, value : state.value - 1};
        }
    }
    return state
}

2 个答案:

答案 0 :(得分:0)

首先,调度是一个商店功能。您需要将商店作为参考,然后导入您的操作并发送它。 reducer将处理逻辑并返回将触发渲染的新状态。

添加:

Pricing.contextTypes = {
    store: React.PropTypes.object
};

你应该有商店参考。

然后只需导入您的操作:

import myAction from './myPath'

然后做:

this.context.store.dispatch(myAction(myVar));

这将触发将返回新状态并触发渲染的调度。

例如:

handleClick() {
    this.context.store.dispatch(myAction());
}

和内部渲染:

<a onClick={this.handleClick}>test</a>

我在那里使用ES6语法。

基本上这个过程应该非常简单,除非我遗漏了你的问题。

或者,如果您在console.log(this.props)中看到调度,那么您可以:

  this.props.dispatch(myAction(myVar));

回答你的两个问题: 我应该如何建立减速器和动作之间的联系?我应该使用mapStateToProps并以某种方式连接吗?

是的,您必须在组件中导入connect才能与商店建立链接:

import { connect } from 'react-redux';

是的,您需要将状态映射到道具:

function mapStateToProps(state) {
    return {
        myVar: state.myReducer.myVar
    }
}

然后使用connect将所有内容组合在一起。

export default connect(mapStateToProps)(Pricing)

答案 1 :(得分:0)

关于编辑2:

onClick方法中,您将调度状态变异的动作(通过handleClick)。每次更新状态时,都会触发重新渲染(通过调用render方法)。如果从render方法中更新状态,则可能会有无限循环的重新渲染。这就是错误消息所抱怨的内容。

此外,onClick期望函数作为参数。您可以通过编写onClick={this.handleClick.bind(this, !data.activated)}来部分应用该功能。