【问题标题】:reactJs redux: how to dispatch an action on a user event and link actions with the reducers and the storereactJs redux:如何在用户事件上分派操作并将操作与减速器和商店链接
【发布时间】:2016-08-22 10:16:37
【问题描述】:

作为 reactJs 和 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

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

第一个错误: 我需要改变

从 './reducers' 导入 * 作为减速器;

进入

import * as reducers from './reducers/pricing.js';

让控制台日志实际显示我的减速器。

为什么?

第二: 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

我已按照建议进行了更改,现在调用了减速器。 但是我没有 UI 更改,所以我猜我做错了什么。 我处理状态/商店/道具的方式正确吗?

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

warning.js:45 警告:setState(...):无法在现有期间更新 状态转换(例如在render 内)。渲染方法应该是 props 和 state 的纯函数。

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

我更新的模块组件,我点击它希望 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
}

【问题讨论】:

    标签: javascript reactjs store redux react-redux


    【解决方案1】:

    首先 dispatch 是一个 store 函数。您需要将商店作为参考,然后导入您的操作并调度它。 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) 看到 dispatch 那里,你可以:

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

    回答您的这两个问题: 我应该如何在 reducer 和 action 之间建立联系?我应该使用 mapStateToProps 并以某种方式连接吗?

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

    import { connect } from 'react-redux';
    

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

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

    最后使用 connect 将所有内容包装在一起。

    export default connect(mapStateToProps)(Pricing)
    

    【讨论】:

    • 嗨,非常感谢,我已经开始执行被调用的操作。你能看看我的编辑并帮助我在动作和减速器之间建立联系吗?
    • 嗨西奥,我越来越近了,它还没有完成。你能看看我的第二次编辑吗?谢谢!
    【解决方案2】:

    关于编辑2:

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

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

    【讨论】:

    • 不错! .bind 技巧确实解决了无限循环问题。但是尽管我的动作和我的减速器被调用,我仍然没有改变用户界面。您是否发现事物的链接方式有问题?我理解它的方式是我以函数的形式调度一个动作,该函数为状态取一个新值。然后我将子组件的道具映射到状态(我对此很了解)。然后我连接映射和组件,read-redux 应该处理将导致 ui 更改的存储数据更改。这里有什么问题吗?
    • 看起来您正在将常量而不是操作导入减速器。
    • 您能找出问题所在吗?
    • 嘿,谢谢!事实上,我在如何定义初始状态方面遇到了问题。在减速器中导入常量是可以的。一旦我有干净的东西,我会发布更新
    猜你喜欢
    • 2018-02-02
    • 2023-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 2017-05-03
    相关资源
    最近更新 更多