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

With redux, where do I place my logic for updating state?

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

在使用redux之前,我创建了一个处理状态过滤和更新的函数。然后将其传递给要在按钮中使用的子组件。

现在我正在学习 Redux,我正在努力弄清楚我把这段代码放在哪里。它是进入我的 reducer 还是作为 action creator?有人可以请教吗?

在父class组件中处理函数

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);

您是永久更改存储状态还是仅呈现过滤后的组件状态?如果你只是渲染,你将它保存在你的组件中。如果您打算永久更新商店状态,您仍将其保留在组件中,但将 return (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
    }
}

示例组件connection/action 调度:

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

除了 reducer 中的某些状态对象突变外,您的内容看起来不错。

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 需要记住的重要一点是它们应该是零副作用的纯函数,并且它们应该 always return 新的对象引用,因此浅引用相等性检查在对 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 函数中完成。在本地组件状态中复制数据应该没有太多需要。

来自评论的其他问题

If i am filtering the state through props as you suggested, how can I update the state? Do you just call a dispatch?

是的,更新 redux 状态是通过分派的操作和 reducer 完成的。也许我们对“过滤器”的含义并不完全一致。通常,当您过滤数据时,您只是将源过滤为简化的数据集以供显示。这不需要改变或更新源。将此与在源数据中添加或删除条目(注意:在实现中可能使用过滤器函数删除特定数据)进行对比,其中必须调度操作以对状态进行操作,即用户点击了删除按钮。

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

Also if I wrap it in a function can I pass it down to child components?

当然,您可以将任何东西作为 prop 传递给子组件。 redux、react context API、Higher Order Components 的美妙之处在于它们都有助于解决 prop 钻孔的问题。 任何 组件都可以订阅 redux 存储并提取 只是 他们需要的数据,并访问 dispatch 功能,甚至有他们的通过调用 dispatch 自动包装动作创建者。因此,虽然您 可以 创建使用 dispatch 的函数并将它们传递给子级,但这并不是真正推荐的 redux 在 react 中的用例。