问题描述
我第一次尝试使用 Redux 在我的应用程序中放入一个表单组件,并且我试图弄清楚我必须为什么创建 Reducers/Actions。
在其他组件中,我将我的用户和消息传递到 mapStatetoProps 并且它们正常工作。但是,在此组件中,我从后端为 componentDidMount 方法中的表字段提取数据,我不确定是否只有要更改的数据存储在 Redux 中。
我是否还需要为表单创建一个 reducer?还是直接发布到后端/节点/postgresql。我打算有一个用所有最新数据更新的表,以便我可以看到它被自动添加到检索数据的逻辑。
我对 React / JavaScript 还很陌生,所以我的逻辑可能有点不对,所以我们不胜感激。
diveLogForm.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,divePoint
});
};
diveLog.action.js
export const registerUserdive = (
diveTypeID,divePoint) => (dispatch) => {
return registerdive(
diveTypeID,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();
}
);
};
我的潜水日志注册操作可能有点不对,因为我在编码时不理解减速器的概念。
解决方法
直到我开始玩代码时我才明白你的问题,但现在我明白你想要做什么了。您有五个不同的列表(regionList
、currentList
等)。这些可能用于生成下拉菜单的选项。
现在您正在从您的 redux 存储中选择所有列表,并通过 mapStateToProps
将它们作为道具提供。您永远不会使用列表将任何更改推送到 redux 存储。您正在 componentDidMount
中调用函数以从后端获取列表数据并将该数据存储在 this.state
中。这有点冲突,因为现在我们在两个地方有数据。我们使用来自 this.props
的列表,还是来自 this.state
的列表?
最终,您想要将哪些数据存储在何处取决于您。在 redux 中存储数据的优点是它可以同时被多个不同的组件使用。它还允许您对后端的每次调用仅执行一次,但为了获得该优势,您需要编写带有条件检查的调用,以便仅在数据不存在时才进行调用。>
我是否还需要为表单创建一个 reducer?还是直接发布到后端/节点/postgresql。
我建议将表单状态保留在组件本身中,因为部分填充的表单仅由该组件使用。
我打算有一个用所有最新数据更新的表,以便我可以看到它被自动添加到检索数据的逻辑。
我不确定什么是什么的父级,但是如果此表单与表格一起显示在屏幕上,那么您可能希望将 isLoading
状态移动到父级并通过回调更新它传递给道具。这样表组件就知道它何时加载新行。或者,当您点击提交时,您可能会调度一个动作来将新的潜水存储到 redux(但我不会在每次击键时存储它)。
在这个组件中,我从后端为 componentDidMount 方法中的表字段提取数据,我不确定是否只有要更改的数据存储在 Redux 中。
通用数据是 redux 的一个很好的候选者。所以在我看来,像所有区域的列表这样的东西存储在 redux 中确实有意义。
我想弄清楚我必须为什么创建 Reducers/Actions。
当您有五个行为相似的不同列表时,最好定义通用操作和将列表名称作为变量的操作创建者。最好也有一个通用的 pullFields
函数!
这有点小题大做,但建议刚入门的任何人都应该学习函数组件和钩子 useSelector
和 useDispatch
,而不是类组件和 connect
。编写组件变得更加容易,并且您可以轻松避免执行诸如 this.handleSubmitDive.bind(this)
之类的一些事情。
我尝试在您的代码中 clean up the repetitions,但我没有解决 redux 问题。所以这里有一个建议的设置,用于处理使用 redux 获取数据。其中一些有点“高级”,但我认为您应该能够复制和粘贴它。
定义一个 async thunk action,它从您的 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 获取的数据的字段的缩减程序。 (使用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;
加入 fields
和 user
切片的基本存储设置
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 />
)
}