0

我第一次尝试使用 Redux 在我的应用程序中放入一个表单组件,并且我试图弄清楚我必须为什么创建 Reducers / Actions。

在其他组件中,我将我的用户和消息传递到 mapStateToProps 并且它们工作正常。但是,在这个组件中,我从后端为 componentDidMount 方法中的表字段提取数据,我不确定是否只有要更改的数据存储在 Redux 中。

我还需要为表单创建一个减速器吗?还是直接发布到后端/节点/postgresql。我打算有一个用所有最新数据更新的表,以便我可以看到它被自动添加到检索数据的逻辑。

我对 React / JavaScript 很陌生,所以我的逻辑可能有点偏离,所以任何建议都将不胜感激。

潜水LogForm.component.js

export class DiveLogForm extends Component  {

        constructor(props){

            super(props);
            this.handleSubmitDive = this.handleSubmitDive.bind(this);
            this.onChangeDiveType = this.onChangeDiveType.bind(this);
            this.onChangeSchoolName = this.onChangeSchoolName.bind(this);
            this.onChangeCurrent = this.onChangeCurrent.bind(this);
            this.onChangeVisibility = this.onChangeVisibility.bind(this);
            this.onChangeDiveDate = this.onChangeDiveDate.bind(this);
            this.onChangeMaxDepth = this.onChangeMaxDepth.bind(this);
            this.onChangeDiverUserNumber = this.onChangeDiverUserNumber.bind(this);
            this.onChangeVerifiedBySchool = this.onChangeVerifiedBySchool.bind(this);
            this.onChangeDiveNotes = this.onChangeDiveNotes.bind(this);
            this.onChangeDivePoint = this.onChangeDivePoint.bind(this);

            this.state = {
                diveTypeID: "",
                diveSchoolNameID: "",
                diveCurrentID: "",
                diveVisibilityID: "",
                diveDate: "",
                diveMaxDepth: "",
                diverUserNumber: "",
                diveVerifiedBySchool: "",
                diveNotes: "",
                divePoint: "",
                currentList: [],
                regionList: [],
                diveTypeList: [],
                visibilityList: [],
                diveSpotList: [],
                currentUser: [],
                loading: false,
            };
        }

        componentDidMount() {
            pullCurrentFields().then((response) => {
                const { data } = response;
                this.setState({ currentList: data.data });
            });
            pullRegionFields().then((response) => {
                const { data } = response;
                this.setState({ regionList: data.data });
            });
            pullDiveTypeFields().then((response) => {
                const { data } = response;
                this.setState({ diveTypeList: data.data });
            });
            pullVisibilityFields().then((response) => {
                const { data } = response;
                this.setState({ visibilityList: data.data });
            });
            pullDiveSpotFields().then((response) => {
                const { data } = response;
                this.setState({ diveSpotList: data.data });
            });

            //this.props.userDiveLogList();
        }

        onChangeDiveType(e) {
            this.setState({
                diveTypeID: e.target.value,
            });

        }

        onChangeSchoolName(e) {
            this.setState({
                diveSchoolNameID: e.target.value,
            });
        }

        onChangeCurrent(e) {
            this.setState({
                diveCurrentID: e.target.value,
            });
        }

        onChangeVisibility(e){
            this.setState({
                diveVisibilityID: e.target.value,
            });
        }

        onChangeDiveDate(e) {
            this.setState({
                diveDate: e.target.value,
            });
        }

        onChangeMaxDepth(e){
            this.setState({
                diveMaxDepth: e.target.value,
            });
        }

        onChangeDiverUserNumber(e){
            this.setState({
                diverUserNumber: e.target.value,
            });
        }

        onChangeVerifiedBySchool(e){
            this.setState({
                diveVerifiedBySchool: e.target.value,
            });
        }

        onChangeDiveNotes(e) {
            this.setState({
                diveNotes: e.target.value,
            });
        }

        onChangeDivePoint(e){
            this.setState({
                divePoint: e.target.value,
            });
        }

        handleSubmitDive(e) {

            e.preventDefault();

            this.setState({
                loading: true,
            });
            this.form.validateAll();

            //const {dispatch, history} = this.props;

            if (this.checkBtn.context._errors.length === 0) {
                this.props
                    .dispatch(registerUserDive(

                        this.state.diveTypeID,
                        this.state.diveSchoolNameID,
                        this.state.diveCurrentID,
                        this.state.diveVisibilityID,
                        this.state.diveDate,
                        this.state.diveMaxDepth,
                        this.state.diverUserNumber,
                        this.state.diveVerifiedBySchool,
                        this.state.diveNotes,
                        this.state.diveNotes))

                    .then(() => {
                        window.history.push("/divelogtable");
                        window.location.reload();
                    })
                    .catch(() => {
                        this.setState({
                            loading: false
                        });
                    });
            }
        }


    render() {

        const { classes } = this.props;
        const { user: currentUser } = this.props;

        if (this.state.currentList.length > 0) {
            console.log("currentList", this.state.currentList);
        }
        if (this.state.regionList.length > 0) {
            console.log("regionList", this.state.regionList);
        }
        if (this.state.diveTypeList.length > 0) {
            console.log("diveTypeList", this.state.diveTypeList);
        }
        if (this.state.visibilityList.length > 0) {
            console.log("visibilityList", this.state.visibilityList);
        }
        if (this.state.diveSpotList.length > 0) {
            console.log("diveSpotList", this.state.diveSpotList);
        }         

        return (

 ...materialUI form code

function mapStateToProps(state){
    const { user } = state.auth;
    const { regionList } = state.region;
    const { currentList } = state.current;
    const { diveTypeList } = state.diveType;
    const { visibilityList } = state.visibility;
    const { diveSpotList } = state.diveSpot;
    return {
        user,
        regionList,
        currentList,
        diveTypeList,
        visibilityList,
        diveSpotList,
    };
}

export default compose(
    connect(
        mapStateToProps,
    ),
    withStyles(useStyles)
)(DiveLogForm);

因为我主要关心将表单数据添加到后端。我已经包含了diveLog.service.js 文件等

export const registerDive = (diveTypeID, diveSchoolNameID, diveCurrentID, diveVisibilityID, diveDate, diveMaxDepth, diveEquipmentWorn, diverUserNumber, diveVerifiedBySchool, diveNotes, divePoint) => {
    return axios.post(API_URL + "registerdive", {
        diveTypeID,
        diveSchoolNameID,
        diveCurrentID,
        diveVisibilityID,
        diveDate,
        diveMaxDepth,
        diveVerifiedBySchool,
        diveNotes,
        divePoint
    });
};

潜水日志.action.js

export const registerUserDive = (
                                    diveTypeID,
                                    diveSchoolNameID,
                                    diveCurrentID,
                                    diveVisibilityID,
                                    diveDate,
                                    diveMaxDepth,
                                    diverUserNumber,
                                    diveVerifiedBySchool,
                                    diveNotes,
                                    divePoint) => (dispatch) => {

    return registerDive(

                                    diveTypeID,
                                    diveSchoolNameID,
                                    diveCurrentID,
                                    diveVisibilityID,
                                    diveDate,
                                    diveMaxDepth,
                                    diveVerifiedBySchool,
                                    diveNotes,
                                    divePoint).then(

        (response) => {
            dispatch ({
                type: successful_reg,
            });
            dispatch({
                type: set_message,
                payload: response.data.message,
            });
            return Promise.resolve();
        },
        (error) => {
            const message =
                (error.response &&
                    error.response.data &&
                    error.response.data.message) ||
                error.message ||
                error.toString();
            dispatch({
                type: set_message,
                payload: message,
            });

            return Promise.resolve();
        },
        (error) => {
            (error.response &&
                error.response.data &&
                error.response.data.message) ||
            error.message ||
            error.toString();

            dispatch({
                type: failed_reg,
            });
            return Promise.reject();
        }
    );
};

我的diveLog 注册操作可能有点偏离,因为我在编码时不理解reducer 的概念。

4

1 回答 1

1

在我开始使用代码之前,我不明白你的问题,但现在我明白你想要做什么了。您有五个不同的列表(regionListcurrentList等)。这些可能用于生成下拉菜单的选项。

现在,您正在从您的 redux 商店中选择所有列表,并通过mapStateToProps. 您永远不会使用列表将任何更改推送到 redux 存储。您正在调用您的函数以componentDidMount从后端获取列表数据并将该数据存储在this.state. 这有点冲突,因为现在我们在两个地方都有数据。我们使用来自 的列表this.props还是来自 的列表this.state

最终取决于您要将哪些数据存储在哪里。在 redux 中存储数据的优点是它可以同时被多个不同的组件使用。它还允许您对后端的每次调用只执行一次,但为了获得这一优势,您需要编写带有条件检查的调用,以便仅在数据不存在时才进行调用。

我还需要为表单创建一个减速器吗?还是直接发布到后端/节点/postgresql。

我建议将表单状态保留在组件本身中,因为部分填充的表单仅由该组件使用。

我打算有一个用所有最新数据更新的表,以便我可以看到它被自动添加到检索数据的逻辑。

我不确定什么是什么的父级,但是如果此表单显示在带有表格的屏幕上,那么您可能希望将isLoading状态向上移动到父级并通过传递给 props 的回调来更新它。这样,表格组件就知道何时加载新行。或者,当您点击提交时,您可能会发送一个操作来将新的潜水存储到 redux(但我不会在每次击键时都存储它)。

在这个组件中,我从后端为 componentDidMount 方法中的表字段提取数据,我不确定是否只有要更改的数据存储在 Redux 中。

通用数据是 redux 的理想选择。因此,在我看来,将所有区域的列表存储在 redux 中确实有意义。

我试图弄清楚我必须为什么创建减速器/动作。

当您有五个行为相似的不同列表时,最好定义将列表名称作为变量的通用操作和操作创建者。也有一个通用的pullFields功能会很好!

这有点附带说明,但建议任何刚入门的人都应该学习函数组件和钩子useSelectoruseDispatch而不是类组件和connect. 编写组件变得更加容易,并且this.handleSubmitDive.bind(this)可以轻松避免您正在做的一些事情。

我试图清理你代码中的重复,但我没有解决 redux 问题。所以这里有一个建议的设置来处理使用 redux 获取数据。其中一些有点“高级”,但我认为你应该能够复制和粘贴它。

定义一个异步 thunk 操作,它从您的 API 获取列表数据并将其存储在 redux 中,但如果数据已经加载,则不执行任何操作。

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

export const requireFieldData = createAsyncThunk(
  'fields/requireData', // action name
  // action expects to be called with the name of the field
  async (field) => {
    // you need to define a function to fetch the data by field name
    const response = await pullField(field);
    const { data } = response;
    // what we return will be the action payload
    return {
      field,
      items: data.data
    };
  },
  // only fetch when needed: https://redux-toolkit.js.org/api/createAsyncThunk#canceling-before-execution
  {
    condition: (field, {getState}) => {
      const {fields} = getState();
      // check if there is already data by looking at the array length
      if ( fields[field].length > 0 ) {
        // return false to cancel execution
        return false;
      }
    }
  }
)
...

用于存储从 API 获取的数据的字段的 Reducer。(使用createSlice

...

const fieldsSlice = createSlice({
  name: 'fields',
  initialState: {
    current: [],
    region: [],
    diveType: [],
    visibility: [],
    diveSpot: [],
  },
  reducers: {},
  extraReducers: {
    // picks up the success action from the thunk
    [requireFieldData.fulfilled.type]: (state, action) => {
      // set the property based on the field property in the action
      state[action.payload.field] = action.payload.items
    }
  }
})

export default fieldsSlice.reducer;

用户减速器需要能够添加潜水。您可能希望在此处存储更多信息并执行更多操作。

const userSlice = createSlice({
  name: 'user',
  initialState: {
    dives: [],
  },
  reducers: {
    // expects action creator to be called with a dive object
    addDive: (state, action) => {
      // append to the dives array
      state.dives.push(action.payload)
    }
  }
})

export const { addDive } = userSlice.actions;
export default userSlice.reducer;

基本商店设置加入fieldsuser切片

import { configureStore } from "@reduxjs/toolkit";
import fieldsReducer from "./fields";
import userReducer from "./user";

export default configureStore({
  // combine the reducers
  reducer: {
    user: userReducer,
    fields: fieldsReducer,
  }
});

该组件可以使用useSelector而不是mapStateToProps从 redux 访问数据。我们将dispatch执行 thunk 操作以确保所有列表都已加载。它们将作为空数组开始,但在操作完成后更新为新值。

const DiveLogForm = (props) => {

  // select user object from redux
  const user = useSelector(state => state.user);

  // get the object with all the fields
  const fields = useSelector(state => state.fields);

  // can destructure individual fields
  const { current, region, diveType, visibility, diveSpot } = fields;

  // state for the current field value
  const [dive, setDive] = useState({
    typeID: "",
    schoolNameID: "",
    currentID: "",
    visibilityID: "",
    date: "",
    maxDepth: "",
    userNumber: "",
    verifiedBySchool: "",
    notes: "",
    point: "",
  });

  // all onChange functions do the exact same thing, so you only need one
  // pass to a component like onChange={handleChange('typeID')}
  const handleChange = (property) => (e) => {
    setDive({
      // override the changed property and keep the rest
      ...dive,
      [property]: e.target.value,
    });
  }

  // get access to dispatch
  const dispatch = useDispatch();

  // useEffect with an empty dependency array is the same as componentDidMount
  useEffect(() => {
    // dispatch the action to load fields for each field type
    // once loaded, the changes will be reflected in the fields variable from the useSelector
    Object.keys(fields).forEach(name => dispatch(requireFieldData(name)));
  }, []); // <-- empty array

  const handleSubmitDive = (e) => {

    // do some stuff with the form

    // do we need to save this to the backend? or just to redux?
    dispatch(addDive(dive));
  }

  return (
    <form />
  )
}
于 2021-03-01T03:08:23.963 回答