与REDUX动作和减速器混淆

时间:2019-02-28 08:41:25

标签: javascript reactjs redux

所以我想重构上一个问题中的一些代码:

React: How to update one component, when something happens on another component

因此,我开始深入研究现有代码模板,以了解其实现方式。

我找到了reducers.js,在其中添加了新的reducer:ActiveTenant

import Auth from './auth/reducer';
import App from './app/reducer';
import ThemeSwitcher from './themeSwitcher/reducer';
import LanguageSwitcher from './languageSwitcher/reducer';
import ActiveTenant from './activetenant/reducer';

export default {
  Auth,
  App,
  LanguageSwitcher,
  ThemeSwitcher,
  ActiveTenant
};

新的减速器是这样的:

import { Map } from 'immutable';
import actions from './actions';
import { adalApiFetch } from '../../adalConfig';

const initState = new Map({
    tenantId: ''
});

export default function(state = initState, action) {
  switch (action.type) {
    case actions.SET_TENANT_ACTIVE:
    {
        const options = { 
            method: 'post'
        };

        adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+state.tenantId, options)
        .then(response =>{
            if(response.status === 200){
                console.log("Tenant activated");
            }else{
                throw "error";
            }
        })
        .catch(error => {
            console.error(error);
        });

        return state.set('tenant', state.Name);
    }
    default:
      return state;
  }
}

以及该减速器的动作

const actions = {
  SET_TENANT_ACTIVE: 'SET_TENANT_ACTIVE',
  setTenantActive: () => ({
    type: actions.SET_TENANT_ACTIVE
  }),
};
export default actions;

然后从组件本身开始,当在前端选择一行时,我需要调用该动作,因此我将注释的代码重构为一行。

import React, { Component } from 'react';
import {  Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
import actions from '../../redux/activetenant/actions';

const { setTenantActive } = actions;

class ListTenants extends Component {

    constructor(props) {
        super(props);
        this.state = {
            data: []
        };
    }



    fetchData = () => {
        adalApiFetch(fetch, "/Tenant", {})
          .then(response => response.json())
          .then(responseJson => {
            if (!this.isCancelled) {
                const results= responseJson.map(row => ({
                    key: row.id,
                    TestSiteCollectionUrl: row.TestSiteCollectionUrl,
                    TenantName: row.TenantName,
                    Email: row.Email
                  }))
              this.setState({ data: results });
            }
          })
          .catch(error => {
            console.error(error);
          });
      };


    componentDidMount(){
        this.fetchData();
    }

    render() {
        const columns = [
                {
                    title: 'TenantName',
                    dataIndex: 'TenantName',
                    key: 'TenantName',
                }, 
                {
                    title: 'TestSiteCollectionUrl',
                    dataIndex: 'TestSiteCollectionUrl',
                    key: 'TestSiteCollectionUrl',
                }, 
                {
                    title: 'Email',
                    dataIndex: 'Email',
                    key: 'Email',
                }
        ];

        // rowSelection object indicates the need for row selection
        const rowSelection = {
            onChange: (selectedRowKeys, selectedRows) => {
                if(selectedRows[0].TenantName != undefined){
                    console.log(selectedRows[0].TenantName);
                    const options = { 
                        method: 'post'
                    };

                    setTenantActive(selectedRows[0].TenantName);
                    /* adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+selectedRows[0].TenantName.toString(), options)
                        .then(response =>{
                        if(response.status === 200){
                            Notification(
                                'success',
                                'Tenant set to active',
                                ''
                                );
                        }else{
                            throw "error";
                        }
                        })
                        .catch(error => {
                        Notification(
                            'error',
                            'Tenant not activated',
                            error
                            );
                        console.error(error);
                    }); */
                }
            },
            getCheckboxProps: record => ({
                type: Radio
            }),
        };

        return (
            <Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
        );
    }
}

export default ListTenants;

但是,我不清楚该操作与reducer之间的关系,如果我检查调试器,则该操作已执行,并且未接收到任何参数,但从未执行该reducer。

我是否必须在某个地方派遣产品?这个难题中我缺少什么?

3 个答案:

答案 0 :(得分:1)

您使用的减速器错误。减速器是supposed to be pure.,您的副作用表明您还不了解Redux。

我建议您花3个小时,仔细阅读Redux文档,然后按照tutorials进行操作(而不是为您写下一个解决方案(因为这将永远需要解释Redux,所以这将永远是永久的)。他们很棒)。

之后,您可能需要研究Redux Thunk。但是,you might not need thunks

PS :(提出来的东西很小,但是我没有看到有人在Redux中使用Map s。您这样做是有原因吗?您可能想使用普通对象。)

答案 1 :(得分:1)

您的操作不正确,您应该传递一个有效的租户名称作为参数。

参考https://redux-starter-kit.js.org/api/createaction

我们可以在两个地方将操作类型编写为内联字符串。 动作创建者很好,但是不需要使用Redux-组件可以跳过提供mapDispatch参数进行连接,而只需调用this.props.dispatch({type:“ CREATE_POST”,有效负载:{id:123, title:“ Hello World”}})本身。

参考https://redux-starter-kit.js.org/usage/usage-guide

答案 2 :(得分:1)

所以首先要了解的是 Redux Cycle

动作创建者->动作->调度->减速器->状态

动作创建者:动作创建者是一个函数,该函数将创建或返回带有type属性且称为 Action 的纯JavaScript对象。 payload属性,它描述了您要对数据进行的一些更改。

payload属性描述了我们要进行的更改的背景。

操作的目的是描述应用程序内部数据的某些更改。

动作创建者将创建动作

dispatch函数将执行一个 Action 动作,并复制该对象并将其传递到应用程序内部的多个不同位置,从而导致我们进入减速器

在Redux中,化简器是负责执行 Action 的功能。它要处理该 Action ,对数据进行一些更改并返回,以便可以将其集中在某个位置。

在Redux中,属性是我们的归约器产生的所有信息的中央存储库。所有信息都被整合到 State 对象中,因此我们的React应用程序可以轻松进入应用程序的Redux端并访问应用程序内部的所有数据。

这样,应用程序不必遍历每个单独的reducer并询问当前的 State

因此,请消化几分钟,然后查看您的体系结构。

让我们跳过减速器

减速器通过由动作创建者创建的动作来调用。减速器将查看该操作,并决定是否需要基于该操作来修改一些数据。

换句话说,reducer的工作不是执行API请求,而是处理动作创建者发送给它的动作。

所以代替这个:

import { Map } from 'immutable';
import actions from './actions';
import { adalApiFetch } from '../../adalConfig';

const initState = new Map({
    tenantId: ''
});

export default function(state = initState, action) {
  switch (action.type) {
    case actions.SET_TENANT_ACTIVE:
    {
        const options = { 
            method: 'post'
        };

        adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+state.tenantId, options)
        .then(response =>{
            if(response.status === 200){
                console.log("Tenant activated");
            }else{
                throw "error";
            }
        })
        .catch(error => {
            console.error(error);
        });

        return state.set('tenant', state.Name);
    }
    default:
      return state;
  }
}

您的减速器应如下所示:

import { SET_TENANT_ACTIVE } from "../actions/types";

const initialState = {
    tenantId: ''
};

export default (state = initialState, action) {
  switch (action.type) {
    case SET_TENANT_ACTIVE:
      return {...state, [action.payload.id]: action.payload };
    default:
      return state;
  }
}

然后在动作创建者文件中,您应该具有一个类似于以下内容的动作创建者:

import axios from 'axios';
import { SET_TENANT_ACTIVE } from "../actions/types";


export const setTenant = id => async (dispatch) => {
  const response = await axios.post(`/tenants/${id}`);

  dispatch({ type: SET_TENANT_ACTIVE, payload: response.data });
};

您还需要了解Redux项目的结构,因为在上述重构之后,您将丢失如何将所有这些连接到组件的方法。在您的组件文件中,没有connect()函数,该函数也需要Provider标记,而您没有。

为此,我建议您首先像这样设置文件夹和文件结构:

  /src
    /actions
    /components
    /reducers
    index.js

因此,在您的index.js文件中,它应该看起来像这样:

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import reduxThunk from "redux-thunk";

import App from "./components/App";
import reducers from "./reducers";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducers,
  composeEnhancers(applyMiddleware(reduxThunk))
);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.querySelector("#root")

因此,您的目标是确保在组件层次结构的最顶层获得Provider标签,并确保将其传递给Redux存储的引用,以获取所有 reducers strong>加载到其中。

因此,在上面我已经创建了商店,并将其传递给我们一组减速器,它将还原为您所有应用程序的状态

最后,您在上面看到的是我创建了一个<Provider>的实例,并用它包装了<App />组件,然后您要传递的<Provider>组件是一个名为{ {1}}。 store是调用store并调用createStore()的结果。

reducers代表我们与Redux <Provider>进行交互。

注意,我还连接了J. Hesters提到的Redux-Thunk,据我从您的代码中可以看到,您正在发出ajax请求,这就是为什么我为您提供了一个异步动作创建者,这意味着您将需要Redux-Thunk或类似的中间件,让我不要冒犯Redux-Saga粉丝,因此至少您有这两种选择。对于Redux来说,您似乎比较陌生,只需与Redux-Thunk一起使用。

现在,您可以在组件文件中使用store组件,以完成将这些动作创建者和减速器连接到组件或应用程序的React端的情况。

connect()

导入import React, { Component } from 'react'; import { connect } from "react-redux"; import { Table, Radio} from 'antd'; import { adalApiFetch } from '../../adalConfig'; import Notification from '../../components/notification'; import actions from '../../redux/activetenant/actions'; 之后,您可以在下面创建一个实例:

connect

请不要就上述语法与我争论(实际上有一位前学生向我报告了管理员使用该语法作为不知道我在做什么的证据。)

然后,如果需要,您需要通过添加export default connect()(ListTenants); 来配置此connect() React组件,但必须将mapStateToProps作为第二个参数传递给actions 。如果您意识到不需要connect(),则只需传入mapStateToProps作为第一个参数,但不能将其留空。

希望所有这些都对您有所帮助,并欢迎您进入React-Redux的美好世界。