使用redux,我应该将更新状态的逻辑放在哪里?

时间:2020-06-26 22:14:10

标签: reactjs redux react-redux redux-thunk

我试图将redux合并到我制作的现有应用程序中,该应用程序从api获取geoJSON并加载地图并可以进行过滤。

在使用redux之前,我创建了一个句柄函数来过滤状态并进行更新。然后将其向下传递给子组件以在按钮中使用。

在学习Redux的过程中,我正在努力弄清楚我将该代码放在哪里。它是放在我的减速器中还是作为动作创建者?有人可以建议吗?

父类组件内部的句柄函数

filterMaterial = (name) => {
    const filteredData = this.state.geoJSON.features.filter(item => item.properties.material === name ? item : false);

    const newobj = {
        type: "FeatureCollection",
        totalFeatures: filteredData.length,
        features: filteredData
    }

    this.setState({mapJSON: newobj});
}

下面是我当前与redux相关的代码,如何将以上代码转换为与redux一起使用?

操作

import { DATA_LOADED } from "../constants/action-types";

export function getData() {
    return function(dispatch) {
        const request = async () => {
            const url = `http://localhost:4000/api/assets`;

            try {
                const response = await fetch(url);
                const data = await response.json();
                
                dispatch({ type: DATA_LOADED, payload: data });
            } catch(err) {
                console.log(err)
            }
        }

        return request();
    };
}

减速器

import { DATA_LOADED } from "../constants/action-types";

const initialState = {
    geoJSON: {},
    mapJSON: {}
};

function rootReducer(state = initialState, action) {
    if(action.type === DATA_LOADED) {
        return Object.assign({}, state, {
            geoJSON: state.geoJSON = {...action.payload},
            mapJSON: state.mapJSON = {...action.payload}
        });
    }

    return state;
};

export default rootReducer;

商店

import { createStore, applyMiddleware, compose } from "redux";
import rootReducer from "../reducers/index";
import thunk from "redux-thunk";

const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(
    rootReducer,
    storeEnhancers(applyMiddleware(thunk))
);

export default store;

父级组件

//...code

const mapStateToProps = state => {
    return { 
        geoJSON: state.geoJSON,
        mapJSON: state.mapJSON
    };
};

export default connect(mapStateToProps, {getData})(FetchData);

2 个答案:

答案 0 :(得分:2)

除了减速器中的一些状态对象突变之外,您所拥有的一切看起来还不错。

import { DATA_LOADED } from "../constants/action-types";

const initialState = {
    geoJSON: {},
    mapJSON: {}
};

function rootReducer(state = initialState, action) {
    if(action.type === DATA_LOADED) {
        return Object.assign({}, state, {
            geoJSON: state.geoJSON = {...action.payload}, // this will mutate current state!
            mapJSON: state.mapJSON = {...action.payload}
        });
    }

    return state;
};

export default rootReducer;

模式是简单地创建新的状态对象,并在当前状态中扩展您想要保留的状态。

import { DATA_LOADED } from "../constants/action-types";

const initialState = {
    geoJSON: {},
    mapJSON: {}
};

function rootReducer(state = initialState, action) {
    if(action.type === DATA_LOADED) {
        const { geoJSON, mapJSON } = action.payload;
        return {
            ...state,
            geoJSON,
            mapJSON
        });
    }

    return state;
};

export default rootReducer;

此OFC假定每次更新状态时,您只需替换现有的geoJSONmapJSON值。如果您需要合并新的有效负载,则将使用以下模式。

return {
  ...state,
  geoJSON: { ...state.geoJSON, ...geoJSON },
  mapJSON: { ...state.mapJSON, ...mapJSON },
});

Reducer要记住的重要一点是它们应该是具有零副作用的纯函数,并且它们应该 始终 返回新的对象引用,以便进行浅引用相等性检查对DOM差异做出反应。

至于过滤。筛选时,通常会保留真相来源,而只是筛选数据的“视图”。在这种情况下,您肯定要在组件内进行过滤。它将从connect HOC中获取地理数据作为道具,因此您可以直接从道具对象中进行过滤。

const filteredData = this.props.geoJSON.features.filter(item => item.properties.material === name ? item : false);

const newobj = {
    type: "FeatureCollection",
    totalFeatures: filteredData.length,
    features: filteredData
}

这可以在render函数中完成。不需要在本地组件状态下复制数据。

评论中的其他问题

如果我按照您的建议通过道具过滤状态,我该怎么办 更新状态?你只是打电话给调度员吗?

是的,更新redux状态是通过调度的动作和reducer完成的。也许我们对“过滤器”的含义并不完全相同。通常,当您过滤数据时,您只是将源过滤到精简的数据集中进行显示。这不需要更改或更新源。将其与诸如在源数据中添加或删除条目(注意:在实现中,过滤器功能可能用于删除特定数据)之类的事情进行对比,在该操作中必须分派操作以对状态进行操作,即用户单击删除按钮。

如果您需要持久保存过滤后的数据,那么我建议您保留原始数据不变,而将过滤条件存储在状态中。然后,您可以使用两个状态切片来得出显示的内容。

如果我将其包装在函数中,也可以将其传递给子级 组件?

当然,您几乎可以将任何东西作为对子组件的支持。 Redux,React上下文API和高阶组件的优点在于,它们都有助于解决道具钻探的问题。 任何组件都可以订阅redux存储并提取 just 所需的数据,并访问dispatch函数,甚至可以将动作创建者自动包裹在呼叫dispatch。因此,尽管您可以创建使用分派并将其传递给子代的函数,但这并不是react中redux的推荐用例。

答案 1 :(得分:1)

您是永久更改商店状态还是仅呈现已过滤的组件状态?如果只是渲染,则将其保留在组件中。如果您打算永久更新存储状态,则仍将其保留在组件中,但将返回值(newobj)作为有效内容分发给动作创建者:

动作创建者文件示例:

const ACTION_TYPE_NAME = "ACTION_TYPE_NAME";
export default function actionCreatorName(payloadName) {
    return { type: ACTION_TYPE, payloadName }
}

减速器示例:

//set default state here
const initial = {mapJSON: "", geoJSON: ""}

export default function rootReducer(state = initial, action) {
    switch (action.type) {
        case ACTION_TYPE_NAME:
            //return new state object with updated prop
            return {...state, mapJSON: action.payloadName}
        default: 
            return state
    }
}

示例组件连接/操作分派:

import React from 'react'
import { connect } from 'react-redux'
import actionCreatorName from '../actions/actionCreatorName.js'


//any state you want to import from store
const mapStateToProps = state => {
    return {
      mapJSON: state.mapJSON,
      geoJSON: state.geoJSON
    }
}
//any imported action creators you want to bind with dispatch
const mapDispatchToProps = {
   actionCreatorName
}

//set the above as props
let ComponentName = ({actionCreatorName, mapJSON, geoJSON}) => {
    const yourFunction = () => {
       //do stuff then dispatch
       actionCreatorName(newobj)
    }
    return (
       <div onClick={yourFunction}></div>
    )
}

ComponentName = connect(
    mapStateToProps,
    mapDispatchToProps
)(ComponentName) 

export default ComponentName